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、设置关注的事件、设置事件的回调函数等等。其中关键的jvmtiCapabilitiesjvmtiEventCallbacksjvmtiEvent三个结构体如下,根据实际情况设置。

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;

具体实现

  1. 编写我们的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;
}
  1. 回调函数,即是上面指定的回调函数,它对应监听的事件,会在对应的事件发生事被调用。这里处理逻辑其实就是判断如果是某包下的类就对其进行解密,否则不处理。
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];
        }
    }
}
  1. 额外写一个加密程序对某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 JNICALL
Java_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;
}
  1. 通过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 VM
Could 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包字节码加密的更多相关文章

  1. 生成Jar包 源码Jar包-字节码Jar包 不可运行Jar包-可运行Jar包

  2. JVM学习笔记——类加载和字节码技术篇

    JVM学习笔记--类加载和字节码技术篇 在本系列内容中我们会对JVM做一个系统的学习,本片将会介绍JVM的类加载和字节码技术部分 我们会分为以下几部分进行介绍: 类文件结构 字节码指令 编译期处理 类 ...

  3. spring各版本jar包和源码

    spring各版本jar包和源码 spring历史版本源码:https://github.com/spring-projects/spring-framework/tags spring历史jar包和 ...

  4. Jmeter用BeanShell Sampler调用java写的jar包进行MD5加密

    [前言] 在工作中,有时候我们请求的参数可能需要加密,比如登录接口中的密码做了加密操作,今天我就给大家介绍一种方法:Jmeter用BeanShell Sampler调用java写的jar包进行MD5加 ...

  5. JVM(三):深入分析Java字节码-上

    JVM(三):深入分析Java字节码-上 字节码文章分为上下两篇,上篇也就是本文主要讲述class文件存在的意义,以及其带来的益处.并分析其内在构成之一 ---字节码,而下篇则从指令集方面着手,讲解指 ...

  6. JAVAEE 是什么,如何获取各种规范jar包及各种规范的jar包源码

    1.什么是JAVA EE JAVA EE是由一系列规范组成的,规范是由JCP制定的,并且提供了参考实现.规范(Specification)是一系列接口,不包含具体实现 有以下常见的JAVA EE实现, ...

  7. IDEA反编译jar包源码

    1.maven 项目查看jar源码 如何在idea中查看jar包源码   文章目录 准备jar包 idea打开文件夹 最后一步 准备jar包 例如,我准备看resin的jar,在桌面准备了一份 ide ...

  8. Jvm加载jar包的顺序

    使用-XX:+TraceClassPaths或者在服务器上执行jinfo时,都能得到classpath包含的jar包,例如: java.class.path = local/aaa/lib/sprin ...

  9. 基于Struts2、Spring、Hibernate实现的包括多条件查询分页的基础Dao层帮助jar包实现

    操作数据库经常使用操作就是增删查改.每做一次就写一次这些操作太麻烦,也不是必需,特别是写多条件查询并分页时.太痛苦了,所以抽出时间写了个dao帮助jar.导入即搞定!妈妈再有不用操心我的项目了! 转载 ...

随机推荐

  1. 20145312 《Java程序设计》第五周学习总结

    20145312 <Java程序设计>第五周学习总结 学习笔记 Chapter8 异常处理 8.1语法与继承架构 1.Java中的错误以对象方式呈现,只要捕捉包装错误的对象,就可以针对该错 ...

  2. 20145322何志威 《Java程序设计》课程总结

    课程总结 每周读书笔记链接汇总 •第一周读书笔记 •第二周读书笔记 •第三周读书笔记 •第四周读书笔记 •第五周读书笔记 •第六周读书笔记 •第七周读书笔记 •第八周读书笔记 •第九周读书笔记 •第十 ...

  3. linux input输入子系统分析《四》:input子系统整体流程全面分析

    1      input输入子系统整体流程 本节分析input子系统在内核中的实现,包括输入子系统(Input Core),事件处理层(Event Handler)和设备驱动层.由于上节代码讲解了设备 ...

  4. ubuntu 18.04在更新软件库时出现E: Release file for http://security.ubuntu.com/ubuntu/dists/bionic-security/InRelease is not valid yet...

    1.完整的错误信息如下: E: Release file for http://security.ubuntu.com/ubuntu/dists/bionic-security/InRelease i ...

  5. 【bzoj2734】集合选数(有点思维的状压dp)

    题目传送门:bzoj2734 这题一个月前看的时候没什么头绪.现在一看,其实超简单. 我们对于每个在$ [1,n] $范围内的,没有因数2和3的数$ d $,将它的倍数$ 2^a 3^b d $一起处 ...

  6. 搭建时间服务器(linux)

    我们搭建集群环境的时候,时间必须是要统一的,才能保证集群数据的一致性. 一般操作是直接使用NTP,跟默认的时间服务器同步,但是最好还是让所有节点跟集群中的某台作为时间服务器的节点同步. 步骤:(节点有 ...

  7. 自学Java测试代码一数据类型、数组使用

    2017-08-22 21:23:37. writer:pprp package test; public class helloWorld { int maxn = 123; //常量,需要定义一个 ...

  8. TSP - 状态压缩dp

    2017-08-11 21:10:21 艾教写的 #include<iostream> #include<cstdio> #include<cstring> #in ...

  9. 'webpack' 不是内部或外部命令解决办法以及npm配置

    昨天在笔记本上安装webpack,按照教程下来,使用webpack命令行,报错:'webpack' 不是内部或外部命令,也不是可运行的程序 或批处理文件.网上有大量的配置方法与解决办法,找了好久才成功 ...

  10. JSP 标准标签库(JSTL)

    JSP 标准标签库(JSTL) JSP标准标签库(JSTL)是一个JSP标签集合,它封装了JSP应用的通用核心功能. JSTL支持通用的.结构化的任务,比如迭代,条件判断,XML文档操作,国际化标签, ...