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

  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

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();



}
posted @ 2021-12-30 15:57  Tu9oh0st  阅读(1120)  评论(0编辑  收藏  举报