frida脚本,自动化寻址JNI方法

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

  1. 通过 ArtMethod 结构体找到 jni 方法在内存中的地址,并把寻址方法通过 rpc.exports 暴露给 Python 脚本调用

jni_addr.js

复制代码
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;
}

/**
 * 遍历类中的 native 方法,打印 JNI 函数的地址、所属模块,以及模块中的偏移量。
 *
 * 调用示例:get_jni_method_addr("lte.NCall")
 *
 * @param className 类名
 */
function get_jni_method_addr(className) {
    Java.perform(function () {
        // 获取指定类的 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);
            }
        }
    })
}

// 暴露给 Python 调用(注意:exports中函数名需要全部小写,而且不能有下划线,不然会找不到方法)
rpc.exports.getjnimethodaddr = get_jni_method_addr
  1. 在 python 脚本中加载 jni_addr.js 并调用 get_jni_method_addr 方法打印指定类中所有 native 方法的内存地址

jni_addr.py

复制代码
import frida


def read_frida_js_source(script):
    with open(script, "r", encoding='utf-8') as f:
        return f.read()


def on_message(message, data):
    print(f"消息: {message['type']}, 数据: {message['payload']}")


def main():
    class_name = "com.cyrus.example.MainActivity"

    device = frida.get_device_manager().add_remote_device("127.0.0.1:1234")
    pid = device.get_frontmost_application().pid
    session: frida.core.Session = device.attach(pid)
    script = session.create_script(read_frida_js_source("jni_addr.js"))
    script.on('message', on_message)
    script.load()

    script.exports.getjnimethodaddr(class_name)

    # 退出
    session.detach()


if __name__ == "__main__":
    main()

运行python脚本,执行结果如下

复制代码
methodName-> public final native java.lang.String com.cyrus.example.MainActivity.getNativeString()
Func.offset== libnative-lib.so 0x24F10
Func.getArtMethod->native_addr: 0X77518B6F10

具体原理可以参考这篇文章【使用 Frida 定位 JNI 方法内存地址

相关推荐
大树886 小时前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠6 小时前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质6 小时前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
Inhand陈工8 小时前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信
酣大智8 小时前
ARP代理--工作原理
运维·网络·arp·arp代理
weiggle8 小时前
第七篇:状态提升与单向数据流——架构设计的核心
android
shushangyun_8 小时前
2026年快消品B2B系统推荐:支持终端门店订货、促销政策自动化的工具?
java·运维·网络·数据库·人工智能·spring·自动化
xingpanvip8 小时前
星盘接口开发文档:本命盘接口指南
android·开发语言·css·php·lua
goldenrolan8 小时前
A公司物料替代测试系统 v1.7:从需求到 exe/apk 的 AI 辅助全链路实践
android·自动化测试·软件测试·python·ai
施努卡机器视觉9 小时前
SNK施努卡侧滑门锁上滑轮总成自动化装配线,从零件到组件,全流程精密制造方案
运维·自动化·制造