论文标题:《Framing Signals—A Return to Portable Shellcode》
作者:Erik Bosman(Vrije Universiteit Amsterdam)
Herbert Bos(Vrije Universiteit Amsterdam)
发表期刊:2014 IEEE Symposium on Security and Privacy
前言
上一期我们详细介绍了关于SROP的主要原理和主要实施工作,承接上节我们于此开始介绍作者在实验过程中的具体过程以及相关应用案例等,希望可以帮助大家跳出对原理的死记硬背,在实践中感受SROP的魅力。
应用
(一)基于 SROP 的漏洞利用
1.基于 SROP 的 execve () 漏洞利用程序
使用该技术成功实现程序的 SROP 漏洞利用,需满足 4 个核心前提:
2.寻找 sigreturn 小工具(gadget)
这类 gadget 其实很好找:FreeBSD、ARM Linux 3.11 之前、x86-64 Linux 等很多系统 / 架构里,sigreturn /syscall 都在固定内存页中。只有当 sigreturn 在 libc 且没有已知 gadget 时,才需要泄露 libc 地址;做了库预链接的系统,gadget 地址全局固定,方便本地利用。以下是在不同系统的可用 gadget 位置

3.执行系统调用
已知攻击者可控数据的地址后,可利用 [vectors] 页中固定的 sigreturn 和 syscall gadget,执行 execve 系统调用(如 Android 下调用 /system/bin/sh),只需将 execve 的文件名、argv 参数指向可控数据即可。
4.Linux x86_64 下基于系统调用链的 SROP 漏洞利用关键
前提条件
基础控制条件:栈指针指向攻击者可控数据且允许空字节,攻击者需控制指令指针 RIP(如栈溢出覆写返回地址),同时需控制 RAX 寄存器使其值为 15;
地址认知条件:只需知晓内存中任意一个可写页的地址即可,无需知道二进制代码的位置 / 内容,即便程序是位置无关(PIE)、二进制信息完全未知,只要泄露可写地址就可行;
系统环境条件:目标系统需启用原生 vsyscall(3.3 之前的 Linux 内核默认开启,如 2012 年版本、CentOS 6);
数据控制条件:攻击者能控制发送到已知文件描述符对应的文件 / 套接字的数据。

vsyscall 是 Linux x86-64 上早期的快速系统调用接口,其代码位于内核固定内存页,为攻击者提供了地址全局固定的 `syscall; ret` 等利用 gadget,只需控制 RAX 寄存器即可执行任意系统调用,该页中的系统时间字段还可被用来构造可用 gadget;Linux 3.1 后虽废弃原生 vsyscall 并新增了无法利用的模拟模式,但仍保留了地址稳定的 `syscall; ret` gadget,且很多老内核默认未启用模拟模式,即便没有固定的系统调用 gadget,也能在程序自身找到所需小 gadget,这使得 Debian 7、Ubuntu 12.04 等搭载 Linux 3.2 内核的大量老系统仍存在相关 SROP 利用风险。
5.引导至任意代码执行
通过构造伪造信号帧并触发 sigreturn,只需保证 x86-64 下代码段寄存器为 0x33、fpstate 指针设为 NULL 即可让内核正常处理,避免程序崩溃;执行 sigreturn 后可完全控制寄存器与程序计数器 RIP,虽会覆盖寄存器中的 ASLR 信息,但可将栈指针指向已知可写页底部,配合 syscall & ret gadget 执行任意系统调用;随后通过调用 read 系统调用将攻击者可控数据写入栈指针位置,该数据会作为返回地址再次指向 syscall & ret gadget,为后续利用搭建环境。
[SROP 6步利用]
1. 初始劫持控制流
RIP → syscall;ret gadget
↓
2. Step1–2:sigreturn + read()
– 伪造信号帧:
CS=0x33, fpstate=NULL
RSP → 已知可写页
RAX=0 (read 系统调用号)
– 执行 read() → 写入可控数据到可写页
– 返回 → syscall;ret
3. Step3:syncfs() 空操作 NOP
– 精准读 306 字节
– RAX=306 → syncfs()
– 无实际操作,RAX=0
– 返回 → syscall;ret
4. Step4:read() 设定 RAX=15
– 读 15 字节
– RAX=15 → sigreturn 调用号
– 返回 → syscall;ret
5. Step5–6:sigreturn 实现任意代码执行
– RAX=15 → 触发 sigreturn
– 信号帧指向**可控已知数据**
– 可执行:
→ mprotect() 开壳执行shellcode
→ execve("/bin/sh") 拿shell
────────────────────────
流程链:
sigreturn → read → syncfs → read → sigreturn → 任意系统调用
原文图如下

6.Asterisk Web Server SROP 利用–案例
[Asterisk CVE-2012-5976 SROP 利用]
1. 漏洞点
↳ 预认证 HTTP POST → 按 Content-Length 栈分配,无长度校验
↳ unbounded stack allocation → 栈溢出
2. 劫持栈指针
↳ 构造超大 Content-Length
↳ RSP 跳转到 Asterisk 另一线程的栈(该线程阻塞在 read)
3. 构造利用
↳ 发送数据覆写第二个线程栈
↳ 发送 15 字节 → 跳转到 syscall & ret gadget
↳ 触发 sigreturn → 启动前文 SROP 引导流程
4. 满足利用条件
↳ 可写页:猜二进制数据段地址(非PIE,固定 0x40000 附近)
↳ 文件描述符:批量开连接,选高值 fd 确保可控
5. 测试与兼容性
↳ 成功:Debian Wheezy / Ubuntu 12.04 LTS / CentOS 6.3
↳ exploit 代码几乎通用
↳ 仅 CentOS 的 syscall & ret 地址略有不同
(二)基于SROP的后门利用
SROP(信号返回导向编程)可被用作高隐蔽性后门,相比传统 shellcode 注入、ROP 利用,其核心优势是将所有逻辑隐藏在数据(信号帧)中,无显性恶意代码,极难被取证 / 扫描工具检测,且通用性极强(不依赖 vsyscall、支持 32/64 位 x86、无需复杂 ROP 编译器)。
- 实现前提:攻击者通过ptrace()注入恶意逻辑到受害进程,借助ptrace()捕获系统调用找到syscall & ret gadget;通过向被追踪进程发信号,让内核在栈顶生成sigreturn() gadget。
- 核心逻辑:伪造多个信号帧构建系统调用链,每个帧配置寄存器执行指定系统调用,栈指针指向下一帧(栈指针充当 “指令指针”,指向完整用户上下文),利用系统调用的阻塞特性实现复杂行为。
- 后门功能:基于inotify API 监控冷门文件的读取事件(如 web 服务器隐蔽文件);触发后创建 TCP 监听套接字,5 秒内有连接则生成绑定套接字的 shell,无连接则停止监听并回到监控状态。

[SROP 隐蔽后门执行流程]
┌─────────────────────────────────────────────────────────────┐
│ 1. 前期准备(攻击者操作) │
│ ├─ ptrace()注入恶意逻辑到受害进程 │
│ ├─ 捕获系统调用 → 找到syscall & ret gadget │
│ └─ 向进程发信号 → 内核在栈顶生成sigreturn() gadget │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 2. 核心逻辑:伪造信号帧构建系统调用链 │
│ ├─ 每个信号帧:配置寄存器→执行指定系统调用,栈指针指向下一帧 │
│ ├─ 栈指针充当“指令指针”,指向完整用户上下文 │
│ └─ 依赖系统调用阻塞特性实现逻辑流转 │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 3. 后门触发逻辑(父线程) │
│ ├─ 调用inotify API → 监控目标冷门文件的读取事件 │
│ ├─ 阻塞read(inotify fd) → 等待文件被读取(核心触发点) │
│ └─ 触发后执行clone() → 创建子线程处理连接,父线程继续监控 │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 4. 连接处理逻辑(子线程) │
│ ├─ 设置alarm(5) → 5秒超时阈值 │
│ ├─ 创建TCP监听套接字 → 阻塞在accept()等待连接 │
│ ├─ 无连接:alarm触发终止进程,父线程回到监控状态 │
│ └─ 有连接:重置alarm → 执行系统调用 → 生成绑定套接字的shell │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 5. 隐蔽性优势 │
│ ├─ 内存无shellcode,安全扫描器难以检测 │
│ ├─ 适配32/64位x86 Linux,跨发行版通用 │
│ └─ 逻辑隐藏在数据(信号帧)中,取证工具无异常特征 │
└─────────────────────────────────────────────────────────────┘
(三)基于 SROP 绕过代码签名
- 核心背景:iOS 仅允许签名代码运行,越狱需利用内核漏洞但漏洞利用需在签名进程中执行,因此提出基于 SROP 的系统调用代理技术。
- iOS SROP 特点:信号处理代码含 3 个实用 gadget(核心用第 1、3 个),但信号帧自带指针,盲执行无数据定位的信号帧难度高。
- 代理实现逻辑:初始信号帧重定位栈并通过 read 加载自动机到新栈;自动机由 read 帧(填充系统调用号 / 参数)+ 执行任意系统调用的帧构成循环,可远程控制进程执行任意系统调用(参数含指针时可先执行 mmap+read)。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
SROP 绕过 iOS 代码签名
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
【一、核心困境:为什么要用 SROP?】
┌─────────────────────────────┐
│ 🍎 iOS 规则:只跑 签名代码 │
│ 🔓 越狱需求:要攻内核关签名 │
│ ⚠️ 矛盾:内核利用代码 │
│ 必须在 签名进程里先跑起来 │
└─────────────────────────────┘
↓ 解决方案
┌─────────────────────────────┐
│ 🧩 SROP 系统调用代理技术 │
└─────────────────────────────┘
【二、iOS 上的 SROP 可用“工具”】
┌─────────────────────────────┐
│ 🛠️ 信号处理里自带 3 个 gadget │
│ ① 从栈取信号帧 → sigreturn │
│ ② 函数参数传帧 → sigreturn │
│ ③ svc 0; bx lr(系统调用) │
│ ↗ ↖ │
│ 【本次核心使用】 │
└─────────────────────────────┘
┌─────────────────────────────┐
│ ⚠️ iOS 难点:信号帧带指针 │
│ 不知道数据地址 → 没法盲用 │
└─────────────────────────────┘
【三、系统调用代理:完整执行流程】
┌─────────────────────────────┐
│ ① 初始化:搭好执行环境 │
│ • 伪造初始信号帧 │
│ • 把栈重定向到安全位置 │
│ • read(攻击者可控fd) │
│ → 把自动机读进新栈 │
└─────────────────────────────┘
↓
┌─────────────────────────────┐
│ ② 自动机循环:远程发号施令 │
│ ├─ read 信号帧 │
│ │ 填:系统调用号 + 参数 │
│ └─ 执行信号帧 │
│ 跑:任意系统调用 │
└─────────────────────────────┘
↓
┌─────────────────────────────┐
│ ③ 复杂调用:带指针也能搞定 │
│ 先 mmap → 再 read 填数据 │
│ 最后执行目标系统调用 │
└─────────────────────────────┘
【四、最终效果】
✅ 在 签名进程里
✅ 不用跑未签名 shellcode
✅ 用 SROP 远程控制内核调用
✅ 完美绕过 iOS 代码签名限制
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
SROP的缓解措施与总结
(一)缓解措施(MITIGATION)
(二)结论(CONCLUSION)
本期分享结束,感谢豆包AI的加持帮助,希望提炼出来的内容可以让读者们深入浅出理解SROP。
我们下期再见🙏🙏🙏




