【题目分析】【吾爱破解2014CrackMe大赛】
注意:只是分析,没有爆破或者注册机!这个Android CM真的太让人头疼了,作者做了很足了反调试和静态分析处理,对so文件都进行了加密处理,分析的太累了,估计在今天20:00之前无法爆破完成,更不用说写注册机了,发此帖就算是记录一下分析过程吧,也供大家以后进行分析。
用到的工具: IDA ARM v6.5, Android逆向助手, Android开发环境Eclipse, 等等(工具使用方法和环境就不细说了)
App包含2个lib文件:libverify.so, libmodule.so
首先先解密so文件:
1. libverify.so
导出了2个API : init_verify,verify
函数init_verify执行时会解密部分加密的数据,包括JNI_OnLoad函数,解密完成后再执行JNI_OnLoad进行初始化工作.
加密的算法还没有研究,简单的办法是用IDA附加运行后的Android App,然后找到libverify.so模块起始地址,对内存进行DUMP,这样出来的so文件就是解密后的,可以用IDA静态分析了。
下面是部分JNI_OnLoad的代码:
int __fastcall JNI_OnLoad(int a1)
{
v21 = a1;
v22 = 0;
if(sub_12A0())
return -1;
v1 = sub_13CC();
stat("/data/data/com.example.crackme52/lib/libmodule.so", (struct stat *)&v23);
dword_5048 = ((((_DWORD)v24 << 20) - (((_DWORD)v24 << 20 >= 1u) + ((_DWORD)v24 << 20) - 1) + (unsigned int)(v24 / 4096)) << 12)
+ 4096;
v2 = (void *)open("/data/data/com.example.crackme52/lib/libmodule.so", 0);
dword_504C = (int)v2;
if(v2 != (void *)-1)
{
v2 = mmap(0, dword_5048, 7, 34, 0, 0);
if(v2 == (void *)-1)
{
v3 = (int)"JNITag";
v4 = (int)"Map load error\n";
goto LABEL_6;
}
v5 = sub_12A0();
if(!v5)
{
v6 = *(_WORD *)(v1 + 44);
v7 = v1 + *(_DWORD *)(v1 + 28);
while(v5 < v6 && *(_DWORD *)v7 != 1)
{
v7 += 32;
++v5;
}
if(v5 != v6)
{
v8 = *(_DWORD *)(v7 + 40) + *(_DWORD *)(v7 + 48) - 4 + v1;
v9 = (const void *)(v8 - *(_DWORD *)v8);
memcpy(v2, (const void *)(v8 - *(_DWORD *)v8), 0x174u);
v10 = 0;
do
{
*((_BYTE *)v2 + v10) = ~(*((_BYTE *)v2 + v10) - 3);
++v10;
} while(v10 != 372);
v11 = 0;
v12 = (int)((char *)v2 + *((_DWORD *)v2 + 7));
while(v11 < *((_WORD *)v2 + 22) && *(_DWORD *)v12 != 1)
{
v12 += 32;
++v11;
}
memcpy(v2, v9, *(_DWORD *)(v12 + 16));
for(i = 0; i < *(_DWORD *)(v12 + 16); ++i)
*((_BYTE *)v2 + i) = ~(*((_BYTE *)v2 + i) - 3);
memcpy((char *)v2 + *(_DWORD *)(v12 + 40), (char *)v9 + *(_DWORD *)(v12 + 36), *(_DWORD *)(v12 + 48));
v14 = 0;
while(v14 < *(_DWORD *)(v12 + 48))
{
*((_BYTE *)v2 + v14 + *(_DWORD *)(v12 + 40)) -= 3;
v15 = (int)((char *)v2 + v14++ + *(_DWORD *)(v12 + 40));
*(_BYTE *)v15 = ~*(_BYTE *)v15;
}
}
*(_DWORD *)"ng;Ljava/lang/String;)V" = v2;
sub_1190();
*(_DWORD *)off_4F64 = (char *)v2
+ *(_DWORD *)(*(_DWORD *)"java/lang/String;)V" + 16 * sub_136C("begin_verify") + 4);
v16 = sub_136C("__stack_chk_guard");
*(_DWORD *)((char *)v2 + sub_127C(*(_DWORD *)"ring;)V", *(_DWORD *)";)V", v16)) = dword_5040;
v17 = sub_136C("memcpy");
*(_DWORD *)((char *)v2 + sub_127C(dword_5028, dword_502C, v17)) = dword_5044;
if(!(*(int(__fastcall **)(int, int *, unsigned int))(*(_DWORD *)v21 + 24))(v21, &v22, 0x10004u))
{
v18 = v22;
if(v22)
{
v19 = (*(int(__fastcall **)(int, _DWORD))(*(_DWORD *)v22 + 24))(v22, "com/example/crackme52/MainActivity");
if(v19)
return ((*(int(__fastcall **)(int, int, void **, signed int))(*(_DWORD *)v18 + 860))(
v18,
v19,
&off_5004,
1) >> 31) | 0x10004;
}
}
}
return -1;
}
v3 = (int)"JNITag";
v4 = (int)"Open libmodule.so error\n";
LABEL_6:
_android_log_print(4, v3, v4);
return (int)v2;
}
2. libmodule.so, 整个文件都进行了加密
导出了1个API : begin_verify
开始猜测可能是异或算法,但测试下来解密不正确,后来经过对比分析,猜出了算法:
解密算法是 :文件的每个字节取反,然后再加1
RR = (~BB) + 0x01;
加密算法是 :文件的每个字节先减1, 再取反
BB = ~(RR - 0x01);
解密代码如下:
FILE *pIn= fopen("D:/temp/vs/libmodule.so", "rb");
FILE *pOut = fopen("D:/temp/vs/libmodule_out.so", "wb");
int inSize = 13432; // 文件大小
char *buffer = new char;
while(true)
{
ret = fread(buffer, 1, inSize, pIn);
if(ret <= 0)
{
break ;
}
for(int i = 0; i < ret; i ++)
{
buffer = (~buffer) + 0x01;
}
ret = fwrite(buffer, 1, ret, pOut);
}
delete []buffer;
fclose(pIn);
fclose(pOut);
加密代码如下:
FILE *pIn= fopen("D:/temp/crack/crackme52/armeabi/libmodule.p.so", "rb");
FILE *pOut = fopen("D:/temp/crack/crackme52/armeabi/libmodule.so", "wb");
int inSize = 13432; // 文件大小
char *buffer = new char;
while(true)
{
ret = fread(buffer, 1, inSize, pIn);
if(ret <= 0)
{
break ;
}
for(int i = 0; i < ret; i ++)
{
buffer = ~(buffer - 0x01);
}
ret = fwrite(buffer, 1, ret, pOut);
}
delete []buffer;
fclose(pIn);
fclose(pOut);
分析算法不擅长,只能先尝试爆破, 从分析可以知道,最终验证函数是 libmodule.so的导出函数 begin_verify,那么就从这个函数进行分析
int __fastcall begin_verify(int a1, int a2, int a3, int a4)
{
v20 = a2;
v19 = a4;
v4 = a1;
v18 = a3;
v25 = _stack_chk_guard;
memcpy(dest, "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm0123456789", 0x3Fu);
v5 = 0;
v21 = 557788716;
v22 = 0;
result = (*(int(__fastcall **)(int, int))(*(_DWORD *)v4 + 672))(v4, v19);
if(result == 10) // 此处条件成立
{
result = (*(int(__fastcall **)(int, int))(*(_DWORD *)v4 + 672))(v4, v18) - 8;
if((unsigned int)result <= 0xC)
{
v7 = (*(int(__fastcall **)(int, int))(*(_DWORD *)v4 + 672))(v4, v18);
v8 = (*(int(__fastcall **)(int, int, _DWORD))(*(_DWORD *)v4 + 676))(v4, v18, 0);
if(v7 <= 12)
{
while(v5 < v7)
{
v23 = *(_BYTE *)(v8 + v5);
++v5;
}
v10 = (char *)&v21 - v7;
while(v7 != 12)
{
v23 = v10;
++v7;
}
}
else
{
for(i = v7 - 12; i != v7; ++i)
v23 = *(_BYTE *)(v8 + i);
}
v11 = 0;
v12 = (*(int(__fastcall **)(int, int, _DWORD))(*(_DWORD *)v4 + 676))(v4, v19, 0);
v13 = 0;
while(1)
{
v11 += (unsigned __int8)v23;
result = v11 / 62;
if(dest != *(_BYTE *)(v12 + v13))
break;// 此处不能被执行
++v13;
if(v13 == 12)
{
result = (*(int(__fastcall **)(int, _DWORD))(*(_DWORD *)v4 + 24))(v4, "android/widget/Toast");
v14 = result;
if(result)
{
result = (*(int(__fastcall **)(int, int, _DWORD, _DWORD))(*(_DWORD *)v4 + 452))(
v4,
result,
"makeText",
"(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;");
v15 = result;
if(result)
{
v16 = *(int(__fastcall **)(_DWORD, _DWORD, _DWORD, _DWORD))(*(_DWORD *)v4 + 456);
(*(void(__fastcall **)(int, _DWORD))(*(_DWORD *)v4 + 668))(v4, "You got it!");
result = v16(v4, v14, v15, v20);
v17 = result;
if(result)
{
result = (*(int(__fastcall **)(int, int, _DWORD, char))(*(_DWORD *)v4 + 132))(
v4,
v14,
"show",
"()V");
if(!v18) // 此处条件成立
result = (*(int(__fastcall **)(int, int, int))(*(_DWORD *)v4 + 244))(v4, v17, result);
}
}
}
break;
}
}
}
}
if(v25 != _stack_chk_guard)
_stack_chk_fail(result);
return result;
}
把几个有判断的部分进行了跳转,使其不论什么条件都可以执行到
(*(void(__fastcall **)(int, _DWORD))(*(_DWORD *)v4 + 668))(v4, "You got it!");
经过多次测试和修改,程序还是不能显示Cracked信息,郁闷,如果不是爆破的问题,或许是文件有检验.
自己对Android方面调试方面接触的太少,经验欠缺,在使用IDA动态调试的时候,IDA经常会崩溃或者异常,或许是作者的反调试处理,所以一直没办法用IDA进行动态跟踪,希望有这方面经验的能够一同探讨。
因为工作上还有事情要忙,只能先分析到这里,后面有进展再继续吧。。。
感谢分享分析心得。虽然没有在比赛时完成破解,但期待赛后更完整的分析,52破解有你更精彩。 开始猜测可能是异或算法,但测试下来解密不正确,后来经过对比分析,猜出了算法:
解密算法是 :文件的每个字节取反,然后再加1
RR = (~BB) + 0x01;
加密算法是 :文件的每个字节先减1, 再取反
BB = ~(RR - 0x01);
这是求补运算啊 currwin 发表于 2014-11-7 17:35
开始猜测可能是异或算法,但测试下来解密不正确,后来经过对比分析,猜出了算法:
解密算法是 :文件 ...
没错,只不过又进行了加1处理。 额,楼主真心不意思告诉你。 被我fake到了。 ThomasKing 发表于 2014-11-7 17:51
额,楼主真心不意思告诉你。 被我fake到了。
这个CM是不是libmodule.so解密了也没用?因为最终不是调用libmodule.so中的begin_verify,而调用函数是在libverify.so中的数据,对数据进行解密,然后来调用的吧? libmodule.so 就是陷阱,对吧? ThomasKing 发表于 2014-11-7 17:51
额,楼主真心不意思告诉你。 被我fake到了。
v2 = (void *)open("/data/data/com.example.crackme52/lib/libmodule.so", 0);
后来我分析了,其实这个返回值v2,一直没有被用到。
所以libmodule.so是陷阱。
不知道是不是我的IDA 配置问题,无法对App进行调试?还请多多指教。 codelive 发表于 2014-11-7 17:49
没错,只不过又进行了加1处理。
不不,求补的定义就是 按位取反+1 codelive 发表于 2014-11-7 17:58
v2 = (void *)open("/data/data/com.example.crackme52/lib/libmodule.so", 0);
后来我分析了,其实这个 ...
嗯嗯,大概是这样子的。 codelive 发表于 2014-11-7 17:55
这个CM是不是libmodule.so解密了也没用?因为最终不是调用libmodule.so中的begin_verify,而调用函数是在l ...
不知道。 防御组能不能把APK的实现原理放出来,如果可以的话,我在详细的写写吧