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

相关推荐
weixin_472339466 分钟前
使用Python提取PDF元数据的完整指南
java·python·pdf
PascalMing10 分钟前
Ruoyi多主键表的增删改查
java·若依ruoyi·多主键修改删除
橘子青衫16 分钟前
Java并发编程利器:CyclicBarrier与CountDownLatch解析
java·后端·性能优化
天天摸鱼的java工程师27 分钟前
高考放榜夜,系统别崩!聊聊查分系统怎么设计,三张表足以?
java·后端·mysql
天天摸鱼的java工程师35 分钟前
深入理解 Spring 核心:IOC 与 AOP 的原理与实践
java·后端
漫步者TZ36 分钟前
【Netty系列】解决TCP粘包和拆包:LengthFieldBasedFrameDecoder
java·网络协议·tcp/ip·netty
木木黄木木41 分钟前
Python制作史莱姆桌面宠物!可爱的
开发语言·python·宠物
愿你是阳光06071 小时前
Java-redis实现限时在线秒杀功能
java·redis·bootstrap
exploration-earth1 小时前
本地优先的状态管理与工具选型策略
开发语言·前端·javascript
我爱Jack1 小时前
Spring Boot统一功能处理深度解析
java·spring boot·后端