ELF文件分析(八) 动态链接之辅助向量

0x01 辅助向量

当内核加载器把程序加载到内存中时,对应可执行文件会被映射到内存中的一片地址空间,同时加载器也会为该进程分配栈、堆等空间。而本次的话题辅助向量(auxv)就是存放在栈上的一组数据,其作用是为动态链接器提供信息。

1. 内存布局

可以查看下这张实例图,栈顶在低地址的范围,按照顺序分别是:

1
[argc][argv][envp][auxv][.ascii data for argv/envp]
elf-8-1

2. 表项结构

1
2
3
# 通过gdb断点然后获取auxv信息,前提是_start开始,如果使用链接器脚本修改会失效
# 设置断点在_start位置,启动,查看auxv信息,退出
gdb -ex "b _start" -ex "run" -ex "info auxv" -ex "q" ELF-file

表项的结构如下图所示,你可以参考glibc的elf.h文件了解相关的宏定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef struct
{
uint32_t a_type; // 表项类型
union
{
uint32_t a_val; // 根据表项类型进行解释的值,可能是数值可能是地址
} a_un;
} Elf32_auxv_t;

typedef struct
{
uint64_t a_type; // 同上32位
union
{
uint64_t a_val;
} a_un;
} Elf64_auxv_t;

下面列出比较重要的一些a_type

  • AT_NULL

    一般是最后一个表项的类型,a_val一般未定义,不关心

  • AT_EXECFD

    程序的文件描述符,a_val保存着该字符串地址值

  • AT_PHENT

    程序头表项的大小

  • AT_PHNUM

    程序头表项的数量

  • AT_PAGESZ

    系统页表大小

  • AT_ENTRY

    程序入口点位置,a_val保存着该地址值

  • AT_BASE

    程序基地址

  • AT_UID

    程序的实际UID信息,在权限访问控制中发挥非常重要的作用

  • AT_GID

    程序的实际GID信息

  • AT_EUID

    程序的有效UID信息

  • AT_EGID

    程序的有效GID信息

3. 实例

运行gdb获取auxv的信息如下:

elf-8-2

0x02 寻找与解析辅助向量数组

基本的代码依然在我的仓库中可以找到,解析辅助向量数组这里提供三种方法:

  • /proc/<pid>/auxv 中读取并解析
  • 从envp结束的NULL后进行读取并解析
  • 通过getauxval函数进行读取并解析

我们主要来来看看第二种方法,直接从栈上进行解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 方法3: 直接从栈上访问 auxv (通过main参数)
void read_auxv_from_stack(char **envp) {
printf("\n========================================\n");
printf("方法3: 从栈上直接访问 auxv\n");
printf("========================================\n");

// 找到环境变量的结尾(NULL)
char **env = envp;
while (*env != NULL) {
env++;
}
env++; // 跳过 NULL

// 现在 env 指向 auxv 数组
auxv_t *auxv = (auxv_t *)env;

printf("auxv 在栈上的地址: %p\n", (void*)auxv);
printf("每个 auxv_t 结构大小: %lu 字节\n\n", sizeof(auxv_t));

int count = 0;
while (auxv[count].a_type != AT_NULL) {
printf("[%2d] 地址: %p | 类型: %-20s (%-2lu) | 值: 0x%016lx\n",
count, (void*)&auxv[count],
auxv_type_name(auxv[count].a_type),
auxv[count].a_type,
auxv[count].a_un.a_val);
count++;
}

// 打印 AT_NULL
printf("[%2d] 地址: %p | 类型: %-20s (%-2lu) | 值: 0x%016lx\n",
count, (void*)&auxv[count],
auxv_type_name(auxv[count].a_type),
auxv[count].a_type,
auxv[count].a_un.a_val);
}

代码主要的逻辑就是通过envp来找到辅助向量的位置,然后进行打印。

0x03 小问题

关于辅助向量

  • 如果能直接修改辅助向量,能实现程序权限上的变化吗?
  • 在IDA中调试程序,观察栈上的布局,指出argc、argv、envp和auxv的位置
  • 为什么辅助向量中有程序入口点和基地址信息,没有这些程序能正常工作吗?