ret2gets的原理与利用方法
字数 1862 2025-08-30 06:50:12
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地址
- 如果有printf,可写入
- 通过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; retgadget情况下的ROP构造问题。该技术展示了现代漏洞利用中深入理解底层机制的重要性。