详解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表组成

  1. nbuckets:哈希桶(bucket)的数量
  2. symbias:符号偏移量,定义符号表中实际存储的符号起始位置
  3. bitmask_nwords:Bloom Filter中的位图单词数量(每个单词64位)
  4. shift:计算Bloom Filter哈希值时使用的位移量
  5. indexes:Bloom Filter的位图数据,每个值64位(8字节)
  6. bucket:每个值表示某个哈希桶中符号链表的起始索引
  7. chain:存储符号的链表哈希值,用于解决哈希冲突

2.2 符号查找算法

  1. 计算符号名的哈希值
  2. 使用Bloom Filter快速检查符号是否存在
  3. 根据哈希值找到对应的bucket
  4. 遍历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 重定位表类型

  1. .rel:不带附加值的重定位表
  2. .rela:包含附加重定位值的重定位表

3.2 重定位条目结构

typedef struct {
    Elf64_Addr r_offset;  // 需要重定位的地址
    Elf64_Xword r_info;   // 符号表索引和重定位类型
    Elf64_Sxword r_addend; // 附加值
} Elf64_Rela;

3.3 重定位表类型

  1. RELA Relocation Table:通用重定位表,可包含任何类型的重定位信息
  2. 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 通过符号名查找共享库中的符号

  1. 获取DT_JMPREL和DT_PLTRELSZ获取JMPREL重定位表的偏移和大小
  2. 获取DT_SYMTAB获取符号表偏移
  3. 获取DT_STRTAB获取字符串表偏移
  4. 遍历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 通过符号名查找程序自带符号

  1. 计算符号名的哈希值
  2. 使用Bloom Filter检查符号是否存在
  3. 根据哈希值找到符号在符号表中的索引
  4. 根据索引获取符号地址

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 关键步骤

  1. 获取libnative2.so的基地址
  2. 解析ELF头部和程序头表
  3. 找到PT_DYNAMIC段
  4. 遍历动态段获取各种表的信息
  5. 查找目标函数(如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流程

  1. 遍历重定位表查找目标函数(如rand)
  2. 修改GOT表中对应条目指向自定义函数
  3. 使用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. 加密函数分析

通过动态信息分析找到的三个加密函数:

  1. iusp9aVAyoMI:异或随机值
  2. SZ3pMtlDTA7Q:RC4加密
  3. 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动态信息是理解动态链接过程的关键,掌握这些信息可以:

  1. 分析程序的动态链接行为
  2. 实现函数hook和补丁
  3. 逆向分析加密算法
  4. 解决CTF中的ELF相关题目

通过解析.dynamic段、GNU_HASH表、重定位表、GOT表等结构,可以深入理解ELF文件的动态链接机制,并应用于实际的分析和修改工作中。

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),包含两个字段: 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实现) 完整查找过程(Python实现) 3. 重定位表(Relocation Table) 重定位表描述程序在加载时需要调整的地址信息。 3.1 重定位表类型 .rel :不带附加值的重定位表 .rela :包含附加重定位值的重定位表 3.2 重定位条目结构 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) 存储程序中的符号信息(变量、函数名等)。 符号条目结构 符号类型(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实现 6.2 通过符号名查找程序自带符号 计算符号名的哈希值 使用Bloom Filter检查符号是否存在 根据哈希值找到符号在符号表中的索引 根据索引获取符号地址 IDA Python实现 7. CTF实例分析 以N1CTF的ezapk为例,分析如何通过动态信息hook函数。 7.1 关键步骤 获取libnative2.so的基地址 解析ELF头部和程序头表 找到PT_ DYNAMIC段 遍历动态段获取各种表的信息 查找目标函数(如rand)并进行hook 7.2 动态信息结构体 7.3 函数hook流程 遍历重定位表查找目标函数(如rand) 修改GOT表中对应条目指向自定义函数 使用mprotect修改内存权限 8. 加密函数分析 通过动态信息分析找到的三个加密函数: iusp9aVAyoMI :异或随机值 SZ3pMtlDTA7Q :RC4加密 UqhYy0F049n5 :Base64编码 8.1 异或加密函数 8.2 RC4加密函数 8.3 Base64编码函数 9. 总结 ELF动态信息是理解动态链接过程的关键,掌握这些信息可以: 分析程序的动态链接行为 实现函数hook和补丁 逆向分析加密算法 解决CTF中的ELF相关题目 通过解析.dynamic段、GNU_ HASH表、重定位表、GOT表等结构,可以深入理解ELF文件的动态链接机制,并应用于实际的分析和修改工作中。