怎么通过 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_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

#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 库:

./gradlew assembleDebug
3. 注入 JVMTI Agent

有两种注入方式:

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

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

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

// 替换方法实现
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 的存在,可以结合代码混淆和反检测技术。

<think>好的,我现在需要帮助用户解决在JNI原生开发hook ClassLoader以输出加载的类信息的问题。首先,我得回忆一下相关的知识。用户提到的是JNI原生开发,也就是使用C/C++面的代码来hook,而不是Java的反射或动态代理。所以,我需要考虑如何通过JNI和C的代码来实现对ClassLoader的监控。 首先,Hook在JNI中的实现通常涉及到修改内存中的函数指针或者使用inline hooking技术。这可能涉及到对目标函数的内存地址进行覆盖,替换为自定义的函数,从而在调用时执行用户的代码。但具体到ClassLoader的hook,可能需要定位到加载类的具体方法,比如ClassLoader的loadClass方法。 接下来,我需要考虑Android中的ClassLoader结构。在Android中,类的加载通常通过DexClassLoader或PathClassLoader,这些都是ClassLoader的子类。每个ClassLoader实例都有一个loadClass方法,当类被加载时,这个方法会被调用。因此,如果能hook这个loadClass方法,就能在类加载时输出类名。 但是,JNI如何hook Java方法呢?通常,这需要用到Java的JNI接口,比如通过JNI的RegisterNatives方法替换方法的实现。不过,ClassLoader的loadClass方法可能不是native方法,因此不能直接使用RegisterNatives。这时候可能需要使用动态字节码操作或者inline hooking技术。 另一种方法是使用JVMTI(JVM Tool Interface),它提供了更底的监控和操作JVM的能力。不过,在Android中,尤其是较新版本,可能不支持JVMTI,或者需要特定的环境配置。但用户的问题是关于JNI原生开发,可能更倾向于使用NDK和JNI技术。 或者,可以考虑在JNI找到ClassLoader对象的loadClass方法,然后替换它的实现。这可能需要获取该方法的jmethodID,然后用一个新的本地方法替换它。不过,Java不允许直接替换非native方法的实现,因此可能需要更底的操作,如修改方法指针。 另外,参考用户提供的引用[4]中的例子,使用了Frida的Java.perform来hook方法。但用户希望的是在JNI原生开发中实现,而不是使用Frida这样的工具。因此,我需要考虑如何在C实现类似的hook。 可能的步骤包括: 1. 获取目标ClassLoader的loadClass方法的jmethodID。 2. 使用JNI的SetMethodImplementation或类似的函数替换方法。但标准JNI可能不支持替换非native方法,因此需要更底方法。 3. 使用inline hooking库,如Substrate或Dobby,在nativehook对应的ART或Dalvik虚拟机函数。例如,在Android ART中,每个Java方法在运行时都有对应的本地代码,通过替换这段代码的指针,可以插入自定义逻辑。 具体来说,ART虚拟机中,每个方法在第一次执行时会被编译或解释执行。方法的结构体中包含指向其代码的指针。通过修改这个指针,可以跳转到自定义的代码,执行日志输出后再跳回原方法。 因此,实现步骤可能如下: - 找到ClassLoader的loadClass方法在内存中的地址。 - 修改该方法的入口代码,使其跳转到自定义的hook函数。 - 在hook函数中,获取类名参数,打印日志,然后继续执行原方法。 这需要深入理解ART虚拟机的内部结构和方法调用机制,以及如何在native进行hook操作。同时,需要注意不同Android版本之间的差异,因为ART的实现可能会有变化。 此外,还需要考虑如何获取类名。在loadClass方法中,参数是类名的字符串,因此在hook函数中需要获取这个字符串参数,并将其输出到日志中。 可能遇到的问题包括: - 如何准确定位loadClass方法在内存中的地址,尤其是在不同的设备和系统版本上。 - 如何编写稳定的hook代码,避免引起虚拟机崩溃。 - 如何处理多线程环境下的hook,确保线程安全。 另外,用户提供的引用[3]中提到了hook应用中的PathClassLoader,替换为自己的ClassLoader。这可能涉及到替换ClassLoader实例,但用户的需求是输出加载的类信息,而不是替换ClassLoader。不过,引用中的方法可能提供一些思路,比如如何通过JNI访问和修改ClassLoader的实例。 总结下来,可能的解决方案是在native使用inline hooking技术,替换ClassLoader.loadClass方法的实现,插入日志输出逻辑。这需要熟悉Android虚拟机的内部机制和native hooking技术。</think>在JNI原生开发Hook ClassLoader输出加载类信息,可通过以下步骤实现: ### 实现步骤 1. **定位目标方法** 需Hook `java.lang.ClassLoader`的`loadClass`方法,其签名: $$(Ljava/lang/String;Z)Ljava/lang/Class;$$ 通过JNI获取其`jmethodID`: ```cpp jclass classLoaderClass = env->FindClass("java/lang/ClassLoader"); jmethodID loadClassMethod = env->GetMethodID(classLoaderClass, "loadClass", "(Ljava/lang/String;Z)Ljava/lang/Class;"); ``` 2. **使用Inline Hook技术** 通过修改ART/Dalvik虚拟机的方法入口代码实现Hook。例如使用开源库[Android Inline Hook](https://github.com/ele7enxxh/Android-Inline-Hook): ```cpp void* target_addr = (void*)env->GetMethodID(...); // 获取目标方法地址 void* new_function = (void*)my_loadClass_hook; // 创建Hook if (registerInlineHook((uint32_t)target_addr, (uint32_t)new_function, (uint32_t**)&orig_loadClass) != 0) { LOG_ERROR("Hook failed"); } ``` 3. **编写Hook处理函数** ```cpp void my_loadClass_hook(JNIEnv* env, jobject loader, jstring className, jboolean resolve) { const char* name = env->GetStringUTFChars(className, nullptr); __android_log_print(ANDROID_LOG_DEBUG, "ClassLoader", "Loading class: %s", name); env->ReleaseStringUTFChars(className, name); // 调用原始方法 return orig_loadClass(env, loader, className, resolve); } ``` 4. **兼容性处理** - 需适配不同Android版本的ART内部结构 - 使用`dlopen`动态加载`libart.so` - 通过符号查找获取关键函数地址[^4] ### 关键注意事项 1. **线程同步** Hook操作需在`JNI_OnLoad`阶段完成,确保线程安全[^3] 2. **日志过滤** 添加包名前缀过滤避免日志爆炸: ```cpp if (strstr(name, "com.target.package") != NULL) { // 输出日志 } ``` 3. **性能影响** 建议通过`__builtin_return_address`判断调用栈,仅监控必要类加载 ### 完整示例代码 ```cpp #include <jni.h> #include <android/log.h> typedef jobject (*orig_loadClass_type)(JNIEnv*, jobject, jstring, jboolean); orig_loadClass_type orig_loadClass; jobject my_loadClass(JNIEnv* env, jobject loader, jstring className, jboolean resolve) { const char* name = env->GetStringUTFChars(className, nullptr); __android_log_print(ANDROID_LOG_VERBOSE, "ClassLoader", "Loading: %s", name); env->ReleaseStringUTFChars(className, name); return orig_loadClass(env, loader, className, resolve); } JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env; vm->GetEnv((void**)&env, JNI_VERSION_1_6); // 获取方法地址并Hook jclass clazz = env->FindClass("java/lang/ClassLoader"); jmethodID method = env->GetMethodID(clazz, "loadClass", "(Ljava/lang/String;Z)Ljava/lang/Class;"); // 使用inline hook库实现函数替换 if (hook_function(env->GetMethodID(method), (void*)my_loadClass, (void**)&orig_loadClass)) { return JNI_ERR; } return JNI_VERSION_1_6; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值