【实战】某习通app逆向分析(二)发包参数加密分析、解密和数据采集

【实战】某习通app逆向分析(二)发包参数加密分析、解密和数据采集

前言

在上一章中我们静态分析了 Frida 的拍照与检测,这一章我们来分析学习通抓包里的参数分析,解密与数据采集
【实战】某习通 app 逆向分析(一)检测切屏、拍照、上传

准备工作

模拟器

Fidder

Charles + Postern

Frida

monitor

开始抓包

登录

打开学习通 点击登录

image-20220617230515450

image-20220617230524452

通过分析 我们发现他是双向证书校验,如何绕过双向证书呢

  1. 把 app 里面的证书导出导入到抓包工具
  2. 通过 hook 干掉 Client cer pfx p12 plc bks crt 包含这些关键词的函数。

在这里我们使用双向证书 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
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
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
Java.perform(function () {
/*
hook list:
1.SSLcontext
2.okhttp
3.webview
4.XUtils
5.httpclientandroidlib
6.JSSE
7.network\_security\_config (android 7.0+)
8.Apache Http client (support partly)
9.OpenSSLSocketImpl
10.TrustKit
11.Cronet
*/

// Attempts to bypass SSL pinning implementations in a number of
// ways. These include implementing a new TrustManager that will
// accept any SSL certificate, overriding OkHTTP v3 check()
// method etc.
var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager')
var HostnameVerifier = Java.use('javax.net.ssl.HostnameVerifier')
var SSLContext = Java.use('javax.net.ssl.SSLContext')
var quiet_output = false

// Helper method to honor the quiet flag.

function quiet_send(data) {
if (quiet_output) {
return
}

send(data)
}

// Implement a new TrustManager
// ref: https://gist.github.com/oleavr/3ca67a173ff7d207c6b8c3b0ca65a9d8
// Java.registerClass() is only supported on ART for now(201803). 所以android 4.4以下不兼容,4.4要切换成ART使用.
/*
06-07 16:15:38.541 27021-27073/mi.sslpinningdemo W/System.err: java.lang.IllegalArgumentException: Required method checkServerTrusted(X509Certificate[], String, String, String) missing
06-07 16:15:38.542 27021-27073/mi.sslpinningdemo W/System.err: at android.net.http.X509TrustManagerExtensions.<init>(X509TrustManagerExtensions.java:73)
at mi.ssl.MiPinningTrustManger.<init>(MiPinningTrustManger.java:61)
06-07 16:15:38.543 27021-27073/mi.sslpinningdemo W/System.err: at mi.sslpinningdemo.OkHttpUtil.getSecPinningClient(OkHttpUtil.java:112)
at mi.sslpinningdemo.OkHttpUtil.get(OkHttpUtil.java:62)
at mi.sslpinningdemo.MainActivity$1$1.run(MainActivity.java:36)
*/
var X509Certificate = Java.use('java.security.cert.X509Certificate')
var TrustManager
try {
TrustManager = Java.registerClass({
name: 'org.wooyun.TrustManager',
implements: [X509TrustManager],
methods: {
checkClientTrusted: function (chain, authType) {},
checkServerTrusted: function (chain, authType) {},
getAcceptedIssuers: function () {
// var certs = [X509Certificate.$new()];
// return certs;
return []
},
},
})
} catch (e) {
quiet_send('registerClass from X509TrustManager >>>>>>>> ' + e.message)
}

// Prepare the TrustManagers array to pass to SSLContext.init()
var TrustManagers = [TrustManager.$new()]

try {
// Prepare a Empty SSLFactory
var TLS_SSLContext = SSLContext.getInstance('TLS')
TLS_SSLContext.init(null, TrustManagers, null)
var EmptySSLFactory = TLS_SSLContext.getSocketFactory()
} catch (e) {
quiet_send(e.message)
}

send('Custom, Empty TrustManager ready')

// Get a handle on the init() on the SSLContext class
var SSLContext_init = SSLContext.init.overload(
'[Ljavax.net.ssl.KeyManager;',
'[Ljavax.net.ssl.TrustManager;',
'java.security.SecureRandom'
)

// Override the init method, specifying our new TrustManager
SSLContext_init.implementation = function (
keyManager,
trustManager,
secureRandom
) {
quiet_send('Overriding SSLContext.init() with the custom TrustManager')

SSLContext_init.call(this, null, TrustManagers, null)
}

/*** okhttp3.x unpinning ***/

// Wrap the logic in a try/catch as not all applications will have
// okhttp as part of the app.
try {
var CertificatePinner = Java.use('okhttp3.CertificatePinner')

quiet_send('OkHTTP 3.x Found')

CertificatePinner.check.overload(
'java.lang.String',
'java.util.List'
).implementation = function () {
quiet_send('OkHTTP 3.x check() called. Not throwing an exception.')
}
} catch (err) {
// If we dont have a ClassNotFoundException exception, raise the
// problem encountered.
if (err.message.indexOf('ClassNotFoundException') === 0) {
throw new Error(err)
}
}

// Appcelerator Titanium PinningTrustManager

// Wrap the logic in a try/catch as not all applications will have
// appcelerator as part of the app.
try {
var PinningTrustManager = Java.use('appcelerator.https.PinningTrustManager')

send('Appcelerator Titanium Found')

PinningTrustManager.checkServerTrusted.implementation = function () {
quiet_send(
'Appcelerator checkServerTrusted() called. Not throwing an exception.'
)
}
} catch (err) {
// If we dont have a ClassNotFoundException exception, raise the
// problem encountered.
if (err.message.indexOf('ClassNotFoundException') === 0) {
throw new Error(err)
}
}

/*** okhttp unpinning ***/

try {
var OkHttpClient = Java.use('com.squareup.okhttp.OkHttpClient')
OkHttpClient.setCertificatePinner.implementation = function (
certificatePinner
) {
// do nothing
quiet_send('OkHttpClient.setCertificatePinner Called!')
return this
}

// Invalidate the certificate pinnet checks (if "setCertificatePinner" was called before the previous invalidation)
var CertificatePinner = Java.use('com.squareup.okhttp.CertificatePinner')
CertificatePinner.check.overload(
'java.lang.String',
'[Ljava.security.cert.Certificate;'
).implementation = function (p0, p1) {
// do nothing
quiet_send('okhttp Called! [Certificate]')
return
}
CertificatePinner.check.overload(
'java.lang.String',
'java.util.List'
).implementation = function (p0, p1) {
// do nothing
quiet_send('okhttp Called! [List]')
return
}
} catch (e) {
quiet_send('com.squareup.okhttp not found')
}

/*** WebView Hooks ***/

/* frameworks/base/core/java/android/webkit/WebViewClient.java */
/* public void onReceivedSslError(Webview, SslErrorHandler, SslError) */
var WebViewClient = Java.use('android.webkit.WebViewClient')

WebViewClient.onReceivedSslError.implementation = function (
webView,
sslErrorHandler,
sslError
) {
quiet_send('WebViewClient onReceivedSslError invoke')
//执行proceed方法
sslErrorHandler.proceed()
return
}

WebViewClient.onReceivedError.overload(
'android.webkit.WebView',
'int',
'java.lang.String',
'java.lang.String'
).implementation = function (a, b, c, d) {
quiet_send('WebViewClient onReceivedError invoked')
return
}

WebViewClient.onReceivedError.overload(
'android.webkit.WebView',
'android.webkit.WebResourceRequest',
'android.webkit.WebResourceError'
).implementation = function () {
quiet_send('WebViewClient onReceivedError invoked')
return
}

/*** JSSE Hooks ***/

/* libcore/luni/src/main/java/javax/net/ssl/TrustManagerFactory.java */
/* public final TrustManager[] getTrustManager() */
/* TrustManagerFactory.getTrustManagers maybe cause X509TrustManagerExtensions error */
// var TrustManagerFactory = Java.use("javax.net.ssl.TrustManagerFactory");
// TrustManagerFactory.getTrustManagers.implementation = function(){
// quiet_send("TrustManagerFactory getTrustManagers invoked");
// return TrustManagers;
// }

var HttpsURLConnection = Java.use('javax.net.ssl.HttpsURLConnection')
/* libcore/luni/src/main/java/javax/net/ssl/HttpsURLConnection.java */
/* public void setDefaultHostnameVerifier(HostnameVerifier) */
HttpsURLConnection.setDefaultHostnameVerifier.implementation = function (
hostnameVerifier
) {
quiet_send('HttpsURLConnection.setDefaultHostnameVerifier invoked')
return null
}
/* libcore/luni/src/main/java/javax/net/ssl/HttpsURLConnection.java */
/* public void setSSLSocketFactory(SSLSocketFactory) */
HttpsURLConnection.setSSLSocketFactory.implementation = function (
SSLSocketFactory
) {
quiet_send('HttpsURLConnection.setSSLSocketFactory invoked')
return null
}
/* libcore/luni/src/main/java/javax/net/ssl/HttpsURLConnection.java */
/* public void setHostnameVerifier(HostnameVerifier) */
HttpsURLConnection.setHostnameVerifier.implementation = function (
hostnameVerifier
) {
quiet_send('HttpsURLConnection.setHostnameVerifier invoked')
return null
}

/*** Xutils3.x hooks ***/
//Implement a new HostnameVerifier
var TrustHostnameVerifier
try {
TrustHostnameVerifier = Java.registerClass({
name: 'org.wooyun.TrustHostnameVerifier',
implements: [HostnameVerifier],
method: {
verify: function (hostname, session) {
return true
},
},
})
} catch (e) {
//java.lang.ClassNotFoundException: Didn't find class "org.wooyun.TrustHostnameVerifier"
quiet_send('registerClass from hostnameVerifier >>>>>>>> ' + e.message)
}

try {
var RequestParams = Java.use('org.xutils.http.RequestParams')
RequestParams.setSslSocketFactory.implementation = function (
sslSocketFactory
) {
sslSocketFactory = EmptySSLFactory
return null
}

RequestParams.setHostnameVerifier.implementation = function (
hostnameVerifier
) {
hostnameVerifier = TrustHostnameVerifier.$new()
return null
}
} catch (e) {
quiet_send('Xutils hooks not Found')
}

/*** httpclientandroidlib Hooks ***/
try {
var AbstractVerifier = Java.use(
'ch.boye.httpclientandroidlib.conn.ssl.AbstractVerifier'
)
AbstractVerifier.verify.overload(
'java.lang.String',
'[Ljava.lang.String',
'[Ljava.lang.String',
'boolean'
).implementation = function () {
quiet_send('httpclientandroidlib Hooks')
return null
}
} catch (e) {
quiet_send('httpclientandroidlib Hooks not found')
}

/***
android 7.0+ network_security_config TrustManagerImpl hook
apache httpclient partly
***/
var TrustManagerImpl = Java.use('com.android.org.conscrypt.TrustManagerImpl')
// try {
// var Arrays = Java.use("java.util.Arrays");
// //apache http client pinning maybe baypass
// //https://github.com/google/conscrypt/blob/c88f9f55a523f128f0e4dace76a34724bfa1e88c/platform/src/main/java/org/conscrypt/TrustManagerImpl.java#471
// TrustManagerImpl.checkTrusted.implementation = function (chain, authType, session, parameters, authType) {
// quiet_send("TrustManagerImpl checkTrusted called");
// //Generics currently result in java.lang.Object
// return Arrays.asList(chain);
// }
//
// } catch (e) {
// quiet_send("TrustManagerImpl checkTrusted nout found");
// }

try {
// Android 7+ TrustManagerImpl
TrustManagerImpl.verifyChain.implementation = function (
untrustedChain,
trustAnchorChain,
host,
clientAuth,
ocspData,
tlsSctData
) {
quiet_send('TrustManagerImpl verifyChain called')
// Skip all the logic and just return the chain again :P
//https://www.nccgroup.trust/uk/about-us/newsroom-and-events/blogs/2017/november/bypassing-androids-network-security-configuration/
// https://github.com/google/conscrypt/blob/c88f9f55a523f128f0e4dace76a34724bfa1e88c/platform/src/main/java/org/conscrypt/TrustManagerImpl.java#L650
return untrustedChain
}
} catch (e) {
quiet_send('TrustManagerImpl verifyChain nout found below 7.0')
}
// OpenSSLSocketImpl
try {
var OpenSSLSocketImpl = Java.use(
'com.android.org.conscrypt.OpenSSLSocketImpl'
)
OpenSSLSocketImpl.verifyCertificateChain.implementation = function (
certRefs,
authMethod
) {
quiet_send('OpenSSLSocketImpl.verifyCertificateChain')
}

quiet_send('OpenSSLSocketImpl pinning')
} catch (err) {
quiet_send('OpenSSLSocketImpl pinner not found')
}
// Trustkit
try {
var Activity = Java.use(
'com.datatheorem.android.trustkit.pinning.OkHostnameVerifier'
)
Activity.verify.overload(
'java.lang.String',
'javax.net.ssl.SSLSession'
).implementation = function (str) {
quiet_send('Trustkit.verify1: ' + str)
return true
}
Activity.verify.overload(
'java.lang.String',
'java.security.cert.X509Certificate'
).implementation = function (str) {
quiet_send('Trustkit.verify2: ' + str)
return true
}

quiet_send('Trustkit pinning')
} catch (err) {
quiet_send('Trustkit pinner not found')
}

try {
//cronet pinner hook
//weibo don't invoke

var netBuilder = Java.use('org.chromium.net.CronetEngine$Builder')

//https://developer.android.com/guide/topics/connectivity/cronet/reference/org/chromium/net/CronetEngine.Builder.html#enablePublicKeyPinningBypassForLocalTrustAnchors(boolean)
netBuilder.enablePublicKeyPinningBypassForLocalTrustAnchors.implementation =
function (arg) {
//weibo not invoke
console.log(
'Enables or disables public key pinning bypass for local trust anchors = ' +
arg
)

//true to enable the bypass, false to disable.
var ret =
netBuilder.enablePublicKeyPinningBypassForLocalTrustAnchors.call(
this,
true
)
return ret
}

netBuilder.addPublicKeyPins.implementation = function (
hostName,
pinsSha256,
includeSubdomains,
expirationDate
) {
console.log('cronet addPublicKeyPins hostName = ' + hostName)

//var ret = netBuilder.addPublicKeyPins.call(this,hostName, pinsSha256,includeSubdomains, expirationDate);
//this 是调用 addPublicKeyPins 前的对象吗? Yes,CronetEngine.Builder
return this
}
} catch (err) {
console.log('[-] Cronet pinner not found')
}
})

去脱壳后的 app 里面分析找

image-20220617230821879

找到了关键类,我们 hook javax.net.ssl 这个类 image-20220617230923249

再次进行抓包

image-20220617230944967

结果出来了 输入正确的账号密码

image-20220617231055315

获取推荐列表

image-20220617231337106

抓包拿到以下这些值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
GET https://home-yd.chaoxing.com/apis/data/getIdxSourceFromRecChaoXing?puid=233132169&cpage=1&size=30&from=xxtclient&token=4faa8662c59590c6f43ae9fe5b002b42&_time=1655478785055&inf_enc=bb0db913892b643161bf55531400e39b HTTP/1.1
User-Agent: Dalvik/2.1.0 (Linux; U; Android 7.1.2; PCRT00 Build/N2G48H) com.chaoxing.mobile/ChaoXingStudy_3_4.3.4_android_phone_494_27 (@Kalimdor)_e084defffb7f4ec5a02ea1cb5bd38199
Accept-Language: zh_CN
Host: home-yd.chaoxing.com
Connection: Keep-Alive
Accept-Encoding: gzip
// ck在这里隐藏了
这是一个get请求

puid=233132169
&cpage=1&size=30&from=xxtclient
&token=4faa8662c59590c6f43ae9fe5b002b42
&_time=1655478785055
&inf_enc=bb0db913892b643161bf55531400e39b

我们进行二次抓包参数对比 time 是时间戳

总结得出

1
2
3
4
token 用户个人的tokne
puid 服务器回调参数
_time 时间戳
inf_enc 加密sing

目标明确 获取这个 inf_enc

image-20220617231846618

image-20220617231851409

找到这个类,编写 frida 进行 hook 目测应该是个 md5

反混淆开启

image-20220617232902452

确认了是我们要的东西,点进去这个 a 看看是啥,把之前的参数删掉与时间戳重新拼接

image-20220617232943514

我们直接上自吐模块 自吐模块可以在我的博客里面搜 由于代码过长 就不在这里写了

image-20220617234148838

image-20220617234217674

抓包得到值:47a7273c8ae78516a453d041a1a77e96

image-20220617234250035

拿到加密字符串 我们再去 Jadx 搜**DESKey=**查看一下这个值是多少 这个问题放到后面再讲

将拿到的字符串放到工具里面生成

image-20220617234917352与抓包值一样。

到这里就结束了

所以现在我们可以直接写 python 代码了

token=4faa8662c59590c6f43ae9fe5b002b42&_time=1655480519505&DESKey=Z(AfY@XS

inf_enc = md5(token + time + Z(AfY@XS)

意外之喜

当我在写 python 爬虫的时候发现,这个 Token 并没有返回,而是通过 app 自己计算得出的,在之前的判断中认为 token 是个人的 Token 但是没想到,token 是写死的!!!!!

image-20220618002104338

python 爬虫

代码

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
# -*- coding: utf-8 -*-
# @Time : 2022-06-17 23:54
# @Author : CodeCat
# @File : demo.py


import time
from hashlib import md5

import requests
from loguru import logger
'''

login
'''


head = {
"User-Agent": "Dalvik/2.1.0 (Linux; U; Android 7.1.2; PCRT00 Build/N2G48H) com.chaoxing.mobile/ChaoXingStudy_3_4.3.4_android_phone_494_27 (@Kalimdor)_e084defffb7f4ec5a02ea1cb5bd38199",
"Accept-Language": "zh_CN",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "passport2-api.chaoxing.com",
"Connection": "Keep-Alive",
"Accept-Encoding": "gzip"
}

session = requests.Session()
session.headers = head

url = "https://passport2-api.chaoxing.com/v11/loginregister?cx_xxt_passport=json"
payload = "uname=&code=&loginType=1&roleSelect=true"
retJson = session.post(url,data=payload).json()

head = {
"User-Agent": "Dalvik/2.1.0 (Linux; U; Android 7.1.2; PCRT00 Build/N2G48H) com.chaoxing.mobile/ChaoXingStudy_3_4.3.4_android_phone_494_27 (@Kalimdor)_e084defffb7f4ec5a02ea1cb5bd38199",
"Accept-Language": "zh_CN",
"Host": "sso.chaoxing.com",
"Connection": "Keep-Alive",
"Accept-Encoding": "gzip"
}
session.headers.update(head)

logger.success(retJson)
url = "https://sso.chaoxing.com/apis/login/userLogin4Uname.do"
retText = session.get(url,allow_redirects=False).text
logger.debug(retText)

timestmp = int(time.time() * 1000)
logger.info(timestmp)
MD5 = md5()
msg = "token=4faa8662c59590c6f43ae9fe5b002b42&_time="+str(timestmp)+"&DESKey=Z(AfY@XS"
MD5.update(msg.encode())
infEnc = MD5.hexdigest()
logger.success(infEnc)

url = "https://home-yd.chaoxing.com/apis/data/getAdsRecommend"
params = {
"token": "4faa8662c59590c6f43ae9fe5b002b42",
"_time": timestmp,
"inf_enc": infEnc
}

head = {
"Host": "home-yd.chaoxing.com"
}
session.headers.update(head)

data = session.get(url,params=params).json()

logger.info(data)

这里面有几个协议头的坑需要注意以下,其次再没了。

总结

在研究学习通的时候,遇到的问题实在是太多了,从 Frida 检测,Objection 检测,root 检测,双向 SSL 检测。本以为它是简单的双进程,没 想到是多进程保护,属实是破防了,在逆向 app 的时候关键函数加混淆,AES MD5 DES 层出不穷,在接下来中将进一步深入探究学习通 APP。


参考文章:

1
2
3
4
5
6
7
8
https://blog.csdn.net/CharlesSimonyi/article/details/90647009

https://blog.csdn.net/freeking101/article/details/105759124

https://blog.csdn.net/welggy/article/details/121716944

https://blog.csdn.net/qq_42196922/article/details/107322504

hello Codecat

​ 如果你愿意参与 Hello Codecat 的网站建设付出一份力,分享有趣好玩的文章、项目与书籍,欢迎联系我:QQ 2372943

【实战】某习通app逆向分析(二)发包参数加密分析、解密和数据采集

https://www.mineseb.cn/2022/06/18/【实战】某习通app逆向分析(二)发包参数加密分析、解密和数据采集/

作者

Codecat

发布于

2022-06-18

更新于

2022-06-18

许可协议

评论