其他
记一次so文件动态解密
本文为看雪论坛优秀文章
看雪论坛作者ID:小白abc
本文为看雪安卓高研2w班(3月班)优秀学员作品。
这道题是对so中函数的一个保护,在被保护的函数的关键逻辑执行前进行解密,执行结束后会再次对关键逻辑进行加密。
因此,正确的时机便是关键逻辑被解密后,抓住这一点便能成功拿到flag。
整个程序基本上就是一个 动态注册 + so函数加密 的逻辑,中间加了一些parser的东西。 主要考察了elf文件结构的一些知识以及在攻防对抗中防止IDA静态分析的姿势。
ps. 题目附件请点击“阅读原文”下载。
现在,看雪《安卓高级研修班(网课)》9月班开始招生啦!点击查看详情报名吧~
题目描述
找到flag。
WriteUp
unsigned int datadiv_decode4192348989750430380()
{
v29 = 0;
do
{
v0 = v29;
Find_ooxx_failed[v29++] ^= 0x14u;
}
while ( v0 < 0x10 );
v28 = 0;
do
{
v1 = v28;
mem_privilege_change_failed[v28++] ^= 0xD3u;
}
while ( v1 < 0x1B );
v27 = 0;
do
{
v2 = v27;
kanxuetest[v27++] ^= 0x63u;
}
while ( v2 < 0xA );
v26 = 0;
do
{
v3 = v26;
Hello_from_Cjiajia[v26++] ^= 0x3Fu;
}
while ( v3 < 0xE );
v25 = 0;
do
{
v4 = v25;
test[v25++] ^= 0xF3u;
}
while ( v4 < 4 );
v24 = 0;
do
{
v5 = v24;
sig_Ljava_lang_Object_Z[v24++] ^= 0xFAu;
}
while ( v5 < 0x15 );
v23 = 0;
do
{
v6 = v23;
com_kanxue_test_MainActivity[v23++] ^= 0x2Du;
}
while ( v6 < 0x1C );
v22 = 0;
do
{
v7 = v22;
maps[v22++] ^= 0xF5u;
}
while ( v7 < 0xD );
v21 = 0;
do
{
v8 = v21;
r[v21++] ^= 0xF8u;
}
while ( !v8 );
v20 = 0;
do
{
v9 = v20;
open_failed[v20++] ^= 0xE6u;
}
while ( v9 < 0xB );
v19 = 0;
do
{
v10 = v19;
heng[v19++] ^= 0x66u;
}
while ( !v10 );
v18 = 0;
do
{
v11 = v18;
Find__dynamic_segment[v18++] ^= 0x2Du;
}
while ( v11 < 0x15 );
v17 = 0;
do
{
v12 = v17;
Find_needed__section_failed[v17++] ^= 9u;
}
while ( v12 < 0x1C );
v16 = 0;
do
{
v13 = v16;
basic_string[v16++] ^= 0x9Eu;
}
while ( v13 < 0xC );
v15 = 0;
do
{
result = v15;
allocate_exceeds_maximum_supported_size[v15++] ^= 0xDBu;
}
while ( result < 0x43 );
return result;
}
int __fastcall find_symbol_value_and_size(int base_addr, char *a2, _DWORD *a3)
{
int v3; // ST38_4
_DWORD *ELF_Hash_Table; // ST28_4
unsigned int v5; // ST20_4
int elf_hash_chain; // [sp+14h] [bp-5Ch]
int ELF_Symbol_Table; // [sp+24h] [bp-4Ch]
int elf_hash_table; // [sp+28h] [bp-48h]
int string_table; // [sp+2Ch] [bp-44h]
int elf_symbol_table; // [sp+30h] [bp-40h]
_DWORD *v12; // [sp+34h] [bp-3Ch]
int dynamic_segment_base_addr; // [sp+40h] [bp-30h]
_DWORD *header_table; // [sp+44h] [bp-2Ch]
signed int i; // [sp+4Ch] [bp-24h]
unsigned int j; // [sp+4Ch] [bp-24h]
int elf_hash_bucket; // [sp+4Ch] [bp-24h]
char v18; // [sp+57h] [bp-19h]
char v19; // [sp+57h] [bp-19h]
char v20; // [sp+57h] [bp-19h]
_DWORD *value; // [sp+58h] [bp-18h]
char *s2; // [sp+5Ch] [bp-14h]
int so_base_addr; // [sp+60h] [bp-10h]
so_base_addr = base_addr;
s2 = a2;
value = a3;
v18 = -1;
header_table = (base_addr + *(base_addr + 0x1C));// header_table_offset
for ( i = 0; i < *(base_addr + 0x2C); ++i ) // *(base_addr + 0x2C) = 8
{
if ( *header_table == 2 )
{
v18 = 0;
puts_0(); // find_dynamic_segment
break;
}
header_table += 8;
}
if ( v18 )
goto LABEL_27;
dynamic_segment_base_addr = header_table[2] + so_base_addr;// 找到dynamic_segment的虚拟地址
v19 = 0;
for ( j = 0; j < header_table[4] >> 3; ++j )
{
v12 = (dynamic_segment_base_addr + 8 * j);
if ( *(dynamic_segment_base_addr + 8 * j) == 6 )
{
elf_symbol_table = v12[1]; // 0x1f0
++v19;
}
if ( *v12 == 4 )
{
elf_hash_table = v12[1]; // 0x46e0
v19 += 2;
}
if ( *v12 == 5 )
{
string_table = v12[1]; // 0x1d00
v19 += 4;
}
if ( *v12 == 10 )
{
v3 = v12[1]; // 0x1eb6
v19 += 8;
}
}
if ( (v19 & 0xF) != 0xF )
{
puts_0();
LABEL_27:
return -1;
}
ELF_Hash_Table = (so_base_addr + elf_hash_table);// v4 =elf_hash_table
v5 = turn_ooxx(s2); // v5 = 0x766f8
ELF_Symbol_Table = so_base_addr + elf_symbol_table;// ELF Symbol Table
elf_hash_chain = &ELF_Hash_Table[*ELF_Hash_Table + 2];
v20 = -1;
for ( elf_hash_bucket = ELF_Hash_Table[v5 % *ELF_Hash_Table + 2];// ELF_Hash_Table[v5 % *ELF_Hash_Table + 2] = 0x4918
elf_hash_bucket;
elf_hash_bucket = *(elf_hash_chain + 4 * elf_hash_bucket) )
{
if ( !strcmp((so_base_addr + string_table + *(ELF_Symbol_Table + 16 * elf_hash_bucket)), s2) )// string_table[] = "ooxx"
{
v20 = 0;
break;
}
}
if ( v20 )
goto LABEL_27;
*value = *(ELF_Symbol_Table + 16 * elf_hash_bucket + 4);
value[1] = *(ELF_Symbol_Table + 16 * elf_hash_bucket + 8);
return 0;
}
2. 第二块,还原ooxx函数中code
3. 第三块,恢复内存页为r-x
这里使用objection在动态运行时dump出对应内存中的数据:
def patchBytes(addr,length):
for i in range(0,length):
byte=get_bytes(addr + i,1)
byte = ord(byte) ^ (i%0xff)
patch_byte(addr+i,byte)
patchBytes(0x8e00,0x8fd0-0x8e00)
int __fastcall ooxx(JNIEnv *a1, int a2, int a3)
{
JNIEnv *v3; // ST20_4
int input; // r0
int v5; // r0
unsigned __int8 v7; // [sp+17h] [bp-19h]
v3 = a1;
sub_8930(); //
v7 = 0;
input = getStringUtf(v3);
if ( input )
{
input = strcmp(aKanxuetest, input);
if ( !input )
{
input = 1;
v7 = 1;
}
}
v5 = *(input + 8);
sub_8930();
return v7;
}
后记
另外,这个程序有点类似于之前寒冰师傅说的在函数执行开始之前对函数内容进行恢复,函数执行结束时再还原回加密状态,再加上插入了一堆MOV R0, R0这种无效代码,让我感觉真像so层的"函数抽取壳"的实现。神奇的题目。
看雪ID:小白abc
https://bbs.pediy.com/user-715334.htm
*本文由看雪论坛 小白abc 原创,转载请注明来自看雪社区。
推荐文章++++
好书推荐