隐私检测篇-对一个虚拟框架进行二次开发

本次选用BlackBox进行二次开发,添加两个功能:

1 做一个隐私检测的功能,并适配我的隐私阅读器

2 设置一个frida开关,使其在非root机也能使用frida

一、隐私检测

1 选用的hook框架为Pine,注入点在BActivityThread的loadXposed函数


    public void loadXposed(Context context) {
        String vPackageName = getAppPackageName();
        String vProcessName = getAppProcessName();
        YLog.info("loadXposed start process -> " + vProcessName);
        if (!TextUtils.isEmpty(vPackageName) && !TextUtils.isEmpty(vProcessName)) {
            if (BXposedManager.get().isXPEnable()){
                // TODO -> xposed hook
                // ... ...
            }else{
                // TODO -> Pine hook (privacy check)
                try{
                    HookHelper.getInstance().registerHook(vPackageName).hook();
                } catch (Throwable th){
                    YLog.error(th.toString());
                }
            }
        }
        if (BlackBoxCore.get().isHideXposed()) {
            NativeCore.hideXposed();
        }
    }
HookHelper.java

public class HookHelper {
    private static HookHelper mHelper;
    private final List hooks = new ArrayList<>();

    public void hook(){
        for (HookMethods hook : hooks) {
            try {
                hook.hook();
            } catch (Exception err) {
                YLog.error(err.toString());
            }
        }
    }

    private HookHelper(){
    }

    public static HookHelper getInstance(){
        if (mHelper == null) {
            synchronized (HookHelper.class) {
                if (mHelper == null) {
                    mHelper = new HookHelper();
                }
            }
        }
        return mHelper;
    }

    public HookHelper registerHook(String pkg) {
        try{
            MsgDispatcher.dispatch();
            //hooks.add(new HookBinderProxy());
            hooks.add(new HookTelephonyManager());
            hooks.add(new HookWifiInfo());
            hooks.add(new HookNetworkInterface());
            hooks.add(new HookLocationManager());
            hooks.add(new HookApplicationPackageManager());
            hooks.add(new HookPackageManager(pkg));
            hooks.add(new HookSettingsSecure());
            hooks.add(new HookSettingsSystem());
            hooks.add(new HookSystemProperties());
            hooks.add(new HookBluetoothAdapter());
            hooks.add(new HookBluetoothDevice());
            hooks.add(new HookCamera());
            hooks.add(new HookInetAddress());
            hooks.add(new HookActivityManager());
            hooks.add(new HookCdmaCellLocation());
            hooks.add(new HookGsmCellLocation());
            hooks.add(new HookWifiManager());
            hooks.add(new HookLocation());
            hooks.add(new HookBuild());
            hooks.add(new HookClipboardManager());
            //hooks.add(new HookSmsManager());
            hooks.add(new HookContextImpl());
            //hooks.add(new HookContentProviderProxy());
        }catch (Exception err) {
            YLog.error(err.toString());
        }
        return this;
    }
}

Hook的相关类

public interface HookInterface {
    void hook();
}


public abstract class HookMethods implements HookInterface {
    public Class mCls = null;
    public String mClz = "";
    public HookMethods(String clz) {
        mClz = clz;
    }

    protected abstract void hookMethod();

    @Override
    public void hook(){
        try{
            mCls = Class.forName(mClz);
            hookMethod();
        }catch (Exception e){
            YLog.error(e.toString());
        }
    }
}



public class HookMessage {
    public String mStackTrace = null;
    public String mClass = null;
    public String mMethod = null;
    public String mMethodSignature = null;
    public List mParams = null;
    public String mReturn = "";
    public String mDescription = null;
    public String mDate = null;
    public int mCount = 0;

    public HookMessage(String stacktrace, String cls, String method, String sig, List params, String ret, String des, int count, String date){
        mStackTrace = stacktrace;
        mClass = cls;
        mMethod = method;
        mMethodSignature = sig;
        mParams = params;
        mReturn = ret;
        mDescription = des;
        mCount = count;
        mDate = date;
    }

    public static class Builder{
        public String mStackTrace = "";
        public String mClass = "";
        public String mMethod = "";
        public String mMethodSignature = "";
        public List mParams = null;
        public String mReturn = "";
        public String mDescription = "";
        public int mCount = 0;
        public String mDate = "";

        public Builder(){
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String currentTime = sdf.format(new Date());
            setDate(currentTime);
        }

        public Builder setCount(int c){
            this.mCount = c;
            return this;
        }

        public Builder setParams(List ls){
            this.mParams = ls;
            return this;
        }

        public Builder setReturn(String s){
            if (s == null){
                this.mReturn = "null";
            }else{
                this.mReturn = s;
            }
            return this;
        }

        public Builder setStackTrace(String s){
            this.mStackTrace = s;
            return this;
        }

        public Builder setClass(String s){
            this.mClass = s;
            return this;
        }

        public Builder setMethod(String s){
            this.mMethod = s;
            return this;
        }

        public Builder setMethodSignature(String s){
            this.mMethodSignature = s;
            return this;
        }

        public Builder setDescription(String s){
            this.mDescription = s;
            return this;
        }

        public Builder setDate(String s){
            this.mDate = s;
            return this;
        }

        public HookMessage build(){
            return new HookMessage(mStackTrace, mClass, mMethod, mMethodSignature, mParams, mReturn, mDescription, mCount, mDate);
        }
    }

}


public class MsgDispatcher {
    public static final LinkedList mMsg = new LinkedList(); //消息队列

    public static void dispatch(){
        new Thread() {
            public void run() {
                while(true){
                    try {
                        HookMessage msg = get();
                        if (msg != null){
                            send(msg);
                        }
                    }catch (Throwable th) {
                        YLog.error(th.toString());
                    }
                }
            }
        }.start();
    }

    public static void put(HookMessage msg){
        synchronized(mMsg) {
            mMsg.add(msg);
        }
    }

    public static HookMessage get(){
        HookMessage msg = null;
        synchronized(mMsg) {
            if (mMsg.isEmpty()){
                return msg;
            }
            msg = (HookMessage)mMsg.poll();
        }
        return msg;
    }

    public static void send(HookMessage msg){
        try{
            JSONObject jSONObject = new JSONObject();
            jSONObject.put("Time", "" + msg.mDate);
            jSONObject.put("Class", "" + msg.mClass);
            jSONObject.put("Method", "" + msg.mMethod + msg.mMethodSignature);
            jSONObject.put("Total", "" + msg.mCount);
            jSONObject.put("Description", msg.mDescription);
            if (msg.mParams == null){
                jSONObject.put("Params", "");
            }else{
                jSONObject.put("Params", msg.mParams.toString());
            }

            jSONObject.put("Return", msg.mReturn);
            jSONObject.put("StackTrace", msg.mStackTrace);
            MsgClient.getInstance().send(jSONObject.toString());
        } catch (Throwable th){
            YLog.error(th.toString());
        }

    }
}

Hook信息转发的客户端


public class MsgClient {
    public static MsgClient client;
    public static boolean isInit = false;
    public Socket mSocket = null;
    public OutputStream mOutputStream = null;
    public LinkedList mMsg = new LinkedList(); //消息队列


    public MsgClient() {
        try{
            mSocket = new Socket(getIP(), 33188);
            mOutputStream = mSocket.getOutputStream();
            loop();
            isInit = true;
            YLog.info(">> Socket客户端启动成功! <<");
        } catch (Throwable th){
            isInit = false;
            YLog.error(th.toString());
        }

    }

    public String getIP(){
        try{
            Class methodClass = Class.forName("android.app.ActivityThread");
            Method currentApplication = methodClass.getDeclaredMethod("currentApplication");
            currentApplication.setAccessible(true);
            Application application = (Application) currentApplication.invoke(null);
            WifiManager wifiManager = (WifiManager)application.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
            int ipAddress = wifiManager.getConnectionInfo().getIpAddress();
            String localIP = String.format("%d.%d.%d.%d", (ipAddress & 0xff), (ipAddress >> 8 & 0xff),
                    (ipAddress >> 16 & 0xff), (ipAddress >> 24 & 0xff));
            return localIP;
        } catch (Throwable th){
            YLog.error(th.toString());
        }
        return null;
    }

    public void loop(){
        new Thread(){
            public void run(){
                try{
                    PrintWriter pw = new PrintWriter(mOutputStream);
                    while (true){
                        String data = "";
                        synchronized(mMsg) {
                            if (mMsg.isEmpty()){
                                continue;
                            }else{
                                data = (String)mMsg.poll();
                            }
                        }
                        pw.write(data);
                        pw.flush();
                    }
                } catch (Throwable th){
                    YLog.error(th.toString());
                }
            }
        }.start();
    }

    public static MsgClient getInstance() {
        if (!isInit) {
            synchronized (MsgClient.class) {
                if (!isInit) {
                    client = new MsgClient();
                }
            }
        }
        return client;
    }


    public void send(@NonNull String data){
        if (isInit){
            synchronized(mMsg) {
                mMsg.add(data);
            }
        }
    }
}

服务端


public class NanoServer extends EdNanoHTTPD {
    private Context mContext;
    public BufferedInputStream input = null;
    public BufferedOutputStream output;
    public SocketAddress scaddr;
    public ServerSocket serverSocket;
    public Socket socket;
    public final LinkedList mMsg = new LinkedList(); //消息队列

    public NanoServer(Context context) throws IOException {
        super(33445);
        this.mContext = context;
        startSocketThread(context);
        start();
        YLog.info(">> NanoHTTPD服务端启动成功! <<");
    }

    public static NanoServer getServer(Context context){
        try {
            Class clz = Class.forName("android.app.ActivityThread", false, context.getClassLoader());
            java.lang.reflect.Method method = clz.getDeclaredMethod("currentProcessName", (Class[]) new Class[0]);
            method.setAccessible(true);
            String invoke = (String)method.invoke(null, new Object[0]);
            if (invoke instanceof String) {
                if(invoke.equals("com.yooha.privacybox64") || invoke.equals("com.yooha.privacybox32")){
                    return new NanoServer(context);
                }
            }
        } catch (Throwable e) {
        }
        return null;
    }

    //创建个socket接受client的消息,然后写进队列
    public void startSocketThread(Context context){
        new Thread() {
            @Override
            public void run() {
                while (true){
                    try{
                        serverSocket = new ServerSocket();
                        serverSocket.setReuseAddress(true);
                        scaddr = new InetSocketAddress(getIP(context), 33188);
                        try{
                            serverSocket.bind(scaddr);
                            YLog.info(">> Socket服务端启动成功! <<");
                            socket = serverSocket.accept();
                        }catch (Throwable th){
                            YLog.err(th.toString());
                            return;
                        }
                        output = new BufferedOutputStream(socket.getOutputStream());
                        input = new BufferedInputStream(socket.getInputStream());
                        String data = "";

                        while (true){
                            byte[] bBuff = new byte[1024];
                            data += new String(bBuff, 0, input.read(bBuff, 0, 1024), "utf-8");
                            if (data.contains("}")){
                                int index = data.indexOf("}");
                                String s = new String(data.substring(0, index + 1));
                                data = data.substring(index + 1);
                                synchronized (mMsg){
                                    mMsg.add(s);
                                }
                            }
                        }
                    } catch (Throwable th){
                        YLog.err(th.toString());
                    }
                }
            }
        }.start();
    }

    public String getIP(Context context){
        try{
            WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
            int ipAddress = wifiManager.getConnectionInfo().getIpAddress();
            String localIP = String.format("%d.%d.%d.%d", (ipAddress & 0xff), (ipAddress >> 8 & 0xff),
                    (ipAddress >> 16 & 0xff), (ipAddress >> 24 & 0xff));
            return localIP;
        } catch (Throwable th){
            YLog.err(th.toString());
        }
        return null;
    }

    @Override
    public Response serve(IHTTPSession session) {
        String msg = "";
        if (Method.GET == session.getMethod()) {
            String uri = session.getUri();
            if (uri.contains("api/getlog")){
                synchronized(mMsg) {
                    if (!mMsg.isEmpty()){
                        msg = (String)mMsg.poll();
                    }
                }
            }else if (uri.contains("api/connect")){
                msg = "connect";
            }
        }

        try {
            return newFixedLengthResponse(msg);
        } catch (Exception exception) {
            return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, "Internal Server Error!!!");
        }

    }
}

服务端在主体中启动,APP类的attachBaseContext中启动

    override fun attachBaseContext(base: Context?) {
        super.attachBaseContext(base)
        mContext = base!!
        AppManager.doAttachBaseContext(base)
        startSrver(base)
    }

    private fun startSrver(context: Context){
        if (mServer == null){
            mServer = NanoServer.getServer(context);
        }
    }

以上能保证每个hook进程 能将消息发送给主体中服务端,然后再有主体与PC端通信

Hook  TelephonyManager.getDeviceId (示例)

       try{
            Method method = mCls.getDeclaredMethod("getDeviceId");
            Pine.hook(method, "android.telephony.TelephonyManager -> getDeviceId():获取DeviceId", new MethodHook() {
                @Override
                public void afterCall(Pine.CallFrame callFrame) {
                    try{
                        mCountgetDeviceId++;
                        String ret = (String)callFrame.getResult();
                        String stack = YLog.getStackTrace();
                        HookMessage msg = new HookMessage.Builder()
                                .setClass(mClz)
                                .setMethod("getDeviceId")
                                .setMethodSignature("()")
                                .setReturn(String.valueOf(ret))
                                .setStackTrace(stack)
                                .setCount(mCountgetDeviceId)
                                .setDescription("获取设备ID")
                                .build();
                        MsgDispatcher.put(msg);
                    } catch (Throwable th){
                        YLog.error(th.toString());
                    }
                }
            });
        } catch (Throwable thr){
            YLog.error(thr.toString());
        }

跑一下看看效果:

隐私检测篇-对一个虚拟框架进行二次开发_第1张图片

隐私检测篇-对一个虚拟框架进行二次开发_第2张图片

二、设置一个frida开关,集成frida hook能力,在非root机也能使用frida

先加一个swith控件

隐私检测篇-对一个虚拟框架进行二次开发_第3张图片

隐私检测篇-对一个虚拟框架进行二次开发_第4张图片

依然是在BActivityThread类的loadXposed函数中添加注入点

    public void loadXposed(Context context) {
        String vPackageName = getAppPackageName();
        String vProcessName = getAppProcessName();
        YLog.info("loadXposed start process -> " + vProcessName);
        if (!TextUtils.isEmpty(vPackageName) && !TextUtils.isEmpty(vProcessName)) {
            if (BXposedManager.get().isFridaEnable()){
                // TODO -> load frida-gumjs (version=16.1.2)
                try{
                    GumManager.loadGumEngine();
                } catch (Throwable th) {
                }
            }
    ........




    public static void loadGumEngine(){
        System.loadLibrary("Yooha");
    }

接下来去github上下载frida-gumjs的静态库和头文件

隐私检测篇-对一个虚拟框架进行二次开发_第5张图片

编写代码加载js引擎


UNEXPORT char *readfile(const char *filepath) {
    FILE *file = fopen(filepath, "r");
    if (file == NULL) {
        LOGE("file open failed : %s " ,filepath);
        return NULL;
    }
    LOGI("file open success : %s", filepath);
    struct stat statbuf{};
    stat(filepath, &statbuf);
    int filesize = statbuf.st_size;

    void *buffer = malloc(filesize + 1);
    memset(buffer, 0, filesize + 1);
    int count = 0;
    int total = 0;
    while ((count = fread((char *) buffer + total, sizeof(char), 1024, file)) != 0) {
        total += count;
    }
    if (file != NULL) {
        fclose(file);
    }
    return (char *) buffer;
}

UNEXPORT static void on_message(const gchar *message,
                                GBytes *data, gpointer user_data) {
    JsonParser *parser;
    JsonObject *root;
    const gchar *type;

    parser = json_parser_new();
    json_parser_load_from_data(parser, message, -1, NULL);
    root = json_node_get_object(json_parser_get_root(parser));

    type = json_object_get_string_member(root, "type");
    if (strcmp(type, "log") == 0) {
        const gchar *log_message;
        log_message = json_object_get_string_member(root, "payload");
        FLOG ("[+] log : %s ", log_message);
    } else {
        FLOG ("[-] %s ", message);
    }
    g_object_unref(parser);
}

UNEXPORT int hookFunc(const char *scriptpath) {
    gum_init_embedded();
    backend = gum_script_backend_obtain_qjs();
    char *js = readfile(scriptpath);
    if (!js) {
        return 1;
    }
    script = gum_script_backend_create_sync(backend, "example", js, NULL, cancellable, &error);
    g_assert (error == NULL);
    gum_script_set_message_handler(script, on_message, NULL, NULL);
    gum_script_load_sync(script, cancellable);
    
    context = g_main_context_get_thread_default();
    while (g_main_context_pending(context))
        g_main_context_iteration(context, FALSE);
    
    pthread_mutex_lock(&mtx);
    pthread_cond_signal(&cond);
    pthread_mutex_unlock(&mtx);

    loop = g_main_loop_new(g_main_context_get_thread_default(), FALSE);
    g_main_loop_run(loop);

    return 0;
}

UNEXPORT int gumjsHook(const char *scriptpath) {
    pthread_t pthread;
    int result = pthread_create(&pthread, NULL, (void *(*)(void *)) (hookFunc),
                                (void *) scriptpath);
    struct timeval now;
    struct timespec outtime;
    pthread_mutex_lock(&mtx);
    gettimeofday(&now, NULL);
    outtime.tv_sec = now.tv_sec + 5;
    outtime.tv_nsec = now.tv_usec * 1000;
    pthread_cond_timedwait(&cond, &mtx, &outtime);
    pthread_mutex_unlock(&mtx);
    if (result != 0) {
        LOGI("create thread failed");
    } else {
        LOGE("create thread success");
    }
    return result;
}

JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved){
    gumjsHook(jspath);
    return JNI_VERSION_1_6;
}

写个hook脚本,将hook脚本push到/data/local/tmp/


function main(){
    hook_open()
    Java.perform(function () {
        Java.use("android.app.Activity").startActivity.overload('android.content.Intent').implementation=function(p1){
            console.log("startActivity = ", p1);
            console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
            this.startActivity(p1);
        }
    })
}

function hook_open(){
    var open_addr = Module.findExportByName("libc.so", "open");
    Interceptor.attach(open_addr,{
        onEnter:function(args){
            var path = args[0].readUtf8String();
            console.log("open -> ", path);
        },onLeave:function(retval){
        }
    })
}
setImmediate(main)

运行测试效果

隐私检测篇-对一个虚拟框架进行二次开发_第6张图片

hook startActivity

隐私检测篇-对一个虚拟框架进行二次开发_第7张图片

hook open

隐私检测篇-对一个虚拟框架进行二次开发_第8张图片

以上在非root机 红米上测试的

隐私检测篇-对一个虚拟框架进行二次开发_第9张图片

你可能感兴趣的:(安卓逆向,安卓安全,安卓系统,android)