详解ELF动态信息
字数 2115 2025-08-20 18:17:07
ELF动态信息详解
1. ELF动态信息概述
ELF动态信息存储在ELF文件的.dynamic段中,用于描述动态链接器运行时所需的信息。动态链接器使用这些信息来加载和链接程序所需的共享库。
1.1 .dynamic段包含的主要信息
- 动态链接库的路径 (DT_NEEDED)
- 动态符号表的位置 (DT_SYMTAB)
- 字符串表的位置 (DT_STRTAB)
- 动态重定位表的位置 (DT_REL或DT_RELA)
- GOT表的地址 (DT_PLTGOT)
- 哈希表信息 (DT_HASH或DT_GNU_HASH)
1.2 Elf64_Dyn结构
每条动态信息是一个Elf64_Dyn结构(32位为Elf32_Dyn),包含两个字段:
typedef struct {
Elf64_Sxword d_tag; // 类型标识符
Elf64_Xword d_val; // 值或指针
} Elf64_Dyn;
2. GNU_HASH表
GNU_HASH表是ELF文件中用于优化符号查找的哈希表,相比传统的.hash表提供了更高效的符号查找算法。
2.1 GNU_HASH表组成
- nbuckets:哈希桶(bucket)的数量
- symbias:符号偏移量,定义符号表中实际存储的符号起始位置
- bitmask_nwords:Bloom Filter中的位图单词数量(每个单词64位)
- shift:计算Bloom Filter哈希值时使用的位移量
- indexes:Bloom Filter的位图数据,每个值64位(8字节)
- bucket:每个值表示某个哈希桶中符号链表的起始索引
- chain:存储符号的链表哈希值,用于解决哈希冲突
2.2 符号查找算法
- 计算符号名的哈希值
- 使用Bloom Filter快速检查符号是否存在
- 根据哈希值找到对应的bucket
- 遍历chain链表查找匹配的符号
哈希计算函数(Python实现)
def get_hash(self, name):
h = 5381
for c in name:
h = (h << 5) + h + ord(c)
h &= 0xFFFFFFFF
return h & 0xFFFFFFFF
完整查找过程(Python实现)
def find_symbol(self, name):
hash_value = self.get_hash(name)
if not self.check(hash_value):
return None
idx = self.get_idx(hash_value)
if idx == -1:
return None
return idx
3. 重定位表(Relocation Table)
重定位表描述程序在加载时需要调整的地址信息。
3.1 重定位表类型
- .rel:不带附加值的重定位表
- .rela:包含附加重定位值的重定位表
3.2 重定位条目结构
typedef struct {
Elf64_Addr r_offset; // 需要重定位的地址
Elf64_Xword r_info; // 符号表索引和重定位类型
Elf64_Sxword r_addend; // 附加值
} Elf64_Rela;
3.3 重定位表类型
- RELA Relocation Table:通用重定位表,可包含任何类型的重定位信息
- JMPREL Relocation Table:与动态链接相关的重定位条目,特别是首次调用外部函数时需要解析的符号
4. 全局偏移表(GOT)
GOT(Global Offset Table)是动态链接中的关键表,用于存储每个动态库函数的实际地址。
4.1 GOT特点
- 每个条目都是指向函数的指针
- 动态链接器在运行时解析符号表,将函数实际地址填入条目
- 避免多次动态解析的开销
5. 字符串表和符号表
5.1 字符串表(String Table)
- 存储符号名和其他字符串
- 以\x00字符分隔字符串
- 动态符号表使用字符串表中的索引引用符号名
5.2 符号表(Symbol Table)
存储程序中的符号信息(变量、函数名等)。
符号条目结构
typedef struct {
Elf64_Word st_name; // 符号名在字符串表中的偏移
unsigned char st_info; // 符号类型和绑定信息
unsigned char st_other; // 符号可见性
Elf64_Half st_shndx; // 节头索引
Elf64_Addr st_value; // 符号地址或值
Elf64_Xword st_size; // 符号大小
} Elf64_Sym;
符号类型(st_info低4位)
- STT_NOTYPE (0)
- STT_OBJECT (1)
- STT_FUNC (2)
- STT_SECTION (3)
符号绑定(st_info高4位)
- STB_LOCAL (0)
- STB_GLOBAL (1)
6. 实践应用
6.1 通过符号名查找共享库中的符号
- 获取DT_JMPREL和DT_PLTRELSZ获取JMPREL重定位表的偏移和大小
- 获取DT_SYMTAB获取符号表偏移
- 获取DT_STRTAB获取字符串表偏移
- 遍历JMPREL重定位表查找目标符号
IDA Python实现
def find_symbol(name):
symtab = get_symtab_addr()
strtab, strtab_size = get_strtab_addr()
jmprel, jmprel_size = get_jmprel_addr()
for i in range(jmprel_size):
sym_idx = idaapi.get_dword(jmprel + 12 + i * 0x18)
str_offset = idaapi.get_dword(symtab + sym_idx * 0x18)
sym_name = idc.get_strlit_contents(strtab + str_offset)
if name.encode() == sym_name:
addr = idaapi.get_qword(jmprel + i * 0x18)
return addr
6.2 通过符号名查找程序自带符号
- 计算符号名的哈希值
- 使用Bloom Filter检查符号是否存在
- 根据哈希值找到符号在符号表中的索引
- 根据索引获取符号地址
IDA Python实现
def find_symbol(name):
gnu_hash = GnuHash()
hash_value = gnu_hash.get_hash(name)
if not gnu_hash.check(hash_value):
return None
idx = gnu_hash.get_idx(hash_value)
if idx == -1:
return None
symtab = get_symtab_addr()
target = symtab + idx * 0x18 + 8
return idaapi.get_qword(target)
7. CTF实例分析
以N1CTF的ezapk为例,分析如何通过动态信息hook函数。
7.1 关键步骤
- 获取libnative2.so的基地址
- 解析ELF头部和程序头表
- 找到PT_DYNAMIC段
- 遍历动态段获取各种表的信息
- 查找目标函数(如rand)并进行hook
7.2 动态信息结构体
struct dynamic_info {
void *libbase;
void *symbolTable;
void *stringTable;
void *gru_hash_table;
void *dt_STRSZ;
_DWORD gnu_hash_nbuckets;
_DWORD gnu_hash_symbias;
_DWORD gnu_hash_bitmask_nwords;
_DWORD gnu_hash_shift;
_QWORD *gnu_hash_indexes_pointer;
_DWORD *gnu_hash_bucket;
_DWORD *gnu_hash_chain;
struct relocation_item *relocationTable;
_QWORD relocationTableSize;
void *global_offset_table;
};
7.3 函数hook流程
- 遍历重定位表查找目标函数(如rand)
- 修改GOT表中对应条目指向自定义函数
- 使用mprotect修改内存权限
// 查找rand函数
while (strcmp(&stringTable[symbolTable[6 * info[1]]], "rand")) {
info += 6;
if (!--relocationSize) goto LABEL_10;
}
v10 = *((_QWORD *)info - 1);
// 修改GOT表
v11 = (_QWORD *)(v10 + v5);
result = mprotect(v11, 8uLL, 3);
*v11 = sub_75E1245140; // 自定义函数
8. 加密函数分析
通过动态信息分析找到的三个加密函数:
- iusp9aVAyoMI:异或随机值
- SZ3pMtlDTA7Q:RC4加密
- UqhYy0F049n5:Base64编码
8.1 异或加密函数
_BYTE *__fastcall iusp9aVAyoMI(__int64 a1, size_t a2) {
v4 = malloc(a2);
__memcpy_chk(v4, a1, a2, -1LL);
for (i = 0LL; i < a2; ++i)
v4[i] ^= rand(); // 使用随机值异或
return v4;
}
8.2 RC4加密函数
_BYTE *__fastcall SZ3pMtlDTA7Q(__int64 a1, int a2) {
// 初始化S盒
for (j = 0; j <= 255; ++j)
v19[j] = j;
// KSA算法
for (k = 0; k <= 255; ++k) {
v2 = (v10 + v19[k] + *((_BYTE *)v20 + k % 16));
v10 = v2;
swap(v19[k], v19[v2]);
}
// PRGA算法
for (m = 0; m < a2; ++m) {
v14 = (v14 + 1);
v11 = (v11 + v19[v3]);
swap(v19[v3], v19[v5]);
v16[m] ^= v19[(v19[v3] + v19[v5])];
}
return v16;
}
8.3 Base64编码函数
_BYTE *__fastcall UqhYy0F049n5(__int64 a1, unsigned __int64 a2) {
qmemcpy(v23, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", sizeof(v23));
// Base64编码过程
for (i = 0; i < a2; ++i) {
v13 = *(_BYTE *)(a1 + i);
if (v19) {
// 处理第二个和第三个字节
} else {
// 处理第一个字节
}
v12 = v13;
}
// 处理填充
if (v19 == 1) {
// 添加两个==
} else if (v19 == 2) {
// 添加一个=
}
return v20;
}
9. 总结
ELF动态信息是理解动态链接过程的关键,掌握这些信息可以:
- 分析程序的动态链接行为
- 实现函数hook和补丁
- 逆向分析加密算法
- 解决CTF中的ELF相关题目
通过解析.dynamic段、GNU_HASH表、重定位表、GOT表等结构,可以深入理解ELF文件的动态链接机制,并应用于实际的分析和修改工作中。