SROP 利用技巧
参考资料:https://xz.aliyun.com/news/12236
SROP 概要:
_**SROP (Sigreturn Oriented Programming) **_SROP 和 ROP 类似,通过一个简单的栈溢出,覆盖返回地址并执行 gadgets 控制执行流。不同的是 SROP 使用能够调用 sigreturn 的 gadget 返回覆盖地址,并将一个伪造的 sigcontext 结构体放到栈中。sigreturn是一个系统调用,在类 unix 系统发生 signal 的时候会被间接地调用,其实就是利用了 linux 中的系统调用号,利用linux下的15号系统调用号调用->rt_sigreturn
signal 机制:
signal 机制是类 unix 系统中进程之间相互传递信息的一种方法。一般,我们也称其为软中断信号,或者软中断。比如说,进程之间可以通过系统调用 kill 来发送软中断信号。一般来说,信号机制常见的步骤如下图所示:

- 当程序运行中有中断或异常发生时,内核会向某个进程发送 signal ,该进程会被暂时挂起,进入内核态。
- 内核会为该进程保存相应的上下文,会将一个 signal frame 添加到栈上,这个 frame 主要是将当前寄存器以及 signal 信息压入栈中。一个新的返回地址会被添加到栈顶,这个返回地址指向 sigreturn 系统调用。之后会跳转到注册过的 signal handler 中处理相应的 signal。因此,当 signal handler 执行完之后,就会执行 sigreturn 代码。
- 当signal返回后内核为该进程恢复之前保存的上下文 最终恢复进程的执行。
这一段内存也被称之为 Signal Frame。
通俗来讲的利用:
①保存上下文环境(即各种寄存器),接下来走到②执行信号处理函数,处理完后③恢复相关栈环境,④继续执行用户程序。而在恢复寄存器环境时没有去校验这个栈是不是合法的,如果我们能够控制栈,就能在恢复上下文环境这个环节直接设定相关寄存器的值。
漏洞利用:
通过上面对面signal机制的认识,我们可以敏锐的发现,在1~2的过程中,此时我们保存的 **sigFrame **是完全在用户空间的,也就是对于进程来说可读可写,而且其实SROP利用的最根本的漏洞是因为,在 1 的时候内核对于进程挂起后保存下的 sigFrame 以及恢复环境是的 sigFrame 是没有关联的,所以我们可以伪造sigFrame从而利用syscall进行调用恶意进程。
SROP 的利用方法:
由于在内核在恢复上下文的时候并没有与保存的上下文做对比,同时内核在恢复上下文时是从构造的Signal Frame中pop出来各个寄存器的值,而此时的Signal Frame是在栈里的并且用户是可读可写的。这两点疏忽就导致了我们可以伪造Signal Frame之后主动执行sigreturn来控制每个寄存器的值。
举个例子,我们修改各个寄存器的值:
- 首先利用一个栈溢出漏洞,将返回地址覆盖为一个指向 sigreturn gadget 的指针。如果只有 syscall,则将 RAX 设置为 0xf,效果是一样的。在栈上覆盖上 fake frame。其中:
1 | RSP:一个可写的内存地址 |
- sigreturn gadget 执行完之后,因为设置了 RIP,会再次执行 syscall;retn。payload 的第二部分就是通过这里读入文件描述符的。这一部分包含了 3 个 syscall;retn_fake frame 和其他的代码或数据。
- 接收完数据后,read 函数返回,返回值即读入的字节数被放到 rax 中。让它等于 15,即 sigreturn 的调用号。
- 执行第二个 “syscall;retn”,即 sigreturn 系统调用。从第二个 fake frame 中恢复寄存器,这里是 execve(“/bin/sh”, …)。另外还可以调用 mprotect 将某段数据变为可执行的。
- 执行 execve,拿到 shell。
使用SROP的前提:
- 攻击者要通过栈溢出等漏洞可以篡改栈上的内容,可以控制返回地址
- 必须能够知道/bin/sh的地址,如果写的.bss段,直接写地址就行,如果写到栈里,还需要想办法去泄露栈地址。
- 需要知道syscall指令在内存中的地址
- 需要知道signal return系统调用的内存地址。如果找不到合适的系统调用号,可以看看能不能利用read函数来控制 rax 的值)
SROP 调用多次函数:
需要指出的是,上面的例子中,我们只是单独的获得一个 shell。有时候,我们可能会希望执行一系列的函数。我们只需要做两处修改即可
- 控制栈指针。
- 把原来 rip 指向的
syscall** gadget 换成**syscall; ret** gadget。**
如下图所示 ,这样当每次 syscall 返回的时候,栈指针都会指向下一个 Signal Frame。因此就可以执行一系列的 sigreturn 函数调用。

可以看一下上图构造的栈结构,我们将rsp中的内容填入下一个片段的rt_sigreturn的地址,而且rip的地址一直指向syscall;ret
需要特别注意的是一定要存在ret,不然我们无法返回到下一个片段。
更新: 2026-04-13 15:43:42
原文: https://www.yuque.com/idcm/wnemg9/dqyvgg8kr46hpybv