练习的例子
链接: https://pan.baidu.com/s/1BhKdDik8zOx_ioPNFdhQDg 密码: 0npf
第一题algorithmbase_20.apk
先是jadx打开apk。然后找到入口页面的代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public void onCreate (Bundle bundle) { super .onCreate(bundle); setContentView(C0575R.layout.activity_main); final TextView textView = (TextView) findViewById(C0575R.C0578id.sample_text); ((Button) findViewById(C0575R.C0578id.button)).setOnClickListener(new View.OnClickListener() { public void onClick (View view) { String randomAlphabetic = RandomStringUtils.randomAlphabetic(RandomUtils.nextInt(16 , 32 )); String encodeFromJni_20 = MainActivity.encodeFromJni_20(randomAlphabetic); Log.e("Kanxue" , randomAlphabetic + " -> " + encodeFromJni_20); textView.setText(encodeFromJni_20); } }); }
然后ida打开看encodeFromJni_20看到下面的c++代码,然后把第一个参数类型改下。把参数和返回值都修改下名字。代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 v3 = a1; input_1 = input; v5 = ((int (__fastcall *)(JNIEnv *, int , _DWORD))(*a1)->GetStringUTFChars)(a1, input, 0 ); sub_87A4(&v11, v5); ((void (__fastcall *)(JNIEnv *, int , int ))(*v3)->ReleaseStringUTFChars)(v3, input_1, v5); v6 = v14; if ( !(v11 & 1 ) ) v6 = v12; v7 = v13; if ( !(v11 & 1 ) ) v7 = (unsigned int )v11 >> 1 ; output_1 = sub_8D24(0 , v6, v7); _aeabi_memclr8(&output, 512 ); sprintf (&output, (const char *)&unk_15728, output_1); v9 = ((int (__fastcall *)(JNIEnv *, char *))(*v3)->NewStringUTF)(v3, &output); sub_87E6(&v11); result = _stack_chk_guard - v16; if ( _stack_chk_guard == v16 ) result = v9; return result;
然后继续根据返回值来向上找相关函数sub_8D24。然后下面贴上相关代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 int __fastcall sub_8D24 (int a1, unsigned __int8 *a2, unsigned int a3) { unsigned int v3; unsigned int v4; unsigned int v5; unsigned int v6; unsigned int v7; unsigned int v8; unsigned int v9; int v10; int v11; unsigned int v12; int v13; int v14; if ( !a2 ) return 0 ; v3 = ~a1; while ( a3 >= 8 ) { a3 -= 8 ; v4 = dword_16974[*a2 ^ (unsigned __int8)v3] ^ (v3 >> 8 ); v5 = dword_16974[(unsigned __int8)v4 ^ a2[1 ]] ^ (v4 >> 8 ); v6 = dword_16974[(unsigned __int8)v5 ^ a2[2 ]] ^ (v5 >> 8 ); v7 = dword_16974[(unsigned __int8)v6 ^ a2[3 ]] ^ (v6 >> 8 ); v8 = dword_16974[(unsigned __int8)v7 ^ a2[4 ]] ^ (v7 >> 8 ); v9 = dword_16974[(unsigned __int8)v8 ^ a2[5 ]] ^ (v8 >> 8 ); v10 = (unsigned __int8)v9 ^ a2[6 ]; v11 = a2[7 ]; a2 += 8 ; v12 = dword_16974[v10] ^ (v9 >> 8 ); v3 = dword_16974[(unsigned __int8)v12 ^ v11] ^ (v12 >> 8 ); } if ( a3 ) { v13 = 0 ; do { v14 = a2[v13++]; v3 = dword_16974[v14 ^ (unsigned __int8)v3] ^ (v3 >> 8 ); } while ( a3 != v13 ); } return ~v3; }
这里发现主要是用dword_16974这个数组来进行加密的。所以进去看一下是什么数据
大致可以确定这里就是加密函数了。那么frida写个脚本来hook一下看看这个函数的参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 function hook_native ( ) { var base_addr=Module.getBaseAddress("libnative-lib.so" ); var sub_8D24=base_addr.add(0x8D24 +1 ); console .log("sub_8D24:" ,sub_8D24) Interceptor.attach(sub_8D24,{ onEnter:function (args ) { console .log("========onenter sub_8D24========" ) this .arg0=args[0 ]; console .log(args[1 ].readCString()); console .log(args[2 ]); },onLeave :function (retval ) { console .log("========onleave sub_8D24========" ) console .log(retval); } }) } function hook_java ( ) { Java.perform(function ( ) { var native_lib=Java.use("com.kanxue.algorithmbase.MainActivity" ); native_lib.encodeFromJni_20.implementation=function (input ) { var res=this .encodeFromJni_20(input); console .log("input:" ,input,"output:" ,res); return res; } }); } function main ( ) { hook_java(); hook_native(); } setImmediate(main)
输出结果如下
1 2 3 4 5 6 7 sub_8D24: 0xd2d98d25 [AOSP on msm8996::com.kanxue.algorithmbase]-> ========onenter sub_8D24======== AIyJZYSYJNokIqoCvAPvAvLJ 0x18 ========onleave sub_8D24======== 0x6d64185d input: AIyJZYSYJNokIqoCvAPvAvLJ output: 6d64185d
看到加密的结果是一串4字节的整数。这个加密可能是adler32或者是crc32之类的。直接用加密工具验证一下
结果一致。证实第一题就是crc32加密的。
===========================================================================================
第二题algorithmbase_21.apk
jadx打开找到是encodeFromJni_21函数。然后继续ida打开。然后找到关键函数sub_8D88。然后写frida来hook
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function hook_java ( ) { Java.perform(function ( ) { var native_lib=Java.use("com.kanxue.algorithmbase.MainActivity" ); native_lib.encodeFromJni_21.implementation=function (input ) { var res=this .encodeFromJni_21(input); console .log("input:" ,input,"output:" ,res); return res; } }); } function main ( ) { hook_java(); } setImmediate(main)
输出结果
1 input: dzkxuTwzdBDUUSHkaN output: bc0d5852
继续拿crc32验证一下
还是一样的。奇怪。和第一题没有区别。继续看第三题。后面发现第二题有啥区别再更新
===========================================================================================
第三题algorithmbase_22.apk
根据之前的经验。先直接frida打印一下encodeFromJni_22函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function hook_java ( ) { Java.perform(function ( ) { var native_lib=Java.use("com.kanxue.algorithmbase.MainActivity" ); native_lib.encodeFromJni_22.implementation=function (input ) { var res=this .encodeFromJni_22(input); console .log("input:" ,input,"output:" ,res); return res; } }); } function main ( ) { hook_java(); } setImmediate(main)
然后输出结果如下
1 input: tzRYRdRASJIAMcNOQfijhCwnxGGQCy output: 1ce88c2
然后再用crc32加密的结果是98be198d,adler32加密的结果是a2370a84。然后用ida进去看看里面是怎么处理的。依然是找到sub_8D88。然后找到我们之前看到的加密关键的那个数组dword_1B064。
发现这个数组变空了。有可能是静态分析无法看到。是动态加载的。并且修改了crc32标准的table。
先贴一下c++的代码分析一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 if ( !a2 ) return 0; v3 = a1; v4 = a3; v5 = a2; if ( !byte_1B060 ) //这里判断加密的数组是否为空。 sub_8D38(); //这里对字典进行加载 v6 = ~v3; while ( v4 >= 8 ) { v4 -= 8; v7 = dword_1B064[*v5 ^ (unsigned __int8)v6] ^ (v6 >> 8); v8 = dword_1B064[(unsigned __int8)v7 ^ v5[1]] ^ (v7 >> 8); v9 = dword_1B064[(unsigned __int8)v8 ^ v5[2]] ^ (v8 >> 8); v10 = dword_1B064[(unsigned __int8)v9 ^ v5[3]] ^ (v9 >> 8); v11 = dword_1B064[(unsigned __int8)v10 ^ v5[4]] ^ (v10 >> 8); v12 = dword_1B064[(unsigned __int8)v11 ^ v5[5]] ^ (v11 >> 8); v13 = (unsigned __int8)v12 ^ v5[6]; v14 = v5[7]; v5 += 8; v15 = dword_1B064[v13] ^ (v12 >> 8); v6 = dword_1B064[(unsigned __int8)v15 ^ v14] ^ (v15 >> 8); } if ( v4 ) { v16 = 0; do { v17 = v5[v16++]; v6 = dword_1B064[v17 ^ (unsigned __int8)v6] ^ (v6 >> 8); } while ( v4 != v16 ); } return ~v6;
上面的代码可以看到这个关键的加密数组是sub_8D38这个函数计算出来的。
所以我们先接下来实现2个步骤。1、还原这个字典的计算算法。2、实现修改加密table的crc32算法
下面贴上ida中看到的字典计算的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 char *sub_8D38 () { unsigned int i; signed int v1; unsigned int v2; unsigned int v3; char *result; for ( i = 0 ; i != 256 ; ++i ) { v1 = 8 ; v2 = i; while ( v1 ) { v3 = (v2 >> 1 ) ^ 0xEDB88310 ; if ( !(v2 << 31 ) ) v3 = v2 >> 1 ; --v1; v2 = v3; } dword_1B064[i] = v2; } result = &byte_1B060; byte_1B060 = 1 ; return result; }
f5出来看到的都是c++的代码。一般我们还原算法。最优先还是使用c++来处理。如果是很简单的。可以用py随便搞一搞。下面我就直接把F5出来的代码修修补补然后给跑起来。下面贴上代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include <iostream> uint32_t CRC32_Table[256 ];char * sub_D2DA9D38 () { unsigned int i; signed int v1; unsigned int v2; unsigned int v3; char *result; for ( i = 0 ; i != 256 ; ++i ) { v1 = 8 ; v2 = i; while ( v1 ) { v3 = (v2 >> 1 ) ^ 0xEDB88310 ; if ( !(v2 << 31 ) ) v3 = v2 >> 1 ; --v1; v2 = v3; } CRC32_Table[i] = v2; } return result; } unsigned int getcrc (char *c, int len) { register unsigned int crc; char *e = c + len; crc = 0xFFFFFFFF ; while (c < e) { crc = ((crc >> 8 ) & 0x00FFFFFF ) ^ CRC32_Table[ (crc^ *c) & 0xFF ]; ++c; } return ( crc^0xFFFFFFFF ); } int main () { sub_D2DA9D38(); char * input="tzRYRdRASJIAMcNOQfijhCwnxGGQCy" ; uint32_t ret= getcrc(input,strlen (input)); printf ("res:0x%x" ,ret); return 0 ; }
最终输出如下。第三题搞定。
=====================================================================================
第四题algorithmbase_23.apk
jadx打开后。老样子找到encodeFromJni_23函数。然后ida打开分析该函数。找到算法关键位置看看有什么不同。结果发现算法的逻辑变了
中间那里在之前的基础上。又固定异或了一个0x29。先写frida脚本跑出一组数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function hook_java ( ) { Java.perform(function ( ) { var native_lib=Java.use("com.kanxue.algorithmbase.MainActivity" ); native_lib.encodeFromJni_23.implementation=function (input ) { var res=this .encodeFromJni_23(input); console .log("input:" ,input,"output:" ,res); return res; } }); } function main ( ) { hook_java(); } setImmediate(main)
输出结果
1 input: zRmkXNUnANXldAEJQWmvOWQ output: a31262b1
然后对我们前面还原的算法做一点点小修改。在后面多异或一个0x29
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <iostream> uint32_t CRC32_Table[256 ];char * sub_D2DA9D38 () { unsigned int i; signed int v1; unsigned int v2; unsigned int v3; char *result; for ( i = 0 ; i != 256 ; ++i ) { v1 = 8 ; v2 = i; while ( v1 ) { v3 = (v2 >> 1 ) ^ 0xEDB88310 ; if ( !(v2 << 31 ) ) v3 = v2 >> 1 ; --v1; v2 = v3; } CRC32_Table[i] = v2; } return result; } unsigned int getcrc (char *c, int len) { register unsigned int crc; char *e = c + len; crc = 0xFFFFFFFF ; while (c < e) { crc = ((crc >> 8 ) & 0x00FFFFFF ) ^ (CRC32_Table[ (crc^ *c) & 0xFF ])^0x29 ; ++c; } return ( crc^0xFFFFFFFF ); } int main () { sub_D2DA9D38(); char * input="zRmkXNUnANXldAEJQWmvOWQ" ; uint32_t ret= getcrc(input,strlen (input)); printf ("res:0x%x" ,ret); return 0 ; }
输出结果和hook到的一致了。第四题搞定
==================================================================================
第五题algorithmbase_30.apk
老样子ida找到encodeFromJni_30。frida也hook打印下参数和结果。下面是输出结果
1 2 input: FARNAcpufWWoTlGjzOdlvTV output: 68a53473854d175332b40a884754d7ef input: 1111111111111111111111111111111111111111111111111 output: 42c3e098e89de933319e0e8b5b33806d
结果是一个固定32位的字符串。说明这应该是一种hash算法。具体是什么算法我们需要分析下c++代码了
这里可以看出v16就是结果值了。所以sub_98B0就是关键函数。先hook下这个函数。看看参数分别是什么含义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 function hook_native ( ) { var base_addr=Module.getBaseAddress("libnative-lib.so" ); var sub_98B0=base_addr.add(0x98B0 +1 ); console .log("sub_98B0:" ,sub_98B0) Interceptor.attach(sub_98B0,{ onEnter:function (args ) { console .log("========onenter sub_98B0========" ) this .arg2=args[2 ]; console .log(args[0 ].readCString()) console .log(args[1 ]); },onLeave :function (retval ) { console .log("========onleave sub_98B0========" ) console .log(this .arg2); console .log(hexdump(this .arg2,{length :0x10 })) } }) } function hook_java ( ) { Java.perform(function ( ) { var native_lib=Java.use("com.kanxue.algorithmbase.MainActivity" ); native_lib.encodeFromJni_30.implementation=function (input ) { input="1111111111111111111111111111111111111111111111111" var res=this .encodeFromJni_30(input); console .log("input:" ,input,"output:" ,res); return res; } }); } function main ( ) { hook_java(); hook_native(); } setImmediate(main)
输出结果如下。
1 2 3 4 5 6 7 8 ========onenter sub_98B0======== 1111111111111111111111111111111111111111111111111 0x31 ========onleave sub_98B0======== 0xff87d308 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF ff87d308 42 c3 e0 98 e8 9d e9 33 31 9e 0e 8b 5b 33 80 6d B......31...[3.m input: 1111111111111111111111111111111111111111111111111 output: 42c3e098e89de933319e0e8b5b33806d
得到结论。参数a1是输入参数。a2是参数长度。a3是返回结果。下面贴上关键加密函数的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 int __fastcall sub_98B0(int input, int cnt, int output) { int input_1; // r10 int cnt_1; // r9 int output_1; // r8 int v7; // [sp+4h] [bp-74h] int v8; // [sp+8h] [bp-70h] int v9; // [sp+Ch] [bp-6Ch] int v10; // [sp+10h] [bp-68h] int v11; // [sp+14h] [bp-64h] int v12; // [sp+18h] [bp-60h] int v13; // [sp+5Ch] [bp-1Ch] input_1 = input; cnt_1 = cnt; output_1 = output; v11 = 0; v12 = 0; v9 = 0x98BADCFE; //这个值搜索一下。发现是MD5算法 v10 = 0x10325476; v7 = 0x67452301; v8 = 0xEFCDAB89; sub_9068((int)&v7, (int)"kanxue", 6u); //md5进行了一点点自定义的改造。 sub_9068((int)&v7, (int)"imyang", 6u); sub_9068((int)&v7, input_1, cnt_1); sub_9810(output_1, &v7); //最后这里进行计算的结果。这里也需要hook查一下 return _stack_chk_guard - v13; }
我们搜索了一下常量。发现这个hash算法就是MD5。然后看到这里不单单对我们输入的值进行了md5.前面还自定义了两个值。所以我们验证一下猜想
对比前面hook到的数据结果。是正确的了。第五题搞定
==================================================================================
第六题algorithmbase_31.apk
先hook打印下输入和结果
1 input: AGBfqjdHpbyRcUzqkLSLsaWVdNA output: 53bc4172d9b48d1e41414044b0cc67c7
然后ida分析之后发现。这个和第五题基本差不多。直接看那个关键函数即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 int __fastcall sub_98B0 (int a1, int a2, int a3) { int v3; int v4; int v5; int v7; int v8; int v9; int v10; int v11; int v12; int v13; v3 = a1; v4 = a2; v5 = a3; v11 = 0 ; v12 = 0 ; v9 = 0xBA98FEDC ; v10 = 0x32107654 ; v7 = 0x45670123 ; v8 = 0xCDEF89AB ; sub_9068(&v7, "kanxue" , 6 ); sub_9068(&v7, "imyang" , 6 ); sub_9068(&v7, v3, v4); sub_9810(v5, &v7); return _stack_chk_guard - v13; }
对比之前MD5的那个算法。v9,v10,v7,v8发生了变化。其实这个应该算是MD5的key。下面是之前第五题的key。我们hook函数sub_9068的第一个参数就可以知道key是多少了。我们再hook下。看看现在的key是多少了
1 01 23 45 67 89 ab cd ef fe dc ba 98 76 54 32 10
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 function hook_native ( ) { var base_addr=Module.getBaseAddress("libnative-lib.so" ); var sub_9068=base_addr.add(0x9068 +1 ); console .log("sub_9068:" ,sub_9068) Interceptor.attach(sub_9068,{ onEnter:function (args ) { console .log("========onenter sub_9068========" ) this .arg0=args[0 ]; console .log(hexdump(this .arg0,{length :0x10 })) },onLeave :function (retval ) { console .log("========onleave sub_9068========" ) console .log(hexdump(this .arg0,{length :0x10 })) } }) } function hook_java ( ) { Java.perform(function ( ) { var native_lib=Java.use("com.kanxue.algorithmbase.MainActivity" ); native_lib.encodeFromJni_31.implementation=function (input ) { var res=this .encodeFromJni_31(input); console .log("input:" ,input,"output:" ,res); return res; } }); } function main ( ) { hook_java(); hook_native(); } setImmediate(main)
输出结果如下
1 2 3 4 5 6 7 8 ========onenter sub_9068======== 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF ff87d25c 23 01 67 45 ab 89 ef cd dc fe 98 ba 54 76 10 32 #.gE........Tv.2 ========onleave sub_9068======== 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF ff87d25c 23 01 67 45 ab 89 ef cd dc fe 98 ba 54 76 10 32 #.gE........Tv.2 input: hgxSclQVesPCYKSnUMdOtnHLaLNS output: 9954c813aa188229945e49f866d37833
接下来就是实现一下MD5函数。并且修改key再测试下。由于MD5有很多人实现的。我就不自己啃了。直接找github上面一个人家实现好的。然后我们改改那个key来测试一下就好了。这里贴上我找的例子https://github.com/JieweiWei/md5.git
然后看看我们关键要改的地方
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 MD5::MD5(const string & message) { finished = false ; count[0 ] = count[1 ] = 0 ; state[0 ] = 0x45670123 ; state[1 ] = 0xCDEF89AB ; state[2 ] = 0xBA98FEDC ; state[3 ] = 0x32107654 ; init((const byte *)message.c_str(), message.length()); }
然后贴上我调用的代码
1 2 3 4 5 6 7 8 9 10 11 12 #include <iostream> #include "md5.h" void printMD5 (const string & message) { std ::cout << "md5(\"" << message << "\") = " << MD5(message).toStr() << std ::endl ; } int main () { char * input="kanxueimyangAGBfqjdHpbyRcUzqkLSLsaWVdNA" ; printMD5(input); return 0 ; }
输出结果如下
1 md5("kanxueimyangAGBfqjdHpbyRcUzqkLSLsaWVdNA") = 53bc4172d9b48d1e41414044b0cc67c7
第六题就完成了。