高级ROP-SROP

介绍
SROP(Sigreturn Oriented Programming) 于 2014 年被 Vrije Universiteit Amsterdam 的 Erik Bosman 提出,其相关研究Framing Signals — A Return to Portable Shellcode发表在安全顶级会议 Oakland 2014 上,被评选为当年的 Best Student Papers。
signal机制
signal机制是类unix系统中进程之间互相传递信息的一种方法,一般,我们也称其为软中断信号,或者软中断。比如许说,进程之间可以通过系统调用kill来发送软中断信号。一般来说,信号机制常见的步骤如下图:

- 内核向进程发送一个signal机制,该进程会被暂时挂起,进入内核态。
- 内核会为该进程保存相应的上下文,主要是将所有寄存器压入栈中,以及压入signal信息,以及指向sigreturn的系统调用地址。此时的栈结构如图,我们成ucontext(上下文)以及siginfo(信号信息)这一段为signal frame(信号帧)。需要注意的是,这一部分是在用户的地址空间的。之后会跳转到注册过的signal handler(信号处理程序)中处理相应的signal。因此,当signal handler执行完之后,就会执行sigreturn代码。

不同的架构signal frame会有所不同
- signal handler返回后,内核为执行sigreturn系统调用,为该程序恢复之前保存的上下文,其中包括将所有压入的寄存器重新pop回对应的寄存器,最后恢复程序的执行。其中32位的sigreturn的调用号为:199(0x77),64位的系统调用号为:15(0xf)。
攻击原理
仔细回顾内核在signal信号处理的过程中的工作,我们能够发现内核主要的工作就是为进程保存上下文,之后恢复上下文。这些变动都在signal frame中。需要注意(为什么能攻击的原因):
- signal fram被保存在用户的地址空间(栈)中,所以用户是可以读写的(我们能修改)
- 内核和信号处理程序无关,它不会去记录这个signal对应的signal frame,所以当执行sigreturn系统调用时,此时的signal frame并不一定是之前内核为用户进程保存的signal frame。(没有检查机制)
所以攻击者可以伪造一个signal frame用来获取shell
攻击要求
- 可以溢出并能控制内容的栈
- 需要知道/bin/sh、signal frame、syscall、sigreturn的地址
- 需要足够的空间存放signal frame
pwntools已经集成了有关signalframe的函数,叫做SigreturnFrame函数
它可以设置各寄存器的值(好厉害)
从一个大佬那里找到的使用方法
1 | from pwn import * |
例题
该题为buuctf的ciscn_2019_s_3
查看保护和反编译
checksec

ida
vuln函数
1 | signed __int64 vuln() |
看汇编

说一下汇编代码
xor rax, rax = = = = = = > 将rax设置为0
mov edx, 400h= = = >将edx设置为0x400
lea rsi, [rsp+buf]= = > 将buf参数的地址传入寄存器rsi
mov rdi, rax= = = = = >将rax寄存器里的值(0)传入rdi寄存器(将rdi设置为0)
syscall = = = = = = = = = >进行系统调用
这段是执行了 read(0,buf,0x400)
下面的write也是一样
两个函数都是通过系统调用的方式实现
对于amd64程序sys_read 的调用号 为 0 ;sys_write 的调用号 为 1;stub_execve 的调用号 为 59;sys_rt_sigreturn调用号为15
这个题有两种解法
这里说第二种SROP
写/bin/sh
这道题没有给我们/bin/sh,需要自己写
write函数打印了0x30字节,而根据ida所写栈的大小只有0x18(包括ebp和返回地址),所以可以通过write函数打印出栈上的地址,再通过事先计算好的偏移获得写入的/bin/sh的地址(虽然地址会变但是偏移量不会)
由vuln函数(汇编)中的 **lea rsi, [rsp+buf]**可知,rsi记录了buf的地址。
gdb调试(断点打在main函数的nop处,输入为aaaa)

可以看到rsi存入的地址为 0x7fffffffdf50
打印此处开始一段内容

注意这三行
1 | 0x7fffffffdf50: 0x00007f0a61616161 0x0000000000400540//前面是输入的aaaa |
且

所以他是在栈上的(最左边的黄色地址是栈的地址,指向的地址是栈地址储存的内容),因此可以利用它来获取/bin/sh的地址
计算它到buf的偏移(buf即我们输入的内容的地址)

得到偏移量是0x128,但是远程是0x118,因为环境不同,libc版本不同,需要改题目文件的libc的版本(但是我改完运行不了了)
0x00007fffffffe068之前有0x20个字节,在接收地址时要先把他们过滤掉。
再计算它到输入内容的偏移
exp
1 | from pwn import* |
上面有vuln的汇编