问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
详解ELF动态信息
了解ELF动态信息,了解各个表之间的联系,并通过一题CTF题目进行实践。
ELF动态信息 ======= ELF 动态信息是存储在 ELF 文件中的一个段(通常是 `.dynamic` 段),用于描述动态链接器运行时所需的信息。 `.dynamic`段包含了动态链接所需的各种信息,例如: - 动态链接库的路径 (`DT_NEEDED`)。 - 动态符号表的位置 (`DT_SYMTAB`)。 - 字符串表的位置 (`DT_STRTAB`)。 - 动态重定位表的位置 (`DT_REL` 或 `DT_RELA`)。 - `GOT` 表的地址 (`DT_PLTGOT`)。 - 哈希表信息 (`DT_HASH` 或 `DT_GNU_HASH`)。 每条信息是一个 `Elf32_Dyn` 或 `Elf64_Dyn` 结构,包含两个字段: - `d_tag`: 类型(例如 `DT_NEEDED`、`DT_SYMTAB` 等)。 - `d_val`: 值,可能是地址、大小或其他信息。 其具体位置可通过010editor解析获取:找`program_header_table`中的`Dunamic Segment`。 `VIRTUAL_ADDRE` 得到地址 `0x41E0`(使用时需加上基地址) ![image-20241211191903072.png](https://shs3.b.qianxin.com/attack_forum/2024/12/attach-bc159a298ca623a3cab1d1c31607ed5bc93d06e8.png) 其结构一般如下: ![image-20241211190958956.png](https://shs3.b.qianxin.com/attack_forum/2024/12/attach-de84c3aefa73b8936803c94e3886a9c54a66d7ad.png) GNU\_HASH Table --------------- ELF 文件可以包含 `GNU_HASH` 表,用于优化符号查找,与传统的 `.hash` 表相比,`GNU_HASH` 表提供了更高效的符号查找算法。 其组成部分: - `nbuckets` - 表示哈希桶(`bucket`)的数量。 - 哈希桶是符号查找的起点,每个桶可以指向符号表中的符号链表。 - 这个值越大,符号分布越均匀,查找效率越高。 - `symbias` - 符号偏移量,用于定义符号表中实际存储的符号起始位置。 - 所有全局符号的索引在符号表中的起始位置为 `symbias` 的值。也就是说,`symbias` 前的符号通常是局部符号,不被动态链接器处理。 - `bitmask_nwords` - 表示 Bloom Filter 中的位图单词数量(每个单词是 64 位)。 - Bloom Filter 是加速符号查找的第一步,用于快速排除符号不可能存在的情况 - `shift` - 在计算 Bloom Filter 的哈希值时使用的位移量。 - 哈希函数会将符号的哈希值右移 26 位,生成另一部分用于 Bloom Filter 检查。 - `indexes` - 这是 Bloom Filter 的位图数据,每个值是 64 位(8 字节)。 - 使用哈希函数计算得到的索引在这些位图中查找,如果相关位未被设置,则说明该符号不存在。 - `bucket` - 每个值表示某个哈希桶中符号链表的起始索引。 - `chain` - 存储符号的链表哈希值,用于解决哈希冲突。 - 每个值是一个符号的哈希值,如果高位设置为 0,表示链表的结束。 根据上文找到 `GRU_HASH Table` 的地址: ![image-20241212145510105.png](https://shs3.b.qianxin.com/attack_forum/2024/12/attach-28178ac50382517137c37ed8ed15e2d21968e190.png) 通过 `GNU_HASH Table` 的信息,可以找到符号在 `Symbol Table` 中的索引。 其具体算法如下:(用Python代码作为演示) - `main` 的代码仅为示例,可知 `iusp9aVAyoMI` 为 `Symbol Table` 的第 15 个元素。 ```python class GnuHash: def __init__(self, nbuckets=0, symbias=0, bitmask_nwords=0, shift=0, indexes=0, bucket=0, chain=0, ELFCLASS_BITS=64): self.nbuckets = nbuckets self.symbias = symbias self.bitmask_nwords = bitmask_nwords self.shift = shift self.indexes = indexes self.bucket = bucket self.chain = chain self.ELFCLASS_BITS = ELFCLASS_BITS def get_hash(self, name): h = 5381 for c in name: h = (h << 5) + h + ord(c) h &= 0xFFFFFFFF return h & 0xFFFFFFFF def check(self, hash_value): bit1 = 1 << (hash_value % self.ELFCLASS_BITS) bit2 = 1 << (hash_value >> self.shift) % self.ELFCLASS_BITS mask = self.indexes[hash_value // self.ELFCLASS_BITS % self.bitmask_nwords] return (mask & bit1) and (mask & bit2) def get_idx(self, hash_value): idx = self.bucket[hash_value % self.nbuckets] while self.chain[idx - self.symbias] & 1 == 0: if (self.chain[idx - self.symbias] ^ hash_value) >> 1 == 0: return idx idx += 1 return -1 def main(): name = "iusp9aVAyoMI" gnu_hash = GnuHash( nbuckets=4, symbias=0xB, bitmask_nwords=4, shift=0x1A, indexes=[ 0x204800080000000, 0x800000000400000, 0x10440840201000, 0x30000804040000 ], bucket=[0xB, 0xE, 0x13, 0x14], chain=[ 0xD3AF6B8C, 0xA9CE198C, 0xA9CE198D, 0x7C988538, 0xB9F51094, 0xB9F51094, 0x69E7D2F4, 0x49A386F5, 0xB9F50D9F, 0xD0C97EE2, 0xD0C97EE2, 0x5BBF417A, 0x5BBF417A, 0x8F30E9A2, 0xCA77EE2E, 0xCA77EE2F ] ) hash_value = gnu_hash.get_hash(name) print(f"hash: {hash_value:X}") if gnu_hash.check(hash_value): idx = gnu_hash.get_idx(hash_value) print(f"idx: {idx}") else: print("Not found") if __name__ == '__main__': main() ``` Relocation Table ---------------- 重定位表用于描述程序在加载时需要调整的地址信息。 ELF 文件中的重定位表主要有以下两种: - `.rel` 表:不带附加的值。 - `.rela` 表:包含附加的重定位值。 重定位条目: - `r_offset`:重定位目标的内存地址,表示需要修正的地址。 - `r_info`:由符号表索引和重定位类型编码的值。 - 高 32 位:符号表索引。 - 低 32 位:重定位类型。 - `r_addend`:附加值,与重定位类型相关。 根据上文找到 `Relocation Table`: ![image-20241212153139763.png](https://shs3.b.qianxin.com/attack_forum/2024/12/attach-a96f0bb6461c12922154982204f9045cf0eb1a8a.png) - RELA Relocation Table: - 一个更通用的重定位表,可以包含任何类型的重定位信息。它不仅限于 PLT 或外部函数调用,还可以处理数据引用、内部符号等的重定位。 - JMPREL Relocation Table: - 包含的是与动态链接相关的重定位条目,特别是那些需要在首次调用外部函数时解析的符号。 部分解释如下: - `Elf64_Rela <0x41D0, 0x403, 0xFF0>` - 重定位类型为 `R_AARCH64_RELATIVE`,不涉及符号表,用于修正与基址相关的绝对地址。 - 典型用于静态地址的重定位。 - 将加载基址加上 `0xFF0`,写入 `0x41D0` 处。 - `Elf64_Rela <0x41D8, 0xE00000101, 0>` - 重定位类型为 `R_AARCH64_ABS64`,符号表相关,用于修正符号的绝对地址。 - `r_info` 高 32 位为 `0xE`,即加载符号表索引指向的符号地址到 `0x41D8`。 - `Elf64_Rela <0x43D0, 0x200000402, 0>` - 重定位类型为 `R_AARCH64_JUMP_SLOT`,动态链接时,为函数调用修正跳转插槽地址。 - 常用于 `.plt` 表。 Global Offset Table (GOT) ------------------------- GOT 是动态链接中的一个关键表,用于存储每个动态库函数的实际地址,供程序直接调用,以避免多次动态解析。 GOT 表的每个条目都是一个指向函数的指针。 动态链接器在运行时解析符号表,将函数的实际地址填入此条目。 根据上文找到 `GLOBAL_OFFSET_TABLE`: ![image-20241212153732982.png](https://shs3.b.qianxin.com/attack_forum/2024/12/attach-1d0831c896a1edf047ad49a1ca74cfd67c2c46dd.png) String Table ------------ 字符串表是 ELF 文件中的一段,用于存储符号名和其他字符串。 - 以 `\x00` 字符分隔字符串。 - 动态符号表和其他部分使用字符串表中的索引来引用符号名。 Symbol Table ------------ 符号表存储了程序中的符号信息,例如变量、函数名等。 符号条目内容: - `st_name (4 bytes)`: 符号名在 String Table 中的偏移。 - `st_info (1 byte)`: - 低 4 位表示符号类型。 - `STT_NOTYPE`:0 - `STT_OBJECT`:1 - `STT_FUNC`:2 - `STT_SECTION`:3 - 高 4 位表示 `Symbol Binding`。 - `STB_LOCAL`:0 - `STB_GLOBAL`:1 - `st_other (1 byte)`:指定了符号的可见性。 - `STV_DEFAULT`:0 - `STV_INTERNAL`:1 - `STV_HIDDEN`:2 - `STV_PROTECTED`:3 - `st_shndx (2 bytes)`:不同的上下文有不同的含义。 - `st_value (8 bytes)`:符号的地址或值。 - `st_size (8 bytes)`:符号的大小。 根据上文找到`Symbol Table`: ![image-20241212161544590.png](https://shs3.b.qianxin.com/attack_forum/2024/12/attach-4752d290184ffff9e6952abfb86efdd49d44c192.png) 实践一下 ==== 通过符号名找共享库中的符号 ------------- 1. 根据 `Elf64_Dyn <0x17, 0x7F0> ; DT_JMPREL`、`Elf64_Dyn <2, 0x120> ; DT_PLTRELSZ` 获取 JMPREL Relocation Table的偏移和大小。 2. 根据 `Elf64_Dyn <6, 0x288> ; DT_SYMTAB` 获取 Symbol Table 的偏移。 3. 根据 `Elf64_Dyn <5, 0x608> ; DT_STRTAB` 获取 String Table 的偏移。 4. 遍历 JMPREL Relocation Table: 1. 获取表项第 2 项的高 32 位,即其在 Symbol Table 中的索引。 2. 根据索引获取 Symbol Table 的相应的符号信息。 3. 根据符号信息找到符号名在 String Table中的索引。 4. 判断符号名是否为目标符号名: 1. 若相同,则符号地址为 JMPREL Relocation Table 表项的第 1 项。 IDA Python 代码如下: ```python import idaapi import idc def get_dynamic_addr(): base = idaapi.get_imagebase() program_header_offset = idaapi.get_qword(base + 0x20) program_header_size = idaapi.get_qword(base + 0x28) program_header = base + program_header_offset ph_itemsize = 0x38 for i in range(program_header_size): ph_type = idaapi.get_dword(program_header + i * ph_itemsize) if ph_type == 2: dynamic_vaddr = idaapi.get_qword(program_header + i * ph_itemsize + 0x10) dynamic_memsz = idaapi.get_qword(program_header + i * ph_itemsize + 0x20) break else: print("No dynamic segment found") exit() return dynamic_vaddr, dynamic_memsz def get_symtab_addr(): dynamic_addr, dynamic_memsz = get_dynamic_addr() dynamic_size = dynamic_memsz // 0x10 symtab = None for i in range(dynamic_size): tag = idaapi.get_qword(dynamic_addr + i * 0x10) if tag == 6: symtab = idaapi.get_qword(dynamic_addr + i * 0x10 + 0x8) break return symtab def get_strtab_addr(): dynamic_addr, dynamic_memsz = get_dynamic_addr() dynamic_size = dynamic_memsz // 0x10 strtab, strtab_size = None, None for i in range(dynamic_size): tag = idaapi.get_qword(dynamic_addr + i * 0x10) if tag == 5: strtab = idaapi.get_qword(dynamic_addr + i * 0x10 + 0x8) if tag == 10: strtab_size = idaapi.get_qword(dynamic_addr + i * 0x10 + 0x8) return strtab, strtab_size def get_jmprel_addr(): dynamic_addr, dynamic_memsz = get_dynamic_addr() dynamic_size = dynamic_memsz // 0x10 jmprel, jmprel_size = None, None for i in range(dynamic_size): tag = idaapi.get_qword(dynamic_addr + i * 0x10) if tag == 23: jmprel = idaapi.get_qword(dynamic_addr + i * 0x10 + 0x8) if tag == 2: jmprel_size = idaapi.get_qword(dynamic_addr + i * 0x10 + 0x8) jmprel_size //= 0x18 return jmprel, jmprel_size def find_symbol(name): symtab = get_symtab_addr() strtab, strtab_size = get_strtab_addr() jmprel, jmprel_size = get_jmprel_addr() if not symtab or not strtab or not jmprel: print("No symbol table or string table found") exit() 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 def main(): name = "free" addr = find_symbol(name) print("Address of %s: 0x%x" % (name, addr)) if __name__ == "__main__": main() ``` 通过符号名找程序自带的符号 ------------- 1. 计算 hash。 2. 检测 hash 是否存在。 3. 根据 hash 寻找其在 Symbol Table 的索引。 4. 根据索引获取其地址。 IDA Python 代码如下: ```python import idaapi import ida_ida import idc def get_dynamic_addr(): base = idaapi.get_imagebase() program_header_offset = idaapi.get_qword(base + 0x20) program_header_size = idaapi.get_qword(base + 0x28) program_header = base + program_header_offset ph_itemsize = 0x38 for i in range(program_header_size): ph_type = idaapi.get_dword(program_header + i * ph_itemsize) if ph_type == 2: dynamic_vaddr = idaapi.get_qword(program_header + i * ph_itemsize + 0x10) dynamic_memsz = idaapi.get_qword(program_header + i * ph_itemsize + 0x20) break else: print("No dynamic segment found") exit() return dynamic_vaddr, dynamic_memsz def get_symtab_addr(): dynamic_addr, dynamic_memsz = get_dynamic_addr() dynamic_size = dynamic_memsz // 0x10 symtab = None for i in range(dynamic_size): tag = idaapi.get_qword(dynamic_addr + i * 0x10) if tag == 6: symtab = idaapi.get_qword(dynamic_addr + i * 0x10 + 0x8) break return symtab class GnuHash: def __init__(self): dynamic_addr, dynamic_memsz = get_dynamic_addr() dynamic_size = dynamic_memsz // 0x10 gnu_hash_addr = None for i in range(dynamic_size): tag = idaapi.get_qword(dynamic_addr + i * 0x10) if tag == 0x6FFFFEF5: gnu_hash_addr = idaapi.get_qword(dynamic_addr + i * 0x10 + 0x8) break if gnu_hash_addr is None: print("No GNU hash found") exit() self.ELFCLASS_BITS = ida_ida.inf_is_64bit() and 64 or 32 self.nbuckets = idaapi.get_dword(gnu_hash_addr) self.symbias = idaapi.get_dword(gnu_hash_addr + 0x4) self.bitmask_nwords = idaapi.get_dword(gnu_hash_addr + 0x8) self.shift = idaapi.get_dword(gnu_hash_addr + 0xC) self.indexes = [idaapi.get_qword(gnu_hash_addr + 0x10 + i * 8) for i in range(self.bitmask_nwords)] self.bucket = [idaapi.get_dword(gnu_hash_addr + 0x10 + self.bitmask_nwords * 8 + i * 4) for i in range(self.nbuckets)] self.chain_addr = gnu_hash_addr + 0x10 + self.bitmask_nwords * 8 + self.nbuckets * 4 i = 0 def get_hash(self, name): h = 5381 for c in name: h = (h << 5) + h + ord(c) h &= 0xFFFFFFFF return h & 0xFFFFFFFF def check(self, hash_value): bit1 = 1 << (hash_value % self.ELFCLASS_BITS) bit2 = 1 << (hash_value >> self.shift) % self.ELFCLASS_BITS mask = self.indexes[hash_value // self.ELFCLASS_BITS % self.bitmask_nwords] return (mask & bit1) and (mask & bit2) def get_idx(self, hash_value): idx = self.bucket[hash_value % self.nbuckets] chain_item = idaapi.get_dword(self.chain_addr + (idx - self.symbias) * 4) while chain_item & 1 == 0: if (chain_item ^ hash_value) >> 1 == 0: return idx idx += 1 chain_item = idaapi.get_dword(self.chain_addr + (idx - self.symbias) * 4) return -1 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() if symtab is None: return None target = symtab + idx * 0x18 + 8 return idaapi.get_qword(target) def main(): name = "iusp9aVAyoMI" addr = find_symbol(name) print("Address of %s: 0x%x" % (name, addr)) if __name__ == "__main__": main() ``` 以CTF赛题为例 -------- 题目来源N1CTF的ezapk,题目本身可以通过Hook等方式轻松解出,但这里以静态分析的方式来进行分析。 首先看java层,没啥逻辑,主要是native层。 ![image-20241119134954082.png](https://shs3.b.qianxin.com/attack_forum/2024/12/attach-ec98af527c7cf1b500f8f22ca0637866b10851eb.png) 在 `libnative1.so` 中 `JNI_Onload` 找到 `method` 表: ![image-20241119135150035.png](https://shs3.b.qianxin.com/attack_forum/2024/12/attach-98d4326f529b64b531fe9254894537557be2f2b0.png) 里面有三个这样子的逻辑进行加密,但不知道 `v10` 具体是什么: ![image-20241119135326771.png](https://shs3.b.qianxin.com/attack_forum/2024/12/attach-4cb5f2bd56c7aab00b2c99091dfcf661eb10582c.png) 然后开始分析: 看 `qword_40F70`怎么来的:`sub_75e17c0540` ![image-20241210201355141.png](https://shs3.b.qianxin.com/attack_forum/2024/12/attach-2e6744a1ea9bc64a551862d3bf4f9038548be77d.png) `/proc/self/maps`大致这样子: ```shell cat /proc/13853/maps | grep libnative2.so 6f6cac0000-6f6cac4000 r-xp 00000000 fc:28 104959 /data/app/~~q5YqQP1HKnoRJ2Foh2PYSg==/com.n1ctf2024.ezapk-L69cpW5lKW-z-LqMNvjOeA==/lib/arm64/libnative2.so 6f6cac4000-6f6cac5000 rw-p 00003000 fc:28 104959 /data/app/~~q5YqQP1HKnoRJ2Foh2PYSg==/com.n1ctf2024.ezapk-L69cpW5lKW-z-LqMNvjOeA==/lib/arm64/libnative2.so ``` 所以应该是获取libnative2.so的起始地址。 然后进入`sub_1B000`: ```c __int64 __fastcall sub_1B000(__int64 base, __int64 out) { number_of_program_header_entries = *(base + 0x38);// base = libnative2.so在内存中的地址 if ( *(base + 0x38) ) { offset_from_file_begin = (*(base + 32) + base + 8);// // base + 0x20 = program_header_offset // base + program_header_offset = program_table_table // program_table_table + 8 = proogram_table_element[0]->offset_from_file_begin do { // offset_from_file_begin - 8 = p_type if ( *(offset_from_file_begin - 2) == 2 ) // PT_DYNAMIC { // Dynamic Segment v4 = 0LL; v5 = 0LL; v6 = 0LL; v7 = 0LL; v8 = 0LL; v9 = 0LL; v10 = 0LL; v11 = 0; v12 = (*offset_from_file_begin + base + 8); ...... } offset_from_file_begin += 7; // 8byte(QWORD) * 7 = 56byte = program_table_element的大小 --number_of_program_header_entries; } while ( number_of_program_header_entries ); } return 0xFFFFFFFFLL; } ``` 通过 010editor 模板对照分析,可以知道他在找 `libnative2.so` 中 `p_flags` 为 `PT_DYNAMIC` 的段,即 `Dynamic Segment`: ![image-20241210213245128.png](https://shs3.b.qianxin.com/attack_forum/2024/12/attach-cd8cd0a128175b1fc5e254300b41b8c771a865dc.png) 在IDA中根据地址`0x41E0`来找到它: - 每个条目代表一个动态链接表项,由`Elf64_Dyn`结构表示,它包含两个字段:一个是类型(d\_tag),另一个是值(d\_un)。 - 这些条目定义了运行时加载器需要的信息来正确加载和链接程序或共享库。 ![image-20241210213328660.png](https://shs3.b.qianxin.com/attack_forum/2024/12/attach-30e5207cec51ec3004488f708024037fbc611853.png) 然后程序遍历每个条目来进行操作: ```c v4 = 0LL; v5 = 0LL; v6 = 0LL; v7 = 0LL; v8 = 0LL; v9 = 0LL; v10 = 0LL; v11 = 0; v12 = (*offset_from_file_begin + base + 8); while ( 1 ) { v13 = *(v12 - 1); switch ( v13 ) { case 0LL: if ( (~v11 & 0xF) != 0 ) return 0xFFFFFFFFLL; v20 = (v8 + base); *(out + 32) = v6; v21 = v7 + base; v22 = v5 + base; *(out + 24) = v20; v23 = *v20++; *(out + 16) = v9 + base; *out = base; *(out + 8) = v10 + base; result = 0LL; *(out + 40) = v23; *(out + 88) = v4; *(out + 96) = v22; v25 = v20 + 8 * DWORD2(v23); *(out + 56) = v20; *(out + 64) = v25; *(out + 72) = &v25[4 * v23]; *(out + 80) = v21; return result; case 1LL: case 4LL: case 7LL: case 8LL: case 9LL: case 11LL: case 12LL: case 13LL: case 14LL: case 15LL: case 16LL: case 17LL: case 18LL: case 19LL: case 20LL: case 21LL: case 22LL: goto LABEL_8; case 2LL: v14 = *v12; v12 += 2; v4 = v14; continue; case 3LL: v5 = *v12; goto LABEL_8; case 5LL: v16 = *v12; v12 += 2; v9 = v16; v11 |= 2u; continue; case 6LL: v17 = *v12; v12 += 2; v10 = v17; v11 |= 1u; continue; case 10LL: v18 = *v12; v12 += 2; v6 = v18; v11 |= 4u; continue; case 23LL: v19 = *v12; v12 += 2; v7 = v19; continue; default: if ( v13 == 0x6FFFFEF5 ) { v15 = *v12; v12 += 2; v8 = v15; v11 |= 8u; } else { LABEL_8: v12 += 2; // 跳到下一个表项 } break; } } ``` 根据 `libnative2.so` 的 `ELF Dynamic Information` ,大致分析后,分析如下: - `case 0`是遍历到的最后一个表项。 - `libnative2.so` 的各个部分拖进IDA都分析好了,可以照着看来确定程序找的是什么。 - 程序在根据动态信息的每一项的 tag 来确定每一项,然后保存其 value。 - 在最后一项 `case 0` 时,进行全局变量的赋值,即把各个表等信息放到全局变量中。 创建个结构体来方便分析: ```C 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; void *none2; void *none3; }; ``` 然后分析后,代码如下: ```c __int64 __fastcall sub_75E1245000(__int64 base, struct dynamic_info *out) { // base = libnative2.so在内存中的地址 number_of_program_header_entries = *(unsigned __int16 *)(base + 0x38); if ( *(_WORD *)(base + 0x38) ) { offset_from_file_begin = (_QWORD *)(*(_QWORD *)(base + 32) + base + 8);// // base + 0x20 = program_header_offset // base + program_header_offset = program_table_table // program_table_table + 8 = proogram_table_element[0]->offset_from_file_begin do { // offset_from_file_begin - 8 = p_type if ( *((_DWORD *)offset_from_file_begin - 2) == 2 )// PT_DYNAMIC { // Dynamic Segment value_0x120 = 0LL; value_0x43b0 = 0LL; value_0x182 = 0LL; value_0x7f0 = 0LL; value_0x588 = 0LL; value_0x608 = 0LL; value_0x288 = 0LL; v11 = 0; v12 = (void **)(*offset_from_file_begin + base + 8); while ( 1 ) { v13 = (__int64)*(v12 - 1); switch ( v13 ) { case 0LL: if ( (~v11 & 0xF) != 0 ) return 0xFFFFFFFFLL; v20 = (__int128 *)&value_0x588[base];// DT_GRU_HASH out->dt_STRSZ = value_0x182; // DT_STRSZ v21 = &value_0x7f0[base]; // DT_JMPREL:Relocation Table v22 = &value_0x43b0[base]; // DT_PLTGOT:_GLOBAL_OFFSET_TABLE_ out->gru_hash_table = v20; *(_OWORD *)elf_gnu_hash_nbuckets = *v20++;// // elf_gnu_hash_nbuckets = *GNU_HASH_TABLE // GNU_HASH_TABLE + 16 = elf_gnu_hash_indexes out->stringTable = &value_0x608[base];// DT_STRTAB:StringTable out->libbase = (void *)base; // // DT_SYMTAB:SymbolTable out->symbolTable = &value_0x288[base]; result = 0LL; *(_OWORD *)&out->gnu_hash_nbuckets = *(_OWORD *)elf_gnu_hash_nbuckets; out->relocationTableSize = value_0x120;// DT_PLTRELSZ out->global_offset_table = v22; elf_gnu_hash_bucket = (char *)v20 + 8 * (unsigned int)elf_gnu_hash_nbuckets[2]; out->gnu_hash_indexes_pointer = v20;// // elf_gnu_hash_bucket out->gnu_hash_bucket = elf_gnu_hash_bucket; out->gnu_hash_chain = &elf_gnu_hash_bucket[4 * elf_gnu_hash_nbuckets[0]]; out->relocationTable = v21; return result; case 1LL: case 4LL: case 7LL: case 8LL: case 9LL: case 11LL: case 12LL: case 13LL: case 14LL: case 15LL: case 16LL: case 17LL: case 18LL: case 19LL: case 20LL: case 21LL: case 22LL: goto LABEL_8; case 2LL: v14 = *v12; v12 += 2; value_0x120 = v14; continue; case 3LL: value_0x43b0 = (char *)*v12; goto LABEL_8; case 5LL: v16 = (char *)*v12; v12 += 2; value_0x608 = v16; v11 |= 2u; continue; case 6LL: v17 = (char *)*v12; v12 += 2; value_0x288 = v17; v11 |= 1u; continue; case 10LL: v18 = *v12; v12 += 2; value_0x182 = v18; v11 |= 4u; continue; case 23LL: v19 = (char *)*v12; v12 += 2; value_0x7f0 = v19; continue; default: if ( v13 == 0x6FFFFEF5 ) { v15 = (char *)*v12; v12 += 2; value_0x588 = v15; v11 |= 8u; } else { LABEL_8: v12 += 2; } break; } } } // 8byte(QWORD) * 7 = 56byte = program_table_element的大小 offset_from_file_begin += 7; --number_of_program_header_entries; } while ( number_of_program_header_entries ); } return 0xFFFFFFFFLL; } ``` 再看`sub_75e17c0540`剩下部分: ```c if ( (int)(nativ2.relocationTableSize / 0x18uLL) < 1 ) { LABEL_10: v10 = 0LL; } else { relocationSize = (unsigned int)(nativ2.relocationTableSize / 0x18uLL); symbolTable = (unsigned int *)nativ2.symbolTable; stringTable = (const char *)nativ2.stringTable; info = nativ2.relocationTable->info; // info[0] -> type // info[1] -> index while ( strcmp(&stringTable[symbolTable[6 * info[1]]], "rand") ) { info += 6; if ( !--relocationSize ) goto LABEL_10; } v10 = *((_QWORD *)info - 1); } v11 = (_QWORD *)(v10 + v5); result = mprotect(v11, 8uLL, 3); *v11 = sub_75E1245140; ``` 这里的流程很明显与上文的通过符号名找共享库中的符号相同。 这里找 `rand` 然后将其替换为 `sub_75E1245140`,使其固定返回 `0x233`。 再折回去看加密的函数,看看调用的那几个函数。 先看第一部分: ```c v4 = a1->functions->GetStringUTFChars(a1, a3, 0LL); v6 = v4; if ( (((1LL << (0xB9F51095uLL >> SLOBYTE(nativ2.gnu_hash_shift))) | 0x200000) & ~nativ2.gnu_hash_indexes_pointer[0x2E7D442u % nativ2.gnu_hash_bitmask_nwords]) == 0 ) { // 检测hash是否存在 v7 = nativ2.gnu_hash_bucket[0xB9F51095 % nativ2.gnu_hash_nbuckets]; if ( v7 ) { v8 = nativ2.gnu_hash_chain[v7 - nativ2.gnu_hash_symbias]; if ( (v8 ^ 0xB9F51094) >= 2 ) { // 检测是否相同(除了最末位) v5 = 1LL; while ( (v8 & 1) == 0 ) { v9 = 1 - nativ2.gnu_hash_symbias + v7; v5 = (unsigned int)++v7; v8 = nativ2.gnu_hash_chain[v9]; if ( (v8 ^ 0xB9F51094) < 2 ) goto LABEL_9; } } else { LODWORD(v5) = nativ2.gnu_hash_bucket[0xB9F51095 % nativ2.gnu_hash_nbuckets]; LABEL_9: v5 = *((_QWORD *)nativ2.symbolTable + 3 * (unsigned int)v5 + 1); } } } v10 = (__int64 (__fastcall *)(const char *, size_t))((char *)nativ2.libbase + v5); v11 = __strlen_chk(v4, 0xFFFFFFFFFFFFFFFFLL); v12 = (const char *)v10(v6, v11); ``` 这里的流程与上文的通过符号名找程序自带的符号类似,但有些不同: - 符号名的 hash 已经计算好了。 - hash 的检测也提前进行了些计算。 其他流程大差不差。 因此,通过简单的静态分析,加上libnative2.so对着看,找到: ![image-20241211171200646.png](https://shs3.b.qianxin.com/attack_forum/2024/12/attach-7e4c814372d807af0ad816f3461d951de99230ec.png) 因此,要寻找的估计为第`15`项,在Symbol Table中找到对应函数:`iusp9aVAyoMI` ![image-20241211171027432.png](https://shs3.b.qianxin.com/attack_forum/2024/12/attach-9e15bf98f97b7bbb031b9df062a9b215ecf70463.png) 接下来同样的方法,找到剩下两个调用的函数: - 第`20`项:`SZ3pMtlDTA7Q` - 第`22`项:`UqhYy0F049n5` 一眼看出三个函数分别为: - `iusp9aVAyoMI`:异或随机值 - `SZ3pMtlDTA7Q`:RC4 - `UqhYy0F049n5`:base64 ```c _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; } _BYTE *__fastcall SZ3pMtlDTA7Q(__int64 a1, int a2) { v20[2] = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40); v16 = malloc(a2); __memcpy_chk(v16, a1, a2, -1LL); v20[1] = 0LL; v20[0] = 0LL; for ( i = 0; i < 16; ++i ) *((_BYTE *)v20 + i) = rand(); for ( j = 0; j <= 255; ++j ) v19[j] = j; v10 = 0; for ( k = 0; k <= 255; ++k ) { v2 = (unsigned __int8)(v10 + v19[k] + *((_BYTE *)v20 + k % 16)); if ( v10 + (unsigned __int8)v19[k] + *((unsigned __int8 *)v20 + k % 16) <= 0 ) v2 = -(unsigned __int8)-(char)(v10 + v19[k] + *((_BYTE *)v20 + k % 16)); v10 = v2; v7 = v19[k]; v19[k] = v19[v2]; v19[v2] = v7; } v14 = 0; v11 = 0; for ( m = 0; m < a2; ++m ) { v3 = (unsigned __int8)(v14 + 1); if ( v14 + 1 <= 0 ) v3 = -(unsigned __int8)-(char)(v14 + 1); v14 = v3; v4 = v11 + (unsigned __int8)v19[v3]; v5 = (unsigned __int8)(v11 + v19[v3]); if ( v4 <= 0 ) v5 = -(unsigned __int8)-(char)v4; v11 = v5; v8 = v19[v3]; v19[v3] = v19[v5]; v19[v5] = v8; v16[m] ^= v19[(unsigned __int8)(v19[v3] + v19[v5])]; } _ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)); return v16; } _BYTE *__fastcall UqhYy0F049n5(__int64 a1, unsigned __int64 a2) { qmemcpy(v23, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", sizeof(v23)); v20 = malloc((4 * ((unsigned int)(((a2 + 2) * (unsigned __int128)0xAAAAAAAAAAAAAAABLL) >> 64) >> 1)) | 1); v19 = 0; v12 = 0; v14 = 0; for ( i = 0; i < a2; ++i ) { v13 = *(_BYTE *)(a1 + i); if ( v19 ) { if ( v19 == 1 ) { v19 = 2; v3 = v14++; v20[v3] = *((_BYTE *)v23 + ((v13 >> 4) & 0xFFFFFFCF | (16 * (v12 & 3)))); } else { v19 = 0; v4 = v14; v15 = v14 + 1; v20[v4] = *((_BYTE *)v23 + ((v13 >> 6) & 0xFFFFFFC3 | (4 * (v12 & 0xF)))); v5 = v15; v14 = v15 + 1; v20[v5] = *((_BYTE *)v23 + (v13 & 0x3F)); } } else { v19 = 1; v2 = v14++; v20[v2] = *((_BYTE *)v23 + ((unsigned __int64)v13 >> 2)); } v12 = v13; } if ( v19 == 1 ) { v6 = v14; v16 = v14 + 1; v20[v6] = v23[v12 & 3]; v7 = v16++; v20[v7] = 61; v8 = v16; v14 = v16 + 1; v20[v8] = 61; } else if ( v19 == 2 ) { v9 = v14; v17 = v14 + 1; v20[v9] = *((_BYTE *)v23 + 4 * (v12 & 0xFu)); v10 = v17; v14 = v17 + 1; v20[v10] = 61; } v20[v14] = 0; _ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)); return v20; } ``` 直接CyberChef解了: ![image-20241211172447918.png](https://shs3.b.qianxin.com/attack_forum/2024/12/attach-e52aee9cfb6d445042675350766e18e16f30dde8.png)
发表于 2025-01-08 09:00:00
阅读 ( 375 )
分类:
二进制
0 推荐
收藏
0 条评论
请先
登录
后评论
Br04e55or
1 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!