书接上回,本次对前面解密后的so进行分析
来到start函数
进入主函数 sub_9960
首先创建本地socket,用于通信,然后while循环中,不断读取java层的消息,执行对应的功能函数。
创建socket的代码如下:
void __fastcall sub_9960(int a1, void **a2)
{
......
......
port = atoi(&s);
v6 = vars;
socket = sub_A258("127.0.0.1", port);
v8 = vars;
*(_DWORD *)(v6 + 4) = socket;
sub_A064(*(_DWORD *)(v8 + 4) > 0);
pid = getppid();
pid2 = pid;
循环处理的功能子函数如下:
void __fastcall sub_9960(int a1, void **a2)
{
......
......
do
{
v16 = (_BYTE *)*v13;
++v13;
v28 = *v16;
v17 = (const char **)sub_9F40();
v18 = v17;
switch ( v28 )
{
case 35:
sub_A198(*(_DWORD *)(vars + 4), v29);
break;
default:
break;
case 72:
v28 = *(_DWORD *)(vars + 4);
v24 = hook(vars, v17, v31);
sub_A0B8(v28, (int)v24, 72);
break;
case 73:
v28 = *(_DWORD *)(vars + 4);
v23 = sub_ABF4();
sub_A0B8(v28, v23, 73);
break;
case 98:
v28 = *(_DWORD *)(vars + 4);
v22 = sub_AC84();
sub_A0B8(v28, v22, 98);
break;
case 99:
v28 = *(_DWORD *)(vars + 4);
v21 = sub_AAFC();
sub_A0B8(v28, v21, 99);
break;
case 112:
v28 = *(_DWORD *)(vars + 4);
v20 = sub_AB34();
sub_A0B8(v28, v20, 112);
break;
case 113:
sub_AA90(vars);
return;
case 116:
sub_B144(v17);
break;
case 117:
v28 = *(_DWORD *)(vars + 4);
v19 = sub_9FF0() != 0;
sub_A0B8(v28, v19, 117);
break;
}
++v12;
}
while ( v12 != v15 );
if ( !v18 )
goto LABEL_3;
free(v18);
v31 = 0;
memset(&buf, 0, 0x200u);
socket2 = *(_DWORD *)(vars + 4);
if ( socket2 > 0 )
goto LABEL_4;
}
}
else
{
LABEL_4:
if ( recv(socket2, &buf, 0x200u, 0) > 0 )
goto LABEL_5;
}
sub_AA90(vars);
}
函数首先进入的是hook函数,该函数名是我自己修改的。
本次,我们主要分析该so函数的核心,hook的原理及实现。
来到hook函数,如下:
const char *__fastcall hook(int a1, const char **a2, int a3)
{
int v3; // r4@1
const char *result; // r0@2
int v5; // r0@3
int v6; // r2@3
unsigned int v7; // r3@5
v3 = a1;
if ( a3 )
{
result = *a2;
if ( *a2 )
{
v5 = atoi(result);
v6 = *(_DWORD *)(v3 + 16);
*(_DWORD *)v3 = v5;
if ( v5 == v6 )
{
result = (const char *)1;
}
else
{
result = (const char *)hookmain(v5, (_DWORD *)(v3 + 12), dword_11B08);
if ( result )
{
result = 0;
}
else
{
v7 = *(_DWORD *)(v3 + 12);
if ( v7 > 0x8000 && v7 != -1 )
{
result = (const char *)1;
*(_DWORD *)(v3 + 16) = *(_DWORD *)v3;
}
}
}
}
}
else
{
result = 0;
}
return result;
}
继续跟进
int __fastcall hook(int a1, _DWORD *a2, int a3)
{
_DWORD *v3; // r5@1
int v4; // r6@1
int v5; // r7@1
int v6; // r5@2
char *v7; // r3@4
char v9; // [sp+8h] [bp-80h]@1
__mode_t mode; // [sp+18h] [bp-70h]@3
v3 = a2;
v4 = a1;
v5 = a3;
if ( stat(timescale_path[0], (struct stat *)&v9) )
{
v6 = -1;
}
else
{
*(_DWORD *)_errno() = 0;
chmod(timescale_path[0], (mode | 4) & 0xFFFF);
if ( v5 )
v7 = "1";
else
v7 = "0";
v6 = inject(v4, timescale_path[0], "init", v7, v3);
chmod(timescale_path[0], (unsigned __int16)mode);
}
return v6;
}
可以看到,首先会修改文件的权限,然后进行注入。注意里面的函数名都是我分析后,自己修改过的
来到inject函数,代码片段如下:
v21 = src + 15360;
v22 = get_remote_addr(v10, linker_path[0], (int)&dlopen);
v23 = get_remote_addr(v10, linker_path[0], (int)&dlsym);
v24 = get_remote_addr(v10, linker_path[0], (int)&dlclose);
dlopen_addr_s = v22;
dlsym_addr_s = v23;
dlclose_addr_s = v24;
strcpy((char *)&unk_110DC, v28);
dlopen_param1_s = (char *)&unk_110DC + v21 - (_DWORD)&inject_start_s;
strcpy((char *)&unk_111DC, v5);
dlsym_param2_s = (char *)&unk_111DC + v21 - (_DWORD)&inject_start_s;
saved_hook_addr = 0x4A0 + v21;
saved_cpsr_s = v56;
unk_112DC = *(_DWORD *)&dest;
unk_112E0 = v41;
unk_112E4 = v42;
unk_112E8 = v43;
unk_112EC = v44;
unk_112F0 = v45;
unk_112F4 = v46;
unk_112F8 = v47;
unk_112FC = v48;
unk_11300 = v49;
unk_11304 = v50;
unk_11308 = v51;
unk_1130C = v52;
unk_11310 = v53;
unk_11314 = v54;
unk_11318 = v55;
saved_r0_pc_s = 0x2A0 + v21;
v25 = strlen(s);
memcpy(&unk_113DC, s, v25 + 1);
inject_function_param_s = 0x3A0 + v21;
write_remote_memory_main(v10, v21, &inject_start_s, 0x600u);
memcpy(&src, &dest, 0x48u);
v38 = v21;
v39 = v21;
v26 = sub_B410(v10, &src);
sub_B5EC(v10);
前面为获取dlopen等系统函数的地址,这个没什么可说的,不明白的,建议上网找下hook注入方面的文章看看。
来到修改内存的函数write_remote_memory_main,如下:
signed int __fastcall write_remote_memory_main(int pid, int remote_clock_gettime, void *pMem, unsigned int length)
{
int remote_clock_gettime1; // r11@1
int pMem1; // r9@1
unsigned int length1; // r4@1
int pid1; // r8@1
unsigned int v9; // r10@3
int v10; // r7@3
int v11; // r4@4
int v12; // r5@4
int v13; // r5@6
int v14; // r3@8
int v15; // [sp+4h] [bp-34h]@3
__int32 dest; // [sp+Ch] [bp-2Ch]@5
remote_clock_gettime1 = remote_clock_gettime;
pMem1 = (int)pMem;
length1 = length;
pid1 = pid;
if ( write_remote_memory1(pid, pMem, length, remote_clock_gettime) == -1 )// 第一种写入内存方式失败,则执行第二种写入内存的方式,采用的是ptrace方式写入
{
v9 = length1 >> 2; // v9 = 2
v10 = remote_clock_gettime1;
v15 = length1 & 3;
if ( length1 >> 2 )
{
v11 = remote_clock_gettime1;
v12 = 0;
do
{
memcpy(&dest, (const void *)(v11 + pMem1 - remote_clock_gettime1), 4u);
++v12;
ptrace(PTRACE_POKETEXT, pid1, v11, dest);// int ptrace(int request, int pid, int addr, int data);
// PTRACE_POKETEXT, PTRACE_POKEDATA往内存地址中写入一个字节。内存地址由addr给出。
v11 += 4;
}
while ( v12 != v9 );
v13 = 4 * v12; // hook的前8个字节
v10 = remote_clock_gettime1 + v13;
pMem1 += v13;
}
if ( v15 )
{
dest = ptrace(PTRACE_PEEKTEXT, pid1, v10, 0);// 从内存地址中读取一个字节
v14 = 0;
do
{
*((_BYTE *)&dest + v14) = *(_BYTE *)(pMem1 + v14);
++v14;
}
while ( v14 != v15 );
ptrace(PTRACE_POKETEXT, pid1, v10, dest);
}
}
return 1;
}
其中采用了两种方式写入内存,分别为:利用修改/proc/pid/mem实现和利用ptrace实现。
下面看下第一种方式的实现:
ssize_t __fastcall write_remote_memory1(int pid, const void *pMem, size_t length, __off_t remote_clock_gettime)
{
size_t v4; // r5@1
const void *v5; // r6@1
__off_t v6; // r7@1
int fd; // r0@1
int v8; // r8@1
ssize_t v9; // r5@2
ssize_t result; // r0@4
char s; // [sp+4h] [bp-34h]@1
int v12; // [sp+1Ch] [bp-1Ch]@1
v4 = length;
v5 = pMem;
v6 = remote_clock_gettime;
v12 = _stack_chk_guard;
snprintf(&s, 0x18u, "/proc/%u/mem", pid);
fd = open(&s, 1);
v8 = fd;
if ( fd == -1 )
{
v9 = -1;
}
else
{
lseek(fd, v6, 0);
v9 = write(v8, v5, v4);
close(v8);
}
result = v9;
if ( v12 != _stack_chk_guard )
_stack_chk_fail(v9);
return result;
比较简单,直接修改/proc/pid/mem实现。
第二种方式,是通过inline hook系统函数的前8个字节实现的。
其实,还有第三种方式的,有时间再详细介绍吧