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

相关推荐
傻乐u兔10 小时前
C语言进阶————指针4
c语言·开发语言
大模型玩家七七10 小时前
基于语义切分 vs 基于结构切分的实际差异
java·开发语言·数据库·安全·batch
历程里程碑10 小时前
Linux22 文件系统
linux·运维·c语言·开发语言·数据结构·c++·算法
牛奔11 小时前
Go 如何避免频繁抢占?
开发语言·后端·golang
寻星探路15 小时前
【深度长文】万字攻克网络原理:从 HTTP 报文解构到 HTTPS 终极加密逻辑
java·开发语言·网络·python·http·ai·https
lly20240617 小时前
Bootstrap 警告框
开发语言
2601_9491465317 小时前
C语言语音通知接口接入教程:如何使用C语言直接调用语音预警API
c语言·开发语言
曹牧17 小时前
Spring Boot:如何测试Java Controller中的POST请求?
java·开发语言
KYGALYX17 小时前
服务异步通信
开发语言·后端·微服务·ruby