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
(使用时需加上基地址)
其结构一般如下:
ELF 文件可以包含 GNU_HASH
表,用于优化符号查找,与传统的 .hash
表相比,GNU_HASH
表提供了更高效的符号查找算法。
其组成部分:
nbuckets
bucket
)的数量。symbias
symbias
的值。也就是说,symbias
前的符号通常是局部符号,不被动态链接器处理。bitmask_nwords
shift
indexes
bucket
chain
根据上文找到 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()
重定位表用于描述程序在加载时需要调整的地址信息。
ELF 文件中的重定位表主要有以下两种:
.rel
表:不带附加的值。.rela
表:包含附加的重定位值。重定位条目:
r_offset
:重定位目标的内存地址,表示需要修正的地址。r_info
:由符号表索引和重定位类型编码的值。
r_addend
:附加值,与重定位类型相关。根据上文找到 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
表。GOT 是动态链接中的一个关键表,用于存储每个动态库函数的实际地址,供程序直接调用,以避免多次动态解析。
GOT 表的每个条目都是一个指向函数的指针。
动态链接器在运行时解析符号表,将函数的实际地址填入此条目。
根据上文找到 GLOBAL_OFFSET_TABLE
:
字符串表是 ELF 文件中的一段,用于存储符号名和其他字符串。
\x00
字符分隔字符串。符号表存储了程序中的符号信息,例如变量、函数名等。
符号条目内容:
st_name (4 bytes)
: 符号名在 String Table 中的偏移。st_info (1 byte)
:
STT_NOTYPE
:0STT_OBJECT
:1STT_FUNC
:2STT_SECTION
:3Symbol Binding
。STB_LOCAL
:0STB_GLOBAL
:1st_other (1 byte)
:指定了符号的可见性。
STV_DEFAULT
:0STV_INTERNAL
:1STV_HIDDEN
:2STV_PROTECTED
:3st_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 的偏移。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()
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()
题目来源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都分析好了,可以照着看来确定程序找的是什么。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);
这里的流程与上文的通过符号名找程序自带的符号类似,但有些不同:
其他流程大差不差。
因此,通过简单的静态分析,加上libnative2.so对着看,找到:
因此,要寻找的估计为第15
项,在Symbol Table中找到对应函数:iusp9aVAyoMI
接下来同样的方法,找到剩下两个调用的函数:
20
项:SZ3pMtlDTA7Q
22
项:UqhYy0F049n5
iusp9aVAyoMI
:异或随机值SZ3pMtlDTA7Q
:RC4UqhYy0F049n5
: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解了:
1 篇文章
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!