使用jvmti dll |so 加密java class jar包
使用到的的项目
需要加密的Jar,主要是里面的一个Test2.class需要加密
java-ext2-jar-0.0.1-SNAPSHOT.jar
需要使用加密jar的项目
java-jvmti-loader-0.0.1-SNAPSHOT.jar
调用DLL中的加密方法进行jar加密的项目
java-jni-call
dll代码
// dllmain.cpp : 定义 DLL 应用程序的入口点。 #include "pch.h" #include <iostream> #include <jni_md.h> #include <jni.h> #include <jvmti.h> #include <string.h> #include <stdio.h> #include "com_example_javajnicall_JniCall.h" using namespace std; void encode(char *str ,int len) { unsigned int m = strlen(str); printf("wait encode len %d arg len %d\n",m ,len); for (int i = 0; i < len; i++) { str[i] = str[i] + k; } /*for (int i = 0; i < m; i++) { str[i] = str[i] + k; }*/ } void decode(char *str,int len) { unsigned int m = strlen(str); for (int i = 0; i < len; i++) { str[i] = str[i] - k; } /*for (int i = 0; i < m; i++) { str[i] = str[i] - k; }*/ } extern"C" JNIEXPORT jbyteArray JNICALL Java_com_example_javajnicall_JniCall_encrypt(JNIEnv * env, jclass cla, jbyteArray text,jint len) { printf("call it!"); char* dst = (char*)env->GetByteArrayElements(text, 0); encode(dst, len); //env->SetByteArrayRegion(text, 0, strlen(dst), (jbyte *)dst); env->SetByteArrayRegion(text, 0, len, (jbyte *)dst); return text; } void JNICALL ClassDecryptHook( 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 ) { *new_class_data_len = class_data_len; jvmti_env->Allocate(class_data_len, new_class_data); unsigned char* _data = *new_class_data; // com/example/javajvmtiloader/scope/Test2 if (name && strncmp(name, "com/far/demo/Test2", 38) == 0 ) { printf("find it len:%d\n", class_data_len); for (int i = 0; i < class_data_len; i++){ _data[i] = class_data[i]; } decode((char*)_data, class_data_len); }else { for (int i = 0; i < class_data_len; i++){ _data[i] = class_data[i]; } } } JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM* vm, char* options, void* reserved) { return JNI_OK; } JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm) { } JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM * vm, char * options, void * reserved) { jvmtiEnv *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; } printf("jvmti oj8k\n"); return JNI_OK; }
com_example_javajnicall_JniCall.h
这个头文件是用javac生成的,实际上这个DLL可以分成两个,我偷懒搞一成一个了
一个用于加密(Java Jni调用),一个用于jvm加载时解密(java agent),对于给jvm加载的dll 包含解密方法即可
关于jni和javac 生成jni头文件可以看
https://blog.csdn.net/qq_40318498/article/details/98330665
调用dll中的加密方法的java项目
package com.example.javajnicall; import java.io.*; import java.lang.management.ManagementFactory; import java.lang.management.RuntimeMXBean; public class JniCall { static { String dllPath = "E:\\AAAA_CODE\\vsproject\\run-jar-app\\x64\\Debug\\run-jar-app.dll"; System.load(dllPath); } public static void init() { System.out.println("初始化"); } public static final int getProcessID() { RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean(); System.out.println(runtimeMXBean.getName()); return Integer.valueOf(runtimeMXBean.getName().split("@")[0]) .intValue(); } public static void enJava() throws IOException { BufferedReader br = null; String file = "E:\\AAAA_CODE\\new-eclipse-workspace\\demoall\\java-demo\\java-ext2-jar\\target\\classes\\com\\far\\demo\\Test2.class"; String fileEncode = "E:\\AAAA_CODE\\new-eclipse-workspace\\demoall\\java-demo\\java-ext2-jar\\target\\classes\\com\\far\\demo\\Test2-en.class"; try { FileInputStream fileInputStream = new FileInputStream(file); byte[] fInput = new byte[1]; int readf; ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); FileOutputStream outputStream = new FileOutputStream(fileEncode); while ((readf = fileInputStream.read(fInput)) != -1) { byteArrayOutputStream.write(fInput); } byte[] datas = byteArrayOutputStream.toByteArray(); datas = JniCall.encrypt(datas, datas.length); outputStream.write(datas); } catch (IOException e) { br.close(); e.printStackTrace(); } } public native static byte[] encrypt(byte[] text, int len); }
执行
java -agentpath:E:\AAAA_CODE\vsproject\run-jar-app\x64\Debug\run-jar-app.dll -cpjava-jvmti-loader-0.0.1-SNAPSHOT.jar com.example.javajvmtiloader.JavaJvmtiLoaderApplication
agentpath 需要完整的DLL路径
agenlib也可以加载DLL jvmti官方文档有介绍
坑点
Springboot的jar包格式导致不触发jvmti的fIleLoad事件
springboot项目jar包结构
正常的Jar包结构
要想触发jvmti中的fileLoad的事件,必须是正常的jar包格式
springboot是把用户写的类单独放到boot-inf目录下,springboot自己去加载,所以触发不了fileLoad事件!
通过maven将springboot打成普通的jar包
<build> <resources> <resource> <!--指定mapping下的所有xml文件打包在jar中--> <targetPath>${project.build.directory}/classes</targetPath> <directory>src/main/resources</directory> <filtering>true</filtering> <includes> <include>mapping/*.xml</include> </includes> </resource> <resource> <!--resources下一级的所有.xml .properties文件复制到config目录下--> <targetPath>${project.build.directory}/config</targetPath> <directory>src/main/resources</directory> <filtering>true</filtering> <includes> <include>**.xml</include> <include>**.yml</include> </includes> </resource> </resources> <plugins> <!--maven-dependency插件,将项目所有依赖包放到lib目录下--> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <executions> <execution> <id>copy-dependencies</id> <phase>package</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <type>jar</type> <includeTypes>jar</includeTypes> <outputDirectory> ${project.build.directory}/lib </outputDirectory> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <configuration> <classesDirectory>target/classes/</classesDirectory> <archive> <!--生成的jar中,不要包含pom.xml和pom.properties这两个文件--> <addMavenDescriptor>false</addMavenDescriptor> <manifest> <mainClass>com.example.javajvmtiloader.JavaJvmtiLoaderApplication</mainClass> <!-- 打包时 MANIFEST.MF文件不记录的时间戳版本 --> <useUniqueVersions>false</useUniqueVersions> <addClasspath>true</addClasspath> <classpathPrefix>lib/</classpathPrefix> </manifest> <manifestEntries> <!--jar中的MANIFEST.MF文件ClassPath需要添加config目录才能读取到配置文件--> <Class-Path>config/</Class-Path> </manifestEntries> </archive> </configuration> </plugin> </plugins> </build>
修改后
该打包方式会把一些依赖的jar包单独放到一个lib目录下,我们只需要把加密的jar放到lib目录下,jvm就会自动加载并触发jvmti的fileLoad事件了,注意,加密的jar 也是用的普通的jar包结构
win下编译的dll 不需要dllMain函数
开始一直以为需要dllmain函数,其实不需要!
想单独加密一个java文件,结果有两个class?
这是因为用了内部类或者lamda方法
找不到DLL
Could not find agent library run-jar-app.dll in absolute path, with error: Can't find dependent libraries
这个问题很诡异 在同事A,B电脑上 一个能跑 一个不能 能跑的同事的电脑上我做了以下操作
复制dll到c windows system32
regsvr32 DLL 完整路径 (但报错了)
之后莫名其妙可以运行了 (但是否和上面两个操作有关 还不确定...)
最后无奈甚至查看了DLL是否还依赖了其他什么DLL(用的一个DLL依赖查看工具) 结果还是没看出个所以然...
JAVA代码编译报错(用于调用DLL中的加密方法的项目java-jin-call)
报错 :程序包org.springframework.boot不存在
把下面的勾选起来
在Linux上编译
名词
AC :vs2017+win
BC:clion+win+linux虚拟机 交叉编译
为了能使用同一套代码,我最终分成AC和BC进行编译,实际cmake可以解决多端编译,配置好win下的clang或者msc编译器就行,但我本地的环境复杂会出问题,所以直接分为两个,so就用BC dll就用AC
Gcc版本 (高点也无所谓)
BC环境下编译
会在linux下生成临时的编译目录在tmp下,生成的SO文件也在里面 (注意:生成的文件在Linux 在linux)
另外会一直找不到jvmti相关的头文件,直接把linux上复制到项目内就行
改了下代码
//不同平台编译时,需要改此处 #define LINUX #ifdef LINUX #elif #include "pch.h" #include <jni_md.h> #include <jni.h> #include <jvmti.h> #endif #include <iostream> #include <linux/jni_md.h> #include <linux/jni.h> #include <linux/jvmti.h> #include <string.h> #include <stdio.h> #include "com_example_javajnicall_JniCall.h"
Cmake
cmake_minimum_required(VERSION 3.5.1) project(run_jar_app) set(CMAKE_CXX_STANDARD 14) include_directories(run-jar-app) include_directories(run-jar-app/Debug) include_directories(run-jar-app/x64) include_directories(run-jar-app/x64/Debug) include_directories(run-jar-app/x64/Release) if (CMAKE_SYSTEM_NAME MATCHES "linux") message("run linux plf....") else() include_directories(D:/AAAA_WORK/java/Java1.8.0-jdk-jre/jdk1.8.0_161/include/win32) include_directories(D:/AAAA_WORK/java/Java1.8.0-jdk-jre/jdk1.8.0_161/include) endif () SET(PROJECT_SRC run-jar-app/dllmain.cpp) ADD_LIBRARY(run-jar-app SHARED ${PROJECT_SRC})
调式
java新手自学群 626070845
java/springboot/hadoop/JVM 群 4915800
Hadoop/mongodb(搭建/开发/运维)Q群481975850
GOLang Q1群:6848027
GOLang Q2群:450509103
GOLang Q3群:436173132
GOLang Q4群:141984758
GOLang Q5群:215535604
C/C++/QT群 1414577
单片机嵌入式/电子电路入门群群 306312845
MUD/LIB/交流群 391486684
Electron/koa/Nodejs/express 214737701
大前端群vue/js/ts 165150391
操作系统研发群:15375777
汇编/辅助/破解新手群:755783453
大数据 elasticsearch 群 481975850
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。