针对Android App隐私信息检测

尝试采用Frida进行处理。

目前的一个思路就是trace app中所有调用系统函数的功能,这个方法不够细致,无法判断是app自身调用的还是app调用的sdk调用的。

https://github.com/zhengjim/camille

昨天尝试了下,发现并没有多好使,还需要继续研究下,自己写个demo试试。

只需要hook相应的隐私API就算是触犯了隐私信息的采集了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//获取手机通信录

function getPhoneAddressBook() {

var contacts_uri = Java.use("android.provider.ContactsContract$Contacts").CONTENT_URI.value.toString();



var contentResolver = Java.use("android.content.ContentResolver");

contentResolver.query.overload('android.net.Uri', '[Ljava.lang.String;', 'android.os.Bundle', 'android.os.CancellationSignal').implementation = function (uri, str, bundle, sig) {

if (uri == contacts_uri) {

alertSend("获取手机通信录", "获取uri为:" + uri)

}

return this.query(uri, str, bundle, sig);

}

}

这样就可以了,无非就是看隐私API都有哪些,有了就行了

还有就是如果是这种写死的方式的话,可能不太方便不适合维护,所以,需要一个单独的接口传入类、类中的函数名,就能够批次hook即可。

可以参考这个:https://github.com/r0ysue/r0tracer

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
import { alertSend2 } from "./alertSend";

var isLite = false;



// trace单个类的所有静态和实例方法包括构造方法 trace a specific Java Method

function traceMethod(targetClassMethod: any) {

var delim = targetClassMethod.lastIndexOf(".");

if (delim === -1) return;

var targetClass = targetClassMethod.slice(0, delim)

var targetMethod = targetClassMethod.slice(delim + 1, targetClassMethod.length)

var hook = Java.use(targetClass);

var overloadCount = hook[targetMethod].overloads.length;

for (var i = 0; i < overloadCount; i++) {

hook[targetMethod].overloads[i].implementation = function () {

//初始化输出

var output = "";

var args = "";

var retvalContent = "";

var stackTrace = "";

//进入函数

output = output.concat("\n*** entered " + targetClassMethod);

output = output.concat("\r\n")

//参数

var retval = this[targetMethod].apply(this, arguments);

if (!isLite) {

// 入参

for (var j = 0; j < arguments.length; j++) {

args = args.concat("arg[" + j + "]: " + arguments[j] + " => " + JSON.stringify(arguments[j]));

args = args.concat("\r\n")

}

//调用栈

stackTrace = stackTrace.concat(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));

//返回值

retvalContent = retvalContent.concat("\nretval: " + retval + " => " + JSON.stringify(retval));



}

alertSend2("", targetClassMethod, args, retvalContent, stackTrace);

return retval;

}

}

}



export function traceClassMethod(params: Map<any, any[]>) {

Java.perform(() => {

params.forEach((methodNames, targetClass) => {

//Java.use是新建一个对象哈,大家还记得么?

var hook = Java.use(targetClass);

//利用反射的方式,拿到当前类的所有方法

var methods = hook.class.getDeclaredMethods();

//建完对象之后记得将对象释放掉哈

hook.$dispose;

//将方法名保存到数组中

var parsedMethods = new Set();

methods.forEach(function (method: any) {

methodNames.forEach(function (methodName) {

if (method.toString().indexOf(methodName) != -1) {

parsedMethods.add(method.toString().replace(targetClass + ".", "TOKEN").match(/\sTOKEN(.*)\(/)[1]);

}

})

});

//对数组中所有的方法进行hook,

parsedMethods.forEach(function (targetMethod: any) {

traceMethod(targetClass + "." + targetMethod);

});

})

})

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

import { traceClassMethod} from "./trace";



Java.perform(function() {

console.log("隐私合规检测敏感接口开始监控...");

send({"type": "isHook"});



let map = new Map<any, any[]>();

// class methods

map.set("android.app.ApplicationPackageManager", ["getInstalledPackages", "getInstalledApplications", "queryIntentActivities", "getApplicationInfo"]);

map.set("android.media.AudioRecord", ["startRecording"]);

traceClassMethod(map);

});
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
export function alertSend2(action: string, className: string, input: string, output: string, stackTrace: string) {

let myDate = new Date();

let _time = myDate.getFullYear() + "-" + myDate.getMonth() + "-" + myDate.getDate() + " " + myDate.getHours() + ":" + myDate.getMinutes() + ":" + myDate.getSeconds();

send({"type": "notice", "time": _time, "action": action, "className": className, "input": input, "output": output, "stacks": stackTrace});

}
import sys

import time

import frida

import signal

import os



#from screen import *



def frida_hook(app_name, wait_time=0):

#isHook = True



def my_message_handler(message, payload):

if message["type"] == "error":

print(message)

os.kill(os.getpid(), signal.SIGTERM)

return

if message['type'] == 'send':

data = message["payload"]

if data['type'] == "notice":

#take_screenshot(None, app_name + "/")

alert_time = data['time']

action = data['action']

className = data['className']

input1 = data['input']

output1 = data['output']

stacks = data['stacks']



# todo,以后将这些数据发送给后端,最好就是保存这些数据,并将图片的位置跟这些数据存储在一起。

print("------------------------------start---------------------------------")

print("[*] 行为时间:\n{0},\nAPP行为:\n{1},\n类名:\n{2},\n入参:\n{3},\n出参:{4}\n".format(alert_time, action, className, input1, output1))

print("[*] 调用堆栈:")

print(stacks)

print("-------------------------------end----------------------------------")



if data['type'] == "app_name":

get_app_name = data['data']

my_data = False if get_app_name == app_name else True

script.post({"my_data": my_data})



if data['type'] == "isHook":

global isHook

isHook = True



#session = None

try:

device = frida.get_usb_device()

pid = device.spawn([app_name])

except Exception as e:

print("[*] hook error")

print(e)

exit()



time.sleep(1)

session = device.attach(pid)

time.sleep(1)

with open("../frida-agent-example/_agent.js", encoding="utf-8") as f:

script_read = f.read()



script = session.create_script(script_read)

script.on("message", my_message_handler)

script.load()

time.sleep(1)

try:

device.resume(pid)

except Exception as e:

print("[*] hook error")

print(e)

exit()



# 等待frida-server发送消息过来,确保是否已经完成attach

wait_time += 1

time.sleep(wait_time)

if isHook:

def stop(signum, frame):

print('[*] You have stoped hook.')

session.detach()

exit()



signal.signal(signal.SIGINT, stop)

signal.signal(signal.SIGTERM, stop)

sys.stdin.read()

else:

print("[*] hook fail, try delaying hook, adjusting delay time")



if __name__ == '__main__':

print("start frida hook")



# 全局变量

isHook = False

frida_hook("com.mepride.freepods")

print("end")

针对这种spawn不上的

attach也不行的

Bypass :

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
// 

export var ByPassTracerPid = () => {

let fgetsPtr = Module.findExportByName("libc.so", "fgets");

if (null != fgetsPtr) {

let fgets = new NativeFunction(fgetsPtr, 'pointer', ['pointer', 'int', 'pointer']);

Interceptor.replace(fgetsPtr, new NativeCallback(function (buffer, size, fp) {

var retval = fgets(buffer, size, fp);

var bufstr = readMemory(buffer);

//var bufstr = buffer?.toString();

if (null != bufstr) {

if (bufstr.indexOf("TracerPid:") > -1) {

writeMemory(buffer, "TracerPid:\t0");

console.log("tracerpid replaced: " + readMemory(buffer));

}

}

return retval;

}, 'pointer', ['pointer', 'int', 'pointer']));

}

}





function writeMemory(addr: NativePointer, str: string) {

Memory.protect(addr, str.length, 'rwx');

addr.writeUtf8String(str);

}



function readMemory(addr: NativePointer) {

return addr.readUtf8String();

}

https://github.com/chame1eon/jnitrace-engine

https://github.com/deathmemory/FridaContainer

https://frida.re/docs/javascript-api/

  1. 稳定的frida环境 => 8 + 12.8.0 \ 10 + 14.2.18
  2. Trace jni https://github.com/chame1eon/jnitrace-engine
  3. Bypass anti frida
  4. Bypass ssl panning https://github.com/r0ysue/r0capture

Bypass anti 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
55
56
57
export function antiAntiFrida() {

var strstr = Module.findExportByName(null, "strstr");

if (strstr !== null) {

Interceptor.attach(strstr, {

onEnter: function (args) {

this.frida = Boolean(0);



this.haystack = args[0];

this.needle = args[1];



if (this.haystack.readCString() !== null && this.needle.readCString() !== null) {

if (this.haystack.readCString().indexOf("frida") !== -1 || this.needle.readCString().indexOf("frida") !== -1 ||

this.haystack.readCString().indexOf("gum-js-loop") !== -1 || this.needle.readCString().indexOf("gum-js-loop") !== -1 ||

this.haystack.readCString().indexOf("gmain") !== -1 || this.needle.readCString().indexOf("gmain") !== -1 ||

this.haystack.readCString().indexOf("linjector") !== -1 || this.needle.readCString().indexOf("linjector") !== -1) {

this.frida = Boolean(1);

}

}

},

onLeave: function (retval) {

if (this.frida) {

retval.replace(ptr("0x0"));

}



}

})

console.log("anti anti-frida");

}

}

Bypass ssl pinning:https://github.com/sensepost/objection/blob/master/agent/src/android/pinning.ts

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
425
426
427
import { wrapJavaPerform } from "./libjava";

import {

ArrayList, CertificatePinner, PinningTrustManager, SSLCertificateChecker,

SSLContext, TrustManagerImpl, X509TrustManager,

} from "./types";



export function byPassSSLPinning() {

const sslContextEmptyTrustManager = (): any => {

// -- Sample Java

//

// "Generic" TrustManager Example

//

// TrustManager[] trustAllCerts = new TrustManager[] {

// new X509TrustManager() {

// public java.security.cert.X509Certificate[] getAcceptedIssuers() {

// return null;

// }

// public void checkClientTrusted(X509Certificate[] certs, String authType) { }

// public void checkServerTrusted(X509Certificate[] certs, String authType) { }

// }

// };

// SSLContext sslcontect = SSLContext.getInstance("TLS");

// sslcontect.init(null, trustAllCerts, null);

return wrapJavaPerform(() => {

const x509TrusManager: X509TrustManager = Java.use("javax.net.ssl.X509TrustManager");

const sSLContext: SSLContext = Java.use("javax.net.ssl.SSLContext");



// Some 'anti-frida' detections will scan /proc/<pid>/maps.

// Rename the tempFileNaming prefix as this could end up in maps.

// https://github.com/frida/frida-java-bridge/blob/8b3790f7489ff5be7b19ddaccf5149d4e7738460/lib/class-factory.js#L94

if (Java.classFactory.tempFileNaming.prefix == 'frida') {

Java.classFactory.tempFileNaming.prefix = 'onetwothree';

}



// Implement a new TrustManager

// ref: https://gist.github.com/oleavr/3ca67a173ff7d207c6b8c3b0ca65a9d8

const TrustManager: X509TrustManager = Java.registerClass({

implements: [x509TrusManager],

methods: {

// tslint:disable-next-line:no-empty

checkClientTrusted(chain, authType) { },

// tslint:disable-next-line:no-empty

checkServerTrusted(chain, authType) { },

getAcceptedIssuers() {

return [];

},

},

// todo 这个是干嘛的

name: "com.sensepost.test.TrustManager",

});



// Prepare the TrustManagers array to pass to SSLContext.init()

const TrustManagers: X509TrustManager[] = [TrustManager.$new()];



// Get a handle on the init() on the SSLContext class

const SSLContextInit = sSLContext.init.overload(

"[Ljavax.net.ssl.KeyManager;", "[Ljavax.net.ssl.TrustManager;", "java.security.SecureRandom");

// Override the init method, specifying our new TrustManager

SSLContextInit.implementation = function (keyManager: any, trustManager: any, secureRandom: any) {



SSLContextInit.call(this, keyManager, TrustManagers, secureRandom);

};



return SSLContextInit;

});

};



const okHttp3CertificatePinnerCheck = (): any | undefined => {

// -- Sample Java

//

// Example used to test this bypass.

//

// String hostname = "swapi.co";

// CertificatePinner certificatePinner = new CertificatePinner.Builder()

// .add(hostname, "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")

// .build();

// OkHttpClient client = new OkHttpClient.Builder()

// .certificatePinner(certificatePinner)

// .build();

// Request request = new Request.Builder()

// .url("https://swapi.co/api/people/1")

// .build();

// Response response = client.newCall(request).execute();

return wrapJavaPerform(() => {

const certificatePinner: CertificatePinner = Java.use("okhttp3.CertificatePinner");



const CertificatePinnerCheck = certificatePinner.check.overload("java.lang.String", "java.util.List");



// tslint:disable-next-line:only-arrow-functions

CertificatePinnerCheck.implementation = function () {

};



return CertificatePinnerCheck;

});

};





const okHttp3CertificatePinnerCheckOkHttp = (): any | undefined => {

// -- Sample Java

//

// Example used to test this bypass.

//

// String hostname = "swapi.co";

// CertificatePinner certificatePinner = new CertificatePinner.Builder()

// .add(hostname, "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")

// .build();

// OkHttpClient client = new OkHttpClient.Builder()

// .certificatePinner(certificatePinner)

// .build();

// Request request = new Request.Builder()

// .url("https://swapi.co/api/people/1")

// .build();

// Response response = client.newCall(request).execute();

return wrapJavaPerform(() => {

const certificatePinner: CertificatePinner = Java.use("okhttp3.CertificatePinner");



const CertificatePinnerCheckOkHttp = certificatePinner.check$okhttp.overload("java.lang.String", "u15");



// tslint:disable-next-line:only-arrow-functions

CertificatePinnerCheckOkHttp.implementation = function () {

};



return CertificatePinnerCheckOkHttp;

});

};

const appceleratorTitaniumPinningTrustManager = (): any | undefined => {

return wrapJavaPerform(() => {

const pinningTrustManager: PinningTrustManager = Java.use("appcelerator.https.PinningTrustManager");



const PinningTrustManagerCheckServerTrusted = pinningTrustManager.checkServerTrusted;



// tslint:disable-next-line:only-arrow-functions

PinningTrustManagerCheckServerTrusted.implementation = function () {

};



return PinningTrustManagerCheckServerTrusted;

});

};



// Android 7+ TrustManagerImpl.verifyChain()

// The work in the following NCC blog post was a great help for this hook!

// hattip @AdriVillaB :)

// https://www.nccgroup.trust/uk/about-us/newsroom-and-events/

// blogs/2017/november/bypassing-androids-network-security-configuration/

//

// More information: https://sensepost.com/blog/2018/tip-toeing-past-android-7s-network-security-configuration/

const trustManagerImplVerifyChainCheck = (): any | undefined => {

return wrapJavaPerform(() => {

const trustManagerImpl: TrustManagerImpl = Java.use("com.android.org.conscrypt.TrustManagerImpl");



// https://github.com/google/conscrypt/blob/c88f9f55a523f128f0e4dace76a34724bfa1e88c/

// platform/src/main/java/org/conscrypt/TrustManagerImpl.java#L650

const TrustManagerImplverifyChain = trustManagerImpl.verifyChain;

// tslint:disable-next-line:only-arrow-functions

TrustManagerImplverifyChain.implementation = function (untrustedChain: any, trustAnchorChain: any,

host: any, clientAuth: any, ocspData: any, tlsSctData: any) {



// Skip all the logic and just return the chain again :P

return untrustedChain;

};



return TrustManagerImplverifyChain;

});

};





// Android 7+ TrustManagerImpl.checkTrustedRecursive()

// The work in the following method is based on:

// https://techblog.mediaservice.net/2018/11/universal-android-ssl-pinning-bypass-2/

const trustManagerImplCheckTrustedRecursiveCheck = (): any | undefined => {

return wrapJavaPerform(() => {

const arrayList: ArrayList = Java.use("java.util.ArrayList");

const trustManagerImpl: TrustManagerImpl = Java.use("com.android.org.conscrypt.TrustManagerImpl");



// https://android.googlesource.com/platform/external/conscrypt/+/1186465/src/

// platform/java/org/conscrypt/TrustManagerImpl.java#391

const TrustManagerImplcheckTrustedRecursive = trustManagerImpl.checkTrustedRecursive;

// tslint:disable-next-line:only-arrow-functions

TrustManagerImplcheckTrustedRecursive.implementation = function (certs: any, host: any, clientAuth: any, untrustedChain: any,

trustAnchorChain: any, used: any) {



// Return an empty list

return arrayList.$new();

};



return TrustManagerImplcheckTrustedRecursive;

});

};



const phoneGapSSLCertificateChecker = (): any | undefined => {

return wrapJavaPerform(() => {

const sslCertificateChecker: SSLCertificateChecker = Java.use("nl.xservices.plugins.SSLCertificateChecker");



const SSLCertificateCheckerExecute = sslCertificateChecker.execute;



SSLCertificateCheckerExecute.overload(

"java.lang.String", "org.json.JSONArray", "org.apache.cordova.CallbackContext").implementation =

// tslint:disable-next-line:only-arrow-functions

function (str: any, jsonArray: any, callBackContext: any) {

callBackContext.success("CONNECTION_SECURE");

return true;

};

});

};



sslContextEmptyTrustManager();

okHttp3CertificatePinnerCheck();

okHttp3CertificatePinnerCheckOkHttp();

appceleratorTitaniumPinningTrustManager();

trustManagerImplVerifyChainCheck();

trustManagerImplCheckTrustedRecursiveCheck();

phoneGapSSLCertificateChecker();



}