排行榜 统计
  • 文章总数:1224 篇
  • 评论总数:5 条
  • 分类总数:8 个
  • 最后更新:13小时前

Java安全之JVMTI

本文阅读 6 分钟
首页 安全资讯 正文

图片

JVMTI 是什么

JVMTI(Java Virtual Machine Tool Interface)是 JVM 提供的一个编程接口,专供开发和监控工具使用。简单来说,它是Java虚拟机(JVM)提供的一套标准接口,开发者可以通过它来监控、调试和分析运行在 JVM 上的程序。该接口提供的功能覆盖多个方面,包括对类(Class)、线程(Thread)、堆(Heap)内存、监视器(Monitor)、栈帧(Frame)等的查询和操作。 这使得开发者能够在无需修改应用程序代码的情况下,监控和分析 Java 进程的运行状态。



图片

JVMTI 能做什么

JVMTI 事件


图片

jvm 提供一组事件,用来通知本机动态库文件 (.dll/.so) 当前 JVM 处于什么状态,并且可以通过 jvmti 提供 SetEventCallbacks 函数设置对应事件的回调函数,通过 SetEventNotificationMode 函数启动或禁用某个事件的通知。


事件简表


图片
图片


应用场景


图片
  • 调试工具: 设置断点、单步执行代码。

  • 性能监控工具: 查看内存使用、CPU 消耗,找出程序慢的原因。

  • 线程分析工具: 检查线程状态,发现死锁等问题。

  • 代码覆盖工具: 测试时看哪些代码行被执行了。

  • 字节码增强工具: 运行时修改 class 文件中 JVM 字节码指令。



图片

JVMTI 加载方式

启动加载:

在 Java 进程启动的时候通过 -agentpath:<pathname>=<options> 的方式启动,pathname 是对应的 jvmti 接口实现的本机动态库文件 (.dll/.so) 的绝对路径,后面可以追加 jvmti 程序需要的参数。 


附加加载:

使用代码附加加载jvmti实现的本机动态库文件(.dll/.so)到 JVM 进程中。



图片

JVMTI 初体验

目标


图片

通过附加 jvm,遍历当前 jvm 已加载的类签名。


代码实现


图片

CMakeLists.txt

cmake_minimum_required(VERSION 3.10)project(jvmtidemo)set(PRODUCT_NAME ${PROJECT_NAME})set(CMAKE_CXX_STANDARD 17)set(JAVA_HOME $ENV{JAVA_HOME})include_directories(${JAVA_HOME}/include)include_directories(${JAVA_HOME}/include/win32)link_directories(${JAVA_HOME}/lib)message(STATUS JAVA_HOME:${JAVA_HOME})aux_source_directory(src SRC_LIST)add_library(${PRODUCT_NAME} SHARED ${SRC_LIST})


jvmtidemo.cpp

#include <jvmti.h>#include <iostream>#include <filesystem>namespace fs = std::filesystem;JNIEXPORT jint printLoadedClasses(JavaVM* vm){  jvmtiEnv* jvmti;  jint result = vm->GetEnv((void**)&jvmti, JVMTI_VERSION_1_2);  if (result != JNI_OK)  {    std::cout << "Unable to access jvm env" << std::endl;    return result;  }  jclass* classes;  jint count;  result = jvmti->GetLoadedClasses(&count, &classes);  if (result != JNI_OK)  {    std::cout << "JVMTI GetLoadedClasses failed" << std::endl;    return result;  }  for (int i = 0; i < count; i++)  {    char* sig;    char* genericSig;    jvmti->GetClassSignature(classes[i], &sig, &genericSig);    std::cout << "class signature = " << sig << std::endl;  }  return 0;}JNIEXPORT jint JNICALLAgent_OnLoad(JavaVM* vm, char* options, void* reserved){  std::cout << "Agent Onload" << std::endl;  return JNI_OK;}JNIEXPORT jint JNICALLAgent_OnAttach(JavaVM* vm, char* options, void* reserved){  std::cout << "Agent OnAttach" << std::endl;  printLoadedClasses(vm);  return JNI_OK;}JNIEXPORT void JNICALLAgent_OnUnload(JavaVM* vm){  std::cout << "Agent OnUnload" << std::endl;}


AgentAttacher.java

import com.sun.tools.attach.VirtualMachine;public class AgentAttacher {    public static void main(String[] args) {        if(args.length != 2) {            System.out.println("Invalid Argument");            return;        }        String pid = args[0];        String agentPath = args[1];        attach(pid, agentPath, "");    }    public static void attach(String pid, String agentPath, String agentArgs) {        try {            VirtualMachine virtualMachine = VirtualMachine.attach(pid);            virtualMachine.loadAgentPath(agentPath, agentArgs);        } catch (Exception e) {            e.printStackTrace();        }    }}


实验


图片

编译

通过CMakeLists.txt和jvmtidemo.cpp编译出来jvmtidemo.dll代理库。

使用javac编译AgentAttacher.java。


加载

通过java AgentAttacher <pid> jvmtidemo.dll命令附加加载代理库到指定jvm进程中, 即可打印出已加载的类签名。



图片

基于字节码增强的 Class 文件保护方案

ClassFileLoadHook 回调声明



图片
void JNICALL ClassFileLoadHook(  jvmtiEnv* jvmti,  JNIEnv* jni,  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);


参数

  • jni_env: 类型为 JNIEnv *,处理事件线程的 JNI 执行环境

  • class_being_redefined: 类型为 jclass,重定义或重转换的类,若是新载入的类,则为 NULL

  • loader: 类型为 jobject,类载入器,若为 NULL,则为启动类载入器

  • name: 类型为 const char*,目标类在 JVM 内部的限定名,例如 java/util/List,使用自定义 UTF-8 编码

  • protection_domain: 类型为 jobject,载入类的保护域

  • class_data_len: 类型为 jint,当前类数据缓冲区的长度

  • class_data: 类型为 const unsigned char*,当前类数据缓冲区

  • new_class_data_len: 类型为 jint*,新的类数据缓冲区的长度

  • new_class_data: 类型为 unsigned char**,新的类数据缓冲区


核心思路


图片

1. 保护class文件

将Class文件中方法的字节码进行加密。


2. 运行时内存解密

通过编译 JVMTI Agent 动态库,

注册 JVMTI_EVENT_CLASS_FILE_LOAD_HOOK 事件回调。

在 ClassFileLoadHook 函数中对已加密的方法的字节进行解密。


安全风险


图片

Agent事件回调调用顺序

Agent解密动态库注册的 JVMTI_EVENT_CLASS_FILE_LOAD_HOOK 的事件回调必须要是最后一个调用,不然后面的事件回调传递的参数就是解密后的class 文件,造成安全隐患。需要注意的是 JVM 规范并没有明确指定多个代理之间的调用顺序,这通常取决于 JVM 实现和代理加载的顺序, 不过一般都是按注册顺序来调用的。


比较推荐的是 Virbox Protector 对于该方案的细节处理的比较好,具体可以参考官网:

https://h.virbox.com/vbp/docs/Java_Protector/Java_BCE_User_Guide/


实验


图片

市面上某个该方案的实现并没有处理好 Agent 事件回调调用顺序。所以我们可以做个简单实验。使用下面代码编译出来jvmtidemo.dll,执行命令

java -agentpath:jvmtidemo -javaagent:Test-encrypted.jar -jar Test-encrypted.jar 命令,可以在输出目录里看到解密后的 class 文件。


效果

Class解密前

图片

Class解密后

图片

代码

#include <jvmti.h>#include <iostream>#include <filesystem>namespace fs = std::filesystem;void save_class_file(const char* class_name, const jbyte* data, jint length){  char file_name[256];  snprintf(file_name, sizeof(file_name), "%s.class", class_name);  std::string filename = file_name;  std::replace(filename.begin(), filename.end(), '/''_');  std::string path = fs::path("./dump").append(filename).string();  FILE* fp = fopen(path.c_str(), "wb");  if (fp) {    fwrite(data, 1, length, fp);    fclose(fp);  }  else  {    std::printf("failed to save class: %s\n", class_name);  }}void JNICALL ClassFileLoadHook(  jvmtiEnv* jvmti,  JNIEnv* jni,  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) {  if (name && strstr(name, "Main"))  {    save_class_file(name, (jbyte*)class_data, class_data_len);  }}JNIEXPORT jint JNICALLAgent_OnLoad(JavaVM* vm, char* options, void* reserved){  std::cout << "Agent Onload" << std::endl;  jvmtiEnv* jvmti;  jvmtiCapabilities capabilities;  jvmtiEventCallbacks callbacks;  // 获取 JVMTI 环境  if (vm->GetEnv((void**)&jvmti, JVMTI_VERSION) != JNI_OK) {    return JNI_ERR;  }  // 启用类加载事件  memset(&capabilities, 0sizeof(capabilities));  capabilities.can_generate_all_class_hook_events = 1;  capabilities.can_retransform_any_class = 1;  capabilities.can_retransform_classes = 1;  jvmti->AddCapabilities(&capabilities);  // 设置回调函数  memset(&callbacks, 0sizeof(callbacks));  callbacks.ClassFileLoadHook = &ClassFileLoadHook;  jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks));  // 启用事件通知  jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, NULL);  return JNI_OK;}JNIEXPORT jint JNICALLAgent_OnAttach(JavaVM* vm, char* options, void* reserved){  std::cout << "Agent OnAttach" << std::endl;  return JNI_OK;}JNIEXPORT void JNICALLAgent_OnUnload(JavaVM* vm){  std::cout << "Agent OnUnload" << std::endl;}



END




👇扫码进群,一起畅聊技术,共享前沿资讯!

图片



👇点击关注,🌟星标深盾,了解更多精彩内容!














本文来自投稿,不代表本站立场,如若转载,请注明出处:https://typecho.firshare.cn/archives/6035.html
免责声明:文章内容不代表本站立场,本站不对其内容的真实性、完整性、准确性给予任何担保、暗示和承诺,仅供读者参考,文章版权归原作者所有。避免网络欺诈,本站不倡导任何交易行为。如您私自与本站转载自公开互联网中的资讯内容中提及到的个人或平台产生交易,则需自行承担后果。本站在注明来源的前提下推荐原文至此,仅作为优良公众、公开信息分享阅读,不进行商业发布、发表及从事营利性活动。如本文内容影响到您的合法权益(内容、图片等),请及时联系本站,我们会及时删除处理。
-- 展开阅读全文 --
每天走万步,冠心病有何益处?超导心磁图来揭秘
« 上一篇 06-23
大脑 “黑科技” 来袭!脑机接口的临床突破与脑磁图的技术革新
下一篇 » 06-23