针对Android App隐私信息检测
针对Android App隐私信息检测
尝试采用Frida进行处理。
目前的一个思路就是trace app中所有调用系统函数的功能,这个方法不够细致,无法判断是app自身调用的还是app调用的sdk调用的。
https://github.com/zhengjim/camille
昨天尝试了下,发现并没有多好使,还需要继续研究下,自己写个demo试试。
只需要hook相应的隐私API就算是触犯了隐私信息的采集了。
//获取手机通信录
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
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);
});
})
})
}
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);
});
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 :
//
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/
- 稳定的frida环境 => 8 + 12.8.0 \ 10 + 14.2.18
- Trace jni https://github.com/chame1eon/jnitrace-engine
- Bypass anti frida
- Bypass ssl panning https://github.com/r0ysue/r0capture
Bypass anti frida
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
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();
}