ret2gets的原理与利用方法
字数 1862 2025-08-30 06:50:12

ret2gets原理与利用方法详解

一、技术概述

ret2gets是一种利用glibc优化特性(高版本编译器)的漏洞利用技术,其核心是通过gets函数配合printf/puts实现libc地址泄露。该技术适用于以下场景:

  1. 存在栈溢出漏洞
  2. 程序包含gets函数
  3. 缺乏直接控制rdi寄存器的gadget(如pop rdi; ret)

二、技术背景

2.1 pop rdi; ret gadget的消失

在传统ROP攻击中,我们通常使用pop rdi; ret gadget来控制rdi寄存器,用于构造函数调用如puts(func_got_addr)system('/bin/sh')

然而在glibc 2.34+中,由于以下变化导致该gadget难以获取:

  1. 补丁移除了__libc_csu_init的二进制生成
  2. 原本pop rdi; ret来自__libc_csu_init函数中的pop r15; ret的一部分
  3. 该补丁旨在删除ret2csu的有用ROP小工具,副作用是移除了pop rdi; ret

2.2 替代方案

虽然程序中可能没有pop rdi; ret,但glibc自身包含大量使用r15的函数,必然存在pop r15; ret。因此可以通过泄露libc基址后,定位libc中的pop rdi; ret偏移。

三、ret2gets原理

3.1 关键发现

在调用gets函数时,观察到以下重要现象:

  1. 调用gets函数前,rdi寄存器指向栈地址
  2. 调用gets函数后,rdi寄存器变为_IO_stdfile_0_lock地址
  3. _IO_stdfile_0_lock是FILE结构体的_IO_lock_t *_lock成员

3.2 结构体分析

_IO_lock_t结构体中的owner成员在一定条件下存储TLS地址,而TLS地址与libc基地址的偏移是固定的。这为地址泄露提供了可能。

四、利用思路

  1. 执行第一次gets,此时rdi寄存器指向_lock
  2. 执行第二次gets,向_lock中写入特定内容
    • 如果有printf,可写入%p等格式化字符串泄露地址
    • 如果是puts,可填充特定内容绕过检测,使puts输出存放在owner中的TLS地址
  3. 通过TLS地址计算libc基地址
  4. 构造最终ROP链获取shell

五、源码分析

5.1 gets函数行为

gets函数的关键行为:

  1. 函数开头使用_IO_acquire_lock
  2. 函数结束时使用_IO_release_lock
  3. 锁机制保证线程安全,防止多线程竞争

5.2 锁机制实现

关键函数和宏:

  • _IO_flockfile_IO_acquire_lock_fct
  • _IO_USER_LOCK=0x8000
  • _IO_lock_lock_IO_lock_unlock

5.3 rdi寄存器变化

在glibc 2.35中,_IO_lock_unlock函数会将_lock地址加载到rdi寄存器中,这是编译器优化的结果:

  1. _lock存储在FILE结构体的0x88偏移处
  2. 函数结束时不会改变rdi寄存器
  3. 在2.30之前的glibc中,行为不同(使用mov+lea加载到rdi)

六、具体利用方法

6.1 需要绕过的检测

  1. _IO_lock_unlock中的--(_name).cnt == 0判断
    • 若判断成功,owner会变为NULL
    • 需要覆盖_IO_lock_t结构体的cnt不为1
  2. _IO_lock_t结构体在owner参数前不能有特定值

6.2 利用步骤

  1. 第一次溢出:控制程序流执行gets,使rdi指向_lock
  2. 第二次溢出:向_lock写入特定内容
    • 设置cnt不为1
    • 设置适当owner值
  3. 调用puts/printf泄露地址
  4. 计算libc基地址
  5. 构造最终ROP链

七、适用条件

  1. glibc版本:主要在2.34+中有效
  2. 程序包含gets函数
  3. 存在栈溢出漏洞
  4. 缺乏直接控制rdi的gadget

八、防御措施

  1. 禁用gets函数(使用更安全的替代函数)
  2. 启用栈保护(如Canary)
  3. 使用地址随机化(ASLR)
  4. 更新到最新glibc版本

九、总结

ret2gets技术利用高版本glibc中gets函数的特殊行为,通过控制锁结构实现地址泄露,解决了缺乏pop rdi; retgadget情况下的ROP构造问题。该技术展示了现代漏洞利用中深入理解底层机制的重要性。

ret2gets原理与利用方法详解 一、技术概述 ret2gets是一种利用glibc优化特性(高版本编译器)的漏洞利用技术,其核心是通过gets函数配合printf/puts实现libc地址泄露。该技术适用于以下场景: 存在栈溢出漏洞 程序包含gets函数 缺乏直接控制rdi寄存器的gadget(如pop rdi; ret) 二、技术背景 2.1 pop rdi; ret gadget的消失 在传统ROP攻击中,我们通常使用 pop rdi; ret gadget来控制rdi寄存器,用于构造函数调用如 puts(func_got_addr) 或 system('/bin/sh') 。 然而在glibc 2.34+中,由于以下变化导致该gadget难以获取: 补丁移除了 __libc_csu_init 的二进制生成 原本 pop rdi; ret 来自 __libc_csu_init 函数中的 pop r15; ret 的一部分 该补丁旨在删除ret2csu的有用ROP小工具,副作用是移除了 pop rdi; ret 2.2 替代方案 虽然程序中可能没有 pop rdi; ret ,但glibc自身包含大量使用r15的函数,必然存在 pop r15; ret 。因此可以通过泄露libc基址后,定位libc中的 pop rdi; ret 偏移。 三、ret2gets原理 3.1 关键发现 在调用gets函数时,观察到以下重要现象: 调用gets函数前,rdi寄存器指向栈地址 调用gets函数后,rdi寄存器变为 _IO_stdfile_0_lock 地址 _IO_stdfile_0_lock 是FILE结构体的 _IO_lock_t *_lock 成员 3.2 结构体分析 _IO_lock_t 结构体中的 owner 成员在一定条件下存储TLS地址,而TLS地址与libc基地址的偏移是固定的。这为地址泄露提供了可能。 四、利用思路 执行第一次gets,此时rdi寄存器指向 _lock 执行第二次gets,向 _lock 中写入特定内容 如果有printf,可写入 %p 等格式化字符串泄露地址 如果是puts,可填充特定内容绕过检测,使puts输出存放在owner中的TLS地址 通过TLS地址计算libc基地址 构造最终ROP链获取shell 五、源码分析 5.1 gets函数行为 gets函数的关键行为: 函数开头使用 _IO_acquire_lock 函数结束时使用 _IO_release_lock 锁机制保证线程安全,防止多线程竞争 5.2 锁机制实现 关键函数和宏: _IO_flockfile 和 _IO_acquire_lock_fct _IO_USER_LOCK=0x8000 宏 _IO_lock_lock 和 _IO_lock_unlock 宏 5.3 rdi寄存器变化 在glibc 2.35中, _IO_lock_unlock 函数会将 _lock 地址加载到rdi寄存器中,这是编译器优化的结果: _lock 存储在FILE结构体的0x88偏移处 函数结束时不会改变rdi寄存器 在2.30之前的glibc中,行为不同(使用mov+lea加载到rdi) 六、具体利用方法 6.1 需要绕过的检测 _IO_lock_unlock 中的 --(_name).cnt == 0 判断 若判断成功,owner会变为NULL 需要覆盖 _IO_lock_t 结构体的cnt不为1 _IO_lock_t 结构体在owner参数前不能有特定值 6.2 利用步骤 第一次溢出:控制程序流执行gets,使rdi指向 _lock 第二次溢出:向 _lock 写入特定内容 设置cnt不为1 设置适当owner值 调用puts/printf泄露地址 计算libc基地址 构造最终ROP链 七、适用条件 glibc版本:主要在2.34+中有效 程序包含gets函数 存在栈溢出漏洞 缺乏直接控制rdi的gadget 八、防御措施 禁用gets函数(使用更安全的替代函数) 启用栈保护(如Canary) 使用地址随机化(ASLR) 更新到最新glibc版本 九、总结 ret2gets技术利用高版本glibc中gets函数的特殊行为,通过控制锁结构实现地址泄露,解决了缺乏 pop rdi; ret gadget情况下的ROP构造问题。该技术展示了现代漏洞利用中深入理解底层机制的重要性。