详解ELF动态信息

了解ELF动态信息,了解各个表之间的联系,并通过一题CTF题目进行实践。

ELF动态信息

ELF 动态信息是存储在 ELF 文件中的一个段(通常是 .dynamic 段),用于描述动态链接器运行时所需的信息。
.dynamic段包含了动态链接所需的各种信息,例如:

  • 动态链接库的路径 (DT_NEEDED)。
  • 动态符号表的位置 (DT_SYMTAB)。
  • 字符串表的位置 (DT_STRTAB)。
  • 动态重定位表的位置 (DT_RELDT_RELA)。
  • GOT 表的地址 (DT_PLTGOT)。
  • 哈希表信息 (DT_HASHDT_GNU_HASH)。

每条信息是一个 Elf32_DynElf64_Dyn 结构,包含两个字段:

  • d_tag: 类型(例如 DT_NEEDEDDT_SYMTAB 等)。
  • d_val: 值,可能是地址、大小或其他信息。

其具体位置可通过010editor解析获取:找program_header_table中的Dunamic Segment
VIRTUAL_ADDRE 得到地址 0x41E0(使用时需加上基地址)
image-20241211191903072.png
其结构一般如下:
image-20241211190958956.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
通过 GNU_HASH Table 的信息,可以找到符号在 Symbol Table 中的索引。
其具体算法如下:(用Python代码作为演示)

  • main 的代码仅为示例,可知 iusp9aVAyoMISymbol Table 的第 15 个元素。
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

  • 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

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

实践一下

通过符号名找共享库中的符号

  1. 根据 Elf64_Dyn <0x17, 0x7F0> ; DT_JMPRELElf64_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 代码如下:

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 代码如下:

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

libnative1.soJNI_Onload 找到 method 表:
image-20241119135150035.png

里面有三个这样子的逻辑进行加密,但不知道 v10 具体是什么:
image-20241119135326771.png

然后开始分析:
qword_40F70怎么来的:sub_75e17c0540
image-20241210201355141.png

/proc/self/maps大致这样子:

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

__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.sop_flagsPT_DYNAMIC 的段,即 Dynamic Segment
image-20241210213245128.png

在IDA中根据地址0x41E0来找到它:

  • 每个条目代表一个动态链接表项,由Elf64_Dyn结构表示,它包含两个字段:一个是类型(d_tag),另一个是值(d_un)。
    • 这些条目定义了运行时加载器需要的信息来正确加载和链接程序或共享库。

image-20241210213328660.png

然后程序遍历每个条目来进行操作:

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.soELF Dynamic Information ,大致分析后,分析如下:

  • case 0是遍历到的最后一个表项。
  • libnative2.so 的各个部分拖进IDA都分析好了,可以照着看来确定程序找的是什么。
  • 程序在根据动态信息的每一项的 tag 来确定每一项,然后保存其 value。
  • 在最后一项 case 0 时,进行全局变量的赋值,即把各个表等信息放到全局变量中。

创建个结构体来方便分析:

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;
};

然后分析后,代码如下:

__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剩下部分:

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
再折回去看加密的函数,看看调用的那几个函数。
先看第一部分:

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

因此,要寻找的估计为第15项,在Symbol Table中找到对应函数:iusp9aVAyoMI
image-20241211171027432.png

接下来同样的方法,找到剩下两个调用的函数:

  • 20项:SZ3pMtlDTA7Q
  • 22项:UqhYy0F049n5
    一眼看出三个函数分别为:
  • iusp9aVAyoMI:异或随机值
  • SZ3pMtlDTA7Q:RC4
  • UqhYy0F049n5:base64
_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

  • 发表于 2025-01-08 09:00:00
  • 阅读 ( 1022 )
  • 分类:二进制

0 条评论

请先 登录 后评论
Br04e55or
Br04e55or

1 篇文章

站长统计