ELF动态信息
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(使用时需加上基地址)
其结构一般如下
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 的地址:
通过 GNU_HASH Table 的信息,可以找到符号在 Symbol Table 中的索引。
其具体算法如下:(用Python代码作为演示)
main 的代码仅为示例,可知 iusp9aVAyoMI 为 Symbol 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:
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:
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:
实践一下
通过符号名找共享库中的符号
根据 Elf64_Dyn <0x17, 0x7F0> ; DT_JMPREL、Elf64_Dyn <2, 0x120> ; DT_PLTRELSZ 获取 JMPREL Relocation Table的偏移和大小。
根据 Elf64_Dyn <6, 0x288> ; DT_SYMTAB 获取 Symbol Table 的偏移。
根据 Elf64_Dyn <5, 0x608> ; DT_STRTAB 获取 String Table 的偏移。
遍历 JMPREL Relocation Table:
获取表项第 2 项的高 32 位,即其在 Symbol Table 中的索引。
根据索引获取 Symbol Table 的相应的符号信息。
根据符号信息找到符号名在 String Table中的索引。
判断符号名是否为目标符号名:
若相同,则符号地址为 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()
通过符号名找程序自带的符号
计算 hash。
检测 hash 是否存在。
根据 hash 寻找其在 Symbol Table 的索引。
根据索引获取其地址。
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层。
在 libnative1.so 中 JNI_Onload 找到 method 表:
里面有三个这样子的逻辑进行加密,但不知道 v10 具体是什么:
然后开始分析:
看 qword_40F70怎么来的:sub_75e17c0540
/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.so 中 p_flags 为 PT_DYNAMIC 的段,即 Dynamic Segment:
在IDA中根据地址0x41E0来找到它:
每个条目代表一个动态链接表项,由Elf64_Dyn结构表示,它包含两个字段:一个是类型(d_tag),另一个是值(d_un)。
这些条目定义了运行时加载器需要的信息来正确加载和链接程序或共享库。
然后程序遍历每个条目来进行操作:
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 时,进行全局变量的赋值,即把各个表等信息放到全局变量中。
创建个结构体来方便分析:
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对着看,找到:
因此,要寻找的估计为第15项,在Symbol Table中找到对应函数:iusp9aVAyoMI
接下来同样的方法,找到剩下两个调用的函数:
第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解了: