美文网首页idaAndroid逆向笔记
Android逆向练习(三)Ericky编写的“简单”crack

Android逆向练习(三)Ericky编写的“简单”crack

作者: h080294 | 来源:发表于2017-09-05 19:17 被阅读115次

    Ericky大牛的一个无反调试CrackMe(Java_by_Ericky_crackme01_JNI_EatRice),非常适合用来练手,姑且先试试。
    链接: https://pan.baidu.com/s/1hr9db96 密码: ntet
    拿到app后先反编译,到RegActivity看到注册button,然后继续跟踪setOnClickListener


    如果不为空切长度不为0,则调用JIN的native的静态方法EatRice,传参为string(注册码),int(注册码长度)
    public class JNI
    {
      static
      {
        System.loadLibrary("xy");
      }
      
      public static native boolean EatRice(String paramString, int paramInt);
    }
    

    之前没接触过so逆向,只能跟着前辈们一点一点的学习。最初使用的是Hopper,乍看起来在反编译和结构上都可以,但是看到伪代码就知道差了很多,参数都没有,也不知道怎么修正。只能想办法转IDA了。。。

    在mac上搞不到能用的IDA,所以只能搭上虚拟机,我这里使用的是Parallels Desktop12版本。安装win7镜像,就能正常使用windows系统啦。
    打开IDA,加载libxy.so

    加载完成后,找到Java_by_Ericky_crackme01_JNI_EatRice


    fn + F5看看代码是什么样

    image.png

    在IDA view里上下翻翻明显代码应该多好多才对,这就是传说的IDA识别问题,需要手动修一下。空格切换到Graph view,看到连个分叉,左边的明显是入口,然后往下查看,发现是箭头指的位置有识别问题。


    定位到这里,点击任意指令,然后Remove function tail

    Edit->Functions->Remove function tail
    

    然后Force BL call

    Edit->others->Force BL call
    

    修复后的看起来就正常了



    fn + F5看一下代码,比之前正常的多了
    接下来还得处理一下结构体识别,就是说的导入JNI.h头文件。我这里怎么试都会报错,试过了注释stdarg.h,改掉#define JNIIMPORT,也更改了complier 目录,但就是不行,还是会报错。google一下也没能解决,感觉可能跟使用的环境有关(mac上运行win虚拟机)。无奈之下只好手动fix。
    切换到Structures,添加standard structure:不知道没有导入.h文件,这个有什么作用

    Edit -> Add struct type->Add standard structure
    

    手动修正第一个参数为JNIEnv*类型,修改第一个变量为env。点击参数按y键进行修改。这里面第二个参数是干什么的我还没弄清楚,看别人说是因为静态方法,因此第二个参数暂时不用管。在反编译代码中也确实没看到第二个参数的使用,这个问题后面再研究一下。第三个就是注册码,第四个是注册码的长度。然后在修正一些变量名字,改成容易识别的,修正后的结果:

    int __fastcall Java_by_Ericky_crackme01_JNI_EatRice(JNIEnv *env, int a2, jstring regString, jint regLenght)
    {
      jint lenght_regCode; // r4@1
      signed int v5; // r6@1
      _BYTE *byte_regCode; // 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 *temp_byte_code; // [sp+10h] [bp-18h]@4
    
      lenght_regCode = regLenght;
      v5 = 0;
      byte_4004 = 0;
      byte_4008 = 0;
      byte_400C = 0;
      byte_4010 = 0;
      byte_regCode = string_to_byte(env, regString);
      if ( *byte_regCode != 88 || byte_regCode[1] != 35 || lenght_regCode != 7 )
      {
        j_j_sleep(3u);
        return 0;
      }
      temp_byte_code = j_j_malloc(1u);
      v7 = 35;
      *temp_byte_code = 35;
      v8 = byte_regCode[2];
      v32 = byte_regCode;
      temp_byte_code[1] = v8;
      v9 = -1;
      v10 = 63689;
      while ( 1 )
      {
        v5 = (unsigned __int8)v7 + v5 * v10;
        if ( v9 == -2 )
          break;
        v10 *= 378551;
        v7 = temp_byte_code[-v9--];
      }
      v31 = 1;
      if ( ((v5 + (v5 >> 31)) ^ (v5 >> 31)) == 2020122470 )
        byte_4004 = 1;
      *temp_byte_code = v8;
      v11 = v32[3];
      temp_byte_code[1] = v11;
      v12 = -1;
      v13 = 1315423911;
      while ( 1 )
      {
        v13 ^= ((unsigned int)v13 >> 2) + 32 * v13 + v8;
        if ( v12 == -2 )
          break;
        v8 = temp_byte_code[-v12--];
      }
      v14 = 0;
      if ( ((v13 + (v13 >> 31)) ^ (v13 >> 31)) == 1532463978 )
        byte_4008 = 1;
      *temp_byte_code = v11;
      v15 = v32[4];
      temp_byte_code[1] = v15;
      for ( i = -1; ; --i )
      {
        v17 = v11 + 16 * v14;
        v14 = v17 & 0xF0000000 ? ((v17 & 0xF0000000) >> 24) ^ v17 & 0xFFFFFFF : v17;
        if ( i == -2 )
          break;
        v11 = temp_byte_code[-i];
      }
      if ( ((v14 + (v14 >> 31)) ^ (v14 >> 31)) == 728 )
        byte_400C = 1;
      *temp_byte_code = v15;
      v30 = v32[5];
      temp_byte_code[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 = temp_byte_code[-v19--];
        v18 = 16 * v21;
      }
      *temp_byte_code = v30;
      temp_byte_code[1] = v32[6];
      v23 = sub_1238(temp_byte_code, 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;
    }
    

    然后慢慢读代码,注册码长度,然后四个变量初始化为0,接着调用sub_10C4()

      lenght_regCode = a4;
      v5 = 0;
      byte_4004 = 0;
      byte_4008 = 0;
      byte_400C = 0;
      byte_4010 = 0;
      v6 = sub_10C4(a1, a3);
    

    双击进入sub_10C4(),同样修正参数

    _BYTE *__fastcall sub_10C4(JNIEnv *env, jstring regCode)
    {
      jstring vRegCode; // ST08_4@1
      JNIEnv *vEnv; // r4@1
      int v4; // ST04_4@1
      int v5; // r7@1
      int v6; // r0@1
      int v7; // r5@1
      int v8; // r7@1
      _BYTE *v9; // r6@1
      int v11; // [sp+8h] [bp-18h]@1
    
      vRegCode = regCode;
      vEnv = env;
      v4 = ((int (*)(void))(*env)->FindClass)();
      v5 = ((int (__fastcall *)(JNIEnv *, const char *))(*vEnv)->NewStringUTF)(vEnv, "utf-8");
      v6 = ((int (__fastcall *)(JNIEnv *, int, const char *, const char *))(*vEnv)->GetMethodID)(
             vEnv,
             v4,
             "getBytes",
             "(Ljava/lang/String;)[B");
      v7 = sub_12AC(vEnv, vRegCode, v6, v5);
      v8 = ((int (__fastcall *)(JNIEnv *, int))(*vEnv)->GetArrayLength)(vEnv, v7);
      v9 = 0;
      v11 = ((int (__fastcall *)(JNIEnv *, int, _DWORD))(*vEnv)->GetByteArrayElements)(vEnv, v7, 0);
      if ( v8 >= 1 )
      {
        v9 = j_j_malloc(v8 + 1);
        j_j___aeabi_memcpy();
        v9[v8] = 0;
      }
      ((void (__fastcall *)(JNIEnv *, int, int, _DWORD))(*vEnv)->ReleaseByteArrayElements)(vEnv, v7, v11, 0);
      return v9;
    }
    

    每个函数都点下


    _BYTE *__fastcall sub_10C4(JNIEnv *env, jstring a2)
    {
      jstring v2; // ST08_4@1
      JNIEnv *v3; // r4@1
      jclass v4; // ST04_4@1
      jstring v5; // r7@1
      jmethodID v6; // r0@1
      void *v7; // r5@1
      jsize v8; // r7@1
      _BYTE *v9; // r6@1
      jbyte *v11; // [sp+8h] [bp-18h]@1
    
      v2 = a2;
      v3 = env;
      v4 = (*env)->FindClass(env, "java/lang/String");
      v5 = (*v3)->NewStringUTF(v3, "utf-8");
      v6 = (*v3)->GetMethodID(v3, v4, "getBytes", "(Ljava/lang/String;)[B");
      v7 = (void *)sub_12AC((int)v3, (int)v2, (int)v6, (int)v5);
      v8 = (*v3)->GetArrayLength(v3, v7);
      v9 = 0;
      v11 = (*v3)->GetByteArrayElements(v3, v7, 0);
      if ( v8 >= 1 )
      {
        v9 = j_j_malloc(v8 + 1);
        j_j___aeabi_memcpy();
        v9[v8] = 0;
      }
      (*v3)->ReleaseByteArrayElements(v3, v7, v11, 0);
      return v9;
    }
    

    分析过后得知sub_10C4函数是将String类型的字符串转为byte类型,然后重新进行取值返回。
    然后继续看代码,这里很明显有个判断逻辑,注册码第一位byte = 88,第二位byte = 35,注册码长度=7,都满足才能继续往后面走。

      if ( *byte_regCode != 88 || byte_regCode[1] != 35 || lenght_regCode != 7 )
      {
        j_j_sleep(3u);
        return 0;
      }
    

    所以前两位很容易就得到了,打开Sublime新建个.cpp,然后build system改成c++ Single File,写完后command + B直接运行。结果分别是“X”和"#"

    #include <iostream> 
    using namespace std;
    
    void funcregCode_1()
    {
        int i = 88;
        cout << "1th is : " << (char)i << "    i = " << i <<endl;
    }
    
    void funcregCode_2()
    {
        int i = 35;
        cout << "2th is : " << (char)i << "    i = " << i <<endl;
    }
    
    int main(int argc, char* argv[])
    {
        funcregCode_1();
        funcregCode_2();
    }
    // 运行结果
    // 1th is : X    i = 88
    // 2th is : #    i = 35
    

    第三位代码修正

      temp_byte_code = j_j_malloc(1u);
      v7 = 35;
      *temp_byte_code = 35;
      v8 = byte_regCode[2];
      v32 = byte_regCode;
      temp_byte_code[1] = v8;
      v9 = -1;
      v10 = 63689;
      while ( 1 )
      {
        v5 = (unsigned __int8)v7 + v5 * v10;
        if ( v9 == -2 )
          break;
        v10 *= 378551;
        v7 = temp_byte_code[-v9--];
      }
      v31 = 1;
      if ( ((v5 + (v5 >> 31)) ^ (v5 >> 31)) == 2020122470 )
        byte_4004 = 1;
    

    计算第三位注册码:

    void funcregCode_3()
    {
        for(int i = 0; i < 0xff; i++)
        {
            // v9 = -1;
            int v5 = 0;
            int v7 = 35;
            int v10 = 63689;
    
            v5 = v7 + v5 * v10;
            v10 *= 378551;
            v7 = i;
            v5 = v7 + v5 * v10;
    
            if ( ((v5 + (v5 >> 31)) ^ (v5 >> 31)) == 2020122470 )
                cout << "3th is : " << (char)i << "    i = " << i <<endl;
        }
    }
    
    // 运行结果
    // 3th is : y    i = 121
    

    第四位代码修正:

     *temp_byte_code = v8;
      v11 = v32[3];
      temp_byte_code[1] = v11;
      v12 = -1;
      v13 = 1315423911;
      while ( 1 )
      {
        v13 ^= ((unsigned int)v13 >> 2) + 32 * v13 + v8;
        if ( v12 == -2 )
          break;
        v8 = temp_byte_code[-v12--];
      }
      v14 = 0;
      if ( ((v13 + (v13 >> 31)) ^ (v13 >> 31)) == 1532463978 )
        byte_4008 = 1;
    

    计算第四位注册码:

    void funcregCode_4()
    {
        for(int i = 0; i < 0xff; i++)
        {
            int v12 = -1;
            signed int v13 = 1315423911;
            unsigned int v8 = 121;
    
            v13 ^= ((unsigned int)v13 >> 2) + 32 * v13 + v8;
            v8 = i;
            v13 ^= ((unsigned int)v13 >> 2) + 32 * v13 + v8;
    
            if ( ((v13 + (v13 >> 31)) ^ (v13 >> 31)) == 1532463978 )
                cout << "4th is : " << (char)i << "    i = " << i << endl;  
        }
    }
    
    // 运行结果
    // 4th is : *    i = 42
    

    第五位注册码修正:

     *temp_byte_code = v11;
      v15 = v32[4];
      temp_byte_code[1] = v15;
      for ( i = -1; ; --i )
      {
        v17 = v11 + 16 * v14;
        v14 = v17 & 0xF0000000 ? ((v17 & 0xF0000000) >> 24) ^ v17 & 0xFFFFFFF : v17;
        if ( i == -2 )
          break;
        v11 = temp_byte_code[-i];
      }
      if ( ((v14 + (v14 >> 31)) ^ (v14 >> 31)) == 728 )
        byte_400C = 1;
    

    计算第五位注册码:

    void funcregCode_5()
    {
        for(unsigned int i = 0; i < 128; i++)
        {
            signed int v14 = 0;
            unsigned int v17;
            unsigned int v11 = 42;
            for(unsigned int j = -1; ; --j)
            {
                v17 = v11 + 16 * v14;
                v14 = v17 & 0xF0000000 ? ((v17 & 0xF0000000) >> 24) ^ (v17 & 0xFFFFFFF) : v17;
                if (j == -2)
                    break;
                v11 = i;
            }
            if ( ((v14 + (v14 >> 31)) ^ (v14 >> 31)) == 728 ){
                cout << "5th is : " << (char)i << "    i = " << i <<endl;
                break;
            }
        }
    }
    // 运行结果
    // 5th is : 8    i = 56
    

    第六位和第七位注册码修正:

      *temp_byte_code = v15;
      v30 = v32[5];
      temp_byte_code[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 = temp_byte_code[-v19--];
        v18 = 16 * v21;
      }
      *temp_byte_code = v30;
      temp_byte_code[1] = v32[6];
      v23 = sub_1238(temp_byte_code, 2);
      v24 = (v23 + (v23 >> 31)) ^ (v23 >> 31);
      if ( ((v22 + (v22 >> 31)) ^ (v22 >> 31)) == 960 && v24 == 789320428 )
      {
        byte_4010 = 1;
    

    这里有个sub_1238,传第六位注册码(byte)和2(int),跟进去看看,里面并不复杂。

    int __fastcall sub_1238(_BYTE *a1, int a2)
    {
      int v2; // r7@1
      int v3; // r3@1
      int v4; // r4@1
      int v5; // r2@1
      unsigned int v6; // r6@2
    
      v2 = 0;
      v3 = 0;
      v4 = 0;
      v5 = 0;
      while ( a2 )
      {
        v6 = *a1;
        v4 += (v6 >> 2) * v4 * v6 + 804604770;
        v2 += 32 * v6 * v2 * v6 + 1491913760;
        v3 = v3 + 8 * v6 * v3 * v6 - 1774596542;
        v5 = v5 + 2 * v6 * v5 * v6 - 1263339326;
        --a2;
        ++a1;
      }
      return v4 + v5 + v3 + v2;
    }
    

    回到外面,这里的v22和v24纠缠了我好久,走进了互相带入的死胡同。自己解决无果,网上查询大牛的思路,发现居然可以拆开第六位和第七位,分开计算。因为这句判断中有两个判断,可以把前面当成第六位的验证,后面的v24用作第七位的验证,这下就容易解决了。

    if ( ((v22 + (v22 >> 31)) ^ (v22 >> 31)) == 960 && v24 == 789320428 )
    

    拆分计算第六位注册码:

    void funcregCode_6()
    {
        signed int v22;
        for(int i = 0; i < 128; i++)
        {
            int v18 = 0;
            signed int v19 = -1;
            unsigned int v15 = 56;
    
            while(1)
            {
                int v20 = v15 + v18;
                unsigned int v21 = v15 + v18;
    
                if ( v20 & 0xF0000000 )
                    v21 = ((v20 & 0xF0000000) >> 24) ^ v20;
                v22 = ((v20 | 0xFFFFFFF) ^ 0xF0000000) & v21;
                if ( v19 == -2 )
                    break;
                v15 = i;
                v18 = 16 * v21;
                v19--;
            }
    
            if ( ((v22 + (v22 >> 31)) ^ (v22 >> 31)) == 960)
            {
                cout << "6th is : " << (char)i << "    i = " << i <<endl;
                break;
            }
        }
    }
    // 运行结果
    // 6th is : @    i = 64
    

    拆分计算第七位注册码:

    void funcregCode_7()
    {
        int a2, v2, v3, v4, v5;
        int v23, v24;
        unsigned int v6;
        for(unsigned int i = 0; i < 128; i++)
        {
            v2 = 0;
            v3 = 0;
            v4 = 0;
            v5 = 0;
            a2 = 2;
    
            v6 = 64;
            v4 += (v6 >> 2) * v4 * v6 + 804604770;
            v2 += 32 * v6 * v2 * v6 + 1491913760;
            v3 = v3 + 8 * v6 * v3 * v6 - 1774596542;
            v5 = v5 + 2 * v6 * v5 * v6 - 1263339326;
    
            v6 = i;
            v4 += (v6 >> 2) * v4 * v6 + 804604770;
            v2 += 32 * v6 * v2 * v6 + 1491913760;
            v3 = v3 + 8 * v6 * v3 * v6 - 1774596542;
            v5 = v5 + 2 * v6 * v5 * v6 - 1263339326;
    
            v23 = v4 + v5 + v3 + v2;
            v24 = (v23 + (v23 >> 31)) ^ (v23 >> 31);
    
            if ( v24 == 0x2F0C12EC )
            {
                cout << "7th is : " << (char)i << "    i = " << i <<endl;
                break;
            }
        }
    }
    // 运行结果
    // 7th is : L    i = 76
    
    

    所以最终的七位注册码就是

    X#y*8@L    
    

    总结:
    第一次接触so的反编译,总得来说对我这样刚接触的还是挺难的,但过程很有意思,虽然是个简单的分析,但也整整花了2天时间,并且还不是所有地方都能100%说出为什么。过程:
    1、在mac上弄虚拟机装win7系统 --- Parallels Desktop
    2、手动修复IDA识别错误 --- Remove function tail && Force BL call
    3、导入JNI.h头文件 --- 失败了
    4、手动修复参数 --- 修改第一个参数JNIEnv*,修改第一个变量为env
    5、理清计算过程,读懂伪代码 --- 汇编没有基础,基本看不懂
    6、伪代码转换成c++代码 --- 赶鸭子上架,0基础学习简单的c++语法,解决各种傻缺的问题(比如没定义变量,格式警告,;号结尾等等)
    7、学习到了别人的解题思路

    相关文章

      网友评论

        本文标题:Android逆向练习(三)Ericky编写的“简单”crack

        本文链接:https://www.haomeiwen.com/subject/nhvujxtx.html