Bootstrap

使用jvmti实现class加密,防止反编译

如果想要保护自己的java代码不被别人反编译,则可以使用下面的jvmti对Class加密,然后在类加载器加载时再进行解密,逃避反编译。
jvmti是java自带的强大工具,我们可以通过jvmti做一些操作(例如:hook class加载,jvm启动前做什么事情,或者jvm初始化时做事情等等)
步骤如下:
1.对class进行加密
2.创建本地方法DLL,实现解密代码
(1)添加jvmti头文件
(2)监听jvm初始化完毕事件,并执行自己的方法
(3)在class类加载的时候实现解密
3.添加解密组件,其实也就是添加启动命令参数

1.对class进行加密,这里只是演示最简单的加密,将java魔数修改,就可以做到防止编译了,当然如果老手一看就知道怎么解密了,具体的加密可以再自己搞。
正常class文件(开头4字节为0xbebafeca):
这里写图片描述
加密后的class文件(开头文件为0xbfbafeca):
这里写图片描述

2.创建本地方法DLL,实现解密代码
(1)添加jvmti头文件,找到自己java的安装路径,然后引入

这里写图片描述
(2)监听jvm初始化完毕事件,并执行自己的方法,以下是具体代码

#include <jvmti.h>
#include <string>
#include <cstring>
#include <iostream>
#include <list>
#include <map>
#include <set>
#include <stdlib.h>
#include <jni_md.h>
#include <string>

typedef struct tagHeader
{
    int nSeqNum;//分割二进制 用来判断是否为0xbfbafeca
    int nHash;//这个应该是对象的hash值
    char strPass[16];
} Header; //这个是对象头

std::string& replace_all(std::string&   str, const  std::string&   old_value, const std::string&   new_value)
{
    while (true)   {
        std::string::size_type   pos(0);
        if ((pos = str.find(old_value)) != std::string::npos)
            str.replace(pos, old_value.length(), new_value);
        else   break;
    }
    return   str;
}
char * jstringTostring(JNIEnv* env, jstring jstr)
{
    char* rtn = NULL;
    jclass clsstring = env->FindClass("java/lang/String");
    jstring strencode = env->NewStringUTF("utf-8");
    jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
    jbyteArray barr = (jbyteArray)env->CallObjectMethod(jstr, mid, strencode);
    jsize alen = env->GetArrayLength(barr);
    jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);
    if (alen > 0)
    {
        rtn = (char*)malloc(alen + 1);
        memcpy(rtn, ba, alen);
        rtn[alen] = 0;
    }
    env->ReleaseByteArrayElements(barr, ba, 0);
    return rtn;
}
//(Ljava/lang/String;[BIILjava/security/ProtectionDomain;Ljava/lang/String;)Ljava/lang/Class;
//(String,byte[],int,int,ProtectionDomainm,string)class
//返回值为class类型
jclass MyDefine1Class(JNIEnv * env, jobject obj, jstring name, jbyteArray b, jint off, jint len, jobject protection_doamin, jstring source)
{
    jbyte * buff = env->GetByteArrayElements(b, NULL);
    char*  buff1 = (char*)env->GetByteArrayElements(b, NULL);
    Header header;
    jclass descClazz;

    memcpy(&header, buff, sizeof(header));//将对象头部单独放到header中
    char * className = jstringTostring(env, name); //
    std::string realName(className);
    replace_all(realName, ".", "/");

    //如果发现加载的class以0xbfbafeca开头的就认为这个class已经被加密了,然后进行解密
    if (header.nSeqNum == 0xbfbafeca)
    {
        //解密,将0xbfbafeca变为0xbebafeca
        header.nSeqNum = header.nSeqNum & 0xFEFFFFFF;
        memcpy(buff, &header.nSeqNum, sizeof(header.nSeqNum));
    }
    //class被解密后或者没有被加密的class,直接就正常可以加载class
    descClazz = env->DefineClass(realName.c_str(), obj, buff, len);
    free(className);
    env->ReleaseByteArrayElements(b, buff, 0);
    return descClazz;


}

//JVM初始化完毕后要进行操作的函数
void JNICALL cbVminit(jvmtiEnv *jvmti_env, JNIEnv* jni_env, jthread thread)
{
    printf("cd VMinit\n");

    jclass clazz = jni_env->FindClass("java/lang/ClassLoader");
    if (clazz != NULL)
    {
        //通过JNI的RegisterNatives方法将ClassLoader的defineClass1重写以实现解密
        //其实就是自己写一个defineClass方法,加到ClassLoader类中,这里是class格式的一些规则,具体可以看一些class格式
        JNINativeMethod methods[] = {
            { "defineClass1", "(Ljava/lang/String;[BIILjava/security/ProtectionDomain;Ljava/lang/String;)Ljava/lang/Class;", (void*)MyDefine1Class },
        };
        jni_env->RegisterNatives(clazz, methods, 1);
    }

}

JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm) {
    // nothing to do
}

//代理加载时执行
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) {
    // nothing to do
    jvmtiEnv *jvmti = NULL;
    jvmtiError error;

    // 获取JVM环境
    jint result = jvm->GetEnv((void **)&jvmti, JVMTI_VERSION_1_1);
    if (result != JNI_OK) {
        printf("ERROR: Unable to access JVMTI!\n");
    }

    jvmtiCapabilities capabilities;//方便判断SetEventNotificationMode是否要开启

    (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;

    error = jvmti->AddCapabilities(&capabilities);


    if (error != JVMTI_ERROR_NONE) 
    {
        printf("ERROR: Unable to AddCapabilities JVMTI!\n");
        return error;
    }

    //定义要监听的模块,这里定义了监听JVM初始化完毕模块
    jvmtiEventCallbacks callbacks;

    (void)memset(&callbacks, 0, sizeof(callbacks));

    callbacks.VMInit = &cbVminit;//赋值JVM初始化完毕以后进行操作函数

    error = jvmti->SetEventCallbacks(&callbacks, (jint)sizeof(callbacks));

    if (error != JVMTI_ERROR_NONE)
    {
        printf("ERROR: Unable to SetEventCallbacks JVMTI!\n");
        return error;
    }
    //定义了监听模块以后,这里还需要开启你要监听的模块,是必须的
    error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_INIT, NULL);
    if (error != JVMTI_ERROR_NONE)
    {
        printf("ERROR: Unable to SetEventNotificationMode JVMTI!\n");
        return error;
    }
    return JNI_OK;
}

3.添加解密组件,其实也就是添加启动命令参数
将生成的DLL放到lib目录,然后添加启动命令参数,然后运行就大功告成啦!
这里写图片描述

;