SROP
Sigreturn Oriented Programming (SROP),面向 Sigreturn 的编程
signal 机制
Signal handing
信号处理(Signal handing)是 UNIX 系统中进程相互通信的一种机制。 在信号处理过程中,首先会保存当前进程的上下文,然后执行信号处理程序,最后恢复上下文并继续正常执行。
SROP 本质是 sigreturn 这个系统调用,主要是将所有寄存器压入栈中,以及压入 signal 信息,以及指向 sigreturn 的系统调用地址。
context
对于信号帧(signal Frame)来说,会因为架构的不同而有所区别。以下分别给出 32 位和 64 位的 sigcontext :
- 32位
1 |
|
- 64位
1 |
|
信号帧都会被直接压入堆栈中存储,如下图中 siginfo 和 ucontext 组成信号帧
简而言之,
在运行信号处理程序之前——上下文被推入堆栈
完成执行信号处理程序后——上下文将从堆栈中弹出
sigreturn
最重要的是 sigreturn 部分,signal handler 返回后,内核为执行 sigreturn 系统调用,要恢复之前保存的上下文,其中包括将所有压入的寄存器,重新 pop 回对应的寄存器,最后恢复进程的执行。它不检查上下文中栈的完整性,只是简单的填充对应的内容到相应的寄存器中。
这就意味着我们只要把想要的值填进寄存器,当系统执行完 sigreturn 系统调用之后,会执行一系列的 pop 指令以便于恢复相应寄存器的值,当执行到 rip 时,就会将程序执行流指向 syscall 地址,根据相应寄存器的值,此时,便会得到一个 shell,达到想要的效果。
其中,32 位的 sigreturn 的调用号为 77,64 位的系统调用号为 15。
利用方法
SROP特点
依赖系统调用(syscall)强但对 libc.so 的依赖极少。
要有空间存放 Signal Frame 的信息.
与其他 rop 相比,对的依赖 gadgets 较少。
原理
当每次 syscall 返回的时候,栈指针都会指向下一个 Signal Frame。因此就可以执行一系列的 sigreturn 函数调用。因此我们就可以在程序某一个地方伪造一个 signal Frame
,再让程序 sys_rt_sigreturn
我们构造的 fake signal Frame,让进程恢复到我们构造的恶意状态。
利用的前提是目标程序中存在 sigreturn片段也就是 syscall;ret;
代码段,我们直接向栈上写入 Signal Frame 即可。
以下是常用的系统调用号:
- i386
NR | syscall name | %eax | arg0 (%ebx) | arg1 (%ecx) | arg2 (%edx) |
---|---|---|---|---|---|
3 | read | 0x03 | unsigned int fd | char *buf | size_t count |
4 | write | 0x04 | unsigned int fd | const char *buf | size_t count |
5 | open | 0x05 | const char *filename | int flags | umode_t mode |
11 | execve | 0x0b | const char *filename | char *const *argv | char *const *envp |
173 | rt_sigreturn | 0xad | ? | ? | ? |
- amd64
NR | syscall name | %rax | arg0 (%rdi) | arg1 (%rsi) | arg2 (%rdx) |
---|---|---|---|---|---|
0 | read | 0x00 | unsigned int fd | char *buf | size_t count |
1 | write | 0x01 | unsigned int fd | const char *buf | |
2 | open | 0x02 | const char *filename | int flags | umode_t mode |
3 | rt_sigreturn | 0x0f | ? | ? | ? |
59 | execve | 0x3b | const char *filename | char *const *argv | char *const *envp |
例题
[CISCN 2019华南] PWN3
题目链接:CISCN 2019华南]PWN3 | NSSCTF
分析题目
checksec
运行起来只有一个输入和输出,没有任何文字提示
函数分析
整个程序很小就只有一个函数 vlun()
,利用的是 syscall 系统调用来调用所用到的函数。
read 可以读取 400 字节,但是 buf 只有 10 字节长度,是一个可以用空间很大的栈溢出问题。同样输出也是大于 buf 大小的,所以可以造成信息泄露。
我们看汇编代码部分,有一个现成的 syscall;ret;
供我们使用。
利用思路
在第一次 read、write 时向栈上写入 ‘/bin/sh\x00’,并泄露出它的地址。
泄露地址后需要计算相应的偏移,我们本地调试,在等待输入的时候输入aaaa,接着直接 search aaaa
查看字符串存储的位置。
我们最开始进入函数的时候的 rsi 指向栈基址,
所以我们的偏移是 offset = 0xe0d8-0xdfc0=0x118
有两种攻击方法:
- execve 与 libc_csu_init
用利用题目中 mov rax, 3Bh;ret
gatget 来修改 rax 为 0x3b(execve),同时利用 libc_csu_init 来修改 rdx 为 0,用 pop rdi;ret
来修改 rdi 的值指向 /bin/sh
。
- srop
利用 srop ,在栈中部署一个伪造 signal Frame sigcontext,然后用 rt_sigreturn 来恶意恢复重而 get shell 。
exp
1 |
|
或者
1 |
|
事情要从我在nepCTF做了一个SROP但根本不会做开始说起···
之前做过这个例题但是就使用 ret2csu 做的,现在发现 SROP 更简单