JNI学习—Java通过JNI加解密并动态加载class文件-自定义ClassLoader④

如果将人生一分为二,前半段的人生哲学是【不犹豫】,后半段的人生哲学是【不后悔】。

最近项目需要把重要class文件加密,加密方法本身没有被加密,所有容易被反编译,然后把class文件解密。

采用JNI方式加密,把解密方法写在动态链接库中,动态链接库不容易被破解,这样调用动态链接库解密class文件并自定义ClassLoader动态加载类。

目录

一:Class文件加密

1.写加解密方法

二:Ant把加密后的class文件打成jar包

三:自定义ClassLoader类并创建JNI接口

1.创建jni接口

2.JNI接口-ClassLoader

四:创建动态链接库并写解密方法

1.创建动态链接库并生成dll

2.写解密方法

五:ClassLoader动态加载class并调用JNI接口解密

六:动态执行方法

七:JNI调式

八:源代码下载


一:Class文件加密

1.写加解密方法

加密和解密的大致思想是:加密就是对class文件进行异或一下,解密就是再对class文件异或回来即可。

当然亦可以采用其它加解密方式。

public class Encrypt {

   private char [] password = {'M','y','k','j','2','0','2','1'};

     /**
     * 加密方法
     * @param classFilePath 需要加密的class文件路径
     */
    public void encryptClass(String classFilePath) {

        // 为待加密的class文件创建File对象
        File classFile = new File(classFilePath);
        if (!classFile.exists()) {
            System.out.println("没有获取到文件");
            return;
        }
        // 加密后的文件去掉文件后缀名
        String encryptedFile = classFile.getParent()+File.separator+classFile.getName();
        encryptedFile = encryptedFile.substring(0,encryptedFile.indexOf("."));
        try (
                FileInputStream fileInputStream = new FileInputStream(classFile);
                BufferedInputStream bis = new BufferedInputStream(fileInputStream);
                FileOutputStream fileOutputStream = new FileOutputStream(encryptedFile);
                BufferedOutputStream bos = new BufferedOutputStream(fileOutputStream)
        ) {

            // 加密
            int data;
            int i=0;
            while ((data = bis.read()) != -1) {
                bos.write(data ^ password[i%password.length]);
                i++;
            }
            bos.flush();
        } catch (Exception e) {
            System.out.println(e.toString());
        }
    }

    /**
     * 解密方法
     * @param className 需要解密的文件名
     */
    protected byte[] decryptClass(String className) {
        String packageName=className.replace(".", "/");
        String classPath = getClassPath();
        // 为待加密的文件创建File对象
        String filePath = classPath+packageName;
        File decryptClassFile = new File(filePath);
        if (!decryptClassFile.exists()) {
            System.out.println("decryptClass() File:" + filePath + " not found!");
            return null;
        }
        byte[] result = null;
        BufferedInputStream bis = null;
        ByteArrayOutputStream bos = null;
        try {
            bis = new BufferedInputStream(new FileInputStream(decryptClassFile));
            bos = new ByteArrayOutputStream();
            // 解密
            int data;
            int i=0;
            while ((data = bis.read()) != -1) {
                bos.write(data ^ password[i%password.length]);
                i++;
            }
            bos.flush();
            result = bos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (bis != null) {
                    bis.close();
                }
                if (bos != null) {
                    bos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return result;
    }

    private static String  getClassPath(){
        String classPath = Encrypt.class.getResource("/").getPath() ;
        if(File.separator.equals("\\")){
            classPath=classPath.substring(1);
        }
        return classPath;
    }

注:加密后的文件需要去掉.class或者改为其它的如.classe,因为在启动服务器的时候,Tomcat会自动检查classpath下的class文件,如果加密的class文件不做后缀名的修改,就会被Tomcat扫描到。如果被扫描到了,但是又是经过加密的,头部信息已被修改,那么Tomcat就会报错,启动不起来。

二:Ant把加密后的class文件打成jar包

如果需要把加密文件打包发布,可以采用ant工具打包;参考:Ant打jar包_青春斗-CSDN博客

反之跳过此步骤,可以直接把相应加密后的class文件放在相应的class路径下。

三:自定义ClassLoader类并创建JNI接口

有些功能Java无法提供,只能通过C/C++来操作,然后用Java去调用,这就需要JNI了。

1.创建jni接口

参考博文:(1条消息) JNI学习—创建jni接口并生成相应头文件①_青春斗-CSDN博客

2.JNI接口-ClassLoader

public class ClassLoadUtil {

    static{
        //系统加载其它语言的函数,并且判断当前系统是什么类型
        String arch = System.getProperty("os.arch");
        String os = System.getProperty("os.name").toLowerCase();
        String path=ClassLoadUtil.class.getResource("/").getPath() ;
        path=path.replace("classes/", "dll/");

        if(os.contains("win")){
            if(arch.contains("64")){
                // windows 64位
                System.load(path+"ClassLoader64.dll");
            }
            else{
                // windows 32位
                System.load(path+"ClassLoader.dll");
            }
        }else{
            System.load(path+"ClassLoader.so");
        }
    }

    //natice关键字.标记这个接口,看起来像是abstract
    private static native byte[] decryptClass(String[] className);

}

四:创建动态链接库并写解密方法

1.创建动态链接库并生成dll

参考博文:(1条消息) JNI学习—创建动态链接库并生成dll②_青春斗-CSDN博客

2.写解密方法

头部引用  #include "com_mykj_classloader_ClassLoadUtil.h" ,把这个头部文件中的方法复制过来即可。

#include "pch.h"
#include "jni.h"
#include "jni_md.h"
#include "com_mykj_classloader_ClassLoadUtil.h"
#include "string"

char password[] = "Mykj2021";

//上抛java异常
void throwException(JNIEnv* env, const char* message) {
	jclass newExcCls = env->FindClass("java/lang/Exception");
	env->ThrowNew(newExcCls, message);
}


JNIEXPORT jclass JNICALL Java_com_mykj_classloader_ClassLoadUtil_decryptClass(JNIEnv* env, jclass object,jstring classFilePath,jstring className, jobject loader) {
	//获取字符串保存在JVM中内存中
	const char* jClassFilePath = env->GetStringUTFChars(classFilePath, NULL);
	FILE* classfile;
	classfile = fopen(jClassFilePath, "rb");
	if (classfile == NULL) {
		throwException(env,"not found classfile");
		return NULL;
	}

	char* pBuff;//定义文件数组
	fseek(classfile, 0, SEEK_END); //把指针移动到文件的结尾 ,获取文件长度
	int size = (int)ftell(classfile);//获取文件长度
	rewind(classfile); //把指针移动到文件开头 因为我们一开始把指针移动到结尾
	pBuff = new char[size + 1];//定义数组长度
	int ch;
	int i = 0;
	size_t pwd_length = strlen(password);
	while ((ch = fgetc(classfile)) != EOF) {//End of File
		pBuff[i] = ch^password[i%pwd_length];
		i++;
	}
	pBuff[size] = 0; //把读到的文件最后一位 写为0 要不然系统会一直寻找到0后才结束
	fclose(classfile); // 关闭文件
	env->ReleaseStringUTFChars(classFilePath,jClassFilePath);//释放JVM保存的字符串的内存

	//char数组转为jbyteArray
	//jbyteArray data = env->NewByteArray(size);
	//env->SetByteArrayRegion(data, 0, size, (jbyte*)pBuff);
	//env->ReleaseByteArrayElements(data, env->GetByteArrayElements(data, JNI_FALSE), 0);

	//加载class
	const char* jClassName = env->GetStringUTFChars(className, NULL);
	jclass clazz = env->DefineClass(jClassName,loader,(jbyte*)pBuff, size);
	env->ReleaseStringUTFChars(className, jClassName);
	return clazz;
}


五:ClassLoader动态加载class并调用JNI接口解密

 //natice关键字.标记这个接口,看起来像是abstract
    private static native byte [] decryptClass(String classFilePath);

    private static String  getClassPath(){
        String classPath = ClassLoadUtil.class.getResource("/").getPath() ;
        if(File.separator.equals("\\")){
            classPath=classPath.substring(1);
        }
        return classPath;
    }

     private Class getClass(String className){
        String packageName=className.replace(".", "/");
        //获取class根路径
        String classPath = getClassPath();
        String classFilePath = classPath+packageName;
        //解密class
        Class clazz = decryptClass(classFilePath,packageName,ClassLoadUtil.class.getClassLoader());
        return clazz;
    }

六:动态执行方法

public  Object execute(String className, String methodName, Object... parameters){
        Class<?> cls = getClass(className);
        Object result;
        try {
            Method method =getMethod(cls, methodName, parameters);
            result= method.invoke(cls.newInstance(), parameters);
        } catch (Exception e) {
            return "Error:" + e.getMessage();
        }
        return result;
    }

    private  Method getMethod(Class<?> cls, String methodName, Object[] parameters) throws NoSuchMethodException {
        Method[] methods =cls.getDeclaredMethods();
        for(Method method:methods){
            Class<?>[] parameterTypes = method.getParameterTypes();
            int len=parameters==null?0:parameters.length;
            if(methodName.equals(method.getName()) && parameterTypes.length==len){
                return method;
            }
        }
        throw new NoSuchMethodException();
    }

七:JNI调式

debug是我们解决问题的常用手段,从Java代码联调到C/C++;

参考博文:(1条消息) JNI学习—jni调试③_青春斗-CSDN博客

八:源代码下载

下载地址:https://github.com/guyuqiang/JNIEncrypt.git

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

成长20171221

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值