Traceview+frida

TraceView 是 Android SDK 中内置的一个工具,它可以加载 trace 文件,用图形的形式展示代码的执行时间、次数及调用栈

利用mprop工具修改当前手机应用都可以调试

​ 使用DDMS时,只能看到手机,看不到进程信息,这样我们就不能获取指定进程的信息

​ 如果需要调试android 的程序,以下两个条件满足一个就行。第一是apk的配置文件内的AndroidManifest.xml的 android:debuggable=”true”,第二就是/default.prop中ro.debuggable=1。两种方式第一种通常是解包添加属性再打包,随着加壳软件以及apk校验等,容易出现安装包异常。第二种由于一般的手机发布时ro.debuggable一般是0 也就是不允许调试,通过修改rom的办法在手机上比较麻烦,需要刷机等等,模拟器上一般是vmdk的虚拟机,也没法修改rom。

​ 这里我们可以直接用mprop这个工具,可以直接修改android属性。

1578290999051

同样有不同的版本,通过adb上传到模拟机,修改属性值。

arm:

1
2
3
4
adb push .\libs\armeabi-v7a\mprop /data/local/tmp/
adb shell "chmod 755 /data/local/tmp/mprop"
adb shell "/data/local/tmp/mprop"
adb shell "setprop ro.debuggable 1"

x86:

1
2
3
4
adb push .\libs\x86\mprop /data/local/tmp/
adb shell "chmod 755 /data/local/tmp/mprop"
adb shell "/data/local/tmp/mprop"
adb shell "setprop ro.debuggable 1"

这样我们就能看到进行信息了

1578291249889

TraceView 追踪函数

这里用到的是DDMS中的traceview工具,直接下载android-sdk工具包,运行里面的ddms.bat即可。

如图选定进程名,选择Trace based profiling。

1578291346817

触发APP条件事件(比如Click或者刷新)生成.trace文件,这样我们就可以看到整个用堆栈过程。

1578305250868

对比AES的解密实现,只需要如下三个点得到,key、模式和iv值就行了。

1578292369510

key值和iv值是构造函数,根据hook构造函数的方法,要用到$init,并且要return this.$init(arg1,arg2)调用原始的函数实现,同时IvParameterSpec方法的参数是一个比特数组的重载函数。

Java中比特数组表示为[B,其他类型的数组如下表示。

1
2
3
4
5
6
7
8
9
[Z = boolean
[B = byte
[S = short
[I = int
[J = long
[F = float
[D = double
[C = char
[L = any non-primitives(Object)

在frida利用如下方式输出比特数组:

1
2
3
4
5
6
for (i=0;i<arg1.length;i++)
{
b=(arg1[i]>>>0)&0xff;
n=b.toString(16);
hexstr += ("00" + n).slice(-2)+" ";
}

完整jscode:

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
Java.perform(function x() {
// Function to hook is defined here
var UtiEncrypt = Java.use('com.bianlidai123.bc.encrypt.UtiEncrypt');

// Whenever button is clicked
UtiEncrypt.decryptAES.overload('java.lang.String').implementation = function (arg1) {
// Show a message to know that the function got called


var sign=this.decryptAES(arg1);

send("arg1:"+arg1);
send("sign:"+sign);
return sign;

};

var Cipher = Java.use('javax.crypto.Cipher');
Cipher.getInstance.overload('java.lang.String').implementation = function (arg1) {
var sign2=this.getInstance(arg1);
send("Instance:"+arg1);
return sign2;

};

var SecretKeySpec = Java.use('javax.crypto.spec.SecretKeySpec');
SecretKeySpec.$init.overload('[B', 'java.lang.String').implementation = function (arg1,arg2) {
hexstr="";
for (i=0;i<arg1.length;i++)
{
b=(arg1[i]>>>0)&0xff;
n=b.toString(16);
hexstr += ("00" + n).slice(-2)+" ";
}
send("Key: " + hexstr);
//send("init1:"+arg1+arg2);
return this.$init(arg1,arg2);

};

var IvParameterSpec = Java.use('javax.crypto.spec.IvParameterSpec');
IvParameterSpec.$init.overload('[B').implementation = function (arg1) {
hexstr="";
for (i=0;i<arg1.length;i++)
{
b=(arg1[i]>>>0)&0xff;
n=b.toString(16);
hexstr += ("00" + n).slice(-2)+" ";
}
send("Iv: " + hexstr);
//send("init4:"+arg1);
return this.$init(arg1);

};

});

hook java原生算法同时打印调用堆栈

如下脚本可以hook java原生MD5、MAC、DES、AES、RSA加密算法并打印出调用堆栈,简单暴力。

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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# -*- coding: UTF-8 -*-
import frida, sys

jsCode = """
function showStacks() {
Java.perform(function() {
send(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()));
});
}

function bytesToHex(arr) {
var str = "";
for (var i = 0; i < arr.length; i++) {
var tmp = arr[i];
if (tmp < 0) {
tmp = (255 + tmp + 1).toString(16);
} else {
tmp = tmp.toString(16);
}
if (tmp.length == 1) {
tmp = "0" + tmp;
}
str += tmp;
}
return str;
}
function bytesToBase64(e) {
var base64EncodeChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
var r, a, c, h, o, t;
for (c = e.length, a = 0, r = ''; a < c;) {
if (h = 255 & e[a++], a == c) {
r += base64EncodeChars.charAt(h >> 2),
r += base64EncodeChars.charAt((3 & h) << 4),
r += '==';
break
}
if (o = e[a++], a == c) {
r += base64EncodeChars.charAt(h >> 2),
r += base64EncodeChars.charAt((3 & h) << 4 | (240 & o) >> 4),
r += base64EncodeChars.charAt((15 & o) << 2),
r += '=';
break
}
t = e[a++],
r += base64EncodeChars.charAt(h >> 2),
r += base64EncodeChars.charAt((3 & h) << 4 | (240 & o) >> 4),
r += base64EncodeChars.charAt((15 & o) << 2 | (192 & t) >> 6),
r += base64EncodeChars.charAt(63 & t)
}
return r
}
function bytesToString(arr) {
if (typeof arr === 'string') {
return arr;
}
var str = '',
_arr = arr;
for (var i = 0; i < _arr.length; i++) {
var one = _arr[i].toString(2),
v = one.match(/^1+?(?=0)/);
if (v && one.length == 8) {
var bytesLength = v[0].length;
var store = _arr[i].toString(2).slice(7 - bytesLength);
for (var st = 1; st < bytesLength; st++) {
store += _arr[st + i].toString(2).slice(2);
}
str += String.fromCharCode(parseInt(store, 2));
i += bytesLength - 1;
} else {
str += String.fromCharCode(_arr[i]);
}
}
return str;
}

Java.perform(function () {
var secretKeySpec = Java.use('javax.crypto.spec.SecretKeySpec');
secretKeySpec.$init.overload('[B','java.lang.String').implementation = function (a,b) {
showStacks();
var result = this.$init(a, b);
send("======================================");
send("算法名:" + b + "|Dec密钥:" + bytesToString(a));
send("算法名:" + b + "|Hex密钥:" + bytesToHex(a));
return result;
}
var mac = Java.use('javax.crypto.Mac');
mac.getInstance.overload('java.lang.String').implementation = function (a) {
showStacks();
var result = this.getInstance(a);
send("======================================");
send("算法名:" + a);
return result;
}
mac.update.overload('[B').implementation = function (a) {
showStacks();
this.update(a);
send("======================================");
send("update:" + bytesToString(a))
}
mac.update.overload('[B','int','int').implementation = function (a,b,c) {
showStacks();
this.update(a,b,c)
send("======================================");
send("update:" + bytesToString(a) + "|" + b + "|" + c);
}
mac.doFinal.overload().implementation = function () {
showStacks();
var result = this.doFinal();
send("======================================");
send("doFinal结果(hex):" + bytesToHex(result));
send("doFinal结果(base64):" + bytesToBase64(result));
return result;
}
mac.doFinal.overload('[B').implementation = function (a) {
showStacks();
var result = this.doFinal(a);
send("======================================");
send("doFinal参数:" + bytesToString(a));
send("doFinal结果(hex):" + bytesToHex(result));
send("doFinal结果(base):" + bytesToBase64(result));
return result;
}
var md = Java.use('java.security.MessageDigest');
md.getInstance.overload('java.lang.String','java.lang.String').implementation = function (a,b) {
showStacks();
send("======================================");
send("算法名:" + a);
return this.getInstance(a, b);
}
md.getInstance.overload('java.lang.String').implementation = function (a) {
showStacks();
send("======================================");
send("算法名:" + a);
return this.getInstance(a);
}
md.update.overload('[B').implementation = function (a) {
showStacks();
send("======================================");
send("update:" + bytesToString(a))
return this.update(a);
}
md.update.overload('[B','int','int').implementation = function (a,b,c) {
showStacks();
send("======================================");
send("update:" + bytesToString(a) + "|" + b + "|" + c);
return this.update(a,b,c);
}
md.digest.overload().implementation = function () {
showStacks();
send("======================================");
var result = this.digest();
send("digest结果(hex):" + bytesToHex(result));
send("digest结果(base64):" + bytesToBase64(result));
return result;
}
md.digest.overload('[B').implementation = function (a) {
showStacks();
send("======================================");
send("digest参数:" + bytesToString(a));
var result = this.digest(a);
send("digest结果(hex):" + bytesToHex(result));
send("digest结果(base64):" + bytesToBase64(result));
return result;
}
var ivParameterSpec = Java.use('javax.crypto.spec.IvParameterSpec');
ivParameterSpec.$init.overload('[B').implementation = function (a) {
showStacks();
var result = this.$init(a);
send("======================================");
send("iv向量:" + bytesToString(a));
send("iv向量(hex):" + bytesToHex(a));
return result;
}
var cipher = Java.use('javax.crypto.Cipher');
cipher.getInstance.overload('java.lang.String').implementation = function (a) {
showStacks();
var result = this.getInstance(a);
send("======================================");
send("模式填充:" + a);
return result;
}
cipher.update.overload('[B').implementation = function (a) {
showStacks();
var result = this.update(a);
send("======================================");
send("update:" + bytesToString(a));
return result;
}
cipher.update.overload('[B','int','int').implementation = function (a,b,c) {
showStacks();
var result = this.update(a,b,c);
send("======================================");
send("update:" + bytesToString(a) + "|" + b + "|" + c);
return result;
}
cipher.doFinal.overload().implementation = function () {
showStacks();
var result = this.doFinal();
send("======================================");
send("doFinal结果(hex):" + bytesToHex(result));
send("doFinal结果(base64):" + bytesToBase64(result));
return result;
}
cipher.doFinal.overload('[B').implementation = function (a) {
showStacks();
var result = this.doFinal(a);
send("======================================");
send("doFinal参数:" + bytesToString(a));
send("doFinal结果(hex):" + bytesToHex(result));
send("doFinal结果(base64):" + bytesToBase64(result));
return result;
}
var x509EncodedKeySpec = Java.use('java.security.spec.X509EncodedKeySpec');
x509EncodedKeySpec.$init.overload('[B').implementation = function (a) {
showStacks();
var result = this.$init(a);
send("======================================");
send("RSA密钥:" + bytesToBase64(a));
return result;
}
var rSAPublicKeySpec = Java.use('java.security.spec.RSAPublicKeySpec');
rSAPublicKeySpec.$init.overload('java.math.BigInteger','java.math.BigInteger').implementation = function (a,b) {
showStacks();
var result = this.$init(a,b);
send("======================================");
//send("RSA密钥:" + bytesToBase64(a));
send("RSA密钥N:" + a.toString(16));
send("RSA密钥E:" + b.toString(16));
return result;
}
});
""";

fw = open(sys.argv[1],'w+',encoding='utf-8')

def message(message, data):
if message["type"] == 'send':
print(u"[*] {0}".format(message['payload']))
fw.write(u"[*] {0}\n".format(message['payload']))
fw.flush()
else:
print(message)

process = frida.get_remote_device().attach(sys.argv[1])
script= process.create_script(jsCode)
script.on("message", message)
script.load()
sys.stdin.read()

python frida-hook.py com.faloo.BookReader4Android脚本加上包名,即可hook所有原生方法。

1578403900989

如图某APP登录处,从burp里的流量记录,流量被加密了,在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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
[*] ======================================
[*] 算法名:AES|Dec密钥:DD240BF148903189BF8DAE0C220C9591
[*] 算法名:AES|Hex密钥:4444323430424631343839303331383942463844414530433232304339353931
[*] java.lang.Exception
at javax.crypto.Cipher.getInstance(Native Method)
at com.faloo.util.AES.encrypt(Proguard:211)
at com.faloo.util.EncryptUtil._e18(Native Method)
at com.faloo.util.EncryptUtil.getAESEncrypt(Proguard:150)
at com.faloo.network.module.b.a(Proguard:49)
at com.faloo.network.util.e.a(Proguard:174)
at com.faloo.network.service.a.c.a(Proguard:141)
at com.faloo.app.activity.LoginPageActivity$13.a(Proguard:941)
at io.reactivex.internal.operators.observable.ObservableCreate.b(Proguard:40)
at io.reactivex.e.a(Proguard:11194)
at io.reactivex.internal.operators.observable.ObservableSubscribeOn$a.run(Proguard:96)
at io.reactivex.k$a.run(Proguard:463)
at io.reactivex.internal.schedulers.ScheduledRunnable.run(Proguard:66)
at io.reactivex.internal.schedulers.ScheduledRunnable.call(Proguard:57)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:152)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:265)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
at java.lang.Thread.run(Thread.java:818)

[*] ======================================
[*] 模式填充:AES/CBC/PKCS5Padding
[*] java.lang.Exception
at javax.crypto.spec.IvParameterSpec.<init>(Native Method)
at com.faloo.util.AES.encrypt(Proguard:212)
at com.faloo.util.EncryptUtil._e18(Native Method)
at com.faloo.util.EncryptUtil.getAESEncrypt(Proguard:150)
at com.faloo.network.module.b.a(Proguard:49)
at com.faloo.network.util.e.a(Proguard:174)
at com.faloo.network.service.a.c.a(Proguard:141)
at com.faloo.app.activity.LoginPageActivity$13.a(Proguard:941)
at io.reactivex.internal.operators.observable.ObservableCreate.b(Proguard:40)
at io.reactivex.e.a(Proguard:11194)
at io.reactivex.internal.operators.observable.ObservableSubscribeOn$a.run(Proguard:96)
at io.reactivex.k$a.run(Proguard:463)
at io.reactivex.internal.schedulers.ScheduledRunnable.run(Proguard:66)
at io.reactivex.internal.schedulers.ScheduledRunnable.call(Proguard:57)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:152)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:265)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
at java.lang.Thread.run(Thread.java:818)

[*] ======================================
[*] iv向量:
[*] iv向量(hex):00000000000000000000000000000000
[*] java.lang.Exception
at javax.crypto.Cipher.doFinal(Native Method)
at com.faloo.util.AES.encrypt(Proguard:213)
at com.faloo.util.EncryptUtil._e18(Native Method)
at com.faloo.util.EncryptUtil.getAESEncrypt(Proguard:150)
at com.faloo.network.module.b.a(Proguard:49)
at com.faloo.network.util.e.a(Proguard:174)
at com.faloo.network.service.a.c.a(Proguard:141)
at com.faloo.app.activity.LoginPageActivity$13.a(Proguard:941)
at io.reactivex.internal.operators.observable.ObservableCreate.b(Proguard:40)
at io.reactivex.e.a(Proguard:11194)
at io.reactivex.internal.operators.observable.ObservableSubscribeOn$a.run(Proguard:96)
at io.reactivex.k$a.run(Proguard:463)
at io.reactivex.internal.schedulers.ScheduledRunnable.run(Proguard:66)
at io.reactivex.internal.schedulers.ScheduledRunnable.call(Proguard:57)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:152)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:265)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
at java.lang.Thread.run(Thread.java:818)

[*] ======================================
[*] doFinal参数:num=0&userid=1388888888&Password=9c9c5f3bff756cf5395265b6c5f0d8f0&tid=2&ts=1578400865589&nonce=fb4f89a9464e3a318111b337f92a9f1c&resign=e8ca11ce1d27aa18ed6840617b8cbcf7&pwdcode=YTEyMzQ1Ng==
&time=2020-01-07 21:31:33&wt=1&uuid=77a0332f1ca14daf9337f941bcc86e70&appversion=3.4.8&Type=Android&xp=0
[*] doFinal结果(hex):5a2439af1041cdd1bd1df38a9d6107932190b83cf6be7610cc54767ee4c97bb6236dc4e9d4b00e7408b9add01d8f9423eebd8d75386c20c1c84f8751104f50c9408f2b28af82d3eab9c56b442984c399d65581bdf68815dea6430166767e8538ba7349846e87ed1322b79066e7dac527587c0c7d9950046e63a87af64ce479e5113fcdfe3f35d0a40f8bdc917142d943ad0224bf0315c967fb969abb2178a1c764e311c67aaaebd6d69362a7fdf2bb64040db54443572fa897cd1733ef9dcd4733a35881083082ebdd2cc41a8d169de824b621e73445b3a5004fccd4b091095f0f2c2efc87116f82723c6e59c7c379c2a0eeeecb6c0deefdda2b1581b5a0e56c7ba7f1eaca8f59acae5a2e5c24d5dc72736a1fc1c0cb7e68336b3365ad250e00f89ba09971403c2ed265a2cd62a9fdc4
[*] doFinal结果(base64):WiQ5rxBBzdG9HfOKnWEHkyGQuDz2vnYQzFR2fuTJe7YjbcTp1LAOdAi5rdAdj5Qj7r2NdThsIMHIT4dREE9QyUCPKyivgtPqucVrRCmEw5nWVYG99ogV3qZDAWZ2foU4unNJhG6H7RMit5Bm59rFJ1h8DH2ZUARuY6h69kzkeeURP83+PzXQpA+L3JFxQtlDrQIkvwMVyWf7lpq7IXihx2TjEcZ6quvW1pNip/3yu2QEDbVEQ1cvqJfNFzPvnc1HM6NYgQgwguvdLMQajRad6CS2Iec0RbOlAE/M1LCRCV8PLC78hxFvgnI8blnHw3nCoO7uy2wN7v3aKxWBtaDlbHun8erKj1msrlouXCTV3HJzah/BwMt+aDNrM2WtJQ4A+JugmXFAPC7SZaLNYqn9xA==

如下数据是最终加密的结果,即发给服务器的数据。

1
WiQ5rxBBzdG9HfOKnWEHkyGQuDz2vnYQzFR2fuTJe7YjbcTp1LAOdAi5rdAdj5Qj7r2NdThsIMHIT4dREE9QyUCPKyivgtPqucVrRCmEw5nWVYG99ogV3qZDAWZ2foU4unNJhG6H7RMit5Bm59rFJ1h8DH2ZUARuY6h69kzkeeURP83+PzXQpA+L3JFxQtlDrQIkvwMVyWf7lpq7IXihx2TjEcZ6quvW1pNip/3yu2QEDbVEQ1cvqJfNFzPvnc1HM6NYgQgwguvdLMQajRad6CS2Iec0RbOlAE/M1LCRCV8PLC78hxFvgnI8blnHw3nCoO7uy2wN7v3aKxWBtaDlbHun8erKj1msrlouXCTV3HJzah/BwMt+aDNrM2WtJQ4A+JugmXFAPC7SZaLNYqn9xA==

1578407350853

这里hook到了加密算法和密钥

1
2
[*] 算法名:AES|Dec密钥:DD240BF148903189BF8DAE0C220C9591
[*] 算法名:AES|Hex密钥:4444323430424631343839303331383942463844414530433232304339353931

偏移模式

1
模式填充:AES/CBC/PKCS5Padding

iv向量

1
iv向量(hex):00000000000000000000000000000000

Password加密过程分析

输出中可以看到其加密之前的数据为

1
2
num=0&userid=1388888888&Password=9c9c5f3bff756cf5395265b6c5f0d8f0&tid=2&ts=1578400865589&nonce=fb4f89a9464e3a318111b337f92a9f1c&resign=e8ca11ce1d27aa18ed6840617b8cbcf7&pwdcode=YTEyMzQ1Ng==
&time=2020-01-07 21:31:33&wt=1&uuid=77a0332f1ca14daf9337f941bcc86e70&appversion=3.4.8&Type=Android&xp=0

数据中userid为用户名,Password为密码,这里的密码是密文9c9c5f3bff756cf5395265b6c5f0d8f0,这里并不知道是什么密文。此密文并非输入的密码的md5值。在输出中我们可以直接搜索这一段密文。

搜索到9c9c5f3bff756cf5395265b6c5f0d8f0为如下输出结果。

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
[*] ======================================
[*] update:@345Kie(873_dfbKe>d3<.d23432=d67d5e705490b20f8d8a3c8731d4c535
[*] java.lang.Exception
at java.security.MessageDigest.digest(Native Method)
at java.security.MessageDigest.digest(MessageDigest.java:278)
at java.security.MessageDigest.digest(Native Method)
at com.faloo.network.util.MD5.MD5(Proguard:22)
at com.faloo.util.EncryptUtil._e16(Native Method)
at com.faloo.util.EncryptUtil.EncryptPwd(Proguard:23)
at com.faloo.network.service.a.c.a(Proguard:119)
at com.faloo.app.activity.LoginPageActivity$13.a(Proguard:941)
at io.reactivex.internal.operators.observable.ObservableCreate.b(Proguard:40)
at io.reactivex.e.a(Proguard:11194)
at io.reactivex.internal.operators.observable.ObservableSubscribeOn$a.run(Proguard:96)
at io.reactivex.k$a.run(Proguard:463)
at io.reactivex.internal.schedulers.ScheduledRunnable.run(Proguard:66)
at io.reactivex.internal.schedulers.ScheduledRunnable.call(Proguard:57)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:152)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:265)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
at java.lang.Thread.run(Thread.java:818)

[*] ======================================
[*] digest结果(hex):9c9c5f3bff756cf5395265b6c5f0d8f0
[*] digest结果(base64):nJxfO/91bPU5UmW2xfDY8A==
[*] digest结果(hex):9c9c5f3bff756cf5395265b6c5f0d8f0
[*] digest结果(base64):nJxfO/91bPU5UmW2xfDY8A==

在这个过程中输入的为@345Kie(873_dfbKe>d3<.d23432=d67d5e705490b20f8d8a3c8731d4c535输出的9c9c5f3bff756cf5395265b6c5f0d8f0

通过md5加密对比如下图,发现此过程为md5加密过程。

1578404071511

@345Kie(873_dfbKe>d3<.d23432=d67d5e705490b20f8d8a3c8731d4c535中,前一段@345Kie(873_dfbKe>d3<.d23432=并没有搜索到,可能为固定值拼接,通过多此测试发现确实为固定值拼接。故直接搜索后32位d67d5e705490b20f8d8a3c8731d4c535跟到如下输出过程。

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
[*] ======================================
[*] update:EW234@![#$&]*{,OP}Kd^w349Op+-32_a1234561578400865589
[*] java.lang.Exception
at java.security.MessageDigest.digest(Native Method)
at java.security.MessageDigest.digest(MessageDigest.java:278)
at java.security.MessageDigest.digest(Native Method)
at com.faloo.network.util.MD5.MD5(Proguard:22)
at com.faloo.util.EncryptUtil._e16(Native Method)
at com.faloo.util.EncryptUtil.EncryptPwd(Proguard:23)
at com.faloo.network.service.a.c.a(Proguard:119)
at com.faloo.app.activity.LoginPageActivity$13.a(Proguard:941)
at io.reactivex.internal.operators.observable.ObservableCreate.b(Proguard:40)
at io.reactivex.e.a(Proguard:11194)
at io.reactivex.internal.operators.observable.ObservableSubscribeOn$a.run(Proguard:96)
at io.reactivex.k$a.run(Proguard:463)
at io.reactivex.internal.schedulers.ScheduledRunnable.run(Proguard:66)
at io.reactivex.internal.schedulers.ScheduledRunnable.call(Proguard:57)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:152)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:265)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
at java.lang.Thread.run(Thread.java:818)

[*] ======================================
[*] digest结果(hex):d67d5e705490b20f8d8a3c8731d4c535
[*] digest结果(base64):1n1ecFSQsg+NijyHMdTFNQ==
[*] digest结果(hex):d67d5e705490b20f8d8a3c8731d4c535
[*] digest结果(base64):1n1ecFSQsg+NijyHMdTFNQ==

如上输入为EW234@![#$&]*{,OP}Kd^w349Op+-32_a1234561578400865589输出为d67d5e705490b20f8d8a3c8731d4c535。这里EW234@![#$&]*{,OP}Kd^w349Op+-32_同样为固定值,a123456为我们输入的密码,1578400865589为时间戳。此过程为MD5加密

1578404464976

至此我们可以获取到

1
2
num=0&userid=1388888888&Password=9c9c5f3bff756cf5395265b6c5f0d8f0&tid=2&ts=1578400865589&nonce=fb4f89a9464e3a318111b337f92a9f1c&resign=e8ca11ce1d27aa18ed6840617b8cbcf7&pwdcode=YTEyMzQ1Ng==
&time=2020-01-07 21:31:33&wt=1&uuid=77a0332f1ca14daf9337f941bcc86e70&appversion=3.4.8&Type=Android&xp=0

aes加密前的数据中password的加密过程:

大致流程为:

  1. string1 = EW234@![#$&]*{,OP}Kd^w349Op+-32_+密码+时间戳,对string1进行md5加密
  2. 对md5(string1)前拼接@345Kie(873_dfbKe>d3<.d23432=,再进行一次MD5加密得到最后的password加密值。

once加密过程分析

1
2
num=0&userid=1388888888&Password=9c9c5f3bff756cf5395265b6c5f0d8f0&tid=2&ts=1578400865589&nonce=fb4f89a9464e3a318111b337f92a9f1c&resign=e8ca11ce1d27aa18ed6840617b8cbcf7&pwdcode=YTEyMzQ1Ng==
&time=2020-01-07 21:31:33&wt=1&uuid=77a0332f1ca14daf9337f941bcc86e70&appversion=3.4.8&Type=Android&xp=0

如上数据中的ts为时间戳即加密用到的时间戳,客户端将此时间戳发给服务端,因为在密码加密中用到了这个时间戳,所以服务器进行密码对比的时候也要用到这个时间戳,所以这个时间戳是一一对应的。

once值fb4f89a9464e3a318111b337f92a9f1c同样的方法去搜索

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[*] update:1578400865530
[*] java.lang.Exception
at java.security.MessageDigest.digest(Native Method)
at org.faloo.app.pay.a.a(Proguard:40)
at com.faloo.app.activity.LoginPageActivity.e(Proguard:483)
at com.faloo.app.activity.LoginPageActivity.b(Proguard:111)
at com.faloo.app.activity.LoginPageActivity$3.a(Proguard:531)
at com.faloo.app.activity.LoginPageActivity$3.onNext(Proguard:517)
at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.drainNormal(Proguard:200)
at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.run(Proguard:252)
at io.reactivex.android.b.b$b.run(Proguard:109)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5293)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)

[*] ======================================
[*] digest结果(hex):fb4f89a9464e3a318111b337f92a9f1c
[*] digest结果(base64):+0+JqUZOOjGBEbM3+SqfHA==

得到如上过程,输入1578400865530输出once值。同样为md5加密。

1578404968965

此时间戳并非在加密过程中用到,可能在后端并未做校验。

resign加密过程分析

1
2
num=0&userid=1388888888&Password=9c9c5f3bff756cf5395265b6c5f0d8f0&tid=2&ts=1578400865589&nonce=fb4f89a9464e3a318111b337f92a9f1c&resign=e8ca11ce1d27aa18ed6840617b8cbcf7&pwdcode=YTEyMzQ1Ng==
&time=2020-01-07 21:31:33&wt=1&uuid=77a0332f1ca14daf9337f941bcc86e70&appversion=3.4.8&Type=Android&xp=0

这个数据中还有一个e8ca11ce1d27aa18ed6840617b8cbcf7为resign签名值。

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
[*] ======================================
[*] update:a1f*(DV<>ME29p08adsfKQ@N>FEP*(F&G)&B)R@PVDbvnTPFPSDFQ>QM@o9i8t5P_)(SGB?9c9c5f3bff756cf5395265b6c5f0d8f0fb4f89a9464e3a318111b337f92a9f1c
[*] java.lang.Exception
at java.security.MessageDigest.digest(Native Method)
at java.security.MessageDigest.digest(MessageDigest.java:278)
at java.security.MessageDigest.digest(Native Method)
at com.faloo.network.util.MD5.MD5(Proguard:22)
at com.faloo.util.EncryptUtil._e14(Native Method)
at com.faloo.util.EncryptUtil.EncryptRePwd(Proguard:46)
at com.faloo.network.service.a.c.a(Proguard:120)
at com.faloo.app.activity.LoginPageActivity$13.a(Proguard:941)
at io.reactivex.internal.operators.observable.ObservableCreate.b(Proguard:40)
at io.reactivex.e.a(Proguard:11194)
at io.reactivex.internal.operators.observable.ObservableSubscribeOn$a.run(Proguard:96)
at io.reactivex.k$a.run(Proguard:463)
at io.reactivex.internal.schedulers.ScheduledRunnable.run(Proguard:66)
at io.reactivex.internal.schedulers.ScheduledRunnable.call(Proguard:57)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:152)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:265)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
at java.lang.Thread.run(Thread.java:818)

[*] ======================================
[*] digest结果(hex):e8ca11ce1d27aa18ed6840617b8cbcf7
[*] digest结果(base64):6MoRzh0nqhjtaEBhe4y89w==
[*] digest结果(hex):e8ca11ce1d27aa18ed6840617b8cbcf7
[*] digest结果(base64):6MoRzh0nqhjtaEBhe4y89w==

该签名与a1f*(DV<>ME29p08adsfKQ@N>FEP*(F&G)&B)R@PVDbvnTPFPSDFQ>QM@o9i8t5P_)(SGB?9c9c5f3bff756cf5395265b6c5f0d8f0fb4f89a9464e3a318111b337f92a9f1c有关。此过程也为MD5加密。其中一段为9c9c5f3bff756cf5395265b6c5f0d8f0为password值。fb4f89a9464e3a318111b337f92a9f1c为once值。前面的一段a1f*(DV<>ME29p08adsfKQ@N>FEP*(F&G)&B)R@PVDbvnTPFPSDFQ>QM@o9i8t5P_)(SGB?为固定值。

所以签名值resign为固定值+password+once然后进行md5加密。

而最后的值uuid=77a0332f1ca14daf9337f941bcc86e70可能为用户名标识或者手机设备识别码。

1
2
num=0&userid=1388888888&Password=9c9c5f3bff756cf5395265b6c5f0d8f0&tid=2&ts=1578400865589&nonce=fb4f89a9464e3a318111b337f92a9f1c&resign=e8ca11ce1d27aa18ed6840617b8cbcf7&pwdcode=YTEyMzQ1Ng==
&time=2020-01-07 21:31:33&wt=1&uuid=77a0332f1ca14daf9337f941bcc86e70&appversion=3.4.8&Type=Android&xp=0

pwdcode值为YTEyMzQ1Ng==为密码的base64。

自此如上加密数据所有部分都通过hook输出内容进行了分析。

RSA加密过程分析

在hook输出内容中还有,RSA加密过程。

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
[*] ======================================
[*] RSA密钥:MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCDiI/dCs429FC75NYnF82omzzAweej2VdpdaKP3DL0D/s3Hg7cnVTGBh6yRrKYI9cvBKorxdrCEaW0SXZYBH5nvmCg8qyzO8jBj08ISiukEQuqG2oS0L2sbcQl0MV7rExsyO0vlPpND7klBWikAIO1UfZW1ab/EWit1XkaXCr6nQIDAQAB
[*] java.lang.Exception
at javax.crypto.Cipher.getInstance(Native Method)
at com.faloo.util.SignUtils.encrypt(Proguard:104)
at com.faloo.util.EncryptUtil._e8(Native Method)
at com.faloo.util.EncryptUtil.getRSAEncrypt(Proguard:166)
at com.faloo.network.module.b.d(Proguard:39)
at com.faloo.network.util.e.a(Proguard:101)
at com.faloo.network.service.a.c.a(Proguard:141)
at com.faloo.app.activity.LoginPageActivity$13.a(Proguard:941)
at io.reactivex.internal.operators.observable.ObservableCreate.b(Proguard:40)
at io.reactivex.e.a(Proguard:11194)
at io.reactivex.internal.operators.observable.ObservableSubscribeOn$a.run(Proguard:96)
at io.reactivex.k$a.run(Proguard:463)
at io.reactivex.internal.schedulers.ScheduledRunnable.run(Proguard:66)
at io.reactivex.internal.schedulers.ScheduledRunnable.call(Proguard:57)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:152)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:265)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
at java.lang.Thread.run(Thread.java:818)

[*] ======================================
[*] 模式填充:RSA/ECB/PKCS1Padding
[*] java.lang.Exception
at javax.crypto.Cipher.doFinal(Native Method)
at com.faloo.util.SignUtils.encrypt(Proguard:106)
at com.faloo.util.EncryptUtil._e8(Native Method)
at com.faloo.util.EncryptUtil.getRSAEncrypt(Proguard:166)
at com.faloo.network.module.b.d(Proguard:39)
at com.faloo.network.util.e.a(Proguard:101)
at com.faloo.network.service.a.c.a(Proguard:141)
at com.faloo.app.activity.LoginPageActivity$13.a(Proguard:941)
at io.reactivex.internal.operators.observable.ObservableCreate.b(Proguard:40)
at io.reactivex.e.a(Proguard:11194)
at io.reactivex.internal.operators.observable.ObservableSubscribeOn$a.run(Proguard:96)
at io.reactivex.k$a.run(Proguard:463)
at io.reactivex.internal.schedulers.ScheduledRunnable.run(Proguard:66)
at io.reactivex.internal.schedulers.ScheduledRunnable.call(Proguard:57)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:152)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:265)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
at java.lang.Thread.run(Thread.java:818)

[*] ======================================
[*] doFinal参数:DD240BF148903189BF8DAE0C220C9591
[*] doFinal结果(hex):028f36e0538d52fe0b243c02cc68fc1a8a741215e868ef500e87142a84850db9e8cbacbf2e4b77b0c5088b9879b3f99d0fd57b239ffa7894b672722143affe03b504b00bf4d4c82264215d61d5e66c8db0f18f5463a544a7a8dff86f77e6ef16b885091bc6f5034003a300a1d9b38022612b4369f47b17eba5a3adfb5857f6c5
[*] doFinal结果(base64):Ao824FONUv4LJDwCzGj8Gop0EhXoaO9QDocUKoSFDbnoy6y/Lkt3sMUIi5h5s/mdD9V7I5/6eJS2cnIhQ6/+A7UEsAv01MgiZCFdYdXmbI2w8Y9UY6VEp6jf+G935u8WuIUJG8b1A0ADowCh2bOAImErQ2n0exfrpaOt+1hX9sU=

如上为RSA加密过程公钥为MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCDiI/dCs429FC75NYnF82omzzAweej2VdpdaKP3DL0D/s3Hg7cnVTGBh6yRrKYI9cvBKorxdrCEaW0SXZYBH5nvmCg8qyzO8jBj08ISiukEQuqG2oS0L2sbcQl0MV7rExsyO0vlPpND7klBWikAIO1UfZW1ab/EWit1XkaXCr6nQIDAQAB

模式填充:RSA/ECB/PKCS1Padding

加密数据为:DD240BF148903189BF8DAE0C220C9591

加密结果为:Ao824FONUv4LJDwCzGj8Gop0EhXoaO9QDocUKoSFDbnoy6y/Lkt3sMUIi5h5s/mdD9V7I5/6eJS2cnIhQ6/+A7UEsAv01MgiZCFdYdXmbI2w8Y9UY6VEp6jf+G935u8WuIUJG8b1A0ADowCh2bOAImErQ2n0exfrpaOt+1hX9sU=

这个过程是为了加密AES密钥,因为RSA加密的加密数据长度是有限的。

RSA一次能加密的明文长度与密钥长度成正比:

len_in_byte(raw_data) = len_in_bit(key)/8 -11,如 1024bit 的密钥,一次能加密的内容长度为 1024/8 -11 = 117 byte。

所以非对称加密一般都用于加密对称加密算法的密钥,而不是直接加密内容

因为AES是对称加密

对称加密就是指,加密和解密使用同一个密钥的加密方式。

发送方使用密钥将明文数据加密成密文,然后发送出去,接收方收到密文后,使用同一个密钥将密文解密成明文读取。

优点:加密计算量小、速度块,适合对大量数据进行加密的场景。

所以通常流量数据为AES,但是AES存在安全问题就是,AES的密钥要是被截获了就可以任意解密数据。

所以目前APP中的加密套路就是:

客户端:流量数据通过AES加密发送给服务端,同时AES密钥随机生成通过RSA公钥加密发服务端

服务端:服务端接收到RSA加密后的AES密钥通过RSA私钥进行解密得到AES密钥,然后通过AES密钥解密流量数据进行验证。