(三)多模块混淆

示例项目: 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。

在这个混淆示例中,相当于只暴露了 UserServiceUser 接口,如果是支持ioc注入的情况下,可以注入 UserService 接口进行使用,但是其他被混淆的就不方便被使用。

虽然这里很简单,但是我实际要处理的这个项目有4级多模块,总共能打包七十几个模块,而且要和旧版打包方式接近,因此耗时很久,最难的就是上一节介绍的,如果有多种混淆配置(<class>),多种配置之间的排除会很麻烦。

到这里就解决了一个难题,但是更难的还在后头,如何在混淆代码的基础上实现反篡改


(三)多模块混淆
https://blog.mybatis.io/post/2c72dea3.html
作者
Liuzh
发布于
2024年6月22日
许可协议