Tomcat Spring web项目源代码加密

为了防止产品代码泄漏或授权等被破解,想到对源码加密,说是对源码加密,实际是需要对class文件进行加密。如果对class文件加密了,那类加载器如何能解析呢?本文讲解的就是SpringWeb项目加密后如何能在tomcat下面启动运行的。

1、war包加密;
2、tomcat类加载器修改;
3、Spring-asm类加载修改;

1、War包加密

War加密其实很简单,就是把war解压缩、将目录WEB-INF/classess下的所有class文件进行加密,然后再压缩成war包。

解压缩并进行加密

  public static List<String> unWarAndEncryptionClass(File srcFile, String destDirPath) throws RuntimeException {
    
    

	//记录解压出来的所有文件名
	List<String> filesName = new ArrayList<>();
	long start = System.currentTimeMillis();
	// 判断源文件是否存在
	if (!srcFile.exists()) {
    
    
		throw new RuntimeException(srcFile.getPath() + "所指文件不存在");
	}
	// 开始解压
	ZipFile zipFile = null;
	try {
    
    
		zipFile = new ZipFile(srcFile, Charset.forName("GBK"));
		Enumeration<?> entries = zipFile.entries();

		while (entries.hasMoreElements()) {
    
    
			ZipEntry entry = (ZipEntry) entries.nextElement();

			//    System.out.println("解压文件:" + entry.getName());
			// 如果是文件夹,就创建个文件夹
			if (entry.isDirectory()) {
    
    
				String dirPath = destDirPath + "/" + entry.getName();
				File dir = new File(dirPath);
				dir.mkdirs();
			} else {
    
    
				//添加进filesName
				filesName.add(entry.getName());
				// 如果是文件,就先创建一个文件,然后用io流把内容copy过去
				File targetFile = new File(destDirPath + "/" + entry.getName());
				// 保证这个文件的父文件夹必须要存在
				if (!targetFile.getParentFile().exists()) {
    
    
					targetFile.getParentFile().mkdirs();
				}
				targetFile.createNewFile();
				// 将压缩文件内容写入到这个文件中
				InputStream is = zipFile.getInputStream(entry);
				FileOutputStream fos = new FileOutputStream(targetFile);
				if (targetFile.getName().endsWith(".class") && targetFile.getPath().indexOf("WEB-INF") > -1) {
    
    
					int data;
					while ((data = is.read()) != -1) {
    
    
						//对每个字节进行加密。
						fos.write(data ^ 0xFF);
					}
				} else {
    
    
					int len;
					byte[] buffer = new byte[BUFFER_SIZE];
					while ((len = is.read(buffer)) != -1) {
    
    
						fos.write(buffer, 0, len);
					}
				}

				// 关流顺序,先打开的后关闭
				fos.close();
				is.close();
			}
		}
		long end = System.currentTimeMillis();
		System.out.println("解压完成,耗时:" + (end - start) + " ms");
	} catch (Exception e) {
    
    
		throw new RuntimeException("unzip error from ZipUtils", e);
	} finally {
    
    
		if (zipFile != null) {
    
    
			try {
    
    
				zipFile.close();
			} catch (IOException e) {
    
    
				e.printStackTrace();
			}
		}
	}
	return filesName;
}

再将解压后的文件夹重新压缩为war包,可使用代码也可以手动压缩后修改后缀都可以。

修改tomcat类加载器。

修改类加载器WebappClassLoaderBase

类加载器:org.apache.catalina.loader.WebappClassLoaderBase
我使用的是apache-tomcat-8.5.87-src进行修改;
修改WebappClassLoaderBase.findClassInternal(String Name)。找到以下方法块

byte[] binaryContent = resource.getContent();
if (binaryContent == null) {
    
    
	// Something went wrong reading the class bytes (and will have
	// been logged at debug level).
	return null;
}

在以上方法块之后添加以下代码:

try {
    
    
	DataInputStream filefosStream = new DataInputStream(new BufferedInputStream(new ByteArrayInputStream(binaryContent)));
	//当发现加载的class文件内容无法解析为class时,对字节码进行解密。
	if (filefosStream.readInt() != Const.JVM_CLASSFILE_MAGIC) {
    
    
		ByteArrayInputStream ois = new ByteArrayInputStream(binaryContent);
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		int data;
		while ((data = ois.read()) != -1) {
    
    
			bos.write(data ^ 0xFF);
		}
		bos.flush();
		bos.close();

		binaryContent = bos.toByteArray();
	}
} catch (IOException e) {
    
    
}

ClassParser类加载修改

类:org.apache.tomcat.util.bcel.classfile.ClassParser
修改方法:

 public ClassParser(final InputStream inputStream){
    
    
 	this.dataInputStream = new DataInputStream(new BufferedInputStream(inputStream, BUFSIZE));
}

修改为:

public ClassParser(final InputStream inputStream) {
    
    
//        this.dataInputStream = new DataInputStream(new BufferedInputStream(inputStream, BUFSIZE));
	//首先判断class文件能否正常解析,如果不能则进行解密
	ByteArrayOutputStream boss = new ByteArrayOutputStream();
	DataInput dataInputStream = null;
	try {
    
    
		int len;
		byte[] buffer = new byte[BUFSIZE];
		while ((len = inputStream.read(buffer)) != -1) {
    
    
			boss.write(buffer,0,len);
		}

		boss.flush();
		boss.close();
		byte[] classByte = boss.toByteArray();
		dataInputStream = new DataInputStream(new BufferedInputStream(new ByteArrayInputStream(classByte)));
		if (dataInputStream.readInt() != Const.JVM_CLASSFILE_MAGIC) {
    
    
			//不是.class 文件,进行解码
			//解码
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			BufferedInputStream bis = new BufferedInputStream(new ByteArrayInputStream(classByte));
			int data;
			while ((data = bis.read()) != -1) {
    
    
				//进行解密操作
				bos.write(data ^ 0xFF);
			}
			try {
    
    
				bos.flush();
				bos.close();
			} catch (IOException e) {
    
    
			}
			
			dataInputStream = new DataInputStream(new BufferedInputStream(new ByteArrayInputStream(boss.toByteArray())));
		}else{
    
    
			dataInputStream = new DataInputStream(new BufferedInputStream(new ByteArrayInputStream(classByte)));
		}
	} catch (IOException e) {
    
    
	}

	this.dataInputStream = dataInputStream;
}

修改jsp解析器。

类:org.apache.jasper.compiler.JDTCompiler
jsp文件在解析的时候可能会用到自定义标签,这时class文件会被加载。找到类JDTCompiler 下generateClass(String [] snap)方法。
找到以下代码块,

try (InputStream is = classLoader.getResourceAsStream(resourceName)) {
    
    
	if (is != null) {
    
    
	//这里开始读取class内容
		byte[] classBytes;
		byte[] buf = new byte[8192];
		ByteArrayOutputStream baos = new ByteArrayOutputStream(buf.length);
		int count;
		while ((count = is.read(buf, 0, buf.length)) > 0) {
    
    
			baos.write(buf, 0, count);
		}
		baos.flush();
		classBytes = baos.toByteArray();
		char[] fileName = className.toCharArray();
		ClassFileReader classFileReader = new ClassFileReader(classBytes, fileName, true);
		return new NameEnvironmentAnswer(classFileReader, null);
	}
} catch (IOException | ClassFormatException exc) {
    
    
	log.error(Localizer.getMessage("jsp.error.compilation.dependent", className), exc);
}

修改以上代码块,增加class内容判断,若不能解析则进行解密

try (InputStream is = classLoader.getResourceAsStream(resourceName)) {
    
    
	if (is != null) {
    
    
		byte[] classBytes;
		byte[] buf = new byte[8192];
		ByteArrayOutputStream baos = new ByteArrayOutputStream(buf.length);
		int count;
		while ((count = is.read(buf, 0, buf.length)) > 0) {
    
    
			baos.write(buf, 0, count);
		}
		baos.flush();
		classBytes = baos.toByteArray();
		//解密开始/
		try {
    
    
			DataInputStream filefosStream = new DataInputStream(new BufferedInputStream(new ByteArrayInputStream(classBytes)));
			if (filefosStream.readInt() != Const.JVM_CLASSFILE_MAGIC) {
    
    
				ByteArrayInputStream ois = new ByteArrayInputStream(classBytes);
				ByteArrayOutputStream bos = new ByteArrayOutputStream();
				int data;
				while ((data = ois.read()) != -1) {
    
    
					bos.write(data ^ 0xFF);
				}
				bos.flush();
				bos.close();

				classBytes = bos.toByteArray();
			}
		} catch (IOException e) {
    
    
		}
		//解密结束/
		char[] fileName = className.toCharArray();
		ClassFileReader classFileReader = new ClassFileReader(classBytes, fileName, true);
		return new NameEnvironmentAnswer(classFileReader, null);
	}
} catch (IOException | ClassFormatException exc) {
    
    
	log.error(Localizer.getMessage("jsp.error.compilation.dependent", className), exc);
}

修改spring-asm包中类加载器

类:org.springframework.asm.ClassReader
找到类文件读取方法,由于没下载到源码,从jar包拉出class文件反编译后修改的,方法名不详,修改后方法块如下:

private static byte[] a(InputStream var0) throws IOException {
    
    
        if (var0 == null) {
    
    
            throw new IOException("Class not found");
        } else {
    
    
            byte[] var1 = new byte[var0.available()];
            int var2 = 0;

            while (true) {
    
    
                int var3 = var0.read(var1, var2, var1.length - var2);
                byte[] var4;
                if (var3 == -1) {
    
    
                    if (var2 < var1.length) {
    
    
                        var4 = new byte[var2];
                        System.arraycopy(var1, 0, var4, 0, var2);
                        var1 = var4;
                    }
                    //判断class文件读取后的内容是否正确,不正确进行解密。
                    DataInputStream filefosStream = new DataInputStream(new BufferedInputStream(new ByteArrayInputStream(var1)));
                    if (filefosStream.readInt() != 0xCAFEBABE) {
    
    
                        ByteArrayInputStream ois = new ByteArrayInputStream(var1);
                        ByteArrayOutputStream bos = new ByteArrayOutputStream();
                        int data;
                        while ((data = ois.read()) != -1) {
    
    
                            bos.write(data ^ 0xFF);
                        }
                        try {
    
    
                            bos.flush();
                            bos.close();
                        } catch (IOException e) {
    
    
                        }
                        var1 = bos.toByteArray();
                    }
                    return var1;
                }

                var2 += var3;
                if (var2 == var1.length) {
    
    
                    var4 = new byte[var1.length + 1000];
                    System.arraycopy(var1, 0, var4, 0, var2);
                    var1 = var4;
                }
            }
        }
    }

至上,我自己项目用到的类加载器修改完毕。你的项目可能会用到其他三方架构或插件可能也会加载类文件。那也需要找到相应类加载器进行修改。

本方所用的方法,只是增加了别人破解代码的成本,别人也知道你修改了类加载器,也能反编译出类加载器里修改的内容,也就找到了class文件加密的方法。

附件:
1、war包加密源码;
2、tomcat修改后的jar包;
3、spring-asm包。(spring-asm.3.1.1.RELEASE.JAR)

猜你喜欢

转载自blog.csdn.net/ldz_wolf/article/details/129620930