练习第一题的algorithmbase_10.apk连接如下

链接: https://pan.baidu.com/s/1hUyNDzd0VDzqD16X_IhiPg 密码: ocg5

首先用jadx打开目标应用。看到下面的代码

image-20210105204400773

看到我们需要还原算法的函数是encodeFromJni_10。参数是随机16个ascii字符。接下来用ida打开libnative-jni.so。找到我们的目标函数。然后修改第一个参数的类型为JNIEnv*,然后把第三个参数的名称修改成input。最后把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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
void __fastcall Java_com_kanxue_algorithmbase_MainActivity_encodeFromJni_110(JNIEnv *a1, __int64 a2, __int64 input)
{
unsigned __int64 v3; // x22
void *input_1; // x20
JNIEnv *v5; // x19
const char *input_2; // x21
int v7; // w0
int v8; // w0
_BOOL4 v9; // w8
_BYTE *v10; // x0
char *v11; // x1
signed int v12; // w2
char *output; // x1
__int64 v14; // [xsp+8h] [xbp-58h]
__int64 v15; // [xsp+10h] [xbp-50h]
void *v16; // [xsp+18h] [xbp-48h]
unsigned __int64 v17; // [xsp+20h] [xbp-40h]
int v18; // [xsp+28h] [xbp-38h]
void *v19; // [xsp+30h] [xbp-30h]
__int64 v20; // [xsp+38h] [xbp-28h]

v3 = _ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2));
input_1 = (void *)input;
v20 = *(_QWORD *)(v3 + 40);
v5 = a1;
input_2 = (*a1)->GetStringUTFChars(a1, (jstring)input, 0LL);
sub_E968(&v17, (__int64)input_2);
(*v5)->ReleaseStringUTFChars(v5, input_1, input_2);
if ( v17 & 1 )
v7 = v18;
else
v7 = (unsigned __int64)(unsigned __int8)v17 >> 1;
v8 = sub_EE10(v7);
v15 = 0LL;
v16 = 0LL;
v14 = 0LL;
if ( v8 )
{
sub_EA94(&v14, v8, 0);
v9 = (v14 & 1) == 0;
}
else
{
v9 = 1;
LOWORD(v14) = 0;
}
if ( v9 )
v10 = (char *)&v14 + 1;
else
v10 = v16;
if ( v17 & 1 )
v11 = (char *)v19;
else
v11 = (char *)&v17 + 1;
if ( v17 & 1 )
v12 = v18;
else
v12 = (unsigned __int64)(unsigned __int8)v17 >> 1;
sub_EE38(v10, (__int64)v11, v12);
if ( v14 & 1 )
output = (char *)v16;
else
output = (char *)&v14 + 1;
((void (__fastcall *)(JNIEnv *, char *))(*v5)->NewStringUTF)(v5, output);
if ( v14 & 1 )
operator delete(v16);
if ( v17 & 1 )
operator delete(v19);
*(_QWORD *)(v3 + 40);
}

这里可以从参数的一步步传递、返回值的向上回溯这两个方式来进行分析。

我先从返回值开始向上查。打开最后和返回值output相关的函数sub_EE38。发现了一些熟悉的东西

image-20210105211902397

点进aAbcdefghijklmn数组里面看看是什么

image-20210105212007265

说明这里可能是一个base64.我们用frida来hook一下encodeFromJni_10这个函数的参数和返回值

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_10.implementation=function(input){
var res=this.encodeFromJni_10(input);
console.log("input:",input,"output:",res);
return res;
}
});
}
function main(){
hook_java();
}
setImmediate(main)

然后看看输出结果

1
input: 2vOHn\n(boFhN|_T output: MnZPSG5cbihib0ZoTnxfVA==

猜测是对参数进行了base64的处理。然后直接拿input值进行base64处理后对比下结果

image-20210105212532999

对比输出结果是一致的。那么第一题的结果就是直接base64加密。

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

练习第一题的algorithmbase_11.apk连接如下

链接: https://pan.baidu.com/s/1_XqSzUqHfRxSBnU0Ddzlog 密码: u37v

第一步同样是jadx打开分析一下。然后看到的代码基本一致。不过这个是用的encodeFromJni_11函数。然后继续ida打开分析。然后用frida再hook看到参数和返回值如下

1
input: i17gs-B[4hbzg@-l output: Kjbw1wBqg+ptKG0Q1tAqLA==

然后我们再用工具对input进行base64加密后。发现和返回值不一样了

1
input: i17gs-B[4hbzg@-l base64: aTE3Z3MtQls0aGJ6Z0AtbA==

接下来。我们从返回值开始分析。先将sub_EE38这个函数进行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
function hook_native(){
var base_addr=Module.getBaseAddress("libnative-lib.so");
var sub_EE38=base_addr.add(0xEE38);
Interceptor.attach(sub_EE38,{
onEnter:function(args){
this.arg0=args[0];
console.log("========onenter========")
console.log(args[1].readCString());
console.log(args[2]);
},onLeave:function(retval){
console.log("========onleave========")
console.log(this.arg0.readCString());
}
})
}
function hook_java(){
Java.perform(function(){
var native_lib=Java.use("com.kanxue.algorithmbase.MainActivity");
native_lib.encodeFromJni_11.implementation=function(input){
var res=this.encodeFromJni_11(input);
console.log("input:",input,"output:",res);
return res;
}
});
}
function main(){
hook_java();
hook_native();
}
setImmediate(main)

然后点击check后得到下面的日志

1
2
3
4
5
6
========onenter========
Wyi":?fq>Dd!xA6z
0x10
========onleave========
lw+Xz8W41VbyhGg6TbbvT5==
input: Wyi":?fq>Dd!xA6z output: lw+Xz8W41VbyhGg6TbbvT5==

说明这个函数就是关键的函数了。然后我们分析里面是如何计算出来。首先根据我们上面得到的数据。知道了三个参数是什么,然后进行重命名。我把新的代码贴下面

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
__int64 __fastcall sub_EE38(_BYTE *output, __int64 input, signed int data_10)
{
signed __int64 v3; // x9
_BYTE *v4; // x12
unsigned __int8 *v5; // x10
unsigned __int64 v6; // x13
_BYTE *v7; // x10
signed __int64 v8; // x11
char v9; // w8
__int64 v10; // x9
__int64 result; // x0

if ( data_10 - 2 < 1 )
{
LODWORD(v3) = 0;
v7 = output;
if ( data_10 <= 0 )
goto LABEL_11;
}
else
{
v3 = 0LL;
v4 = output;
do
{
v5 = (input + v3);
v6 = *(input + v3);
v3 += 3LL;
*v4 = aAyzabfghz0cmbd[v6 >> 2];
v4[1] = aAyzabfghz0cmbd[16 * *v5 & 0x30LL | (v5[1] >> 4)];
v4[2] = aAyzabfghz0cmbd[4 * v5[1] & 0x3CLL | (v5[2] >> 6)];
LOBYTE(v6) = aAyzabfghz0cmbd[v5[2] & 0x3F];
v7 = v4 + 4;
v4[3] = v6;
v4 += 4;
}
while ( v3 < data_10 - 2 );
if ( v3 >= data_10 )
goto LABEL_11;
}
*v7 = aAyzabfghz0cmbd[*(input + v3) >> 2];
v8 = 16 * *(input + v3) & 0x30LL;
if ( v3 == data_10 - 1 )
{
v7[1] = aAyzabfghz0cmbd[v8];
v9 = 61;
}
else
{
v10 = input + v3;
v7[1] = aAyzabfghz0cmbd[v8 | (*(v10 + 1) >> 4)];
v9 = aAyzabfghz0cmbd[4 * *(v10 + 1) & 0x3CLL];
}
v7[2] = v9;
v7[3] = 61;
v7 += 4;
LABEL_11:
result = (v7 - output + 1);
*v7 = 0;
return result;
}

前面那段可以看到之前分析的是base64.然后由于第三个参数我们hook知道了值。所以分析一下就可以知道必然是走以下的代码来进行加密的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
index = 0LL;
output_1 = output;
do
{
v5 = (input + index);
v6 = *(input + index);
index += 3LL;
*output_1 = aAyzabfghz0cmbd[v6 >> 2];
output_1[1] = aAyzabfghz0cmbd[16 * *v5 & 0x30LL | (v5[1] >> 4)];
output_1[2] = aAyzabfghz0cmbd[4 * v5[1] & 0x3CLL | (v5[2] >> 6)];
LOBYTE(v6) = aAyzabfghz0cmbd[v5[2] & 0x3F];
v7 = output_1 + 4;
output_1[3] = v6;
output_1 += 4;
}
while ( index < data_10 - 2 );
if ( index >= data_10 )
goto LABEL_11;

然后对照逻辑写个py版本的加密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
idx=0
data=10
input="""Wyi":?fq>Dd!xA6z"""
input_bytes=bytes(input,"utf-8")
bkey="AYZabFGHz0cmBdefghijklCDE1KLMNTU56789+/VWXnopq23IJrstuvwOPQRSxy4"
output=""
while True:
v5 = input_bytes[idx]
v5_1 = input_bytes[idx+1]
v5_2 = input_bytes[idx + 2]
print(input_bytes[idx])
v6 = input_bytes[idx]
idx += 3
output += bkey[v6 >> 2]
output += bkey[16 * v5 & 0x30 | (v5_1 >> 4)]
output += bkey[4 * v5_1 & 0x3C | (v5_2 >> 6)]
v6 = bkey[v5_2 & 0x3f]
output += v6
if idx > data-2:
break
print(output)
print(idx)

写到这里之后计算一下看看结果

1
2
lw+Xz8W41Vby
9

对比ida上的逻辑发现并不符合结束的判断。这里的结果是9.所以还得继续往后执行。并且前面部分的计算已经和我们的输出结果一致了。

1
if ( index >= data_10 )

继续把后面的代码贴一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
*v7 = aAyzabfghz0cmbd[*(input + v3) >> 2];
v8 = 16 * *(input + v3) & 0x30LL;
if ( v3 == data_10 - 1 )
{
v7[1] = aAyzabfghz0cmbd[v8];
v9 = 61;
}
else
{
v10 = input + v3;
v7[1] = aAyzabfghz0cmbd[v8 | (*(v10 + 1) >> 4)];
v9 = aAyzabfghz0cmbd[4 * *(v10 + 1) & 0x3CLL];
}
v7[2] = v9;
v7[3] = 61;
v7 += 4;

然后继续再完善下py的模拟代码

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
idx=0
data=10
input="""Wyi":?fq>Dd!xA6z"""
input_bytes=bytes(input,"utf-8")
bkey="AYZabFGHz0cmBdefghijklCDE1KLMNTU56789+/VWXnopq23IJrstuvwOPQRSxy4"
output=""
v7=0
while True:
v5 = input_bytes[idx]
v5_1 = input_bytes[idx+1]
v5_2 = input_bytes[idx + 2]
v6 = input_bytes[idx]
idx += 3
v7 += 4
output += bkey[v6 >> 2]
output += bkey[16 * v5 & 0x30 | (v5_1 >> 4)]
output += bkey[4 * v5_1 & 0x3C | (v5_2 >> 6)]
v6 = bkey[v5_2 & 0x3f]
output += v6
if idx > data+2:
break

output+=bkey[input_bytes[idx]>>2]
v8 = 16 * input_bytes[idx] & 0x30
if idx > data - 1:
output += bkey[v8]
v9 = '='
else:
output += bkey[v8 | (input_bytes[idx+1]>> 4)]
v9 = bkey[4 * input_bytes[idx+1] & 0x3C]
output += v9
output += '='
print(output)

最后输出结果

1
lw+Xz8W41VbyhGg6TbbvT5==

到这里第二题就搞定了。

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

第三题algorithmbase_12.apk

链接: https://pan.baidu.com/s/1imjPHj7eSnBUJmWWeB3NKQ 密码: 9bk2

同样用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_12.implementation=function(input){
var res=this.encodeFromJni_12(input);
console.log("input:",input,"output:",res);
return res;
}
});
}
function main(){
hook_java();
}
setImmediate(main)
1
input: 5Ki*$:&xh.!u4{|[mAR*a^x"yK5 output: |_zxO-qJia.+]-)C|RzH*uCB_-x.PaN-h_lC

ida分析同样是从返回值向上回溯。找到第一个相关函数sub_8B04进行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_8B04=base_addr.add(0x8B04+1);
console.log("sub_8b04:",sub_8B04)
Interceptor.attach(sub_8B04,{
onEnter:function(args){
console.log("========onenter========")
this.arg0=args[0];
console.log(args[1].readCString());
console.log(args[2]);
},onLeave:function(retval){
console.log("========onleave========")
console.log(this.arg0.readCString());
}
})
}
function hook_java(){
Java.perform(function(){
var native_lib=Java.use("com.kanxue.algorithmbase.MainActivity");
native_lib.encodeFromJni_12.implementation=function(input){
var res=this.encodeFromJni_12(input);
console.log("input:",input,"output:",res);
return res;
}
});
}
function main(){
hook_java();
hook_native();
}
setImmediate(main)
1
2
3
4
5
6
========onenter========
3ez|q|z\8QzIU]}`bM ZMlJ]
0x18
========onleave========
~v]IlQ*Kk_!W\S{j]]@`T+jR*{Zi{}
input: 3ez|q|z\8QzIU]}`bM ZMlJ] output: ~v]IlQ*Kk_!W\S{j]]@`T+jR*{Zi{}

对比输出结果。看出加密位置确实也还是这里。接下来贴上里面的加密逻辑

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
data_0x16 = data_0x18 - 2;
data_0x18_1 = data_0x18;
output_1 = output;
v6 = 0;
v7 = 0;
while ( 1 )
{
v8 = (_BYTE *)(output_1 + v6);
if ( v7 >= data_0x16 )
break;
v9 = input + v7;
v6 += 4;
*v8 = aAyzpq23ijrtffg[(unsigned int)*(unsigned __int8 *)(input + v7) >> 2];
v10 = *(unsigned __int8 *)(input + v7 + 1);
v11 = *(_BYTE *)(input + v7);
v7 += 3;
v8[1] = aAyzpq23ijrtffg[(v10 >> 4) & 0xFFFFFFCF | 16 * (v11 & 3)];
v8[2] = aAyzpq23ijrtffg[((unsigned int)*(unsigned __int8 *)(v9 + 2) >> 6) & 0xFFFFFFC3 | 4
* (*(_BYTE *)(v9 + 1) & 0xF)];
v8[3] = aAyzpq23ijrtffg[*(_BYTE *)(v9 + 2) & 0x3F];
}
if ( v7 < data_0x18_1 )
{
*v8 = aAyzpq23ijrtffg[(unsigned int)*(unsigned __int8 *)(input + v7) >> 2];
v12 = 16 * *(unsigned __int8 *)(input + v7) & 0x30;
if ( data_0x18_1 - 1 == v7 )
{
v13 = 61;
v8[1] = aAyzpq23ijrtffg[v12];
}
else
{
v8[1] = aAyzpq23ijrtffg[v12 | ((unsigned int)*(unsigned __int8 *)(input + v7 + 1) >> 4)];
v13 = aAyzpq23ijrtffg[4 * (*(_BYTE *)(input + v7 + 1) & 0xF)];
}
v8[3] = 61;
v8[2] = v13;
v8 += 4;
}
*v8 = 0;
return (int)&v8[-output_1 + 1];

aAyzpq23ijrtffg这个数组静态分析的时候是没有特殊字符的。实际上是有动态的对其进行赋值的。修改下frida脚本。将其打印一下

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
48
49
50
51
52
53
54
function hook_native(){
var base_addr=Module.getBaseAddress("libnative-lib.so");
var sub_8B04=base_addr.add(0x8B04+1);
Interceptor.attach(sub_8B04,{
onEnter:function(args){
console.log("========onenter========")
this.arg0=args[0];
console.log("input:",args[1].readCString());
console.log(args[2]);
//0001B000
var dump_addr=base_addr.add(0x0001B000);
console.log("aAyzpq23ijrtffg sub_8B04:",dump_addr.readCString())
},onLeave:function(retval){
console.log("========onleave========")
console.log("output:",this.arg0.readCString());
}
})
var sub_8ABC=base_addr.add(0x8ABC+1);
Interceptor.attach(sub_8ABC,{
onEnter:function(args){
var dump_addr=base_addr.add(0x0001B000);
console.log("aAyzpq23ijrtffg sub_8ABC enter:",dump_addr.readCString())
},
onLeave:function(retval){
var dump_addr=base_addr.add(0x0001B000);
console.log("aAyzpq23ijrtffg sub_8ABC leave:",dump_addr.readCString())
}
})
var encode= Module.getExportByName("libnative-lib.so","Java_com_kanxue_algorithmbase_MainActivity_encodeFromJni_112");
Interceptor.attach(encode,{
onEnter:function(args){
var dump_addr=base_addr.add(0x0001B000);
console.log("aAyzpq23ijrtffg encode enter:",dump_addr.readCString())
},onLeave:function(retval){

}
})
}
function hook_java(){
Java.perform(function(){
var native_lib=Java.use("com.kanxue.algorithmbase.MainActivity");
native_lib.encodeFromJni_12.implementation=function(input){
input="3ez|q|z\\8QzIU]}`bM ZMlJ]"
var res=this.encodeFromJni_12(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
9
10
aAyzpq23ijrtffg encode enter: YABhi*+QRjL^~pqrst[\])STUuZ|}klM-./ _Pb({mnoyzV!37NO@vwWHIJK`a,
aAyzpq23ijrtffg sub_8ABC enter: YABhi*+QRjL^~pqrst[\])STUuZ|}klM-./ _Pb({mnoyzV!37NO@vwWHIJK`a,
aAyzpq23ijrtffg sub_8ABC leave: YABhi*+QRjL^~pqrst[\])STUuZ|}klM-./ _Pb({mnoyzV!37NO@vwWHIJK`a,
========onenter========
input: 3ez|q|z\8QzIU]}`bM ZMlJ]
0x18
aAyzpq23ijrtffg sub_8B04: YABhi*+QRjL^~pqrst[\])STUuZ|}klM-./ _Pb({mnoyzV!37NO@vwWHIJK`a,
========onleave========
output: ~v]IlQ*Kk_!W\S{j]]@`T+jR*{Zi{}
input: 3ez|q|z\8QzIU]}`bM ZMlJ] output: ~v]IlQ*Kk_!W\S{j]]@`T+jR*{Zi{}

这里可以看出aAyzpq23ijrtffg修改的时机似乎比较早。在调用加密前的时候这个数据就已经修改了。得到正确的key之后。再根据上面的c++代码写出下面的py模拟算法

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
bkey="""YABhi*+QRjL^~pqrst[\])STUuZ|}klM-./ _Pb({mnoyzV!37NO@vwWHIJK`a,"""
input="3ez|q|z\8QzIU]}`bM ZMlJ]"
input_bytes=bytes(input,"utf-8")
output=""
cnt=len(input)
precnt=cnt-2
cnt1=cnt
v6=0
v7=0
while True:
if v7 >= precnt:
break
v9 = v7
v6 += 4
output += bkey[input_bytes[v7] >> 2]
v10 = input_bytes[v7+1]
v11 = input_bytes[v7]
v7 += 3
output += bkey[(v10 >> 4) & 0xFFFFFFCF | 16 * (v11 & 3)]
output += bkey[((input_bytes[v9+2]>>0x6) & 0xFFFFFFC3) | (4 * (input_bytes[v9+1] & 0xF))]
output += bkey[input_bytes[v9+2] & 0x3F]
if v7 < cnt1:
output += bkey[input_bytes[v7] >> 2]
v12 = 16 * input_bytes[v7] & 0x30
if cnt1 - 1 == v7:
v13 = '='
output += bkey[v12]

else:
output += bkey[v12 | (input_bytes[v7+1] >> 4)]
v13 = bkey[4 * (input_bytes[v7+1] & 0xF)]
output += v13
output += '='
print(output)

最后输出结果

1
~v]IlQ*Kk_!W\S{j]]@`T+jR*{Zi{}

和那边的结果一致了。但是还需要继续排查那个key是如何转变过来的。因为每个不同的input。key都会发生变化。所以key应该是根据input来进行计算的。上面的hook脚本处理的sub_8ABC这个函数是看到非常疑似修改了这个bkey的函数。但是hook后发现好像不是。为了精确。我调整了一下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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65

function hook_native(){
var base_addr=Module.getBaseAddress("libnative-lib.so");
var sub_8B04=base_addr.add(0x8B04+1);
console.log("sub_8b04:",sub_8B04)
Interceptor.attach(sub_8B04,{
onEnter:function(args){
console.log("========onenter sub_8b04========")
this.arg0=args[0];
console.log(args[1].readCString());
console.log(args[2]);
},onLeave:function(retval){
console.log("========onleave sub_8b04========")
console.log(this.arg0.readCString());
}
})
var sub_8ABC=base_addr.add(0x8ABC+1);
console.log("sub_8ABC:",sub_8ABC)
Interceptor.attach(sub_8ABC,{
onEnter:function(args){
console.log("========onenter sub_8ABC========")
console.log(args[0]);
var dump_addr=base_addr.add(0x1B000);
console.log("dump_addr:",dump_addr.readCString());
},onLeave:function(retval){
console.log("========onleave sub_8ABC========");
var dump_addr=base_addr.add(0x1B000);
console.log("dump_addr:",dump_addr.readCString());

}
})
}
function hook_init_so(){
var dlopen_ext=Module.getExportByName(null,"android_dlopen_ext");
Interceptor.attach(dlopen_ext,{
onEnter:function(args){
var soname=ptr(args[0]).readCString();
if(soname.indexOf("libnative-lib.so")!=-1){
this.hook_start=true;
}
},onLeave:function(retval){
if(this.hook_start){
console.log("start hook");
hook_native();
this.hook_start=false;
}
}
})
}
function hook_java(){
Java.perform(function(){
var native_lib=Java.use("com.kanxue.algorithmbase.MainActivity");
native_lib.encodeFromJni_12.implementation=function(input){
input="TjFJ,U.@IyVufb'X{"
var res=this.encodeFromJni_12(input);
console.log("input:",input,"output:",res);
return res;
}
});
}
function main(){
hook_java();
hook_init_so();
}
setImmediate(main)

将时机调整为打开时就hook。

1
frida -U --no-pause -f com.kanxue.algorithmbase -l lianxi2.js

然后下面是触发时的输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
sub_8b04: 0xd199bb05
sub_8ABC: 0xd199babd
========onenter sub_8ABC========
0x11
dump_addr: AYZpq23IJrTFfghijklCDE1KLMmBdestU5678GHz0cuvwabN9+/VWXnoOPQRSxy4
========onleave sub_8ABC========
dump_addr: PHKa`#"X[cEWwvyx{z}RUT Z]\|SutbeD$'&)VYk!rdgfps_(:>GFI~^A@CBih%
========onenter sub_8b04========
TjFJ,U.@IyVufb'X{
0x11
========onleave sub_8b04========
T"r"}':TW)HcbT\I\Y[k Xf=
input: TjFJ,U.@IyVufb'X{ output: T"r"}':TW)HcbT\I\Y[k Xf=

看到这里就证明确实是这个在这个函数里面发生的变动。接着看这个函数的处理。并且发现sub_8ABC的入参和input的长度一致。最后我们参考下面的c++代码。修改成py的实现

1
2
3
4
5
6
7
8
9
10
int __fastcall sub_8ABC(int count)
{
int v1; // r8
size_t i; // r6

v1 = count;
for ( i = 0; i < strlen(aAyzpq23ijrtffg); ++i )
aAyzpq23ijrtffg[i] = aAyzpq23ijrtffg_0[i] ^ v1;
return 4 * sub_133E8(v1 + 2, 3) + 1;
}

下面放上完整代码

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
48

def getKey(cnt):
bkey = "AYZpq23IJrTFfghijklCDE1KLMmBdestU5678GHz0cuvwabN9+/VWXnoOPQRSxy4"
bkey_bytes=bytes(bkey,"utf-8")
res=bytearray()
for i in range(len(bkey)):
res.append(bkey_bytes[i]^cnt)
return res.decode("utf-8")
input="""TjFJ,U.@IyVufb'X{"""
bkey=getKey(len(input))

cnt=len(input)
cntpre=cnt-2
cnt1=cnt
output=""
print("bkey:"+bkey)
input_bytes=bytes(input,"utf-8")
v6=0
v7=0
idx=0
print(input_bytes[0])
print(input_bytes[0]&0xffffffff)
while True:
v8 = idx+v6
if v7 >= cntpre:
break
v9 = v7
v6 += 4
output+=bkey[input_bytes[v7] >> 2]
v10=input_bytes[v7+1]
v11=input_bytes[v7]
v7+=3
output +=bkey[(v10 >> 4) & 0xFFFFFFCF | 16 * (v11 & 3)]
output +=bkey[((input_bytes[v9 + 2] >> 6) & 0xFFFFFFC3) | (4 * (input_bytes[v9 + 1] & 0xF))]
output +=bkey[(input_bytes[v9 + 2])&0x3f]

if v7 < cnt1 :
output +=bkey[input_bytes[v7] >> 2]
v12 = 16 * input_bytes[v7] & 0x30
if cnt1 - 1 == v7:
v13 = '='
output+=bkey[v12]
else:
output+=bkey[v12 | (input_bytes[v7+1] >> 4)]
v13=bkey[4 * (input_bytes[v7+1] & 0xF)]
output+='='
output+=v13
print(output)

输出结果如下。和日志中的一致。第三题搞定。这几个算法看下来。算法部分基本是差不多的。主要变化的就是那个bkey部分。应该都是使用的base64的算法。只是key进行了调整。

1
PHKa`#"X[cEWwvyx{z}RUT Z]\|SutbeD$'&)VYk!rdgfps_(:>GFI~^A@CBih%

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

第四题algorithmbase_13.apk

和上面的例子差不多的步骤。依然是ida打开。然后直接分析encodeFromJni_13函数。然后根据返回值找到最后的处理函数sub_8B04。然后写个hook脚本。这次我们直接将前面注意到每次都变化的base64的key给打印出来。脚本直接用上面的hook脚本。只修改为encodeFromJni_13即可。输出如下

1
2
3
4
5
6
7
8
9
10
11
12
13
sub_8b04: 0xd67a1b05
sub_8ABC: 0xd67a1abd
========onenter sub_8ABC========
0x18
dump_addr: AYZpq23IJrTFfghijklCDE1KLMmBdestU5678GHz0cuvwabN9+/VWXnoOPQRSxy4
========onleave sub_8ABC========
dump_addr: YABhi*+QRjL^~pqrst[\])STUuZ|}klM-./ _Pb({mnoyzV!37NO@vwWHIJK`a,
========onenter sub_8b04========
#e9mUmm7YI!eEn}`b6jXg;Qg
0x18
========onleave sub_8b04========
JvDHB]EyB[eUlt2_k)P`L+Jvm_5bh@2b
input: #e9mUmm7YI!eEn}`b6jXg;Qg output: JvDHB]EyB[eUlt2_k)P`L+Jvm_5bh@2b

老样子。我还是用之前的py模拟来进行处理。先放上c++的版本。然后对照修改成py的版本

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
prelen = len - 2;
cnt = len;
output_1 = output;
v6 = 0;
v7 = 0;
while ( 1 )
{
v8 = (_BYTE *)(output_1 + v6);
if ( v7 >= prelen )
break;
v9 = input + v7;
v6 += 4;
*v8 = aAyzpq23ijrtffg[(unsigned int)*(unsigned __int8 *)(input + v7) >> 2] ^ cnt;
v10 = *(unsigned __int8 *)(input + v7 + 1);
v11 = *(_BYTE *)(input + v7);
v7 += 3;
v8[1] = aAyzpq23ijrtffg[(v10 >> 4) & 0xFFFFFFCF | 16 * (v11 & 3)];
v8[2] = aAyzpq23ijrtffg[((unsigned int)*(unsigned __int8 *)(v9 + 2) >> 6) & 0xFFFFFFC3 | 4
* (*(_BYTE *)(v9 + 1) & 0xF)] ^ cnt;
v8[3] = aAyzpq23ijrtffg[*(_BYTE *)(v9 + 2) & 0x3F];
}
if ( v7 < cnt )
{
*v8 = aAyzpq23ijrtffg[(unsigned int)*(unsigned __int8 *)(input + v7) >> 2];
v12 = 16 * *(unsigned __int8 *)(input + v7) & 0x30;
if ( cnt - 1 == v7 )
{
v13 = 61;
v8[1] = aAyzpq23ijrtffg[v12];
}
else
{
v8[1] = aAyzpq23ijrtffg[v12 | ((unsigned int)*(unsigned __int8 *)(input + v7 + 1) >> 4)];
v13 = aAyzpq23ijrtffg[4 * (*(_BYTE *)(input + v7 + 1) & 0xF)];
}
v8[3] = 61;
v8[2] = v13;
v8 += 4;a
}

下面贴上完整py的代码

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
def getKey(cnt):
bkey = "AYZpq23IJrTFfghijklCDE1KLMmBdestU5678GHz0cuvwabN9+/VWXnoOPQRSxy4"
bkey_bytes=bytes(bkey,"utf-8")
res=bytearray()
for i in range(len(bkey)):
res.append(bkey_bytes[i]^cnt)
return res

input="""#e9mUmm7YI!eEn}`b6jXg;Qg"""
input_bytes=bytes(input,"utf-8")
bkey=getKey(len(input))

cnt = len(input)
prelen = cnt - 2
output = ""
v6 = 0
v7 = 0
while True:
if v7 >= prelen:
break
v9 = v7
v6 += 4
output += chr(bkey[input_bytes[v7] >> 2] ^ cnt)
v10 = input_bytes[v7+1]
v11 = input_bytes[v7]
v7 += 3
output += chr(bkey[(v10 >> 4) & 0xFFFFFFCF | 16 * (v11 & 3)])
output += chr(bkey[(input_bytes[v9+2] >> 6) & 0xFFFFFFC3 | 4 * (input_bytes[v9+1] & 0xF)] ^ cnt)
output += chr(bkey[input_bytes[v9+2] & 0x3F])
if v7 < cnt:
output += chr(bkey[input_bytes[v7] >> 2])
v12 = 16 * input_bytes[v7] & 0x30
if cnt - 1 == v7:
v13 = '='
output += bkey[v12]
else:
output += bkey[v12 | (input_bytes[v7+1] >> 4)]
v13 = bkey[4 * (input_bytes[v7+1] & 0xF)]
output += v13
output += '='

print(output)

输出结果对比正确。第四题完成

1
JvDHB]EyB[eUlt2_k)P`L+Jvm_5bh@2b