JVM层对jar包字节码加密
来源:互联网 发布:为什么监控无网络视频 编辑:程序博客网 时间:2024/04/29 03:22
github
https://github.com/sea-boat/ByteCodeEncrypt
需求
拿到的需求是要对某特定的jar包实现加密保护,jar包需要提供给外部使用,但核心逻辑部分需要保护以免被简单反编译即能看到。
几个思路
大致想到以下几种方式:
1. 混淆器,将jar包混淆后反编译出来的东西看起来就很眼花,但如果耐心一点也是可以看出来的。
2. 对jar包进行加密,然后在Java层重写类加载器对其进行解密,以达到对jar包的加密保护。包括用对称加密算法和非对称加密算法。不管用什么算法,在Java层面的类加载器实现的话,其实也作用不大,因为类加载器本身被反编译出来后就基本暴露无遗了。
3. 可以修改java编译后的class文件的某些属性,以让反编译软件分析不了,但它也不可靠,只要按照class格式深入分析下也能反编译出来。
4. 修改JDK源码,定制JDK就涉及到JVM的整体改动,而且还要求外部使用,不太可行。
5. 利用JDK中JVM的某些类似钩子机制和事件监听机制,监听加载class事件,使用本地方式完成class的解密。C/C++被编译后想要反编译就很麻烦了,另外还能加壳。这里就看看这种方式。
关于JVMTI
JVMTI即JVM Tool Interface,提供了本地编程接口,主要是提供了调试和分析等接口。JVMTI非常强大,通过它能做很多事,比如可以监听某事件、线程分析等等。
那么一般怎么使用JVMTI?一般使用Agent方式来使用,就是通过-agentlib
和-agentpath
指定Agent的本地库,然后Java启动时就会加载该动态库。这个时刻其实可以看成是JVM启动的时刻,而并非是Java层程序启动时刻,所以此时还不涉及与Java相关的类和对象什么的。
agent动态库被加载后,JVM肯定会指定一个入口函数,该入口函数为:
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm,char *options,void *reserved)
于是在JVM启动前要做的事都可以放到这个函数中,比如设置jvmtiCapabilities、设置关注的事件、设置事件的回调函数等等。其中关键的jvmtiCapabilities
、jvmtiEventCallbacks
、jvmtiEvent
三个结构体如下,根据实际情况设置。
typedef struct { unsigned int can_tag_objects : 1; unsigned int can_generate_field_modification_events : 1; unsigned int can_generate_field_access_events : 1; unsigned int can_get_bytecodes : 1; unsigned int can_get_synthetic_attribute : 1; unsigned int can_get_owned_monitor_info : 1; unsigned int can_get_current_contended_monitor : 1; unsigned int can_get_monitor_info : 1; unsigned int can_pop_frame : 1; unsigned int can_redefine_classes : 1; unsigned int can_signal_thread : 1; unsigned int can_get_source_file_name : 1; unsigned int can_get_line_numbers : 1; unsigned int can_get_source_debug_extension : 1; unsigned int can_access_local_variables : 1; unsigned int can_maintain_original_method_order : 1; unsigned int can_generate_single_step_events : 1; unsigned int can_generate_exception_events : 1; unsigned int can_generate_frame_pop_events : 1; unsigned int can_generate_breakpoint_events : 1; unsigned int can_suspend : 1; unsigned int can_redefine_any_class : 1; unsigned int can_get_current_thread_cpu_time : 1; unsigned int can_get_thread_cpu_time : 1; unsigned int can_generate_method_entry_events : 1; unsigned int can_generate_method_exit_events : 1; unsigned int can_generate_all_class_hook_events : 1; unsigned int can_generate_compiled_method_load_events : 1; unsigned int can_generate_monitor_events : 1; unsigned int can_generate_vm_object_alloc_events : 1; unsigned int can_generate_native_method_bind_events : 1; unsigned int can_generate_garbage_collection_events : 1; unsigned int can_generate_object_free_events : 1; unsigned int can_force_early_return : 1; unsigned int can_get_owned_monitor_stack_depth_info : 1; unsigned int can_get_constant_pool : 1; unsigned int can_set_native_method_prefix : 1; unsigned int can_retransform_classes : 1; unsigned int can_retransform_any_class : 1; unsigned int can_generate_resource_exhaustion_heap_events : 1; unsigned int can_generate_resource_exhaustion_threads_events : 1; unsigned int : 7; unsigned int : 16; unsigned int : 16; unsigned int : 16; unsigned int : 16; unsigned int : 16;} jvmtiCapabilities;
typedef struct { /* 50 : VM Initialization Event */ jvmtiEventVMInit VMInit; /* 51 : VM Death Event */ jvmtiEventVMDeath VMDeath; /* 52 : Thread Start */ jvmtiEventThreadStart ThreadStart; /* 53 : Thread End */ jvmtiEventThreadEnd ThreadEnd; /* 54 : Class File Load Hook */ jvmtiEventClassFileLoadHook ClassFileLoadHook; /* 55 : Class Load */ jvmtiEventClassLoad ClassLoad; /* 56 : Class Prepare */ jvmtiEventClassPrepare ClassPrepare; /* 57 : VM Start Event */ jvmtiEventVMStart VMStart; /* 58 : Exception */ jvmtiEventException Exception; /* 59 : Exception Catch */ jvmtiEventExceptionCatch ExceptionCatch; /* 60 : Single Step */ jvmtiEventSingleStep SingleStep; /* 61 : Frame Pop */ jvmtiEventFramePop FramePop; /* 62 : Breakpoint */ jvmtiEventBreakpoint Breakpoint; /* 63 : Field Access */ jvmtiEventFieldAccess FieldAccess; /* 64 : Field Modification */ jvmtiEventFieldModification FieldModification; /* 65 : Method Entry */ jvmtiEventMethodEntry MethodEntry; /* 66 : Method Exit */ jvmtiEventMethodExit MethodExit; /* 67 : Native Method Bind */ jvmtiEventNativeMethodBind NativeMethodBind; /* 68 : Compiled Method Load */ jvmtiEventCompiledMethodLoad CompiledMethodLoad; /* 69 : Compiled Method Unload */ jvmtiEventCompiledMethodUnload CompiledMethodUnload; /* 70 : Dynamic Code Generated */ jvmtiEventDynamicCodeGenerated DynamicCodeGenerated; /* 71 : Data Dump Request */ jvmtiEventDataDumpRequest DataDumpRequest; /* 72 */ jvmtiEventReserved reserved72; /* 73 : Monitor Wait */ jvmtiEventMonitorWait MonitorWait; /* 74 : Monitor Waited */ jvmtiEventMonitorWaited MonitorWaited; /* 75 : Monitor Contended Enter */ jvmtiEventMonitorContendedEnter MonitorContendedEnter; /* 76 : Monitor Contended Entered */ jvmtiEventMonitorContendedEntered MonitorContendedEntered; /* 77 */ jvmtiEventReserved reserved77; /* 78 */ jvmtiEventReserved reserved78; /* 79 */ jvmtiEventReserved reserved79; /* 80 : Resource Exhausted */ jvmtiEventResourceExhausted ResourceExhausted; /* 81 : Garbage Collection Start */ jvmtiEventGarbageCollectionStart GarbageCollectionStart; /* 82 : Garbage Collection Finish */ jvmtiEventGarbageCollectionFinish GarbageCollectionFinish; /* 83 : Object Free */ jvmtiEventObjectFree ObjectFree; /* 84 : VM Object Allocation */ jvmtiEventVMObjectAlloc VMObjectAlloc;} jvmtiEventCallbacks;
typedef enum { JVMTI_MIN_EVENT_TYPE_VAL = 50, JVMTI_EVENT_VM_INIT = 50, JVMTI_EVENT_VM_DEATH = 51, JVMTI_EVENT_THREAD_START = 52, JVMTI_EVENT_THREAD_END = 53, JVMTI_EVENT_CLASS_FILE_LOAD_HOOK = 54, JVMTI_EVENT_CLASS_LOAD = 55, JVMTI_EVENT_CLASS_PREPARE = 56, JVMTI_EVENT_VM_START = 57, JVMTI_EVENT_EXCEPTION = 58, JVMTI_EVENT_EXCEPTION_CATCH = 59, JVMTI_EVENT_SINGLE_STEP = 60, JVMTI_EVENT_FRAME_POP = 61, JVMTI_EVENT_BREAKPOINT = 62, JVMTI_EVENT_FIELD_ACCESS = 63, JVMTI_EVENT_FIELD_MODIFICATION = 64, JVMTI_EVENT_METHOD_ENTRY = 65, JVMTI_EVENT_METHOD_EXIT = 66, JVMTI_EVENT_NATIVE_METHOD_BIND = 67, JVMTI_EVENT_COMPILED_METHOD_LOAD = 68, JVMTI_EVENT_COMPILED_METHOD_UNLOAD = 69, JVMTI_EVENT_DYNAMIC_CODE_GENERATED = 70, JVMTI_EVENT_DATA_DUMP_REQUEST = 71, JVMTI_EVENT_MONITOR_WAIT = 73, JVMTI_EVENT_MONITOR_WAITED = 74, JVMTI_EVENT_MONITOR_CONTENDED_ENTER = 75, JVMTI_EVENT_MONITOR_CONTENDED_ENTERED = 76, JVMTI_EVENT_RESOURCE_EXHAUSTED = 80, JVMTI_EVENT_GARBAGE_COLLECTION_START = 81, JVMTI_EVENT_GARBAGE_COLLECTION_FINISH = 82, JVMTI_EVENT_OBJECT_FREE = 83, JVMTI_EVENT_VM_OBJECT_ALLOC = 84, JVMTI_MAX_EVENT_TYPE_VAL = 84} jvmtiEvent;
具体实现
- 编写我们的agent动态库,使之在JVM加载时完成一些逻辑,从前面也知道,主要就是在
Agent_OnLoad
函数中编写逻辑,先获取jvmtiEnv,在通过它设置jvmtiCapabilities,完了再设置回调函数及需要监听的事件。这里关注的事class文件加载时事件。
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; } return JNI_OK;}
- 回调函数,即是上面指定的回调函数,它对应监听的事件,会在对应的事件发生事被调用。这里处理逻辑其实就是判断如果是某包下的类就对其进行解密,否则不处理。
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; if (name&&strncmp(name, "com/seaboat/", 11) == 0) { for (int i = 0; i < class_data_len; i++) { _data[i] = class_data[i] - 4; } } else { for (int i = 0; i < class_data_len; ++i) { _data[i] = class_data[i]; } }}
- 额外写一个加密程序对某jar包加密进行加密处理,这里同样用本地库方式,但加密解密动态库不要一起对外发布,还有Java层调用本地库加密的程序也不要对外发布。
Java层
public class ByteCodeEncryptor { static{ System.loadLibrary("ByteCodeEncryptor"); } public native static byte[] encrypt(byte[] text); public static void(String[] args){ try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buf = new byte[1024]; File srcFile = new File(fileName); File dstFile = new File(fileName.substring(0, fileName.indexOf("."))+"_encrypted.jar"); FileOutputStream dstFos = new FileOutputStream(dstFile); JarOutputStream dstJar = new JarOutputStream(dstFos); JarFile srcJar = new JarFile(srcFile); for (Enumeration<JarEntry> enumeration = srcJar.entries(); enumeration.hasMoreElements();) { JarEntry entry = enumeration.nextElement(); InputStream is = srcJar.getInputStream(entry); int len; while ((len = is.read(buf, 0, buf.length)) != -1) { baos.write(buf, 0, len); } byte[] bytes = baos.toByteArray(); String name = entry.getName(); if(name.endsWith(".class")){ try { bytes = ByteCodeEncryptor.encrypt(bytes); } catch (Exception e) { e.printStackTrace(); } } JarEntry ne = new JarEntry(name); dstJar.putNextEntry(ne); dstJar.write(bytes); baos.reset(); } srcJar.close(); dstJar.close(); dstFos.close(); } catch (Exception e) { e.printStackTrace(); } }}
本地库
void encode(char *str){ unsigned int m = strlen(str); for (int i = 0; i < m; i++) { str[i] = str[i]+4; }}extern"C" JNIEXPORT jbyteArray JNICALLJava_com_seaboat_bytecode_ByteCodeEncryptor_encrypt(JNIEnv * env, jclass cla,jbyteArray text){ char* dst = (char*)env->GetByteArrayElements(text, 0); encode(dst); env->SetByteArrayRegion(text, 0, strlen(dst), (jbyte *)dst); return text;}
- 通过agentlib参数启动Java,实现字节码解密,从而实现字节码保护。
java -agentlib:xxxxx\ByteCodeEncryptor -cp test_encrypted.jar com.seaboat.AA
反编译前后效果
可能报错
下面的错误说明编译的是32位的动态库,不能再64位操作系统运行,可以到vs的vc目录下执行 C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC>vcvarsall.bat amd64
,重新编译64位的动态库即可。
Error occurred during initialization of VMCould not find agent library D:\kuaipan\workspace\CPP-workspace\Project3\ByteCodeEncryptor on the library path, with error: Can't load IA 32-bit .dll on a AMD 64-bit platform
========广告时间========
鄙人的新书《Tomcat内核设计剖析》已经在京东销售了,有需要的朋友可以到 https://item.jd.com/12185360.html 进行预定。感谢各位朋友。
为什么写《Tomcat内核设计剖析》
=========================
欢迎关注:
- JVM层对jar包字节码加密
- ProGuard对java jar包实现混淆加密
- ProGuard对java jar包实现混淆加密
- 对字节码文件的抑或加密
- 对 Lua 字节码进行加密
- java jar包加密
- Java代码加密与反编译(一):利用混淆器工具proGuard对jar包加密
- Java代码加密与反编译(一):利用混淆器工具proGuard对jar包加密
- Java代码加密与反编译(一):利用混淆器工具proGuard对jar包加密
- JVM虚拟机中对字节码优化的策略
- java之jvm学习笔记八(实践对jar包的代码签名)
- java之jvm学习笔记八(实践对jar包的代码签名)
- java之jvm学习笔记八(实践对jar包的代码签名)
- 使用proguard对jar包做混淆加密遇到的问题及解决办法
- JVM字节码初探
- JVM字节码
- jvm字节码详解
- JVM字节码
- CodeForces 406 D.Hill Climbing (凸包-Graham扫描法+在线倍增LCA)
- 如何理解与有效避免安卓加载Bitmap造成的OOM异常
- 象棋(Xiangqi, uva1589)
- 【PAT】【Advanced Level】1025. PAT Ranking (25)
- java算法学习笔记--排序算法篇(上)
- JVM层对jar包字节码加密
- ioctl()函数详解
- Java8系列--Java Stream入门篇(流的操作)
- 用链表实现二叉树的实现(递归)
- LeetCode
- Centos7系统下安装opencv2.4.9+ffmpeg读取视频文件
- jsDOM增删改查操作
- 刚哥遇到了感情问题(二)
- OSD原理介绍