怎么通过 jvmti 去 hook java 层函数

使用 JVMTI 手动实现 Android Java 函数 Hook

要通过 JVMTI 手动实现 Android Java 函数 Hook,需要编写 Native 层代码并注入到目标进程中。以下是详细步骤和示例:

一、核心实现原理

JVMTI 提供两种主要 Hook 方式:

  1. Method Entry/Exit 事件:监听方法调用前后的事件
  2. 字节码修改:在类加载时动态修改方法字节码

下面介绍如何通过 JVMTI Agent 实现这两种方式。

二、实现步骤

1. 创建 JVMTI Agent 项目

使用 Android NDK 创建一个 Native 库项目,包含以下文件:

CMakeLists.txt

cmake 复制代码
cmake_minimum_required(VERSION 3.4.1)

add_library(
    jvmti_hook
    SHARED
    jvmti_hook.cpp
)

find_library(log-lib log)

target_link_libraries(
    jvmti_hook
    ${log-lib}
)

jvmti_hook.cpp

cpp 复制代码
#include <jvmti.h>
#include <string>
#include <unordered_map>
#include <android/log.h>

#define LOG_TAG "JVMTI_HOOK"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

// 方法ID到原始字节码的映射
std::unordered_map<jmethodID, unsigned char*> original_bytecode_map;

// 类文件加载钩子回调
void JNICALL ClassFileLoadHook(
    jvmtiEnv *jvmti_env,
    JNIEnv* jni_env,
    jclass class_being_redefined,
    jobject loader,
    const char* name,
    jobject protection_domain,
    jint class_data_len,
    const unsigned char* class_data,
    jint* new_class_data_len,
    unsigned char** new_class_data
) {
    // 过滤目标类
    std::string className(name);
    if (className.find("com/example/target/Class") != std::string::npos) {
        LOGD("Found target class: %s", name);
        
        // 这里可以使用 ASM 等库修改字节码
        // 简化示例:直接返回原始字节码
        *new_class_data_len = class_data_len;
        unsigned char* modified_data = new unsigned char[class_data_len];
        memcpy(modified_data, class_data, class_data_len);
        *new_class_data = modified_data;
        
        // 实际场景中,你需要:
        // 1. 解析 class_data (使用 ASM 或自定义解析器)
        // 2. 修改目标方法的字节码
        // 3. 返回修改后的字节码
    }
}

// 方法进入回调
void JNICALL MethodEntry(jvmtiEnv *jvmti_env, JNIEnv* jni_env, jthread thread, jmethodID method) {
    char* method_name = nullptr;
    char* class_signature = nullptr;
    char* method_signature = nullptr;
    
    // 获取方法信息
    jvmti_env->GetMethodName(method, &method_name, &method_signature, nullptr);
    
    jclass declaring_class;
    jvmti_env->GetMethodDeclaringClass(method, &declaring_class);
    jvmti_env->GetClassSignature(declaring_class, &class_signature, nullptr);
    
    // 过滤目标方法
    if (std::string(class_signature).find("Lcom/example/target/Class;") != std::string::npos &&
        std::string(method_name) == "targetMethod") {
        LOGD("Entering target method: %s%s", class_signature, method_name);
        
        // 这里可以获取和修改局部变量
        // jvmti_env->GetLocalObject(...)
        // jvmti_env->SetLocalObject(...)
    }
    
    // 释放资源
    if (method_name) jvmti_env->Deallocate((unsigned char*)method_name);
    if (class_signature) jvmti_env->Deallocate((unsigned char*)class_signature);
    if (method_signature) jvmti_env->Deallocate((unsigned char*)method_signature);
}

// Agent 初始化函数
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) {
    jvmtiEnv *jvmti_env;
    jint result = vm->GetEnv((void **)&jvmti_env, JVMTI_VERSION_1_2);
    if (result != JNI_OK) {
        LOGE("ERROR: GetEnv failed, error code: %d", result);
        return JNI_ERR;
    }
    
    // 设置 JVMTI 能力
    jvmtiCapabilities caps;
    memset(&caps, 0, sizeof(caps));
    caps.can_generate_method_entry_events = 1;
    caps.can_generate_all_class_hook_events = 1;
    caps.can_generate_class_load_hook_events = 1;
    caps.can_get_bytecodes = 1;
    caps.can_modify_classes = 1;
    
    if (jvmti_env->AddCapabilities(&caps) != JNI_OK) {
        LOGE("ERROR: AddCapabilities failed");
        return JNI_ERR;
    }
    
    // 设置回调函数
    jvmtiEventCallbacks callbacks;
    memset(&callbacks, 0, sizeof(callbacks));
    callbacks.ClassFileLoadHook = &ClassFileLoadHook;
    callbacks.MethodEntry = &MethodEntry;
    
    if (jvmti_env->SetEventCallbacks(&callbacks, sizeof(callbacks)) != JNI_OK) {
        LOGE("ERROR: SetEventCallbacks failed");
        return JNI_ERR;
    }
    
    // 启用事件
    jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, nullptr);
    jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_METHOD_ENTRY, nullptr);
    
    LOGD("JVMTI Agent loaded successfully");
    return JNI_OK;
}
2. 编译和打包

使用 NDK 编译生成 .so 库:

bash 复制代码
./gradlew assembleDebug
3. 注入 JVMTI Agent

有两种注入方式:

方式一:Root 设备注入
  1. .so 库推送到设备:

    bash 复制代码
    adb push libs/armeabi-v7a/libjvmti_hook.so /data/local/tmp/
  2. 使用 ptrace 注入(需要 root 权限):

    bash 复制代码
    # 使用 frida-gadget 或自定义 injector
    frida -U -f com.example.target -l /data/local/tmp/libjvmti_hook.so
方式二:非 Root 设备(需应用配合)
  1. 在应用代码中添加:

    java 复制代码
    public class MainActivity extends AppCompatActivity {
        static {
            System.loadLibrary("jvmti_hook");
        }
        
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            
            // 初始化 Agent
            initJvmtiAgent();
        }
        
        private native void initJvmtiAgent();
    }
4. 高级 Hook:替换方法实现

如果需要完全替换方法实现,可以结合 JNI 和 JVMTI:

cpp 复制代码
// 替换方法实现
void ReplaceMethodImplementation(jvmtiEnv* jvmti_env, JNIEnv* jni_env, jmethodID target_method) {
    // 创建一个新的本地方法
    jclass target_class;
    jvmti_env->GetMethodDeclaringClass(target_method, &target_class);
    
    // 注册新的本地方法
    JNINativeMethod native_method;
    native_method.name = "targetMethod"; // 目标方法名
    native_method.signature = "(Ljava/lang/String;I)V"; // 方法签名
    native_method.fnPtr = &NewMethodImplementation; // 新的方法实现
    
    jni_env->RegisterNatives(target_class, &native_method, 1);
    
    // 使类重新定义
    jvmtiClassDefinition class_def;
    class_def.class_ = target_class;
    class_def.class_byte_count = 0;
    class_def.class_bytes = nullptr;
    
    jvmti_env->RedefineClasses(1, &class_def);
}

// 新的方法实现
JNIEXPORT void JNICALL NewMethodImplementation(JNIEnv* env, jobject obj, jstring str, jint num) {
    LOGD("Hooked method called with params: %s, %d", 
        env->GetStringUTFChars(str, nullptr), num);
    
    // 执行自定义逻辑
    // ...
    
    // 可以选择调用原始方法
    // CallOriginalMethod(env, obj, str, num);
}

三、注意事项

  1. JVMTI 版本兼容性

    不同 Android 版本的 JVMTI 接口可能有差异,需要针对目标版本进行适配。

  2. 性能开销

    频繁的事件监听和字节码修改会影响应用性能,生产环境慎用。

  3. 线程安全

    JVMTI 回调可能在不同线程中执行,需要注意线程同步。

  4. 反调试措施

    目标应用可能检测 JVMTI Agent 的存在,可以结合代码混淆和反检测技术。

相关推荐
wjs202438 分钟前
状态模式(State Pattern)
开发语言
我命由我1234542 分钟前
Kotlin 数据容器 - List(List 概述、创建 List、List 核心特性、List 元素访问、List 遍历)
java·开发语言·jvm·windows·java-ee·kotlin·list
liulilittle43 分钟前
C++ TAP(基于任务的异步编程模式)
服务器·开发语言·网络·c++·分布式·任务·tap
励志要当大牛的小白菜2 小时前
ART配对软件使用
开发语言·c++·qt·算法
武子康3 小时前
Java-80 深入浅出 RPC Dubbo 动态服务降级:从雪崩防护到配置中心秒级生效
java·分布式·后端·spring·微服务·rpc·dubbo
爱装代码的小瓶子5 小时前
数据结构之队列(C语言)
c语言·开发语言·数据结构
YuTaoShao5 小时前
【LeetCode 热题 100】131. 分割回文串——回溯
java·算法·leetcode·深度优先
源码_V_saaskw6 小时前
JAVA图文短视频交友+自营商城系统源码支持小程序+Android+IOS+H5
java·微信小程序·小程序·uni-app·音视频·交友
Maybe_ch6 小时前
.NET-键控服务依赖注入
开发语言·c#·.net
超浪的晨6 小时前
Java UDP 通信详解:从基础到实战,彻底掌握无连接网络编程
java·开发语言·后端·学习·个人开发