JVM 層對 jar 包位元組碼加密

超人汪小建發表於2017-07-18

github

github.com/sea-boat/By…

需求

拿到的需求是要對某特定的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;
    }複製程式碼
  2. 回撥函式,即是上面指定的回撥函式,它對應監聽的事件,會在對應的事件發生事被呼叫。這裡處理邏輯其實就是判斷如果是某包下的類就對其進行解密,否則不處理。

    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];
         }
     }
    }複製程式碼
  3. 額外寫一個加密程式對某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核心設計剖析》已經在京東銷售了,有需要的朋友可以到 item.jd.com/12185360.ht… 進行預定。感謝各位朋友。

為什麼寫《Tomcat核心設計剖析》

=========================

歡迎關注:

這裡寫圖片描述
這裡寫圖片描述

相關文章