You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Frida Stalker跟踪JNI程序时JVM崩溃及native.dll覆盖异常问题

Frida Stalker跟踪JNI Java程序的问题与解决方案

问题背景

  • 使用Frida Stalker跟踪调用JNI的极简Java程序时,JVM关闭阶段固定触发访问违例崩溃
  • 仅当Stalker.trustThreshold设置为-1时才能成功跟踪JVM进程,即便添加-Xint参数禁用JIT编译也不改变该现象
  • 若Stalker.trustThreshold设为非-1值,JVM不会发生崩溃,但Stalker完全采集不到自定义JNI库native.dll的基本块(BB)覆盖数据

复现代码与报错信息

1. Java主程序代码

// Main.java
public class Main {
    static {
        System.loadLibrary("native");
    }

    public static void main(String[] args) throws Exception {
        Thread.sleep(20_000); // 预留足够时间附加到进程
        new Main().sayHello();
    }

    private native void sayHello();
}

2. JNI Native层实现代码

// Main.cpp
#include "Main.h"
#include <iostream>
#include <fstream>
#include <windows.h>

void sayHello() {
    std::ofstream file;
    file.open("output.tmp");
    file << "Hello World\n";
    file.close();
    std::cout << "Current Thread id = " << GetCurrentThreadId() << "\n";
}

JNIEXPORT void JNICALL Java_Main_sayHello
(JNIEnv *, jobject) {
    sayHello();
}

3. Windows环境编译运行命令

# 编译native动态库(Windows环境)
cl.exe /LD /EHsc /I "%JAVA_HOME%\include" /I "%JAVA_HOME%\include\win32" Main.cpp /link /DLL /DEBUG /OUT:native.dll

# 编译Java字节码
javac Main.java

# 启动Java程序
java -cp . -Djava.library.path=%cd% Main

4. Frida跟踪脚本代码

# frida-agent.py
import sys
import frida

def on_message(message, data):
    print(str(message))

def main():
    device = frida.get_local_device()
    pid = -1
    for proc in device.enumerate_processes():
        if sys.argv[1] in [str(proc.pid), proc.name]:
            pid = proc.pid
            break
    session = device.attach(pid)
    print("Attaching to PID=" + str(pid))

    script = session.create_script(
        """
Stalker.trustThreshold = -1;
const moduleMap = new ModuleMap();
moduleMap.update();

const createCoverageMap = (events) => {
    moduleMap.update();
    const coverageMap = {};
    for (const event of events) {
        const [start, _] = event;
        const pStart = new NativePointer(start);
        const module = moduleMap.find(pStart);
        if (module) {
            const offset = pStart.sub(module.base).toInt32();
            if (!(module.path in coverageMap)) {
                coverageMap[module.path] = 0;
            }
            coverageMap[module.path] += 1;
        }
    }
    return coverageMap;
};

const stalkThread = (threadId) => {
    Stalker.follow(threadId, {
        events: {
            call: false,
            ret: false,
            exec: false,
            block: false,
            compile: true,
        },
        onReceive: function (events) {
            const bbEvents = Stalker.parse(events, {stringify: false, annotate: false});
            send({coverage: createCoverageMap(bbEvents)});
        }
    });
} 

const currentThreads = Process.enumerateThreads();
for (const thread of currentThreads) {
    console.log("Stalking thread: " + thread.id);
    stalkThread(thread.id);
}
"""
    )
    script.load()
    script.on("message", on_message)
    sys.stdin.read()
    sys.exit(0)

if __name__ == "__main__":
    main()

5. 运行报错信息

执行python frida-agent.py 8232附加目标进程后,程序输出如下崩溃日志:

Current Thread id = 9632
#
# Java运行时环境检测到致命错误:
#
#  EXCEPTION_ACCESS_VIOLATION (0xc0000005) 出错地址pc=0x00000287f70dae25, 进程pid=8232, 线程tid=0x00000000000025a0
#
# JRE版本: OpenJDK Runtime Environment (8.0_292-b10) (build 1.8.0_292-b10)
# Java虚拟机: OpenJDK 64-Bit Server VM (25.292-b10 mixed mode windows-amd64 compressed oops)
# 出错帧:
# C  0x00000287f70dae25
#
# 核心转储写入失败,Windows客户端版本默认不启用Minidump生成
#
# 详细错误报告已保存至:
# C:\workspace\simple-jni-app\hs_err_pid8232.log
#

解决方案

问题1:修复JVM关闭阶段的访问违例崩溃

崩溃核心原因:附加时无差别对所有JVM线程注入Stalker,JVM关闭时直接销毁线程,未给Stalker留出unfollow清理时机,线程销毁后残留的Stalker转译代码指针被访问就会触发0xc0000005访问违例。
修复操作:

  • 放弃全线程Stalk逻辑,仅跟踪会执行目标native方法的业务线程
  • 注册线程销毁、进程退出回调,回调中第一时间对对应线程执行Stalker.unfollow(threadId),等待Stalker完成代码块回收后再放行退出流程
  • 不要在JVM内部工作线程(GC线程、JIT线程、信号处理线程等)上开启Stalker,这类线程生命周期完全由JVM管控,退出逻辑不允许外部插桩打断,是崩溃高发点。

问题2:非-1 trustThreshold下采集不到native.dll基本块数据

现象核心原因:Stalker信任阈值机制默认会跳过识别为系统模块、JVM核心模块的代码插桩,而native.dll加载时机晚于Stalker初始化的模块扫描,加上JVM调用JNI方法是通过存放在可写内存的函数指针完成跳转,Stalker预扫描逻辑没有追踪到该跳转路径,直接将执行流判定为可信路径跳过插桩。即便加-Xint禁用JIT,JVM解释器、字节码分发逻辑依然是内存中动态生成的可执行代码,Stalker默认信任这类内存段,不会自动跟进跨模块的JNI跳转。
修复操作:

  • 不要在附加后立刻全量stalk线程,改为监听native.dll加载事件,模块加载完成后拿到JNI导出函数Java_Main_sayHello的地址,在该地址下执行断点,断点命中时再对当前线程调用Stalker.follow()
  • 配置Stalker.exclude()规则,显式排除Windows系统模块、JVM核心模块的跟踪范围,避免Stalker预扫描逻辑将跳转到native.dll的路径误判为可信系统路径直接跳过
  • Stalker.trustThreshold推荐设为2,同时显式标记native.dll的内存地址范围为插桩目标,强制Stalker对该模块内所有代码执行基本块插桩。

内容的提问来源于stack exchange,提问作者DEls

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 12:01:12