练习的例子

链接: 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() {
/* class com.kanxue.algorithmbase.MainActivity.C05741 */

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; // r3
unsigned int v4; // r3
unsigned int v5; // r3
unsigned int v6; // r3
unsigned int v7; // r3
unsigned int v8; // r3
unsigned int v9; // r3
int v10; // r4
int v11; // r5
unsigned int v12; // r3
int v13; // r4
int v14; // r5

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这个数组来进行加密的。所以进去看一下是什么数据

image-20210107203544370

大致可以确定这里就是加密函数了。那么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之类的。直接用加密工具验证一下

image-20210107201714394

结果一致。证实第一题就是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验证一下

image-20210107202658513

还是一样的。奇怪。和第一题没有区别。继续看第三题。后面发现第二题有啥区别再更新

===========================================================================================

第三题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。

image-20210107203645257

发现这个数组变空了。有可能是静态分析无法看到。是动态加载的。并且修改了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; // r0
signed int v1; // r3
unsigned int v2; // r4
unsigned int v3; // r5
char *result; // r0

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; // r0
signed int v1; // r3
unsigned int v2; // r4
unsigned int v3; // r5
char *result; // r0
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;
}

最终输出如下。第三题搞定。

1
0x1ce88c2

=====================================================================================

第四题algorithmbase_23.apk

jadx打开后。老样子找到encodeFromJni_23函数。然后ida打开分析该函数。找到算法关键位置看看有什么不同。结果发现算法的逻辑变了

image-20210107222651224

中间那里在之前的基础上。又固定异或了一个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; // r0
signed int v1; // r3
unsigned int v2; // r4
unsigned int v3; // r5
char *result; // r0
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到的一致了。第四题搞定

1
0xa31262b1

==================================================================================

第五题algorithmbase_30.apk

老样子ida找到encodeFromJni_30。frida也hook打印下参数和结果。下面是输出结果

1
2
input: FARNAcpufWWoTlGjzOdlvTV output: 68a53473854d175332b40a884754d7ef
input: 1111111111111111111111111111111111111111111111111 output: 42c3e098e89de933319e0e8b5b33806d

结果是一个固定32位的字符串。说明这应该是一种hash算法。具体是什么算法我们需要分析下c++代码了

image-20210107224731712

这里可以看出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.前面还自定义了两个值。所以我们验证一下猜想

image-20210107231632946

对比前面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; // r10
int v4; // r9
int v5; // 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]

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;
/* Reset number of bits. */
count[0] = count[1] = 0;
/* Initialization constants. */
// state[0] = 0x67452301;
// state[1] = 0xefcdab89;
// state[2] = 0x98badcfe;
// state[3] = 0x10325476;
state[0] = 0x45670123;
state[1] = 0xCDEF89AB;
state[2] = 0xBA98FEDC;
state[3] = 0x32107654;
/* Initialization the object according to message. */
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

第六题就完成了。