引入
本文是我在学习安卓逆向时的一些步骤、心得,逆向的是2016年腾讯游戏安全技术竞赛提供的文件,因为文件比较简单仅仅从静态分析便能找到对应的注册码。
所用到的工具有: IDA、CodeBlocks。
逆向步骤
首先先看下程序运行结果,如下图所示:
这是一个输入name和password的程序,我们输入后会出现check failed的提示,那么问题就简单了,猜想:是把验证放到了native层,下面通过静态分析来验证。
将apk文件中的libCheckRegister.so拖入到ida中,打开的图片如下所示。
随便在函数列表翻翻,便可发现一个奇怪的函数Java_com_tencent_tencent2016a_MainActivity_NativeCheckRegister,它的命名规则刚好符合Android Native层命名规则,之前的猜想成立,真的就是把验证放到了native层,这个就是我们要分析的函数。
双击函数跳转到相应的ARM汇编代码如下图所示
按F5反编译得到对应C代码,如下图所示。
发现传进去的参数有点不太对,第一个应该是JNIEnv,第二个是jobject或者jclass,因此我们在Structures视图中加载并修改下数据类型,修改后结果如下图所示:
然后返回到C代码中在相应参数上按Y修改参数类型,可以发现下面函数中对应的函数名已经找到,修改四个参数类型以及名字以便阅读,如下图所示:
很显然,sub_1634(v7, v8)这个函数就是调用验证的函数, 点进去。分析这个函数,显然返回0为失败,返回1为成功,返回结果放在result变量中。
bool __fastcall sub_1634(char *cname, char *cpw)
{
char *v2; // r6
int v3; // r5
_BOOL4 result; // r0
int v5; // r4
char *v6; // r7
int v7; // r3
int v8; // r4
int v9; // r4
int v10; // r1
char *v11; // [sp+Ch] [bp-464h]
int v12; // [sp+18h] [bp-458h]
int v13; // [sp+1Ch] [bp-454h]
int v14; // [sp+20h] [bp-450h]
int v15; // [sp+24h] [bp-44Ch]
int v16; // [sp+28h] [bp-448h]
char v17[4]; // [sp+2Ch] [bp-444h]
int v18; // [sp+30h] [bp-440h]
int v19; // [sp+34h] [bp-43Ch]
int v20; // [sp+38h] [bp-438h]
int v21; // [sp+3Ch] [bp-434h]
char v22[20]; // [sp+40h] [bp-430h]
char v23[936]; // [sp+54h] [bp-41Ch]
v2 = cpw;
v11 = cname;
v3 = j_strlen();
if ( (unsigned int)(v3 - 6) > 0xE )
return 0;
j_memset(v22, 0, 20);
v5 = 0;
do
{
v6 = &v22[v5];
v7 = (unsigned __int8)v11[v5 % v3] * (v5 + 20160126) * v3;
++v5;
*(_DWORD *)v6 += v7;
}
while ( v5 != 16 );
j_memset(v23, 0, 1024);
if ( sub_146C(v2) > 1024 )
return 0;
v8 = sub_1498(v23, v2);
if ( v8 != 20 )
return 0;
j_memset(&v12, 0, 20);
j_memset(v17, 0, 20);
v9 = 0;
do
{
v10 = *(_DWORD *)&v23[v9];
*(int *)((char *)&v12 + v9) = *(_DWORD *)&v22[v9] / 10;
*(_DWORD *)&v17[v9] = v10;
v9 += 4;
}
while ( v9 != 20 );
result = 0;
if ( v21 + v12 == v19 && v21 + v12 + v13 == 2 * v21 && v14 + v20 == *(_DWORD *)v17 && v14 + v20 + v15 == 2 * v20 )
result = (unsigned int)(v16 + v18 - 3 * v14) <= 0;
return result;
}
从(cname_length - 6) > 0xE 中可以看出名字的长度需要在[6, 20]间,将函数变量稍微处理处理得到以下代码:
bool __fastcall sub_1634(char *cname, char *cpw)
{
char *pw_copy; // r6
signed int cname_length; // r5
_BOOL4 result; // r0
int i; // r4
char *tmp; // r7
int v7; // r3
int pw_encode_length; // r4
int j; // r4
int *temp; // r1
char *name_copy; // [sp+Ch] [bp-464h]
int *v12; // [sp+18h] [bp-458h]
int v13; // [sp+1Ch] [bp-454h]
int v14; // [sp+20h] [bp-450h]
int v15; // [sp+24h] [bp-44Ch]
int v16; // [sp+28h] [bp-448h]
int *v17; // [sp+2Ch] [bp-444h]
int v18; // [sp+30h] [bp-440h]
int v19; // [sp+34h] [bp-43Ch]
int v20; // [sp+38h] [bp-438h]
int v21; // [sp+3Ch] [bp-434h]
char name_encode[20]; // [sp+40h] [bp-430h]
char pw_encode[936]; // [sp+54h] [bp-41Ch]
pw_copy = cpw;
name_copy = cname;
cname_length = j_strlen(cname);
if ( (cname_length - 6) > 0xE )
return 0;
j_memset(name_encode, 0, 20);
i = 0;
do
{
tmp = &name_encode[i];
v7 = name_copy[i % cname_length] * (i + 20160126) * cname_length;
++i;
*tmp += v7;
}
while ( i != 16 );
j_memset(pw_encode, 0, 1024);
if ( sub_146C(pw_copy) > 1024 )
return 0;
pw_encode_length = sub_1498(pw_encode, pw_copy);
if ( pw_encode_length != 20 )
return 0;
j_memset(&v12, 0, 20);
j_memset(&v17, 0, 20);
j = 0;
do
{
temp = *&pw_encode[j * 4];
(&v12)[j] = (*&name_encode[j * 4] / 10);
(&v17)[j] = temp;
++j;
}
while ( j != 5 );
result = False;
if ( (v12 + v21) == v19 && (v12 + v21 + v13) == (2 * v21) && (v14 + v20) == v17 && v14 + v20 + v15 == 2 * v20 )
result = (v16 + v18 - 3 * v14) <= 0;
return result;
那么问题就变得简单了,知道name怎么转化成name_encode,通过最后的if判断将name_endoce转化成pw_encode再将pw_encode转化成pw就行了。前面的转化通过代码可简单实现,最后一步需关注sub_146C(pw_copy)和sub_1498(pw_encode, pw_copy)函数,在代码中可以很清楚的看到加载一个表, 因此在逆向时需要将这个表写入到代码中,如下图所示。
通过简单的打印输出便可找到相应的对应关系。如下图所示。
(因时间有限,我写的C++逆向代码还有bug就不贴了,下周补上。未完待续…)