JVMTI
即 Java Virtual Machine Tool Interface(JVM工具接口), 是 JVM 的一个 native 接口: 一个用C或C++编写的库,在 JVM 的初始化过程中被加载。该库通过调用 JVMTI 和 JNI(Java Native Interface)来访问 JVM 状态,并可以使用事件处理函数注册接收 JVMTI 事件,当这些事件发生时,JVM会调用该函数。
利用 JVMTI 保护 Jar 包
截取 agentlib 的关键代码如下
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved)
{
jvmtiEnv *jvmti;
//Create the JVM TI environment(jvmti)
jint ret = vm->GetEnv((void **)&jvmti, JVMTI_VERSION);
if (JNI_OK != ret)
{
printf("ERROR: Unable to access JVMTI!\n");
return ret;
}
jvmtiCapabilities capabilities;
(void)memset(&capabilities, 0, sizeof(capabilities));
capabilities.can_generate_all_class_hook_events = 1;
capabilities.can_tag_objects = 1;
capabilities.can_generate_object_free_events = 1;
capabilities.can_get_source_file_name = 1;
capabilities.can_get_line_numbers = 1;
capabilities.can_generate_vm_object_alloc_events = 1;
jvmtiError error = jvmti->AddCapabilities(&capabilities);
if (JVMTI_ERROR_NONE != error)
{
printf("ERROR: Unable to AddCapabilities JVMTI!\n");
return error;
}
jvmtiEventCallbacks callbacks;
(void)memset(&callbacks, 0, sizeof(callbacks));
callbacks.ClassFileLoadHook = &ClassDecryptHook;
error = jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks));
if (JVMTI_ERROR_NONE != error) {
printf("ERROR: Unable to SetEventCallbacks JVMTI!\n");
return error;
}
error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, NULL);
if (JVMTI_ERROR_NONE != error) {
printf("ERROR: Unable to SetEventNotificationMode JVMTI!\n");
return error;
}
return JNI_OK;
}
总结一下, 就是在 Agent_OnLoad 函数中:
- 通过
AddCapabilities
注册所需的功能 - 使用
SetEventCallbacks
指定 ClassFileLoadHook 事件回调函数 - 调用
SetEventNotificationMode
开启事件通知
ClassFileLoadHook
事件结构
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)
每当 JVM 获得类文件数据, 但在构建该类的内存表示之前,会发送这个事件。其中参数 class_data
即 JVM 读入的加密过的类文件, 在此回调完成解密后, 将解密的类文件传回 new_class_data
类加载过程
简而言之,注册一个ClassFileLoadHook,传入被加密的 class 文件,并对其解密。
解密
此类加密最明显的特征是启动时会添加参数-agentlib:xxx.dll
,而xxx.dll
就是在运行时解密 class 的核心所在。
根据上述流程,解密的话直接调用 ClassFileLoadHook 中的解密函数即可
具体实现
由于我们只需要对类进行解密, 不必依赖于真正的JVM环境, 这里我选择手动创建一个JavaVM对象
JavaVM jvm = JavaVM();
并将其中的GetEnv
指向自己定义的函数myGetEnv
jvm.functions = (JNIInvokeInterface_*)malloc(sizeof(JNIInvokeInterface_));
memset(jvm.functions, 0, sizeof(JNIInvokeInterface_));
jvm.functions->GetEnv = myGetEnv;
在myGetEnv
中, 对需要用的函数进行模拟
jint JNICALL myGetEnv(JavaVM* vm, void** penv, jint version) {
cout << "Invoke getEnv, version:" << version << endl;;
jvmti.functions = (jvmtiInterface_1_*)malloc(sizeof(jvmtiInterface_1_));
memset((jvmtiInterface_1_*)jvmti.functions, 0, sizeof(jvmtiInterface_1_));
jvmti.functions->AddCapabilities = myAddCapabilities;
jvmti.functions->SetEventCallbacks = mySetEventCallbacks;
jvmti.functions->SetEventNotificationMode = mySetEventNotificationMode;
jvmti.functions->Allocate = myAllocate;
*penv = &jvmti;
return JNI_OK;
}
其中在mySetEventCallbacks
中即可获取解密函数的地址, 并进行解密
jvmtiError JNICALL mySetEventCallbacks(jvmtiEnv* env,
const jvmtiEventCallbacks* callbacks,
jint size_of_callbacks) {
cout << "Invoke SetEventCallbacks:" << callbacks << ", Get Decrypt Func Addr:" << callbacks->ClassFileLoadHook << endl;
// ...
return JVMTI_ERROR_NONE;
}
加载 dll 并手动调用Agent_OnLoad
HMODULE hdll = LoadLibraryExA("agent.dll", NULL, NULL);
// ...
pAgent_OnLoad fOnload = getOnloadAddr(hdll);
// ...
if (fOnload(&jvm, nullptr, nullptr) == JNI_OK)
cout << "Succes to exec onload Func" << endl;
else
cout << "An error occure when invoke onload Func" << endl;
解密工具(含源码)
Usage
> dejvmti.exe -h
Usage:
-h [ --help ] produce help message
-l [ --lib ] arg set agentlib path
-i [ --in ] arg set input path
-o [ --out ] arg (=decrypted.jar) set output path
> dejvmti -l jagent.dll -i test.jar
系统 | |
---|---|
Windows | 本地下载 |
Linux x64 | 本地下载 |