如果将人生一分为二,前半段的人生哲学是【不犹豫】,后半段的人生哲学是【不后悔】。
最近项目需要把重要class文件加密,加密方法本身没有被加密,所有容易被反编译,然后把class文件解密。
采用JNI方式加密,把解密方法写在动态链接库中,动态链接库不容易被破解,这样调用动态链接库解密class文件并自定义ClassLoader动态加载类。
目录
五:ClassLoader动态加载class并调用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接口
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
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++;