怎么通过 jvmti 去 hook java 层函数
使用 JVMTI 手动实现 Android Java 函数 Hook
要通过 JVMTI 手动实现 Android Java 函数 Hook,需要编写 Native 层代码并注入到目标进程中。以下是详细步骤和示例:
一、核心实现原理
JVMTI 提供两种主要 Hook 方式:
- Method Entry/Exit 事件:监听方法调用前后的事件
- 字节码修改:在类加载时动态修改方法字节码
下面介绍如何通过 JVMTI Agent 实现这两种方式。
二、实现步骤
1. 创建 JVMTI Agent 项目
使用 Android NDK 创建一个 Native 库项目,包含以下文件:
CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1)add_library(jvmti_hookSHAREDjvmti_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 设备注入
-
将
.so
库推送到设备:adb push libs/armeabi-v7a/libjvmti_hook.so /data/local/tmp/
-
使用
ptrace
注入(需要 root 权限):# 使用 frida-gadget 或自定义 injector frida -U -f com.example.target -l /data/local/tmp/libjvmti_hook.so
方式二:非 Root 设备(需应用配合)
- 在应用代码中添加:
public class MainActivity extends AppCompatActivity {static {System.loadLibrary("jvmti_hook");}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 初始化 AgentinitJvmtiAgent();}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);
}
三、注意事项
-
JVMTI 版本兼容性
不同 Android 版本的 JVMTI 接口可能有差异,需要针对目标版本进行适配。 -
性能开销
频繁的事件监听和字节码修改会影响应用性能,生产环境慎用。 -
线程安全
JVMTI 回调可能在不同线程中执行,需要注意线程同步。 -
反调试措施
目标应用可能检测 JVMTI Agent 的存在,可以结合代码混淆和反检测技术。