怎么通过 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 的存在,可以结合代码混淆和反检测技术。

相关推荐
坊钰1 天前
【Rabbit MQ】Rabbit MQ 介绍
java·rabbitmq
一切尽在,你来1 天前
C++多线程教程-1.2.2 C++标准库并发组件的设计理念
开发语言·c++
雀啼春1 天前
Java中的数据类型
java
80530单词突击赢1 天前
C++关联容器深度解析:set/map全攻略
java·数据结构·算法
m0_561359671 天前
代码热更新技术
开发语言·c++·算法
兩尛1 天前
c++知识点1
java·开发语言·c++
凯子坚持 c1 天前
Qt常用控件指南(9)
开发语言·qt
ONE_PUNCH_Ge1 天前
Go 语言泛型
开发语言·后端·golang
舟舟亢亢1 天前
JVM复习笔记——下
java·jvm·笔记