使用 Frida 定位 JNI 方法内存地址

版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/

java 调用 native 方法流程

ArtMethod 是 Android Runtime (ART) 中一个非常重要的数据结构,它在 Android 系统中用于表示每个 Java 方法。

entry_point_from_jni_ 是 ArtMethod 结构体的一个字段,专门用于存储一个 Java 方法的 JNI(Java Native Interface)入口点。

当 Java 代码调用一个 native 方法时,ART 会通过entry_point_from_jni_ 字段找到对应的 JNI 函数入口,然后切换到 C/C++ 层执行该方法。

确定 entry_point_from_jni 的偏移量_

先通过查找 ART 中 ArtMethod 结构体中 entry_point_from_jni_ 的偏移量,最终确定 JNI 函数的地址。

let entry_point_from_jni_offset = -1;

/**
 * 找到 entry_point_from_jni_ 在 ArtMethod 结构体中的偏移量(根据 Android 版本不同可能会变化)
 *
 * @returns {number} 返回 entry_point_from_jni_ 的偏移量,若未找到返回 -1
 */
function get_jni_offset() {

    // 如果偏移量已经计算过(不为 -1),直接返回已保存的偏移量
    if (entry_point_from_jni_offset !== -1) {
        return entry_point_from_jni_offset;
    }

    // 获取 getUidForName JNI 方法的内存地址,该方法位于 "libandroid_runtime.so" 中
    let native_addr = Module.findExportByName("libandroid_runtime.so", "_Z32android_os_Process_getUidForNameP7_JNIEnvP8_jobjectP8_jstring");
    // console.log("native_addr:",native_addr);

    // 目标类名 "android.os.Process"
    let className = "android.os.Process";
    // 使用 Java.use 获取该类的 Java 类对象,并访问其 Class 对象
    let clazz = Java.use(className).class;
    // 获取该类的所有声明的方法
    let methods = clazz.getDeclaredMethods();

    // 遍历类中的所有方法
    for (let i = 0; i < methods.length; i++) {

        // 获取方法的字符串表示形式(如方法的完整签名)
        let methodName = methods[i].toString();

        // 获取方法的修饰符,flags 是该方法的访问标志(修饰符),如 public、private、static、native 等
        let flags = methods[i].getModifiers();

        // 通过与 256 位运算判断方法是否为 native 方法(256 代表 native 修饰符)
        if (flags & 256) {

            // 如果方法名中包含 "getUidForName",说明找到了目标方法
            if (methodName.indexOf("getUidForName") != -1) {

                // 获取该方法的 ArtMethod 对象(ArtMethod 是方法的内部表示,包含了方法的很多底层信息)
                let art_method = methods[i].getArtMethod();

                // 遍历从 ArtMethod 开始的内存地址,以找到与 native_addr 相等的 JNI 函数地址
                for (let j = 0; j < 30; j = j + 1) {

                    // 读取 ArtMethod 的内存偏移位置,尝试获取 JNI 函数地址
                    let jni_native_addr = Memory.readPointer(ptr(art_method + j));

                    // 比较 JNI 函数地址是否与我们查找到的 native_addr 相等
                    if (native_addr.equals(jni_native_addr)) {
                        // 找到正确的偏移量,将其保存并返回
                        entry_point_from_jni_offset = j;
                        return j;
                    }
                }
            }
        }
    }

    // 如果未找到 JNI 方法对应的偏移量,返回 -1
    return -1;
}

寻找 JNI 函数地址

得到当前系统中 entry_point_from_jni_ 的偏移量后,通过ArtMethod 的内存地址 + entry_point_from_jni_ 的偏移量 = JNI 函数地址。

遍历指定类的 native 方法,并打印 JNI 函数的地址、所属模块,以及模块中的偏移量。

/**
 * 遍历类中的 native 方法,打印 JNI 函数的地址、所属模块,以及模块中的偏移量。
 *
 * @param className 类名
 */
function get_jni_method_addr(className) {

    // 获取指定类的 Class 对象
    let obj = Java.use(className);
    let clazz = obj.class;

    // 获取当前系统的 JNI 偏移量
    let jni_offset = get_jni_offset();

    // 获取该类中的所有声明的方法
    let methods = clazz.getDeclaredMethods();

    // 遍历类中的所有方法
    for (let i = 0; i < methods.length; i++) {

        // 将方法转为字符串形式(完整的描述,包括修饰符、返回类型、参数等)
        let methodName = methods[i].toString();

        // 获取方法的修饰符,flags 代表访问权限和其他属性(如 native 修饰符)
        let flags = methods[i].getModifiers();

        // 检查该方法是否为 native 方法(通过与 256 位运算判断,256 代表 native 修饰符)
        if (flags & 256) {

            // 获取该方法的 ArtMethod 对象,ArtMethod 是方法在 ART 虚拟机中的内部表示
            let art_method = methods[i].getArtMethod();

            // 通过 ArtMethod 的内存地址 + jni_offset = JNI 函数地址
            let native_addr = Memory.readPointer(ptr(art_method + jni_offset));

            // 根据 JNI 函数地址中找到所在的模块,并计算该函数在模块中的偏移量
            let module;
            let offset;

            // 打印方法名
            console.log("methodName->", methodName);
            try {
                // 通过函数地址找到所属的模块
                module = Process.getModuleByAddress(native_addr);

                // 计算函数在模块中的偏移量(函数地址减去模块基地址)
                offset = native_addr - module.base;

                // 打印模块名称及偏移量,偏移量以十六进制格式显示,并且字母大写
                console.log("Func.offset==", module.name, "0x" + offset.toString(16).toUpperCase());
            } catch (err) {
                
            }

            // 打印该方法的 JNI 函数地址
            console.log("Func.getArtMethod->native_addr:", native_addr.toString().toUpperCase());
            
            // console.log("Func.flags->", flags);
        }
    }
}

调用示例

获取 lte.NCall 类中 native 方法地址

[Remote::com.xxxx.duapp]-> Java.perform(function () {
    get_jni_method_addr("lte.NCall")
})

关于 Frida 的使用可以参考这篇文章【使用 Frida Hook Android App

输出如下


methodName-> public static native byte lte.NCall.IB(java.lang.Object[])
Func.offset== libGameVMP.so 0xDFA8
Func.getArtMethod->native_addr: 0X72E9D19FA8
methodName-> public static native char lte.NCall.IC(java.lang.Object[])
Func.offset== libGameVMP.so 0xDFA8
Func.getArtMethod->native_addr: 0X72E9D19FA8
methodName-> public static native double lte.NCall.ID(java.lang.Object[])
Func.offset== libGameVMP.so 0xE028
Func.getArtMethod->native_addr: 0X72E9D1A028
methodName-> public static native float lte.NCall.IF(java.lang.Object[])
Func.offset== libGameVMP.so 0xDFE8
Func.getArtMethod->native_addr: 0X72E9D19FE8
methodName-> public static native int lte.NCall.II(java.lang.Object[])
Func.offset== libGameVMP.so 0xDFA8
Func.getArtMethod->native_addr: 0X72E9D19FA8
methodName-> public static native long lte.NCall.IJ(java.lang.Object[])
Func.offset== libGameVMP.so 0xDFA8
Func.getArtMethod->native_addr: 0X72E9D19FA8
methodName-> public static native java.lang.Object lte.NCall.IL(java.lang.Object[])
Func.offset== libGameVMP.so 0xDFA8
Func.getArtMethod->native_addr: 0X72E9D19FA8
methodName-> public static native short lte.NCall.IS(java.lang.Object[])
Func.offset== libGameVMP.so 0xDFA8
Func.getArtMethod->native_addr: 0X72E9D19FA8
methodName-> public static native void lte.NCall.IV(java.lang.Object[])
Func.offset== libGameVMP.so 0xDFA8
Func.getArtMethod->native_addr: 0X72E9D19FA8
methodName-> public static native boolean lte.NCall.IZ(java.lang.Object[])
Func.offset== libGameVMP.so 0xDFA8
Func.getArtMethod->native_addr: 0X72E9D19FA8
methodName-> public static native int lte.NCall.dI(int)
Func.offset== libGameVMP.so 0xB93C
Func.getArtMethod->native_addr: 0X72E9D1793C
methodName-> public static native long lte.NCall.dL(long)
Func.offset== libGameVMP.so 0xBAD0
Func.getArtMethod->native_addr: 0X72E9D17AD0
methodName-> public static native java.lang.String lte.NCall.dS(java.lang.String)
Func.offset== libGameVMP.so 0xBAEC
Func.getArtMethod->native_addr: 0X72E9D17AEC

比如,native 方法 lte.NCall.IL 在 libGameVMP.so 中偏移量为 0xDFA8。

在 IDA Pro 中跳转到 0xDFA8 就是 native 方法的入口点。
image.png

参考:

<think>好的,用户想了解如何用Frida绕过小红书的安全检测。首先,我需要回忆一下Frida的基本原理。Frida是一个动态插桩工具,允许用户注入JavaScript代码到目标进程中,从而监控或修改应用的行为。不过,小红书这类应用通常会有反调试机制,比如检测Frida的存在。 根据用户提供的引用[1],检测Frida的一种方法是通过检查/proc/self/fd和/proc/self/task/pid/fd下的文件,寻找与Frida相关的文件。所以,绕过这种检测可能需要隐藏这些文件痕迹,或者修改Frida的默认配置,比如改变端口号,避免使用默认的27042端口。 接下来,引用[2]提到小红书可能使用了libtiny.so和libxyass.so这样的混淆库来处理一些请求头参数,比如x-mini-sig和shield。这意味着可能需要用Unidbg这样的工具来模拟执行这些so文件,或者通过动态分析来绕过混淆。用户可能需要学习Unidbg来应对控制流混淆的问题。 用户可能需要具体的步骤,比如如何配置Frida避免被检测,如何Hook关键函数来绕过签名验证。例如,Hook与请求头生成相关的函数,替换或绕过shield参数的生成过程。此外,还需要注意时间戳相关的头信息,如x-b3-traceid,可能需要动态生成正确的值。 同时,需要考虑小红书的检测机制是否还有其他层面,如环境检测(root、模拟器)、SSL Pinning等。用户可能需要结合多种工具,比如Xposed模块配合使用,或者使用r2frida进行更底层的分析。 最后,要提醒用户注意法律风险,逆向工程可能违反用户协议,需谨慎操作。需要确保回答中提到的步骤合法,并强调仅供学习研究之用。</think>关于Frida工具在小红书应用中的安全检测绕过方法,需结合应用防护机制及动态调试技术进行分析。以下是关键步骤和注意事项: ### 一、Frida检测规避基础 1. **默认端口修改** 小红书的防护系统会监控默认的Frida服务端口$27042$。建议使用非标准端口启动: ```bash frida-server -l 0.0.0.0:8080 ``` 通过修改`adb forward`转发规则建立连接[^1]。 2. **特征文件隐藏** 使用`readlink`检查文件描述符时需规避特征: ```python Interceptor.attach(Module.findExportByName("libc.so", "readlink"), { onLeave: function(retval) { const path = retval.readCString(); if(path.includes("frida")) { retval.writeUtf8String("/dev/null"); } } }); ``` ### 二、核心防护机制分析 小红书使用的防护体系包含: 1. **动态签名验证** 在`libtiny.so`中实现请求头$x-mini-sig$的动态生成,该库采用控制流平坦化混淆技术。可通过Hook`JNI_OnLoad`函数定位关键算法: ```javascript Java.perform(function() { const System = Java.use('java.lang.System'); System.currentTimeMillis.implementation = function() { return 1234567890; // 固定时间戳绕过时间校验 }; }); ``` 2. **设备指纹绑定** `libxyass.so`负责生成$x-legacy-did$等设备指纹参数。需Hook`__system_property_get`函数修改设备特征值: ```javascript Interceptor.attach(Module.findExportByName("libc.so", "__system_property_get"), { onEnter: function(args) { this.propName = Memory.readCString(args[0]); }, onLeave: function(retval) { if(this.propName === "ro.serialno") { Memory.writeUtf8String(retval, "custom_serial"); } } }); ``` ### 三、高级对抗技术 1. **内存特征擦除** 针对Frida线程特征进行内存扫描防护: ```javascript const pthread_create = Module.findExportByName("libc.so", "pthread_create"); Interceptor.attach(pthread_create, { onEnter: function(args) { const threadName = Memory.readCString(args[3]); if(threadName.indexOf("gum-js-loop") !== -1) { args[3].writeUtf8String("app_worker_thread"); } } }); ``` 2. **SSL Pinning绕过** 结合JustTrustMe模块与自定义证书锁定解除: ```javascript SSL_CTX_set_cert_verify_callback.implementation = function(ctx, callback) { return 1; // 强制返回验证成功 }; ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值