Signal机制
SROP
的全称是Sigreturn Oriented Programming
。sigreturn
是一个系统调用。
当内核向某个进程发起一个 signal
,该进程会被暂时挂起,进入内核。内核会帮用户进程将其上下文保存在该进程的栈上,然后在栈顶填上一个地址 rt_sigreturn
,这个地址指向一段代码,在这段代码中会调用sigreturn
系统调用。因此,当 signal handler
执行完之后,栈指针就指向 rt_sigreturn
,所以,signal handler
函数的最后一条ret
指令会使得执行流跳转到这段 sigreturn
代码,被动地进行 sigreturn
系统调用。
我们将被挂起的进程称为 Signal Frame
。
在内核sigreturn
系统调用处理函数中,会根据当前的栈指针指向的 Signal Frame
对进程上下文进行恢复,并返回用户态,从挂起点恢复执行。
Signal机制漏洞
-
这个
Signal Frame
是被保存在进程的地址空间中的,是可以修改的; -
内核并没有将保存的过程和恢复的过程进行一个比较,也就是说,在
sigreturn
这个系统调用的处理函数中,内核并没有判断要恢复的这个Signal Frame
就是之前内核为进程保存的那个SignalFrame
。
Signal机制利用
假设一个攻击者可以控制用户进程的栈,那么它就可以伪造一个 Signal Frame
。
可以利用 SigreturnFrame
函数来伪造 Signal Frame
。
格式:xxx = SigreturnFrame()
在这个伪造的 Signal Frame
中,将 rax = constants.SYS_xxx
,将 rdi
设置成字符串 /bin/sh
的地址,将 rip
设置成系统调用指令 syscall
的内存地址,最后,将 rt_sigreturn
手动设置成 sigreturn
系统调用的内存地址。
当这个伪造的 sigreturn
系统调用返回之后,相应的寄存器就被设置成了攻击者可以控制的值。
SROP成立的条件
- 可以通过
stack overflow
等漏洞控制栈上的内容; - 需要知道栈的地址(比如需要知道自己构造的字符串
/bin/sh
的地址); - 需要知道
syscall
指令在内存中的地址; - 需要知道
sigreturn
调用的内存地址。
常用系统调用调用号
64位
系统调用 | 调用号 | 函数原型 |
---|---|---|
read | 0 | read(int fd, void *buf, size_t count) |
write | 1 | write(int fd, const void *buf, size_t count) |
sigreturn | 15 | int sigreturn(…) |
execve | 59 | execve(const char *filename, char *const argv[],char *const envp[]) |
32位
系统调用 | 调用号 | 函数原型 |
---|---|---|
read | 3 | read(int fd, void *buf, size_t count) |
write | 4 | write(int fd, const void *buf, size_t count) |
sigreturn | 119 | int sigreturn(…) |
execve | 11 | execve(const char *filename, char *const argv[],char *const envp[]) |