使用 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
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 设备注入
-
将
.so
库推送到设备:bashadb push libs/armeabi-v7a/libjvmti_hook.so /data/local/tmp/
-
使用
ptrace
注入(需要 root 权限):bash# 使用 frida-gadget 或自定义 injector frida -U -f com.example.target -l /data/local/tmp/libjvmti_hook.so
方式二:非 Root 设备(需应用配合)
-
在应用代码中添加:
javapublic 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);
}
三、注意事项
-
JVMTI 版本兼容性
不同 Android 版本的 JVMTI 接口可能有差异,需要针对目标版本进行适配。
-
性能开销
频繁的事件监听和字节码修改会影响应用性能,生产环境慎用。
-
线程安全
JVMTI 回调可能在不同线程中执行,需要注意线程同步。
-
反调试措施
目标应用可能检测 JVMTI Agent 的存在,可以结合代码混淆和反检测技术。