-
-
[原创]ROP(Return-Oriented Programming)返回导向编程 · 游戏安全相关技术边界探索【2】
-
发表于: 7小时前 252
-
系列目录(共四篇)
初步探索 — 基于ROP技术弹个MessageBox & 安全退出
历史回顾:第 1 期:初步认识 ROP
上一期介绍了 ROP 的基本原理。
本期在 notepad.exe 里做实战:调用 ROP 链弹出 MessageBox,用户点「确定」后安全结束。
效果如下(图 1):

图 1. notepad.exe 中 MessageBox 运行效果。
我们先回顾下使用 ShellCode 的流程:
1, Open目标进程 OpenProcess
1, 划分远程进程内存 VirtualAllocEx(RWX)
2, 写入ShellCode WriteProcessMemory
3, CreateRemoteThread 跑起来
ShellCode代码:(如图2)

图 2. ShellCode 示例。
你会发现ShellCode最令ROP羡慕的就是毫无顾忌的使用栈。随意的 push,随意的 pop,随意的 call。因为 ShellCode 技术和常规编程无异:代码控制执行流程,栈负责临时数据、传参、调用。互不干扰。
区别于ShellCode;ROP 如图3:

图 3. ROP 链在栈上的布局(8 字节数组)。
而这一切在ROP这边产生了变化,栈被赋予了控制执行流程的重任。(图3)展示了ROP链,ROP链是布局在栈内存中的,形如8字节的数组。数组中的成员大多数情况是 gadget(小零件)的地址(实时获取并填入)和立即数(上一期 rcx = 0 中的 0ull)。
基于本期工程复杂度分析,由于只需调用 MessageBoxA 与 RtlExitUserThread,因此只需规划一块远程内存,即可同时放下 ROP 主链、字符串数据,以及 Win32 API 调用时的栈空间。
演示中划分 1MB,便于观察与调试;就本期链路而言,4KB 量级通常也够用。
1MB 远程内存自高地址向低地址分为三区:

图 4 · A / B / C 分区(与上图对应)。
第一个 gadget: push rcx; pop rsp; ret(chain entry pivot)
讲解:
疑问: 入口 gadget 能否用 mov rsp, rcx; ret?语义上不是更直接吗?
答: 理论上可以——此时 rcx 已携带 A 区基址,一条指令即可完成栈切换。只不过,此小零件往往并不存在(这需要你根据具体不同的进程,使用 ROP 工具自行确认)。ROP 只能基于二进制里现成的指令片段拼装;理想路径一旦缺失,就必须用等价 gadget 组合(如 push rcx; pop rsp; ret)或改用其他启动方式,这是 ROP 工程里常遇到的约束。
如图5中选中的 gadgets 会在调用 MessageBoxA 前,按 x64 约定写好 rcx、rdx、r8、r9。其中两个是立即数,两个是指向 B 区字符串的指针:

图 5. 参数相关 gadget 与栈布局。
如图6选中的 gadgets,在调用 MessageBoxA 前会把 RSP 从 A 区(主链所在)暂时切到 C 区。API 会在栈上压参、保存现场,耗栈深度事先难以估准;若仍让栈在 A 区向下生长,就可能覆盖 B 区 里的字符串与布局。因此用 C 区 单独承担这次调用的栈空间。
对于红队渗透测试,ROP 常用于拉起一个 Shell 进程,宿主进程的奔溃往往并不在意,这种情况下无需任何「合理栈安排工作」。如果用作后门,或者游戏外挂程序,则需要严格做好「合理栈安排工作」。

图 6. 调用 MessageBoxA 前将 RSP 切换到 C 区。
如图6(续)选中的 gadgets 在 MessageBoxA 调用结束后,会再次切换 RSP 至 A 区 ROP 主链,以便跑后续逻辑。

图 6(续). C 区顶部:返回 A 区主链的布局。
如图7选中的 gadgets:收尾工作。调用 RtlExitUserThread 安全结束线程。
补充说明: 由于调用 RtlExitUserThread 结束本线程,因此无需再切 RSP;此时已不在意该函数执行过程中是否会破坏 B 区。

图 7. 调用 RtlExitUserThread 结束线程。
以上完成了 ROP 技术闭环的讲解。
[招生]科锐逆向工程师培训(2026年11月13日实地,远程教学同时开班, 第57期)