分析样例下载:链接: https://pan.baidu.com/s/1sl0rnYC2u0wre0YYutIwhg 密码: 26b9

练习algorithmbase_71.apk

ida打开libnative-lib.so找到encodeFromJni_171。然后frida打印下输出结果。观察看有什么特征

1
2
3
4
5
6
7
function call_encodeFromJni_71(input){
Java.perform(function(){
var native_lib=Java.use("com.kanxue.algorithmbase.MainActivity");
var res= native_lib.encodeFromJni_71(input)
console.log("input:",input,"output:",res);
});
}

然后主动填参数调用一下。看看长度不同的时候加密数据的特征

1
2
3
4
5
input: 0000 output: 290BE933FC53A33BB5DA56800BBCE79C
input: 00000000 output: 5E88221ADFE7AC9EC43B51D6547D697B
input: 0000000000000000 output: 5E88221ADFE7AC9EC43B51D6547D697B
input: 00000000000000000000 output: 5E88221ADFE7AC9EC43B51D6547D697B
input: 00000000000000000000000000000000000000000000 output: 290BE933FC53A33BB5DA56800BBCE79

然后发现加密数据的长度不会变化。那么这个可能是一个hash算法。比如MD5、SHA之类的。但是意外的发现第2、3、4条数据的结果一致。并且直接hook的时候。结果的长度也发生了巨大的变化。如下

1
input: PPZOJXVYlPosRUmYAEFvDaaGyxfqIqaLKHnE output: FF9314F66ACB16596546183E91989D09D78F7E38D8970BDA322E791AD1718B6C793C4C09F2B1F01BFC56474603FF6112

看起来似乎是一个非标准算法。接着我们先尝试下trace。由于刚看视频学了下stalker。所以这里就不用unidbg来跑了。下面先写一个frida来hook所有的call

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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
//打印参数
function hexdumpMem(addr){
if(Process.findRangeByAddress(addr)){
return hexdump(ptr(addr),{length:0x40})+"\r\n"
}else{
return ptr(addr)+"\r\n";
}
}
//比较通用的hook地址并且打印5个参数。如果参数是地址就打印下内存信息
function nativeHookFunction(addr){
var base_addr=Module.getBaseAddress("libnative-lib.so");
var hook_addr=base_addr.add(addr);
Interceptor.attach(hook_addr,{
onEnter:function(args){
this.logs=[];
this.logs.push("call",addr.sub(base_addr));
this.arg0=args[0];
this.arg1=args[1];
this.arg2=args[2];
this.arg3=args[3];
this.arg4=args[4];
this.arg5=args[5];
this.logs.push("arg0:",hexdumpMem(this.arg0));
this.logs.push("arg1:",hexdumpMem(this.arg1));
this.logs.push("arg2:",hexdumpMem(this.arg2));
this.logs.push("arg3:",hexdumpMem(this.arg3));
this.logs.push("arg4:",hexdumpMem(this.arg4));
this.logs.push("arg5:",hexdumpMem(this.arg5));
},onLeave:function(retval){
this.logs.push("arg0 leave:",hexdumpMem(this.arg0));
this.logs.push("arg1 leave:",hexdumpMem(this.arg1));
this.logs.push("arg2 leave:",hexdumpMem(this.arg2));
this.logs.push("arg3 leave:",hexdumpMem(this.arg3));
this.logs.push("arg4 leave:",hexdumpMem(this.arg4));
this.logs.push("arg5 leave:",hexdumpMem(this.arg5));
this.logs.push("retval leave:",hexdumpMem(retval));
console.log(this.logs);
}
})
}

function hook_native(){
var base_addr=Module.getBaseAddress("libnative-lib.so");
nativeHookFunction(ptr(0x1894c));
nativeHookFunction(ptr(0x15da4));
nativeHookFunction(ptr(0x517c4));
nativeHookFunction(ptr(0x5c6d8));
// nativeHookFunction(ptr(0xfd50)); .memcpy
nativeHookFunction(ptr(0x8bef4));
// nativeHookFunction(ptr(0xfd00)); .strlen
nativeHookFunction(ptr(0x93cd4));
nativeHookFunction(ptr(0x17428));
// nativeHookFunction(ptr(0x8fafc));
nativeHookFunction(ptr(0x163ec));
nativeHookFunction(ptr(0x12d38));
nativeHookFunction(ptr(0x9429c));
// nativeHookFunction(ptr(0xfdb0)); .free
nativeHookFunction(ptr(0x10ad0));
nativeHookFunction(ptr(0x648a8));
nativeHookFunction(ptr(0x133b8));
nativeHookFunction(ptr(0x145fc));
nativeHookFunction(ptr(0x59684));
nativeHookFunction(ptr(0x6b640));
nativeHookFunction(ptr(0x5ac58));
nativeHookFunction(ptr(0x5799c));
nativeHookFunction(ptr(0x91b84));
nativeHookFunction(ptr(0x10638));
nativeHookFunction(ptr(0x8cc8c));
nativeHookFunction(ptr(0x8d678));
nativeHookFunction(ptr(0x1716c));
nativeHookFunction(ptr(0x4f458));
nativeHookFunction(ptr(0x17704));
nativeHookFunction(ptr(0x4f018));
nativeHookFunction(ptr(0x18c7c));
nativeHookFunction(ptr(0x58f38));
// nativeHookFunction(ptr(0xfd40)); .malloc
nativeHookFunction(ptr(0x52814));
nativeHookFunction(ptr(0x97568));
nativeHookFunction(ptr(0x50790));
nativeHookFunction(ptr(0x56808));
nativeHookFunction(ptr(0x103b4));
nativeHookFunction(ptr(0x191c4));
nativeHookFunction(ptr(0x1a73c));
nativeHookFunction(ptr(0x8c944));
nativeHookFunction(ptr(0x8cc10));
nativeHookFunction(ptr(0x8bfc0));
// nativeHookFunction(ptr(0xf9e0)); ._Znwm
nativeHookFunction(ptr(0x975f4));
// nativeHookFunction(ptr(0xfd70)); .memset
nativeHookFunction(ptr(0x58190));
nativeHookFunction(ptr(0x1846c));
nativeHookFunction(ptr(0x68b1c));
nativeHookFunction(ptr(0x17954));
nativeHookFunction(ptr(0x506e4));
// nativeHookFunction(ptr(0xfc50)); ._ZdlPv
nativeHookFunction(ptr(0x536d4));
nativeHookFunction(ptr(0x4f6c0));
var encodeFromJni_71=base_addr.add(0x156B4);
Interceptor.attach(encodeFromJni_71,{
onEnter:function(args){
this.tid=Process.getCurrentThreadId();
console.log("enter encodeFromJni_71 tid:",this.tid);
Stalker.follow(this.tid, {
events: {
call: true, // CALL instructions: yes please
// Other events:
ret: false, // RET instructions
exec: false, // all instructions: not recommended as it's
// a lot of data
block: false, // block executed: coarse execution trace
compile: false // block compiled: useful for coverage
},
onCallSummary:function(summary){ //调用的地址和调用的次数
for(var iter in summary){
var module= Process.getModuleByAddress(ptr(iter))
if(module.name.indexOf("libnative-lib.so")!=-1){
console.log(ptr(iter).sub(module.base));
}
}
},
// onReceive:function(events){ //调用的流程,地址1是哪里发生的调用。地址2是调用到了哪里
// console.log("enter onReceive")
// var eventsData=Stalker.parse(events,{
// annotate: true,
// stringify: true
// });
// for(var idx in eventsData){
// var dataSp=eventsData[idx];
// var addr1=dataSp[1];
// var addr2=dataSp[2];
// try{
// var module1=Process.getModuleByAddress(ptr(addr1));
// if(module1.name.indexOf("libnative-lib.so")!=-1){
// var module2=Process.getModuleByAddress(ptr(addr2));
// console.log(dataSp[0],module1.name,addr1,module2.name,addr2);
// }
// }catch(err){
// console.log(dataSp);
// }
// }
// }
})
},onLeave(retval){
Stalker.unfollow(this.tid);
}
})
}
function hook_java(){
Java.perform(function(){
var native_lib=Java.use("com.kanxue.algorithmbase.MainActivity");
native_lib.encodeFromJni_71.implementation=function(input){
var res=this.encodeFromJni_71(input);
console.log("input:",input,"output:",res);
return res;
}
});
}
function call_encodeFromJni_71(input){
Java.perform(function(){
var native_lib=Java.use("com.kanxue.algorithmbase.MainActivity");
var res= native_lib.encodeFromJni_71(input)
console.log("input:",input,"output:",res);
});
}
function main(){
hook_java();
hook_native();
}
setImmediate(main)

这里使用stalker把所有调用的函数获取出来。并且把每个函数都hook上。然后参数都打印出来。这样我们可以搜一下加密结果。然后找到最早先出现的位置。当前这次trace的加密结果如下

1
input: ZUKZKXAJ output: FF9314F66ACB16596546183E91989D09776013E072D330921D4CE13D47B1D091

然后搜索FF 93 14 F6。就可以搜索到很多地方有出现了。这里我们找到最早先出现的位置数据如下

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
call,0x4f6c0,arg0:,             0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
77ca768900 78 6e 61 6b 69 5f 65 75 6e 61 79 6d 5f 5f 5f 67 xnaki_eunaym___g
77ca768910 fd a1 ae a5 94 fe cb d0 fa 9f b2 bd a5 c0 ed da ................
77ca768920 aa a7 14 f2 3e 59 df 22 c4 c6 6d 9f 61 06 80 45 ....>Y."..m.a..E
77ca768930 c4 48 7b 3b fa 11 a4 19 3e d7 c9 86 5f d1 49 c3 .H{;....>..._.I.
,arg1:, 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
7fcd649d08 6b 61 6e 78 75 65 5f 69 6d 79 61 6e 67 5f 5f 5f kanxue_imyang___
7fcd649d18 23 d3 eb 22 f2 f5 9e 79 d0 a0 64 cd 7f 00 00 00 #.."...y..d.....
7fcd649d28 80 b1 62 be 77 00 00 00 a0 ea 8b d5 77 00 00 00 ..b.w.......w...
7fcd649d38 a1 95 d3 6e 00 00 00 00 65 a2 50 e8 00 00 00 00 ...n....e.P.....
,arg2:, 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
7fcd649d08 6b 61 6e 78 75 65 5f 69 6d 79 61 6e 67 5f 5f 5f kanxue_imyang___
7fcd649d18 23 d3 eb 22 f2 f5 9e 79 d0 a0 64 cd 7f 00 00 00 #.."...y..d.....
7fcd649d28 80 b1 62 be 77 00 00 00 a0 ea 8b d5 77 00 00 00 ..b.w.......w...
7fcd649d38 a1 95 d3 6e 00 00 00 00 65 a2 50 e8 00 00 00 00 ...n....e.P.....
,arg3:,0xb559a344
,arg4:,0xdc3faab
,arg5:,0xe825ecad
,arg0 leave:, 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
77ca768900 78 6e 61 6b 69 5f 65 75 6e 61 79 6d 5f 5f 5f 67 xnaki_eunaym___g
77ca768910 fd a1 ae a5 94 fe cb d0 fa 9f b2 bd a5 c0 ed da ................
77ca768920 aa a7 14 f2 3e 59 df 22 c4 c6 6d 9f 61 06 80 45 ....>Y."..m.a..E
77ca768930 c4 48 7b 3b fa 11 a4 19 3e d7 c9 86 5f d1 49 c3 .H{;....>..._.I.
,arg1 leave:, 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
7fcd649d08 ff 93 14 f6 6a cb 16 59 65 46 18 3e 91 98 9d 09 ....j..YeF.>....
7fcd649d18 23 d3 eb 22 f2 f5 9e 79 d0 a0 64 cd 7f 00 00 00 #.."...y..d.....
7fcd649d28 80 b1 62 be 77 00 00 00 a0 ea 8b d5 77 00 00 00 ..b.w.......w...
7fcd649d38 a1 95 d3 6e 00 00 00 00 65 a2 50 e8 00 00 00 00 ...n....e.P.....
,arg2 leave:, 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
7fcd649d08 ff 93 14 f6 6a cb 16 59 65 46 18 3e 91 98 9d 09 ....j..YeF.>....
7fcd649d18 23 d3 eb 22 f2 f5 9e 79 d0 a0 64 cd 7f 00 00 00 #.."...y..d.....
7fcd649d28 80 b1 62 be 77 00 00 00 a0 ea 8b d5 77 00 00 00 ..b.w.......w...
7fcd649d38 a1 95 d3 6e 00 00 00 00 65 a2 50 e8 00 00 00 00 ...n....e.P.....

这里虽然是最早先出现的位置。但是只有前16字节是相同的。后16字节完全不一样。说明可能是分了两次处理的。再找一下后16字节最早出现的位置。

搜索77 60 13 E0。然后找到第一个找到的数据。列出这块数据如下

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
call,0x4f6c0,arg0:,             0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
77ca768900 78 6e 61 6b 69 5f 65 75 6e 61 79 6d 5f 5f 5f 67 xnaki_eunaym___g
77ca768910 fd a1 ae a5 94 fe cb d0 fa 9f b2 bd a5 c0 ed da ................
77ca768920 aa a7 14 f2 3e 59 df 22 c4 c6 6d 9f 61 06 80 45 ....>Y."..m.a..E
77ca768930 c4 48 7b 3b fa 11 a4 19 3e d7 c9 86 5f d1 49 c3 .H{;....>..._.I.
,arg1:, 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
7fcd649d08 ef 83 04 e6 7a db 06 49 75 56 08 2e 81 88 8d 19 ....z..IuV......
7fcd649d18 23 d3 eb 22 f2 f5 9e 79 d0 a0 64 cd 7f 00 00 00 #.."...y..d.....
7fcd649d28 80 b1 62 be 77 00 00 00 a0 ea 8b d5 77 00 00 00 ..b.w.......w...
7fcd649d38 a1 95 d3 6e 00 00 00 00 65 a2 50 e8 00 00 00 00 ...n....e.P.....
,arg2:, 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
7fcd649d08 ef 83 04 e6 7a db 06 49 75 56 08 2e 81 88 8d 19 ....z..IuV......
7fcd649d18 23 d3 eb 22 f2 f5 9e 79 d0 a0 64 cd 7f 00 00 00 #.."...y..d.....
7fcd649d28 80 b1 62 be 77 00 00 00 a0 ea 8b d5 77 00 00 00 ..b.w.......w...
7fcd649d38 a1 95 d3 6e 00 00 00 00 65 a2 50 e8 00 00 00 00 ...n....e.P.....
,arg3:,0x6c7298f0
,arg4:,0xee0b0e20
,arg5:,0xa3437e07
,arg0 leave:, 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
77ca768900 78 6e 61 6b 69 5f 65 75 6e 61 79 6d 5f 5f 5f 67 xnaki_eunaym___g
77ca768910 fd a1 ae a5 94 fe cb d0 fa 9f b2 bd a5 c0 ed da ................
77ca768920 aa a7 14 f2 3e 59 df 22 c4 c6 6d 9f 61 06 80 45 ....>Y."..m.a..E
77ca768930 c4 48 7b 3b fa 11 a4 19 3e d7 c9 86 5f d1 49 c3 .H{;....>..._.I.
,arg1 leave:, 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
7fcd649d08 77 60 13 e0 72 d3 30 92 1d 4c e1 3d 47 b1 d0 91 w`..r.0..L.=G...
7fcd649d18 23 d3 eb 22 f2 f5 9e 79 d0 a0 64 cd 7f 00 00 00 #.."...y..d.....
7fcd649d28 80 b1 62 be 77 00 00 00 a0 ea 8b d5 77 00 00 00 ..b.w.......w...
7fcd649d38 a1 95 d3 6e 00 00 00 00 65 a2 50 e8 00 00 00 00 ...n....e.P.....
,arg2 leave:, 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
7fcd649d08 77 60 13 e0 72 d3 30 92 1d 4c e1 3d 47 b1 d0 91 w`..r.0..L.=G...
7fcd649d18 23 d3 eb 22 f2 f5 9e 79 d0 a0 64 cd 7f 00 00 00 #.."...y..d.....
7fcd649d28 80 b1 62 be 77 00 00 00 a0 ea 8b d5 77 00 00 00 ..b.w.......w...
7fcd649d38 a1 95 d3 6e 00 00 00 00 65 a2 50 e8 00 00 00 00 ...n....e.P.....
,arg3 leave:,0x6c7298f0
,arg4 leave:,0xee0b0e20
,arg5 leave:,0xa3437e07
,retval leave:,0x0

我们先看前16字节的计算的位置是0x4f6c0。打开这个函数后发现这个函数很有可能就是算法里面了。我们找到一个全局变量

image-20210127214658775

然后进去看看。里面有我们需要的算法用的常量。可以随便搜索一个看看

image-20210127214747050

查到的是aes的算法。那么这个分成两段加密。前面这段应该是aes。后面一段还不清楚。我们继续查查aes的key和iv分别是多少。找到调用0x4f6c0函数的sub_4F018。然后直接在日志里面搜索4f018找相应的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
call,0x4f018,arg0:,             0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
77bea66080 6b 61 6e 78 75 65 5f 69 6d 79 61 6e 67 5f 5f 5f kanxue_imyang___
77bea66090 00 00 00 00 00 00 00 00 62 61 73 69 63 5f 73 74 ........basic_st
77bea660a0 72 69 6e 67 00 00 00 00 00 00 00 00 00 00 00 00 ring............
77bea660b0 61 6c 6c 6f 63 61 74 6f 72 3c 54 3e 3a 3a 61 6c allocator<T>::al
,arg1:, 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
77bea66080 6b 61 6e 78 75 65 5f 69 6d 79 61 6e 67 5f 5f 5f kanxue_imyang___
77bea66090 00 00 00 00 00 00 00 00 62 61 73 69 63 5f 73 74 ........basic_st
77bea660a0 72 69 6e 67 00 00 00 00 00 00 00 00 00 00 00 00 ring............
77bea660b0 61 6c 6c 6f 63 61 74 6f 72 3c 54 3e 3a 3a 61 6c allocator<T>::al
,arg2:, 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
77bd8610b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
77bd8610c0 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 ................
77bd8610d0 00 72 69 6e 67 3b 00 00 6e 67 2e 53 74 72 69 00 .ring;..ng.Stri.
77bd8610e0 00 00 00 00 00 00 00 00 6c 2a b1 ff 07 00 00 00 ........l*......

看到参数1和参数2了。这两个根据上一个例子的经验。kanxue_imyang___就是key和iv了。但是参数3应该是明文。但是结果却是16个00加上16个0x10并不是我们的明文。暂时先不管这段明文怎么来的。我们先验证一下结果对不对

image-20210127221158903

这里结果可以看到。虽然长度不对。但是前面已经对的上了。说明里面应该是分割了之后。只保留前面的64个字符

1
2
3
4
//之前日志中的记录
input: ZUKZKXAJ output: FF9314F66ACB16596546183E91989D09776013E072D330921D4CE13D47B1D091
//我们的aes加密结果
ff9314f66acb16596546183e91989d09776013e072d330921d4ce13d47b1d091 e71526567e0ed0abd725d4941d5ab617

这里直接就看到结果都在这个里面了。最后我们只要知道怎么从明文数据转换成16个0x0和16个0x10。就可以完成了。搜索16个0x10最早先出现的位置

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
call,0x1846c,arg0:,             0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
7fcd649dd8 31 00 00 00 00 00 00 00 20 00 00 00 00 00 00 00 1....... .......
7fcd649de8 b0 10 86 bd 77 00 00 00 10 5a 55 4b 5a 4b 58 41 ....w....ZUKZKXA
7fcd649df8 4a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 J...............
7fcd649e08 23 d3 eb 22 f2 f5 9e 79 a0 ea 8b d5 77 00 00 00 #.."...y....w...
,arg1:,0x57505bf4
,arg2:,0xea90a458
,arg3:, 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
2fc91a41 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
2fc91a51 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
2fc91a61 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
2fc91a71 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
,arg4:,0xc8f899d0
,arg5:,0xea90a459
,arg0 leave:, 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
7fcd649dd8 31 00 00 00 00 00 00 00 20 00 00 00 00 00 00 00 1....... .......
7fcd649de8 b0 10 86 bd 77 00 00 00 10 5a 55 4b 5a 4b 58 41 ....w....ZUKZKXA
7fcd649df8 4a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 J...............
7fcd649e08 23 d3 eb 22 f2 f5 9e 79 a0 ea 8b d5 77 00 00 00 #.."...y....w...
,arg1 leave:,0x57505bf4
,arg2 leave:,0xea90a458
,arg3 leave:, 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
2fc91a41 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
2fc91a51 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
2fc91a61 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
2fc91a71 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
,arg4 leave:,0xc8f899d0
,arg5 leave:,0xea90a459
,retval leave:, 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
77bd8610b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
77bd8610c0 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 ................
77bd8610d0 00 72 69 6e 67 3b 00 00 6e 67 2e 53 74 72 69 00 .ring;..ng.Stri.
77bd8610e0 00 00 00 00 00 00 00 00 6c 2a b1 ff 07 00 00 00 ........l*......

这里我们锁定到了0x1846c这个函数将明文转换成了用来加密的数据。先用frida再针对这一个函数来hook一下。把参数1里面的指针打印下看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function hook_native_2(){
var base_add=Module.getBaseAddress("libnative-lib.so");
var sub_1846c=base_add.add(0x1846c);
Interceptor.attach(sub_1846c,{
onEnter:function(args){
console.log("onenter sub_1846c")
console.log(Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') + '\n');
console.log(args[1]);
console.log(hexdumpMem(args[0].add(0x10).readPointer()));

},onLeave:function(retval){
console.log("onenter onleave")
console.log(hexdumpMem(retval));
}
})
}

结果发现果然并不是在这里面计算的。其中某一次调用的时候传了这个结果进来。可能只是在这里转存。我们根据打印的堆栈找到encodeFromJni_171+0x4b8的位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
onenter sub_1846c
0x77be9b4b6c libnative-lib.so!Java_com_kanxue_algorithmbase_MainActivity_encodeFromJni_171+0x4b8
0x77bec190cc base.odex!oatexec+0xcc

0x57505bf4
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
77bd861f50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
77bd861f60 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 ................
77bd861f70 00 72 69 6e 67 3b 00 00 6e 67 2e 53 74 72 69 00 .ring;..ng.Stri.
77bd861f80 61 6e 64 72 6f 69 64 2e 76 69 65 77 2e 52 65 6e android.view.Ren

onenter onleave
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
77bd861f50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
77bd861f60 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 ................
77bd861f70 00 72 69 6e 67 3b 00 00 6e 67 2e 53 74 72 69 00 .ring;..ng.Stri.
77bd861f80 61 6e 64 72 6f 69 64 2e 76 69 65 77 2e 52 65 6e android.view.Ren

找到这里之后。接着就找参数1的值是走过哪些函数。

image-20210127225602919

我们从sub_15DA4开始。挨个hook一次。看看是哪个函数里面计算出来的这个结果。只要打印参数0的指针数据就可以看出来了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function hook_native_2(){
var base_add=Module.getBaseAddress("libnative-lib.so");
var sub_15DA4=base_add.add(0x15DA4);
Interceptor.attach(sub_15DA4,{
onEnter:function(args){
console.log("onenter sub_15DA4")
this.arg0=args[0];
console.log(Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') + '\n');
console.log(hexdumpMem(this.arg0.add(0x10).readPointer()));

},onLeave:function(retval){
console.log("onleave sub_15DA4")
console.log(hexdumpMem(this.arg0.add(0x10).readPointer()));
}
})
}

然后看下输出结果。确定就是在这个里面计算出的结果。我们继续追踪。看看里面的哪个函数计算的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
onenter sub_15DA4
0x77be9b4b18 libnative-lib.so!Java_com_kanxue_algorithmbase_MainActivity_encodeFromJni_171+0x464
0x77bec190cc base.odex!oatexec+0xcc

0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
77d58a0000 c0 ff 89 d5 77 00 00 00 6e 74 61 4c 54 47 90 8a ....w...ntaLTG..
77d58a0010 6e 74 61 4c 54 47 90 8a 00 00 00 00 00 00 00 00 ntaLTG..........
77d58a0020 80 d8 89 d5 77 00 00 00 6e 74 61 4c 43 45 90 9a ....w...ntaLCE..
77d58a0030 6e 74 61 4c 43 45 90 9a 00 00 00 00 00 00 00 00 ntaLCE..........

onleave sub_15DA4
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
77bd862d30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
77bd862d40 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 ................
77bd862d50 00 72 69 6e 67 3b 00 00 6e 67 2e 53 74 72 69 00 .ring;..ng.Stri.
77bd862d60 49 6e 63 72 65 61 73 69 6e 67 20 63 6f 64 65 20 Increasing code

再往里面找。就有很多函数传递这个参数了。我们可以选择优化那个stalker或者是直接用unidbg的内存写入监控来找。我先找到sub_15DA4这个函数的结束位置0x163E8。然后unidbg给这里打个断点。然后查下r0的值

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
public class lianxi71 {
public static void main(String[] args) {
lianxi71 lianxi = new lianxi71();
lianxi.Call_EncodeFromJni_71();
}
private final AndroidEmulator emulator;
private final VM vm;
private DvmClass Clazz;
private lianxi71() {
emulator = new AndroidARM64Emulator();
Memory memory = emulator.getMemory();
LibraryResolver resolver = new AndroidResolver(23);
memory.setLibraryResolver(resolver);
vm = emulator.createDalvikVM(null);
vm.setVerbose(false);
DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/resources/example_binaries/lianxi71/libnative-lib.so"), false);
dm.callJNI_OnLoad(emulator);
Clazz = vm.resolveClass("com/kanxue/algorithmbase/MainActivity");
Debugger dbg= emulator.attach();
dbg.addBreakPoint(0x400163E8);
dbg.debug();
}
public void Call_EncodeFromJni_71(){
String res=Clazz.callStaticJniMethodObject(emulator,"encodeFromJni_171(Ljava/lang/String;)Ljava/lang/String;","ZUKZKXAJ").toString();
System.out.print(res);
}
}

这里查到r0的值。然后取0x10字节的指针打印一下。确定是16个0和16个0x10的那段之后。我们就加个内存写入监控

1
2
3
4
5
6
7
8
9
emulator.getBackend().hook_add_new(new WriteHook() {
@Override
public void hook(Backend backend, long address, int size, long value, Object user) {
RegisterContext context = emulator.getContext();
UnidbgPointer lr = context.getLRPointer();
UnidbgPointer pc = context.getPCPointer();
System.out.println(String.format("write mem address:0x%x value:%x lr=%s pc=%s",address,value,lr,pc));
}
},0x403d3060,0x403d3060+0x20,null);

然后结果如下。成功找到计算的位置了

1
2
3
4
5
6
7
8
9
10
11
12
write mem address:0x403d3060 value:0 size:8 lr=RX@0x4001a050[libnative-lib.so]0x1a050 pc=RX@0x4020b188[libc.so]0x1c188
write mem address:0x403d3068 value:0 size:8 lr=RX@0x4001a050[libnative-lib.so]0x1a050 pc=RX@0x4020b188[libc.so]0x1c188
write mem address:0x403d3070 value:1010101010101010 size:8 lr=RX@0x40016248[libnative-lib.so]0x16248 pc=RX@0x4020b640[libc.so]0x1c640
write mem address:0x403d3078 value:1010101010101010 size:8 lr=RX@0x40016248[libnative-lib.so]0x16248 pc=RX@0x4020b640[libc.so]0x1c640
write mem address:0x403d3070 value:1010101010101010 size:8 lr=RX@0x40016248[libnative-lib.so]0x16248 pc=RX@0x4020b640[libc.so]0x1c640
write mem address:0x403d3078 value:1010101010101010 size:8 lr=RX@0x40016248[libnative-lib.so]0x16248 pc=RX@0x4020b640[libc.so]0x1c640
write mem address:0x403d3080 value:0 size:1 lr=RX@0x400163a4[libnative-lib.so]0x163a4 pc=RX@0x40015e60[libnative-lib.so]0x15e60
write mem address:0x403d3060 value:5916cb6af61493ff size:8 lr=RX@0x4004f204[libnative-lib.so]0x4f204 pc=RX@0x4004f204[libnative-lib.so]0x4f204
write mem address:0x403d3068 value:99d98913e184665 size:8 lr=RX@0x4004f204[libnative-lib.so]0x4f204 pc=RX@0x4004f204[libnative-lib.so]0x4f204
write mem address:0x403d3070 value:9230d372e0136077 size:8 lr=RX@0x4004f204[libnative-lib.so]0x4f204 pc=RX@0x4004f204[libnative-lib.so]0x4f204
write mem address:0x403d3078 value:91d0b1473de14c1d size:8 lr=RX@0x4004f204[libnative-lib.so]0x4f204 pc=RX@0x4004f204[libnative-lib.so]0x4f204
"FF9314F66ACB16596546183E91989D09776013E072D330921D4CE13D47B1D091"

这里我们看到写了两次8字节的0和2次8字节的10。很明显这里就是我们想要的了。找到0x1a050。这里是个memcpy。我们需要搞清楚x1和x2是什么值

image-20210127235306220

我测试调用了多次。发现x1指向的数据始终都是0。那么关键就是x2,决定前面这里写入几个0。锁定x2数据的来源。然后同样对内存进行监控。然后找到写入0x10长度的位置

1
write mem address:0xbffff470 value:10 size:8 lr=RX@0x400162e8[libnative-lib.so]0x162e8 pc=RX@0x40019290[libnative-lib.so]0x19290

找下下面写入的代码。0x10来自于x4。而x4又是函数的参数。所以必须继续向上找。找到第四个参数是怎么赋值的。一值向上找。最终找到0x10638这里计算出来的。hook一下看看。结果再hook的时候看到一条关键数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
onenter sub_10638
0x77be9b5314 libnative-lib.so!0x16314
0x77be9b6a24 libnative-lib.so!0x17a24
0x77be9b4acc libnative-lib.so!Java_com_kanxue_algorithmbase_MainActivity_encodeFromJni_171+0x418
0x77bec190cc base.odex!oatexec+0xcc

0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
77d58a6e80 78 9c 8b 0a f5 8e f2 8e 70 f4 02 00 0b af 02 83 x.......p.......
77d58a6e90 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
77d58a6ea0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
77d58a6eb0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................

0x60f29f24
onleave sub_10638
arg0: 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
77d58a6e80 78 9c 8b 0a f5 8e f2 8e 70 f4 02 00 0b af 02 83 x.......p.......
77d58a6e90 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
77d58a6ea0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
77d58a6eb0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................

这个数据明显是zlib压缩的。我们测试一下发现这个值就是明文的zlib压缩。那么大胆猜测一下。0x10个0是不是指的是zlib的长度呢。

image-20210128001355525

修改一下明文。然后查下0的长度是否会发生变化。

1
2
3
4
5
6
7
8
9
10
input: ZUKZKXAJJ output: FF9314F66ACB16596546183E91989D0985B7861EBCBEC8C1D2FE94B1B1F070FF
aes明文
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f
aes密文
ff9314f66acb16596546183e91989d0985b7861ebcbec8c1d2fe94b1b1f070ff 2f2a5a7bee9760cc4ee94111128e6924
0的长度 0x11
明文的zlib
78 9c 8b 0a f5 8e f2 8e 70 f4 f2 02 00 0e 7c 02 cd
zlib的长度 0x11

如此看来。0的长度确实是和zlib的长度挂钩。但是又有另外一个问题。如果zlib的长度大于0x20会怎么样呢?测试一下看看

1
2
3
4
5
6
7
8
9
10
11
12
13
input: ZUKZKXAJJABCDEFGHIJKabcsdd output: FF9314F66ACB16596546183E91989D09D78F7E38D8970BDA322E791AD1718B6C4AE85C0F5817CE451A6F98F26808E29F
aes明文
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e
aes密文
ff9314f66acb16596546183e91989d09d78f7e38d8970bda322e791ad1718b6c4ae85c0f5817ce451a6f98f26808e29f a95ad4f41ff1e4851cbd5df68fe03b64
0的长度0x22
明文的zlib
78 9c 8b 0a f5 8e f2 8e 70 f4 f2 72 74 72 76 71
75 73 f7 f0 f4 f2 4e 4c 4a 2e 4e 49 01 00 6a 04
08 30
zlib的长度0x22

到这里第一段就出来了。0是固定的。长度是根据明文的zlib计算的。然后后面的部分我们也可以推测出来长度是填充到满16的倍数字节。接下来找怎么得到的0xe的。根据之前找的写入监控的线索。知道是在0x16248这个位置写入的。找到那里的代码。发现调用的是memset。

image-20210128003614078

然后unidbg断点到那里。看看x0 x1 x2的值

1
x0=0x40437022 x1=0xe x2=0xe

这就巧合了。后面补充0x10个字节的时候就是0x10。补充0xe个字节的时候。补充的值就是0xe。上面另外一个例子是0xf。也是补充0xf个字节。那么这里就能确定出来这个值是如何算出来的了。

我们最后最后确定一下。如果是0x20长度的zlib数据。是否会补充到0x30。

1
2
3
4
5
6
7
8
明文:ZUKZKXAJJABCDEFGHIJKabcs
zlib:
78 9c 8b 0a f5 8e f2 8e 70 f4 f2 72 74 72 76 71
75 73 f7 f0 f4 f2 4e 4c 4a 2e 06 00 5a 08 07 68
aes加密的数据
77ca663f40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
77ca663f50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
77ca663f60 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import struct,zlib

from Crypto.Cipher import AES

def b2hex(bins):
return ''.join(["%02X" % x for x in bins]).strip().lower()

def zlib_compress(zlib_data):
data = zlib_data.encode("utf-8")
outData = zlib.compress(data)
return outData

def pad(s): return s + bytes([16 - len(s) % 16] * (16 - len(s) % 16))
# 加密函数
def cbc_encrypt(key,iv,data):
mode = AES.MODE_CBC
cryptos = AES.new(key, mode, iv)
cipher_text = cryptos.encrypt(pad(data))
# 因为AES加密后的字符串不一定是ascii字符集的,输出保存可能存在问题,所以这里转为16进制字符串
return cipher_text

def calc_71(inputdata):
zlibdata=zlib_compress(inputdata)
zliblen=len(zlibdata)
# print("zliblen:%x"%zliblen)
outdata=bytes([0x0]*zliblen)
if zliblen%0x10==0:
outdata+=bytes([0x10]*0x10)
else:
mo=0x10-zliblen%0x10
mobytes=struct.pack("B",mo)
for i in range(mo):
outdata+=mobytes
# print(outdata)
key="kanxue_imyang___".encode("utf-8")
iv="kanxue_imyang___".encode("utf-8")
res=cbc_encrypt(key,iv,outdata)
res=res[0:len(outdata)]
return b2hex(res)

print(calc_71("0000"))
print(calc_71("00000000"))
print(calc_71("0000000000000000"))
print(calc_71("00000000000000000000"))
print(calc_71("00000000000000000000000000000000000000000000"))
print(calc_71("PPZOJXVYlPosRUmYAEFvDaaGyxfqIqaLKHnE"))
print(calc_71("ZUKZKXAJ"))
print(calc_71("ZUKZKXAJJABCDEFGHIJKabcsdd"))

然后贴上对比结果。和上面的都一致。搞定睡觉

1
2
3
4
5
6
7
8
0000: 290be933fc53a33bb5da56800bbce79c
00000000: 5e88221adfe7ac9ec43b51d6547d697b
0000000000000000: 5e88221adfe7ac9ec43b51d6547d697b
00000000000000000000: 5e88221adfe7ac9ec43b51d6547d697b
00000000000000000000000000000000000000000000: 290be933fc53a33bb5da56800bbce79c
PPZOJXVYlPosRUmYAEFvDaaGyxfqIqaLKHnE: ff9314f66acb16596546183e91989d09d78f7e38d8970bda322e791ad1718b6c793c4c09f2b1f01bfc56474603ff6112
ZUKZKXAJ: ff9314f66acb16596546183e91989d09776013e072d330921d4ce13d47b1d091
ZUKZKXAJJABCDEFGHIJKabcsdd: ff9314f66acb16596546183e91989d09d78f7e38d8970bda322e791ad1718b6c4ae85c0f5817ce451a6f98f26808e29f