文章目录

  • 概述
  • 资源准备
  • 环境准备(简单操作可跳过)
  • 改造编译插件
  • 改造Tomcat源码
  • 改造Spring源码
  • 环境测试

概述

 本文主要是介绍如何通过改造Maven-war-plugin插件,Spring源码,Tomcat容器以达到代码加密解密的效果。这里选择war包+原生Tomcat的部署方式来进行讲解,其他形式可自主实验,原理大致相同。大致流程如下:项目代码通过Maven-war-plugin插件对编译(java compile)完成的项目打包,会对目标目录的资源进行复制,完成war包打包,在复制过程中对其class加密,已达到防止反编译的效果。war包部署到tomcat启动后,tomcat类加载器加载Web应用的class文件到JVM中,在类加载最开始的加载过程,对读取的class文件进行解密。Spring容器类加载过程相同,相应地在Spring读取class文件进行解密。

资源准备

 这里主要以Spring项目来进行讲解,需要准备Maven-war-plugin(2.6版本),Spring(5.2.x版本),Tomcat(8.5.73版本)对应版本的源码,还需下载对应版本的Tomcat安装包(8.5.73版本),版本可自主选择。

Maven,JDK环境默认已安装
Maven-war-plugin:源码下载
Spring:源码下载
Tomcat:源码/软件包下载

环境准备(简单操作可跳过)


 创建的工程比较简单,只是为了方便演示代码加密的效果,通过访问localhost:8080/hello接口前端回显“hello”字样。
 这里主要讲解下Tomcat部署war包的过程。


 1.将下载的Tomcat软件安装包apache-tomcat-8.5.73.zip解压

 2.进入/apache-tomcat-8.5.73/bin目录,双击startup.bat启动,访问localhost:8080,能成功访问安装成功!!!

首次启动窗口日志为乱码,可以修改/apache-tomcat-8.5.73/conf/logging.properties文件,将字符编码全部改成GBK即可。

 3.修改配置文件
 修改/apache-tomcat-8.5.73/conf/server.xml

 4.编译CodeEncryption工程,打包成war包部署

 5.将CodeEncryption-1.0-SNAPSHOT.war解压到步骤3配置路径

再次启动Tomcat,访问localhost:8080/hello

 到这里部署环境就OK了,下面就进入正题。

改造编译插件

 将下载的插件源码包maven-war-plugin-2.6-source-release.zip解压,导入idea。
 工程结构如下:

 改造插件源码最主要的是找到复制目标目录资源的方法入口。
 下面是Web工程中对Maven插件的配置项:

<build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-war-plugin</artifactId><version>2.6</version><configuration><warSourceDirectory>src\main\webapp</warSourceDirectory><failOnMissingWebXml>false</failOnMissingWebXml></configuration></plugin></plugins></build>

 对项目打包package可以看到日志信息,可以发现日志打印了复制资源的信息,我们在插件源码全局搜索Copying webapp resources定位到复制资源的代码位置:

 到这里我们就找到了插件在复制资源的入口,就是copyFiles方法:


 我们只用看正常复制文件的分支,确定代码改造位置。

 进入copyFile方法,
 对于文件插件会直接对其复制到指定目录,所以我们需要在这之前对文件进行改造。改造文件的对象就是class文件。怎么对指定class文件进行改造处理?这里选择异或运算对其处理。原因:A ^ 0XFF ^ 0XFF = A,两次异或后的结果为它本身。这里就选定对0XFF做异或运算。

 改造后的代码如下图,这里只是简易的处理,主要是演示效果,可自行优化。

protected boolean copyFile(WarPackagingContext context, File source, File destination, String targetFilename,boolean onlyIfModified)throws IOException {if (onlyIfModified && destination.lastModified() >= source.lastModified()) {context.getLog().debug(" * " + targetFilename + " is up to date.");return false;} else {if (source.isDirectory()) {context.getLog().warn(" + " + targetFilename + " is packaged from the source folder");try {JarArchiver archiver = context.getJarArchiver();archiver.addDirectory(source);archiver.setDestFile(destination);archiver.createArchive();} catch (ArchiverException e) {String msg = "Failed to create " + targetFilename;context.getLog().error(msg, e);IOException ioe = new IOException(msg);ioe.initCause(e);throw ioe;}} else {// 只对工程源码编译的class文件处理if (source.getAbsolutePath().indexOf("youngqinger") != -1 && source.getName().endsWith(".class")) {context.getLog().info("Read class file ==> " + source.getName());long len = source.length();byte[] data = new byte[(int) len];try (FileInputStream fileInputStream = new FileInputStream(source)) {int read = fileInputStream.read(data);if (read != len) {throw new IOException("Read class file:" + source + " length error");}}byte[] result = new byte[(int) len];for (int i = 0; i < data.length; i++) {byte value = data[i];// A ^ 0xAC ^ 0XAC = Aresult[i] = (byte) (value ^ 0XFF);}try (FileOutputStream fileOutputStream = new FileOutputStream(source)) {fileOutputStream.write(result);}}FileUtils.copyFile(source.getCanonicalFile(), destination);// preserve timestampdestination.setLastModified(source.lastModified());context.getLog().debug(" + " + targetFilename + " has been copied.");}return true;}}

 将源码编译打包,得到改造后的插件jar包,重命名防止与中央仓库的jar包混淆:maven-war-plugin-2.6-encrypt.jar。手动将改造后的jar包发布到Maven本地仓库:mvn install:install-file -DgroupId=org.apache.maven.plugins -DartifactId=maven-war-plugin -Dversion=2.6 -Dpackaging=jar -Dfile=maven-war-plugin-2.6-encrypt.jar。

之前是想改下坐标以区别原坐标,但是发现执行命令时,会校验插件坐标和版本名称,导致不能使用改造插件,故与原坐标同名。


 导入改造的插件,编译打包后:

 重新部署war包,启动Tomcat,可以看到一场:It is not a Java .class file,在class文件格式校验时报错,说明加密后的class文件已经不符合class文件规范了。这时就需要对称的改造Tomcat,来解密加密后的class文件。

改造Tomcat源码

编译Tomcat源码->可参考文档:https://blog.csdn.net/m0_38131096/article/details/123104515

 这里默认编译环境OK,不再介绍。就不带大家去找程序入口了,直接改造。org.apache.catalina.webresource.FileResource是Tomcat去加载class文件或资源的类。


 简单的改造代码:

     @Overrideprotected InputStream doGetInputStream() {if (needConvert) {byte[] content = getContent();if (content == null) {return null;} else {return new ByteArrayInputStream(content);}}try {if(resource.getAbsolutePath().indexOf("youngqinger") != -1 && resource.getName().endsWith(".class")){byte[] content = getContent();return new ByteArrayInputStream(content);}return new FileInputStream(resource);} catch (FileNotFoundException fnfe) {// Race condition (file has been deleted) - not an errorreturn null;}}
    @Overridepublic final byte[] getContent() {// Use internal version to avoid loop when needConvert is truelong len = getContentLengthInternal(false);if (len > Integer.MAX_VALUE) {// Can't create an array that bigthrow new ArrayIndexOutOfBoundsException(sm.getString("abstractResource.getContentTooLarge", getWebappPath(),Long.valueOf(len)));}if (len < 0) {// Content is not applicable here (e.g. is a directory)return null;}int size = (int) len;byte[] result = new byte[size];int pos = 0;try (InputStream is = new FileInputStream(resource)) {while (pos < size) {int n = is.read(result, pos, size - pos);if (n < 0) {break;}pos += n;}} catch (IOException ioe) {if (getLog().isDebugEnabled()) {getLog().debug(sm.getString("abstractResource.getContentFail",getWebappPath()), ioe);}return null;}byte[] decrypt = new byte[size];if(resource.getAbsolutePath().indexOf("youngqinger") != -1 && resource.getName().endsWith(".class")){for (int i = 0; i < result.length; i++) {byte value = result[i];decrypt[i] = (byte) (value ^ 0XFF);}result = decrypt;}if (needConvert) {// Workaround for certain files on platforms that use// EBCDIC encoding, when they are read through FileInputStream.// See commit message of rev.303915 for original details// https://svn.apache.org/viewvc?view=revision&revision=303915String str = new String(result);try {result = str.getBytes(StandardCharsets.UTF_8);} catch (Exception e) {result = null;}}return result;}

 通过Ant编译打包,这个class文件打包后的目标jar包就是catalina.jar,所以我们只用替换掉Tomcat安装包,lib目录下得catalina.jar即可,现在我们替换掉原来的jar包,重新部署启动Tomcat。启动完之后发现,仍然有异常。观察日志发现是由于Spring容器去解析class文件出错,而且直接指明了是ASM ClassReader去解析的。继续改造Spring…

改造Spring源码

 编译Spring源码需要Grandle环境,默认已安装成功。全局搜下ClassReader(asm包下),定位到改造位置,看到在SimpleMetadataReader类实例化了ClassReader,Spring通过Resource接口获取输入流,定位到FileSystemResource获取输入流的位置,对class文件的字节进行解密。改造代码后,编译打包到目标jar(spring-core-5.2.x.BUILD-SNAPSHOT.jar),加载到本地仓库中:mvn install:install-file -DgroupId=com.youngqinger.springframework -DartifactId=spring-core -Dversion=decrypt -Dpackaging=jar -Dfile=spring-core-5.2.x.BUILD-SNAPSHOT.jar。
 主要改造两个类:

  1. org.springframework.core.io.FileSystemResource
  2. org.springframework.cglib.core.ReflectUtils

org.springframework.core.io.FileSystemResource改造代码:

 /*** 获取读取资源的输入流*/@Overridepublic InputStream getInputStream() throws IOException {try {// TODO 改造位置String paths = this.filePath.toFile().getAbsolutePath();if (paths.indexOf("youngqinger") != -1 && paths.endsWith("class")) {InputStream input = new FileInputStream(this.filePath.toFile());byte[] byt = new byte[input.available()];byte[] value = new byte[input.available()];input.read(byt);for (int i = 0; i < byt.length; i++) {value[i] = (byte)(byt[i] ^ 0XFF);}return new ByteArrayInputStream(value);}return Files.newInputStream(this.filePath);}catch (NoSuchFileException ex) {throw new FileNotFoundException(ex.getMessage());}}

org.springframework.cglib.core.ReflectUtils改造代码:

 @SuppressWarnings("deprecation")  public static Class defineClass(String className, byte[] b, ClassLoader loader,ProtectionDomain protectionDomain, Class<?> contextClass) throws Exception {Class c = null;Throwable t = THROWABLE;// Preferred option: JDK 9+ Lookup.defineClass API if ClassLoader matchesif (contextClass != null && contextClass.getClassLoader() == loader &&privateLookupInMethod != null && lookupDefineClassMethod != null) {try {MethodHandles.Lookup lookup = (MethodHandles.Lookup)privateLookupInMethod.invoke(null, contextClass, MethodHandles.lookup());c = (Class) lookupDefineClassMethod.invoke(lookup, b);}catch (InvocationTargetException ex) {Throwable target = ex.getTargetException();if (target.getClass() != LinkageError.class && target.getClass() != IllegalArgumentException.class) {throw new CodeGenerationException(target);}// in case of plain LinkageError (class already defined)// or IllegalArgumentException (class in different package):// fall through to traditional ClassLoader.defineClass belowt = target;}catch (Throwable ex) {throw new CodeGenerationException(ex);}}// Classic option: protected ClassLoader.defineClass methodif (c == null && classLoaderDefineClassMethod != null) {if (protectionDomain == null) {protectionDomain = PROTECTION_DOMAIN;}// TODO 改造位置if(className.indexOf("youngqinger") != -1){byte[] value = new byte[b.length];for (int i = 0; i < b.length; i++) {value[i] = (byte) (b[i] ^ 0XFF);}b = value;}Object[] args = new Object[]{className, b, 0, b.length, protectionDomain};try {if (!classLoaderDefineClassMethod.isAccessible()) {classLoaderDefineClassMethod.setAccessible(true);}c = (Class) classLoaderDefineClassMethod.invoke(loader, args);}catch (InvocationTargetException ex) {throw new CodeGenerationException(ex.getTargetException());}catch (Throwable ex) {// Fall through if setAccessible fails with InaccessibleObjectException on JDK 9+// (on the module path and/or with a JVM bootstrapped with --illegal-access=deny)if (!ex.getClass().getName().endsWith("InaccessibleObjectException")) {throw new CodeGenerationException(ex);}t = ex;}}

Spring版本不一样,获取class文件输入流的入口也不同,根据具体版本来改造。

 <dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.2.5.RELEASE</version><exclusions><exclusion><groupId>org.springframework</groupId><artifactId>spring-core</artifactId></exclusion></exclusions></dependency><!-- 使用自定义core依赖 --><dependency><groupId>com.youngqinger.springframework</groupId><artifactId>spring-core</artifactId><version>decrypt</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.2.5.RELEASE</version><exclusions><exclusion><groupId>org.springframework</groupId><artifactId>spring-core</artifactId></exclusion></exclusions></dependency><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version><scope>provided</scope></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>5.2.5.RELEASE</version><exclusions><exclusion><groupId>org.springframework</groupId><artifactId>spring-core</artifactId></exclusion></exclusions></dependency></dependencies>

环境测试

 重新刷新pom导入本地仓库改造jar包后,编译打包,重新部署。

可能会遇到下面问题,在示例工程导入commons-logging依赖可解决
能够成功访问:

 感谢浏览,欢迎指正!!!

Java防止反编译实践相关推荐

  1. Java 7 –反编译项目硬币

    大家好,该是从2012年开始写作的时候了.正如您在其他博客中可能已经看到的那样,有一些更改可以使您使用Java编程时的开发人员生活变得更加轻松:Diamond运算符,Switchs中的Strings, ...

  2. 如何在Eclipse 3.3上安装jadclipse[java的反编译工具] 收藏

    如何在Eclipse 3.3上安装jadclipse[java的反编译工具] 收藏 jad是java的反编译工具,是命令行执行,反编译出来的源文件可读性较高.可惜用起来不太方便.还好 找到eclips ...

  3. Java的反编译工具提供官网下载

    今天我们要来分享一些关于Java的反编译工具,反编译听起来是一个非常高上大的技术词汇,通俗的说,反编译是一个对目标可执行程序进行逆向分析,从而得到原始代码的过程.尤其是像.NET.Java这样的运行在 ...

  4. 【项目实战】Java代码反编译工具的使用 以及 如何对Java代码进行混淆?

    一.背景 现在交付给客户的代码,虽然不是以源码的形式交付,但是还经常会需要进行反编译,如何更好的做到反编译呢?本文探讨的是如何进行反编译的方法. 二. Java代码反编译工具的使用 (1)使用jad ...

  5. java如何编译和反编译,JAVA 如何反编译的自己的程序

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 JAVA 如何反编译的自己的程序 反编译的工具有好多, 在众多的JAVA反编译工具中,有几种非常著名的工具使用了相同的核心引擎--JAD,其中主要包括:F ...

  6. 最简单的 java 防反编译技巧

    源码使用try-catch代码包装下,如下: public class CompileForbid {public static void main(String[] args) {try {Syst ...

  7. 关于java的反编译的一些坑,反编译后代码中的$+数字是什么

    本人菜鸡一名,说的不够周到还请见谅.现在拿到一份虚机环境然后想把环境中的war还原成代码,反编译嘛,我觉得大部分人都接触过,看看源码啊啥的.先简单说说本次我用到的反编译工具. 首先说推荐的好用的工具J ...

  8. Java 在线反编译

    使用jd-gui反编译java提示 // INTERNAL ERROR // 的类,用在线反编译直接反编译.class http://www.showmycode.com/

  9. java jar反编译后保存_java根据jar包反编译后修改再打包回jar的做法

    1. 得到一个待要修改的jar包 2. 我的环境是windows,然后解压这个jar包,得到一堆class文件,这时候就找到你需要的那个class文件 3. 我首先是使用jd-gui工具看一下这个cl ...

最新文章

  1. iOS 隐藏顶部状态栏方式和更改颜色
  2. 九种将元器件从PCB上拆焊下的方法
  3. 【Groovy】自定义 Xml 生成器 BuilderSupport ( 创建 XmlNode 节点 | 管理 XmlNode 节点并将根节点转为 Xml 信息 | 完整代码示例 )
  4. BRCM5.02编译九:cannot find -lncurses
  5. 享学金三银四一线大厂面试专题学习笔记
  6. 简单的前端上传图片代码
  7. 我的2017年前端之路总结
  8. MATLAB(六)数据处理
  9. iview table 自定义列_基于VueJS的render渲染函数打造一款非常强大的IView 的Table组件...
  10. GitHub Pages自定义域名如何支持https
  11. 滑轮控件研究四、VelocityTracker的简单研究
  12. Java、JSP网上花店系统
  13. hanoi塔栈递归算法c语言,c++递归函数,c语言递归算法经典实例
  14. python计算圆锥体积和表面积_圆柱和圆锥表面积和体积的计算练习
  15. 阿里云注册域名,购买云服务器,备案,域名解析图文教程
  16. 信息检索2.1书刊资料检索工具--书目note
  17. java对excel进行加密_用poi-3.6-20091214.jar 实现java给excel资料加密
  18. 10分钟快速学Handlebars
  19. leetcode 1103分糖果II
  20. layui:图片上传

热门文章

  1. vue实战-mockjs模拟数据
  2. 什么牌子的蓝牙耳机好用?盘点五款性价比超高的蓝牙耳机品牌
  3. P4342 [IOI1998]Polygon —— 断链成环
  4. keil编译器的优化问题 关键字volatile的使用
  5. LED PWM控制芯片PCA9685的Linux 驱动
  6. Python下基于栈和逆波兰算法实现四则运算
  7. 华为手机双卡有android,安卓卡慢?余承东:国内或只有华为能解决
  8. 线索二叉树-C语言实现
  9. python中的pattern什么意思_正则表达式中(?:pattern)、(?=pattern)、(?!pattern)、(?=pattern)和(?!pattern)...
  10. Asp.net中Request对象的使用