CTF笔记-绕过 NX 保护的 Ret2libc 漏洞利用(Pwn25)
思路
(题目链接 https://ctf.show/challenges#pwn25 )
本程序的保护机制开启了 NX (No-eXecute) 保护,这意味着栈上的数据不具备可执行权限,传统的直接注入 Shellcode 方法失效。
为了绕过 NX 保护,我们可以利用程序自带的函数库(动态链接库)进行 Ret2libc 攻击。核心逻辑为:先通过 IDA 静态分析找到栈溢出漏洞点,再通过 ROP 链泄露某一已解析函数的真实内存地址。利用 LibcSearcher 检索 libc 库中相应的偏移量,推算出 system 函数和 /bin/sh 字符串的绝对地址,最终构造 Payload 劫持控制流拿到 Shell。
解题过程
一、 静态分析与漏洞定位
首先checksec检查程序
在进行动态调试前,首先使用 IDA Pro (32-bit) 载入附件程序进行静态分析。

通过伪代码可以发现,程序在读取用户输入时,使用了未限制长度的危险函数(read) 读取的长度远大于分配给 buf 缓冲区的空间,这就构成了典型的栈缓冲区溢出漏洞。
进一步观察 IDA 提取的栈帧结构(如图 3),buf 变量距离 __saved_registers (ebp) 为 132 字节,再加上 ebp 本身的 4 字节。由此我们可以静态推断出,覆盖到返回地址的精准偏移量应为 136 + 4 = 140 字节。 接下来我们将通过动态调试来验证这一点。
二、 利用 GDB 和 Cyclic 获取偏移值
确定存在溢出后,需要精准测算覆盖到返回地址(EIP)的垃圾数据长度。
- 终端输入
gdb ./pwn启动调试。 - 新开一个终端,利用 cyclic 工具生成 200 字节的有序字符串:
cyclic 200 > pattern - 切回刚才的 GDB 终端,执行
run < pattern - 当程序报
Segmentation fault(段错误)时,说明非法数据已经覆盖了返回地址,导致 CPU 寻址失败。我们来捕获崩溃时的 EIP 寄存器值:输入info register eip - 得到 EIP 的值为
0x6261616b。 - 输入
q退出 GDB,回普通终端访问 cyclic 工具。 - 输入
cyclic -l 0x6261616b,得出精准偏移量为 140。
三、 阶段性测试:编写独立地址泄露脚本
为了保证漏洞利用的稳定性,我们首先编写一段独立的测试脚本,验证是否能成功泄露底层真实地址。
1 | from pwn import * |
四、 完整攻击 EXP(标准三段式)
在确认地址泄露无误后,我们引入 LibcSearcher 模块,完成从“地址泄露”到“基址计算”,再到“提权执行”的完整自动化攻击流程。
1 | from pwn import * |
五、 确定系统版本号(LibcSearcher 盲盒排雷)
发送完最终 EXP 后,若遇到 LibcSearcher 弹出多个匹配版本的选项,不要盲目去猜,可以利用静态分析来辅助选择。

(注:服务器返回多个匹配的 Libc 库版本)
在本地终端输入以下指令提取系统指纹:
1 | strings ./pwn | grep Ubuntu |
查询得到:GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0
根据常识,我们知道 Ubuntu 版本号与 GLIBC 的对应关系为:
- Ubuntu 16.04 -> GLIBC 2.23
- Ubuntu 18.04 -> GLIBC 2.27
- Ubuntu 20.04 -> GLIBC 2.31
带着这个决定性线索,再来审视终端给出的十个选项:
- 排除 0~3(2.19 版本,属于古老的 Ubuntu 14.04)。
- 排除 4(根本不是 Ubuntu 系统的库)。
- 排除 6、9(2.17 版本,版本太老)。
剩下三个版本的唯一区别是尾部的微小补丁号(基础版、.3 补丁版、.4 补丁版)。此时迅速输入 5 即可成功拿到 Shell 并提取 Flag。
漏洞利用稳定性排错(踩坑提醒):
当出现多选弹窗时,服务器的超时断开倒计时(Timeout)仍在走。一定要迅速输入数字并回车,如果手速慢了或者 5 号小版本不对导致程序崩溃报错EOFError,不要慌张,立刻重跑脚本并尝试该版本的其他补丁序号(如 7 或 8)即可。
1 | ctfshow{b513b07d-d063-400c-8429-64451fa11b8b} |