您的位置:首页 > 其它

Ericky的一个crackme的分析

2017-03-30 10:53 190 查看
本文转载自一枚无混淆无反调试keygenme

JEB载入。



跟进注册按钮的点击事件。



先判断注册码是否为空,不为空则调用JNI.EatRice()方法,定义如下。



这是一个native函数并且是静态方法,两个参数分别是string类型的注册码和int类型的注册码长度。从代码中可以看出这个函数在libxy.so中,所以接下来我们来分析libxy.so。IDA载入libxy.so,找到Java_by_Ericky_crackme01_JNI_EatRice()方法,双击进入,可以看到这段代码还是比较长的。



很明显可以发现汇编窗口的代码多于F5之后的代码。



这其实是因为IDA在反编译so的时候会出现一些识别错误,只需要人为修正一下就好。回到反编译窗口, 按一下空格来查看Graph View。可以看到IDA识别出了两个入口,很明显左边才是入口,右边那个应该是中间一个地方识别出错导致调用图断开。



红框选中的地方识别有点问题。



单击红框中任意指令,然后如图点击Edit->Functions->Remove function tail。



点击完后会发现Graph View发生了变化。



这次单击里面的BL指令,然后如图点击Edit->others->Force BL call。



那么现在就可以看到整个函数的逻辑就恢复了,大概一看还是比较好理解的。



F5反编译出来的代码也已经比较合理。



int __fastcall Java_by_Ericky_crackme01_JNI_EatRice(JNIEnv *env, int a2, jstring regCode, jint regLength)
{
jint v4; // r4@1
signed int v5; // r6@1
_BYTE *byte_code; // r5@1
char v7; // r3@4
unsigned __int8 v8; // r2@4
signed int v9; // r5@4
signed int v10; // r7@4
unsigned __int8 v11; // r6@9
signed int v12; // r4@9
signed int v13; // r5@9
signed int v14; // r7@12
unsigned __int8 v15; // r3@14
signed int i; // r5@14
unsigned int v17; // r6@16
int v18; // r4@22
signed int v19; // r7@22
int v20; // r5@24
unsigned int v21; // r4@24
signed int v22; // r5@26
int v23; // r0@27
int v24; // r0@27
signed int v26; // r4@33
signed int v27; // r3@35
int v28; // r3@37
signed int v29; // r2@37
char v30; // [sp+0h] [bp-28h]@22
signed int v31; // [sp+4h] [bp-24h]@7
_BYTE *v32; // [sp+8h] [bp-20h]@4
_BYTE *v33; // [sp+10h] [bp-18h]@4

v4 = regLength;
v5 = 0;
byte_4004 = 0;
byte_4008 = 0;
byte_400C = 0;
byte_4010 = 0;
byte_code = jstringtobyte(env, regCode);
if ( *byte_code != 88 || byte_code[1] != 35 || v4 != 7 )
{
j_j_sleep(3u);
return 0;
}
v33 = j_j_malloc(1u);
v7 = 35;
*v33 = 35;
v8 = byte_code[2];
v32 = byte_code;
v33[1] = v8;
v9 = -1;
v10 = 63689;
while ( 1 )
{
v5 = (unsigned __int8)v7 + v5 * v10;
if ( v9 == -2 )
break;
v10 *= 378551;
v7 = v33[-v9--];
}
v31 = 1;
if ( ((v5 + (v5 >> 31)) ^ (v5 >> 31)) == 2020122470 )
byte_4004 = 1;
*v33 = v8;
v11 = v32[3];
v33[1] = v11;
v12 = -1;
v13 = 1315423911;
while ( 1 )
{
v13 ^= ((unsigned int)v13 >> 2) + 32 * v13 + v8;
if ( v12 == -2 )
break;
v8 = v33[-v12--];
}
v14 = 0;
if ( ((v13 + (v13 >> 31)) ^ (v13 >> 31)) == 1532463978 )
byte_4008 = 1;
*v33 = v11;
v15 = v32[4];
v33[1] = v15;
for ( i = -1; ; --i )
{
v17 = v11 + 16 * v14;
v14 = v17 & 0xF0000000 ? ((v17 & 0xF0000000) >> 24) ^ v17 & 0xFFFFFFF : v17;
if ( i == -2 )
break;
v11 = v33[-i];
}
if ( ((v14 + (v14 >> 31)) ^ (v14 >> 31)) == 728 )
byte_400C = 1;
*v33 = v15;
v30 = v32[5];
v33[1] = v30;
v18 = 0;
v19 = -1;
while ( 1 )
{
v20 = v15 + v18;
v21 = v15 + v18;
if ( v20 & 0xF0000000 )
v21 = ((v20 & 0xF0000000) >> 24) ^ v20;
v22 = ((v20 | 0xFFFFFFF) ^ 0xF0000000) & v21;
if ( v19 == -2 )
break;
v15 = v33[-v19--];
v18 = 16 * v21;
}
*v33 = v30;
v33[1] = v32[6];
v23 = sub_1238(v33, 2);
v24 = (v23 + (v23 >> 31)) ^ (v23 >> 31);
if ( ((v22 + (v22 >> 31)) ^ (v22 >> 31)) == 960 && v24 == 789320428 )
{
byte_4010 = 1;
LABEL_32:
v31 = 0;
goto LABEL_33;
}
if ( byte_4010 )
goto LABEL_32;
LABEL_33:
v26 = 1;
if ( byte_4008 )
v26 = 0;
v27 = 1;
if ( byte_4004 )
v27 = 0;
v28 = v27 | v26;
v29 = 1;
if ( byte_400C )
v29 = 0;
return ~(v28 | v29 | v31) & 1;
}


然后我们来处理一下结构体的识别还有命名等初始工作。导入JNI.h,修正第一个参数为JNIEnv*类型,修改第一个变量为env。那么a3,a4的类型分别是jstring和jint,分别修正并重新命名为regCode和regCodeLength。同时这是一个静态方法,第二个参数可以不用管。关于这些IDA识别的问题在另外一篇博客中已经说得很清楚了-那些年使用IDA的事防止F5错误优化。然后开始读代码,v4位注册码长度,四个变量初始化为0,接着调用sub_10C4()。双击进入sub_10C4(),来修正一下,第一个参数是JNIEnv*, 第二个参数是jstring。修正完类型和重命名之后,可以看到有些函数识别出来了。



但是这时候看着还不是很爽,如下所示,每一个env函数都点击一遍。



结果如下,这下子看着好多了。



代码比较简单,就是将Java的String类型的字符串转为byte类型, 然后重新进行取值返回。



如果不熟悉的话可以跟进sub_12AC(),就是一个调用函数的操作。



那么现在我们可以回到最开始,这就是一个jstringtobyte的方法,修改一下返回的结果。



可以看到前两位的判断写在了if里,一个是88,一个是35。



并且还有一个条件,注册码长度是7,不满足直接返回失败。接下来第三位注册码赋值给v8,同时注册码首地址赋值给v32,也就是v32的值就是注册码数组的首地址,重命名一下,v33就当做一个临时变量好了。下面进入一个while(1)的循环,这个循环起始会执行两次,当第一次执行完的时候v9自减1,第二次在if那里就会break。下面的if其实可以缩减一下,因为异或不同为1,相同为0。



计算第三位注册码的代码如下。



第四位注册码的校验代码如下。



计算第四位注册码的代码如下。



第五位注册码的校验代码如下。



计算第五位注册码的代码如下。



接下来第六位和第七位注册码混在了一起。可以看到最后if有两个条件判断, 第一个v22参数由前面while(1)里校验第六位注册码的代码决定, 第二个v24参数由后面校验第七位注册码的代码决定。



计算第六位注册码的代码如下。



第七位注册码的校验在sub_1238里。





计算第七位注册码的代码如下。



整个代码运行的结果如下。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: