示例项目: https://github.com/abel533/yguard-modules-parent
假设有如下多模块项目:
1 2 3 4 module -parent ├─module -a ├─module -b └─module -c
在 混淆技术研究笔记(一)常见工具介绍 中提到,默认只能使用单模块混淆,每个模块构建时的上下文只有自己,无法对其他模块进行处理,虽然 <<inoutpair/>
能配置多个,比如 a 依赖 b,b 依赖 c 的情况,如果配置到 a 中,在 a 中对a,b,c进行混淆,确实能实现对 jar 包内容的混淆,但是当执行 mvn clean deploy
命令时,这种混淆只对 a 自己有效。
主要的原因和 Maven 的生命周期有关,在 module-parent
这一级执行 mvn clean deploy
发布时,Maven会根据模块依赖顺序计算构建的顺序,第一个构建的模块会走完全部的生命周期后,再对第二个模块进行相同的处理,依次执行完全部的模块。
在 a 中配置多个时,当 a 模块执行时,其他模块构建完就已经发布了,对发布后的 b,c 再进行混淆已经没有意义。
该如何实现呢?
Maven在package打包时有个特性,如果源码没有变化就不会再次进行编译,已经构建的 jar 不会被覆盖,Maven只检测源码的变化,不会检测 jar 包的变化,从这点入手就能实现多模块混淆。
上面这种方式执行时,需要避免混淆插件执行两次,因此最好的办法就是在多模块基础上增加一个新的模块,例如 module-yguard
,变成如下结构:
1 2 3 4 5 module -parent ├─module -a ├─module -b ├─module -c └─module -yguard
在module-yguard
中添加a,b,c的依赖,保证最后执行,在module-yguard
中配置yGuard插件,在配置中对a,b,c进行混淆,并且在执行发布命令时通过 -pl !module-yguard
排除混淆模块的构建,避免多次混淆。
在构建时分成两步不会增加更多的时间,也没有变的更复杂,通过这种方式就实现了多模块混淆。
下面是本文示例的配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 <tasks > <property name ="runtime_classpath" refid ="maven.runtime.classpath" /> <taskdef name ="yguard" classname ="com.yworks.yguard.YGuardTask" classpath ="${runtime_classpath}" /> <yguard > <inoutpair in ="..\module-b\target\module-b-${project.version}.jar" out ="..\module-b\target\module-b-${project.version}.jar" /> <inoutpair in ="..\module-c\target\module-c-${project.version}.jar" out ="..\module-c\target\module-c-${project.version}.jar" /> <inoutpair in ="..\module-a\target\module-a-${project.version}.jar" out ="..\module-a\target\module-a-${project.version}.jar" /> <attribute name ="SourceFile, LineNumberTable, LocalVariableTable" > <patternset > <include name ="org.example.**" /> </patternset > </attribute > <rename logfile ="${project.build.directory}/yguard.log.xml" replaceClassNameStrings ="true" > <keep > <class classes ="none" methods ="none" fields ="none" > <patternset > <include name ="org.example.a." /> <include name ="org.example.b." /> <include name ="org.example.c.util.FileUtil" /> </patternset > </class > <class classes ="private" methods ="private" fields ="private" > <patternset > <include name ="org.example.c." /> <exclude name ="org.example.c.util.FileUtil" /> </patternset > </class > </keep > <property name ="naming-scheme" value ="best" /> <property name ="language-conformity" value ="illegal" /> <property name ="overload-enabled" value ="true" /> </rename > <externalclasses > <pathelement path ="${runtime_classpath}" /> </externalclasses > </yguard > </tasks >
混淆后的代码效果:
使用的jadx反编译工具,一次可以同时查看多个jar。
在这个混淆示例中,相当于只暴露了 UserService
和 User
接口,如果是支持ioc注入的情况下,可以注入 UserService
接口进行使用,但是其他被混淆的就不方便被使用。
虽然这里很简单,但是我实际要处理的这个项目有4级多模块,总共能打包七十几个模块,而且要和旧版打包方式接近,因此耗时很久,最难的就是上一节介绍的,如果有多种混淆配置(<class>
),多种配置之间的排除会很麻烦。
到这里就解决了一个难题,但是更难的还在后头,如何在混淆代码的基础上实现反篡改 ?