Frida保姆级教程

Hook原理

在程序运行时通过修改函数地址或者替换函数指针,实现当程序调用函数的时候跳转到自定义的函数内,实现Hook,简单来说就是自定义一个函数,用于替换掉原本执行的函数,需要注意的是,自定义的函数中参数和返回值要与原函数一致,因为程序上下文的传参是规定好的,如果不同会报错。

配置Hook环境

设备

  • PC

  • 雷电模拟器-9.1.28

  • Sdk工具-Adb

下载模块

uname -m

x86-64表示64位,i686或i386标识32位 在后续hook应用中可以用作条件判断语句

GitHub仓库地址:

Releases · frida/frida

配置模拟器环境

  • 开启模拟器root权限

  • 查看模拟器对应的系统架构

  • 从github上下载对应的系统架构的frida-server文件

  • Win+R打开cmd窗口

adb push xxx/xxx/frida-server /data/local/tmp #将文件推送到tmp目录下
adb shell #进入模拟器shell页面,因为Android使用Linux内核,大多数命令都可以使用
su #管理员用户 同linux一样
chmod 777 frida-server #文件赋予权限

配置PC端环境

  • 安装Python环境,可以通过官网或者清华镜像源下载对应系统的安装包

Download Python

pip install frida==x.x.x

这里安装的版本需要与从仓库中下载的server文件上的版本号相同

  • Win+R打开cmd窗口进行测试 如下输出即为安装完毕

pip show frida

clipboard_2025-02-20_14-45.bmp

编写一个Js脚本进行测试

Java.perform(function() {    const MainActivity = Java.use('com.example.hotfix.MainActivity');    MainActivity.getarrly.implementation = function() {        console.log('getarrly() 方法被调用');        printStack();        this.getarrly();    };});
//打印调用栈
function printStack() {
    Java.perform(function () {
        var Exception = Java.use("java.lang.Exception");
        var ins = Exception.$new("Exception");
        var straces = ins.getStackTrace();
        if (straces != undefined && straces != null) {
            var strace = straces.toString();
            var replaceStr = strace.replace(/,/g, "\r\n");
            console.log("=============================Stack strat=======================");
            console.log(replaceStr);
            console.log("=============================Stack end=======================\r\n");
            Exception.$dispose();
        }
    });
}

该脚本用于打印当MainActivity中getarrly()方法被调用时打印调用堆栈,主要用于追踪数据传输以及方法的调用

Frida使用方法

frida -U -l exploit.js -f xxx #启动app并注入脚本
frida-ps -U
frida-ps -Ua
frida -U -l exploit.js "XXXX" #app已经启动 注入脚本

Hook技术实战应用

现在需要你找出在Android系统中 启动一个App最先调用的方法是什么 并且通过Hook的形式实现查看该App的网络请求地址

采用如下思路

1:hook关键的类以及方法

2:打印出关键方法的堆栈调用信息

3:分析打印出的调用堆栈查找出最快启动的方法

4:打印出App调用的Android系统Api以及网络请求的库

5:通过查看网络请求的库从而编写Js脚本实现将该App发送的网络请求进行打印

6:使用Python编写脚本将请求地址进行去重

  1. 将App拖入Jadx进行反编译查看信息

在AndroidManifest.xml文件中有个标签为application,name字段下的值即为该App第一个启动的Application(这个Application不一定每一个App都有)根据这一点进入这个App进行Hook方法的确定

Snipaste_2025-02-19_11-21-08.png

  1. 确定好需要Hook的方法

image.png

  1. 编写Js脚本打印堆栈信息

Java.perform(function() {    
    const MainActivity = Java.use('com.sevengms.myframe.MyApplication');   
    MainActivity.attachBaseContext.overload('android.content.Context').implementation = function(base) {     
        console.log(' 方法被调用');       
        printStack();       
        return this.attachBaseContext(base);   
    };
});

//打印调用栈
function printStack() {
    Java.perform(function () {
        var Exception = Java.use("java.lang.Exception");
        var ins = Exception.$new("Exception");
        var straces = ins.getStackTrace();
        if (straces != undefined && straces != null) {
            var strace = straces.toString();
            var replaceStr = strace.replace(/,/g, "\r\n");
            console.log("=============================Stack strat=======================");
            console.log(replaceStr);
            console.log("=============================Stack end=======================\r\n");
            Exception.$dispose();
        }
    });
}
  1. 使用Frida进行Hook测试

这里需要注意的是 需要提前使用adb进入模拟器运行frida-server文件 之后窗口不要关闭 新开一个窗口去运行frida脚本

imageaaa.png

  1. 分析打印的堆栈信息

由调用堆栈可得最开始启动的方法名为main,该方法在ZygoteInit这个类里,在Android系统中Zygote进程(又叫孵化进程)为所有应用进程的父进程,在系统创建时进行创建,再通过fork机制去创建新的应用进程,所有使用同样的方法去hook不同的App,最先启动的方法都是这个作为入口函数的main

  1. 编写Js脚本打印出App调用的系统Api以及用作网络请求的第三方库

// android_api_logger.js
Java.perform(function () {
    // 需要监控的系统API列表(可根据需求扩展)
    const targetAPIs = [
        'java.net.URL.openConnection',
        'okhttp3.OkHttpClient.newCall',
        'android.webkit.WebView.loadUrl',
        'javax.net.ssl.HttpsURLConnection.getInputStream',
        'android.net.http.X509TrustManagerExtensions.checkServerTrusted'
    ];

    // 动态监控未明确指定的类
    const dynamicClasses = [
        /^okhttp3\./,
        /^com.android.okhttp.internal/,
        /^java\.net\./,
        /^android\.net\./,
        /^android\.webkit\./
    ];

    // 监控通用网络相关方法
    const commonMethods = [
        'connect',
        'execute',
        'enqueue',
        'loadUrl',
        'getInputStream',
        'doOutput'
    ];

    // 记录已Hook的类和方法
    const hookedMethods = new Set();

    // Hook方法的核心函数
    function hookMethod(className, methodName) {
        const key = `${className}.${methodName}`;
        if (hookedMethods.has(key)) return;

        try {
            const targetClass = Java.use(className);
            const overloads = targetClass[methodName].overloads;

            overloads.forEach(function (overload) {
                overload.implementation = function () {
                    // 打印调用信息
                    console.log(`\n[${className}] 调用方法: ${methodName}`);
                    
                    // 打印参数
                    const args = Array.prototype.slice.call(arguments);
                    args.forEach((arg, index) => {
                        console.log(`  参数 ${index + 1}: ${arg}`);
                    });

                    // 打印堆栈跟踪(可选)
                    // console.log(Java.use("android.util.Log").getStackTraceString(
                    //     Java.use("java.lang.Throwable").$new()
                    // ));

                    // 调用原始方法
                    return this[methodName].apply(this, arguments);
                };
            });

            hookedMethods.add(key);
            console.log(`成功Hook: ${key}`);
        } catch (e) {
            // console.log(`Hook失败: ${key} - ${e}`);
        }
    }

    // Hook预定义的目标API
    targetAPIs.forEach(api => {
        const [className, methodName] = api.split('.');
        hookMethod(className, methodName);
    });

    // 动态查找并Hook网络相关类
    Java.enumerateLoadedClasses({
        onMatch: function (className) {
            // 检查是否匹配动态类模式
            if (dynamicClasses.some(regex => regex.test(className))) {
                const targetClass = Java.use(className);
                const methods = targetClass.class.getDeclaredMethods();

                // Hook类中的所有方法
                methods.forEach(method => {
                    const methodName = method.getName();
                    if (commonMethods.includes(methodName)) {
                        hookMethod(className, methodName);
                    }
                });
            }
        },
        onComplete: function () {}
    });

    // 监控SSL/TLS握手
    const sslContextClass = Java.use("javax.net.ssl.SSLContext");
    sslContextClass.init.overload(
        '[Ljavax.net.ssl.KeyManager;',
        '[Ljavax.net.ssl.TrustManager;',
        'java.security.SecureRandom'
    ).implementation = function (km, tm, sr) {
        console.log("\n[SSLContext] 初始化SSL上下文");
        if (tm) {
            tm.forEach(manager => {
                console.log(`  使用TrustManager: ${manager.$className}`);
            });
        }
        return this.init(km, tm, sr);
    };
});

imagebbb.png


Frida保姆级教程
http://1.95.139.200:8090/archives/FridaTutorial
发布于
2025年04月10日
许可协议