ctfshow pwn049
1. 简介
本篇分享对pwn049的解题思路,以及在pwn视角以mprotect()+read()+shellcode的方法解决大部分静态链接题目
参考文章:【CTFshow-pwn系列】03_栈溢出【pwn 049】详解:静态编译下的 mprotect 权限修改技巧_ctfshowpwn049-CSDN博客
2. 题目信息
1. 文件基本信息

32位程序、无PIE、NX打开、静态链接
补充:
虽然在checksec中,我们可以看到 Canary found,但这是对整份 ELF 的整体判断,不代表每个函数都受保护。通过ida可以得到:
IDA 里漏洞函数 ctfshow() 在 0x80488a5,伪代码是:
1 | int ctfshow() |
它的汇编序言/尾声是:
1 | 80488a5 push ebp |
这里没有出现 canary 函数应有的两类指令:
- 从 gs:0x14 取栈 cookie 保存到栈上
- 返回前比较 cookie,不一致时调用
__stack_chk_fail_local
也就是说,ctfshow() 的返回路径就是普通的 leave; retn,覆盖返回地址后会直接生效,不会先被 canary 拦住。
2. 函数执行逻辑
(1) 静态分析
丢进IDA中看一眼:


简单分析一下可以得知:read()引发的栈溢出漏洞,然后重点关注静态链接。
那么静态链接意味着什么?
- 文件巨大:程序把所有用到的库函数(如
printf, read, mprotect等)都打包进了二进制文件里。 - 无需泄露:因为
No PIE,且本题利用到的mprotect/read/gadget地址都能直接从静态链接后的二进制中拿到。 - Gadget 宝库:由于代码量大,程序中包含了大量的汇编片段(Gadget),非常适合进行
ROP(Return Oriented Programming)攻击。
3. 攻击思路
整体思路为:
利用mprotect()修改.bss/.data使其变成可读可写可执行的区域,然后调用read()把shellcode写入这片区域,最终Get shell。
4. 编写exp
1. payload构造
<1> 偏移量(offset)
通过cyclic能比较方便地获得offset

所以,offset = 18 + 4 = 22
<2> payload构造
(1) gadgets寻找

因为是静态链接的文件,所以文件包含充足的gadgets,因为这个 gadget 用来在 mprotect 返回
后清理 3 个参数,所以我们要找能pop三次的gadget,尽量选择普通寄存器,所以我们选择:
1 | 0x08049bd9 : pop ebx ; pop esi ; pop edi ; ret |
(2)要构造的栈结构sz


2. 最终脚本:
1 | # 静态编译,修改mprotect权限,利用read()函数溢出覆盖返回地址,执行shellcode |
5. 补充
<1> 页对齐
- 为什么
mprotect需要页对齐?
- 操作系统底层机制:无论是 32 位(i386)还是 64 位(amd64),现代操作系统管理虚拟内存的基本单位不是“字节”,而是“内存页(Page)”。
- 默认页大小:默认情况下,一个内存页的大小是 4KB(也就是 4096 字节,十六进制为
0x1000)。 - 权限颗粒度:CPU 的内存管理单元(MMU)赋予内存的读、写、可执行(R/W/X)权限是一整页一整页赋予的。因此,
mprotect的第一个参数必须明确指向“某一页的开头”,也即是“页对齐”。如果传入的地址不对齐(比如低 12 位不是000),系统调用就会直接拒绝并返回失败(-1)
- Pwn 题通用的页对齐位运算公式
- 在写
exp.py时,针对页对齐有一个万能公式(32位、64位都能用):
1 | page_aligned_addr = target_addr & ~0xfff |
- 原理:
0x1000的整数倍意味着地址的最低 12 个二进制位必须全部为 0(也就是十六进制的后 3 位全是 0)。 ~0xfff按位取反后,低 12 位是 0,高位是 1。- 任意地址与它进行按位与(
&)计算,就会直接把低 12 位强行清零,实现了“向下取整到最近的页边界”。
- 权限与数据的分离
在通过
mprotect将.bss或.data段改为可执行并打入Shellcode后,比较容易混淆“对齐地址”和“写入地址”只改权限,不改数据:
mprotect(page_aligned_addr, 0x1000, 7)。
这只是给整页贴上了一个RWX的标签,它绝对不会破坏该页中原本存放的任何程序数据。找一块“干干净净”的安身之所:
.bss或.data段的开头往往存放着程序依赖的全局变量等数据。如果把 Shellcode 直接紧贴着page_aligned_addr写入,就会覆盖并摧毁这些重要数据,瞬间引发SIGSEGV(段错误)崩溃。“安全选址”套路:
找地址时,一定要在段基址的基础上加上一个较大的偏移(比如+ 0x500),确保 Shellcode 落在无人使用的荒地(大片的00或db ?)里:1
2shellcode_addr = elf.bss() + 0x500 # shellcode 写入和执行的安全地址
page_aligned_addr = shellcode_addr & ~0xfff # 给 mprotect 用的提权页首地址
<2> pwndbg调试验证
接下来我们用pwndbg来动态调试看一下mprotect()的效果:
具体做法是在脚本中添加gdb.attach(p, "b *0x80db820")
运行脚本后,在gdb页面输入vmmap查看,看到 0x80d8000~`0x80dc000的权限是rw-p`
然后连续ni步过,运行过mprotect后,再次用vmmap查看,发现0x80db000~`0x80dc000这一页的权限都变成了rwxp,说明mprotect()`起作用了。

- 合法授权:本文所述技术仅适用于已获得明确书面授权的目标或自己的靶场内系统。未经授权的渗透测试、漏洞扫描或暴力破解行为均属违法,可能导致法律后果(包括但不限于刑事指控、民事诉讼及巨额赔偿)。
- 道德约束:黑客精神的核心是建设而非破坏。请确保你的行为符合道德规范,仅用于提升系统安全性,而非恶意入侵、数据窃取或服务干扰。
- 风险自担:使用本文所述工具和技术时,你需自行承担所有风险。作者及发布平台不对任何滥用、误用或由此引发的法律问题负责。
- 合规性:确保你的测试符合当地及国际法律法规(如《计算机欺诈与滥用法案》(CFAA)、《通用数据保护条例》(GDPR)等)。必要时,咨询法律顾问。
- 最小影响原则:测试过程中应避免对目标系统造成破坏或服务中断。建议在非生产环境或沙箱环境中进行演练。
- 数据保护:不得访问、存储或泄露任何未授权的用户数据。如意外获取敏感信息,应立即报告相关方并删除。
- 免责范围:作者、平台及关联方明确拒绝承担因读者行为导致的任何直接、间接、附带或惩罚性损害责任。
- 先授权,再测试
- 只针对自己拥有或有权测试的系统
- 发现漏洞后,及时报告并协助修复
- 尊重隐私,不越界







