关于代码混淆,看这篇就够了
关于代码混淆,看这篇就够了
代码混淆一.基本概念 java 的 bytecode 很容易通过 JAD 等反编译工具还原出源代码。这样势必不满足安全的定义。如何一定程度上保护需要防止被反编译的源代码呢?混淆(obfuscate)技术。注意:用 obfuscate 防盗版是根本不可能,连汇编这种东西都能被**掉,而 java 代码基本上等同于开源的同义词。用 obfuscate 只是为了增加反编译的难度,保护源代码的知识产权。混淆包照常运行,没有任何问题。可以使用反编译工具如 jd-gui 查看混淆后的包,验证混淆效果。
二.混淆技术名称混淆 name obfuscode 将有意义的类,字段、方法名称更改为无意义的字符串。生成的新名称越 短,字节代码越小。在名称混淆的字节代码中,包,类,字段和方法名称已重命名,并且永远不能恢复原始名称。
流混淆 Flow Obfuscation 用于 if, switch, while,for 等关键字,对字节码进行细微的修改,模糊控制流,而不改变代码在运行时的行为。通常情况下,选择和循环等逻辑构造会被更改,因此它们不再具有直接等效的 Java 源代码。流模糊的字节码通常强制反编译器将一系列标签和非法的 goto 语句插入到它们生成的源代码中。源代码有时会因为反编译错误而变得更加模糊。
其他异常混淆 Exception Obfuscation 字符串加密混淆 String Encryption 引用混淆 Reference Obfuscation 三.常用工具 1.ipaguardproguard 是一个免费的 混淆重签名,优化,混肴器。不需要 ios app 源码,直接对 ipa 文件进行混淆加密。可对 IOS ipa 文件的代码,代码库,资源文件等进行混淆保护。 可以根据设置对函数名、变量名、类名等关键代码进行重命名和混淆处理,降低代码的可读性,增加 ipa 破解反编译难度。可以对图片,资源,配置等进行修改名称,修改 md5。只要是 ipa 都可以,不限制 OC,Swift,Flutter,React Native,H5 类 app
官网地址:IpaGuard官网--IOS 应用程序ipa文件混淆加密保护工具
2.yGuardyGuard 是一款免费的 Java 混淆器(非开源),它有 Java 和.NET 两个版本。yGuard 完全免费,基于 Ant 任务运行,提供高可配置的混淆规则。官网地址:www.yworks.com/products/yg…
3.allatori 第二代 Java 混淆器。所谓第二代混淆器,不仅仅能进行字段混淆,还能实现流混淆。命名混淆,流混淆,调试信息混淆,字符串编码,以及水印技术。对于教育和非商业项目来说这个混淆器是免费的。支持 war 和 jar 格式,支持对需要混淆代码的应用程序添加有效日期。官网地址:www.allatori.com/…
4.总结推荐使用 proguard :开源, 使用简单 ,文档丰富完善。
四.工具对比工具 官网地址 官方文档 开源免费 名称混淆 流混淆 maven 支持 功能 proguard www.guardsquare.com/proguard www.guardsquare.com/manual/home √ √ ✕ √ yGuard www.yworks.com/products/yg… yworks.github.io/yGuard/ √ √ ✕ √ allatori allatori.com/ ✕(免费用于教育和非商业项目) √ √ √ 减小包大小;混淆代码;添加水印五.详细内容 1.yGuard(yworks.github.io/yGuard/)易于设置:yGuard 是一个 Ant 任务!作为 Ant 任务,yGuard 可以无缝集成到您在 Ant、Maven 和 Gradle 等众多构建系统中的部署过程中高级收缩:yGuard 通过依赖分析提供精细的代码收缩功能。可配置/安全代码:yGuard 提供高度可配置的名称混淆,可保护您的知识产权免受逆向工程。开源:yGuard 是完全开源的!与昂贵的商业产品相反,yGuard 是并且永远都是免费的。Java 兼容性:要运行 yGuard 软件,您需要 JDK 1.7.x 或更高版本以及 Ant 1.5.x 或更高版本(它可能与任一软件的早期版本兼容,但尚未经过测试)。yGuard 与所有已发布的 Java 版本(最高 Java 17)兼容。但是,根据使用的版本,功能可能会略有不同。该文档包含不同版本支持的功能的详细说明。如果您打算将 yGuard 与 Java 以外的东西一起使用,还有一个关于 3rd 方 JVM 支持的部分。ProGuard 是一个开源的 Java 类文件收缩器、优化器、混淆器和预验证器。因此,ipaguard 处理的应用程序和库更小、更快,并且在一定程度上可以抵御逆向工程。
收缩步骤检测并删除未使用的类、字段、方法和属性。优化器 步骤优化字节码并删除未使用的指令。混淆步骤使用简短无意义的名称重命名剩余的类、字段和方法。最后的预验证步骤将预验证信息添加到类中,这是 Java Micro Edition 和 Java 6 及更高版本所必需的。yGuard1.maven 引用方式<build> <plugins> <plugin> <!--结合 ant run 来使用 yguard --> <artifactId>maven-antrun-plugin</artifactId> <dependencies> <dependency> <groupId>com.yworks</groupId> <artifactId>yguard</artifactId> <version>3.1.0</version> </dependency> </dependencies> <executions> <execution> <phase>package</phase> <goals> <goal>run</goal> </goals> <configuration> <tasks> <property refid="maven.compile.classpath" name="mvn.classpath"/> <!-- <echo message="Using Maven Classpath: ${mvn.classpath}" /> --> <taskdef name="yguard" classname="com.yworks.yguard.YGuardTask"/> <yguard> <!-- yguard 配置 --> </yguard> </tasks> </configuration> </execution> </executions> </plugin> </plugins> </build>复制代码
2.yguard 配置解析写在前面 yguard 共分为两大任务配置:rename 混淆名称 :主要用于修改类名,属性名,方法名以及参数名等。shrink 收缩代码 : 主要用于从多个入口点中删除所有无法访问的类、字段和方法。keep keep 是 rename 和 shrink 的子元素,用于指定从父级 rename 或 shrink 任务中排除的元素。注意事项注意事项 1 :如果项目需要 shrink, shrink 最好是配置在 rename 之前。即在进行代码混淆之前,先进行代码压缩。 因为压缩代码需要指定压缩的根代码。举个例子:比如制定 main 方法: 。如果先混淆,DplDbtransferApplication 类名被修改的话(比如为 A.class),该配置将会无效,所有代码都会被 shrink 删除,因为已经找不到 DplDbtransferApplication 这个类了(类名被修改)。其他配置请参考官方文档。配置说明基础配置
<!-- yguard 是公用配置和 rename 以及 shrink 配置的容器标签 --> <yguard> <!-- 必须至少指定一个 inoutpair 元素或一个非空 inoutpairs 元素才能运行 yguard 任务。此元素指定输入和输出 jar 文件的路径 in out 必须指定设置值,不指定报错 1.in 指定一个现有的 jar/war 文件,其中包含未收缩和未混淆的 .class 文件。 2.out 指定一个 jar/war 文件的路径,该文件将被创建并用于放置收缩和混淆过程的结果。 3.resources 如何处理资源文件,支持三种值:copy,auto,none。 默认配置 copy。 1.copy 直接复制资源文件 默认情况下,只需将所有资源文件复制到输出 jar 中 2.auto 仅复制那些在压缩后仍包含一个或多个 .class 文件的目录中的资源文件。 3.none 丢弃所有资源文件。--> <inoutpair in="${project.build.directory}/${project.build.finalName}.${project.packaging}" out="${project.build.directory}/${project.build.finalName}.${project.packaging}" /> <externalclasses> </externalclasses> <!-- 用于配置一些属性 ,保存类的调试信息:比如 LineNumberTable:行号表;LocalVariableTable:本地变量表等。 --> <attribute name="SourceFile, LineNumberTable, LocalVariableTable"> <patternset> <include name="com.ewa.pipe.**"/> </patternset> </attribute> <!-- shrink 用于收缩代码配置。 1.logfile shrink 过程的日志文件 2.在 shrink 过程中,如果有错误代码,代码将会被替换为: throw new InternalError("Badly shrinked")。 比如:当某些类的 private 属性被删除,但是 public 方法中有引用该属性,而属性被删除了,即会输出该异常错误。 --> <shrink logfile="${project.build.directory}/yshrink.log.xml" createStubs="true"> <!-- shrink 中的 keep 和 rename 中的 keep 一致。 --> <keep> <method name="void main(java.lang.String[])" class="${mainclass}" /> </keep> </shrink> <!-- 用于自定义某些配置和属性更改。 1.mainclass 用于设置主程序启动位置,该文件将不会被混淆。 2.logfile 混淆过程中的日志文件保存地址。名称以“.gz”结尾,yGuard 将自动创建文件的 gzip 压缩版本,默认为 yguardlog.xml 3.conservemanifest 当为 false 时,重新生成 MANIFEST.MF 的内容。默认为 false。 4.replaceClassNameStrings 是否混淆代码中某些字符串跟类名相关的东西。默认为 true 比如源码: System.out.print("com.ewa.pipe.dbtransfer.dpl.mapper.BlendMapper");混淆后: System.out.print("com.ewa.pipe.dbtransfer.dpl.A.B"); 5.scramble 是否随机混淆代码,默认 false。如果为 true 即每次打包生成的类名将随机替换。比如 Test.class 第一次混淆为 A.class,第二次就为 B.class 6.annotationClass 某些不必要混淆的数据,比如如下配置为 Test 注解,当配置到类上时,类中的所有东西不会被混淆;当配置到属性时,属性名称不会被混淆。 --> <rename mainclass="com.ewa.pipe.dbtransfer.dpl.DplDbtransferApplication" logfile="${project.build.directory}/yguard.log.xml" conservemanifest = "false" replaceClassNameStrings="true" scramble = "false" annotationClass="com.ewa.pipe.dbtransfer.dpl.Test" > <!-- --> <keep> </keep> <!-- 1.error-checking 用于检测错误,检测到错误就失败停止。 --> <property name="error-checking" value="pedantic"/> <!-- 2.error-checking 可用于告诉重命名引擎在混淆期间使用不同的命名方案。目前可以将此属性设置为以下值之一(默认 small,通常使用 small 就行了): small:将产生非常短的名称,即生成的 jar 文件将尽可能小。 best:会产生很可能被反编译器和反汇编器误解的名称。使用这种命名方案,在大多数文件系统上甚至不可能成功解压缩或解压缩生成的 jar 文件(Windows、Standard Unix、Standard Linux、MacOS)。然而,这种方案占用了大量空间,并且生成的 jar 可能会变大(通常大约是两倍大小)。 mix:是其他两个值的混合,这会导致合理的小但仍然难以反编译 jar 文件。 --> <!-- 其他属性( language-conformity ,overload-enabled,obfuscation-prefix,digests,expose-attributes)请参考官方文档。 --> <property name="naming-scheme" value="small"/> </rename> </yguard>复制代码
keep 配置说明
class 元素
class 用于在 rename 和 shrink 过程中排除某些类,字段和方法。其是 keep 的子元素。以下是配置说明(- 表示会被收缩,即被删除 ):
可见性(是否被收缩) public protected friendly privatenone - - - -public * - - -protected * * - -friendly * * * -private * * * *属性说明
name 指定要保留的类名。 在 shrink 中,只会保留类名称,类中的属性和方法都会被删除掉。classes 保持类的可见性 :其值是上述:none , public , protected , friendly , private 。默认为 nonemethods 保留方法的可见性 , 值同 classes 的描述。默认为 nonefields 保留属性的可见性 , 值同 classes 的描述。默认为 noneextends 保留对继承了该类的可见性 。1.在 shrink 中凡是继承了该类的子类都不会被删除。 2.在 rename 中凡是继承了该类的子类都不会被修改名称。implements 保留对实现该接口的可见性 。1.在 shrink 中凡是实现该接口的类都不会被删除。 2.在 rename 中凡是实现该接口的类都不会被修改名称。注意事项
以上属性可以单独使用,一可以混合使用。其 extends/implements 可以和 classes, methods,fields 混合使用。参考列 2 说明。列 1:
<shrink logfile="${project.build.directory}/yshrink.log.xml"> <keep> <!-- 保留 NameTest 类不被删除,但是内部的方法和会属性会被删除,不论私有还是共有。 --> <class name="com.arm.code.mix.base.NameTest"/> <!-- 保留所有公用的类,方法和属性。其关联的类,方法和属性会被保留,不会被删除 --> <class classes="public" methods="public" fields="public"/> <!-- 保留所有继承了 BaseClass 的类不被删除,但是内部的方法和会属性会被删除,不论私有还是共有。 --> <class extends="com.arm.code.mix.base.BaseClass"/> </keep> </shrink>复制代码列 2:
<shrink logfile="${project.build.directory}/yshrink.log.xml"> <keep> <!-- 保留 NameTest 类不被删除,并保留其 private 级别的方法和属性 --> <class name="com.arm.code.mix.base.NameTest" methods="private" fields="private"/> <!-- 保留所有继承了 BaseClass 的类不被删除,并保留其 private 级别的方法和属性 --> <class extends="com.arm.code.mix.base.BaseClass" methods="private" fields="private"/> </keep> </shrink>复制代码列 3:
一下举列几个模式集的列子,模式集可以参考 ant。 <!-- include shrink:不需要被删除的类,保留的类。rename:不需要被混淆的类名 --><class> <patternset> <include name="com.mycompany.**.*Bean"/> <exclude name="com.mycompany.secretpackage.*"/> <exclude name="com.mycompany.myapp.SecretBean"/> <!-- 由于 Ant'$'用作转义字符,因此如果您想将一个作为参数传递给任务,则必须使用两个连续的'$'s( )。'$$'--> <exclude name="org.w3c.sax?.**.*$$*"/> </patternset></class>复制代码 method
method 用于在 rename 和 shrink 过程中排除方法。其是 keep 的子元素。以下是配置说明(- 表示会被收缩,即被删除 ):
<!-- 这将保留 MyClass 类的 main 和 foo 方法。此外,所有 readObject 和 writeObject 方法(用于序列化)都将保存在 com.mycompany.myapp.data 包的所有类中。 请注意,您必须指定返回参数的类型,即使它是 void,并且您必须为所有类使用完全限定名称,即使是 java.lang package. --><method class="com.mycompany.myapp.MyClass" name="void main(java.lang.String[])"/><method class="com.mycompany.myapp.MyClass" name="int foo(double[][], java.lang.Object)"/><method name="void writeObject(java.io.ObjectOutputStream)"> <patternset> <include name="com.mycompany.myapp.data.*"/> </patternset></method><method name="void readObject(java.io.ObjectInputStream)"> <patternset> <include name="com.mycompany.myapp.data.*"/> </patternset></method>复制代码
field
field 您可以按名称指定应从收缩或名称混淆中要保留的字段
<!-- 保留 MyClass 类中的所有字段。 此外,所有 serialVersionUID 字段(用于序列化)都将保存在 com.mycompany.myapp.data 包的所有类中。 --><field class="com.mycompany.myapp.MyClass" name="field"/><field name="serialVersionUID"> <patternset> <include name="com.mycompany.myapp.data.*"/> </patternset></field>复制代码 package
package 用于从重命名过程中排除某些包的名称。它不能用于收缩(shrink)过程 。这对类、方法或字段名称没有影响。
<package> <patternset> <!-- com.mycompany.myapp 不被混淆。myapp 下的包名还是会被混淆 --> <include name="com.mycompany.myapp.*"/> <!-- com.mycompany.myapp 不被混淆。myapp 下的包名也不会被混淆 --> <include name="com.mycompany.myapp.**"/> </patternset> </package>复制代码 3.几种情况下的使用方式 springboot 项目 1.注意事项
yguard 插件执行要放在 spring boot 打包项目之前,因为反置的话,会造成 jar 中的 springboot 的启动相关类被混淆,而造成启动项目失败。2.项目使用失败的问题收集总结
本地打包之后启动项目失败:由于是本地 idea 将 jdk 设置成 jdk17 了,导致打包失败。 设置为 jdk8 后成功启动。项目使用 mybaties plus,项目里只有一个接口:public interface TimePullLogMapper extends BaseMapper{} , 造成混淆后打包报错:spring 至少一个 bean 实现。后面加上: 后正常。==service 的接口和实现都要暴露,不然 spring 的注入和 nacos 的服务发现都会存在问题。3.配置模版
<keep> <!--包名不混淆配置--> <package> <patternset> <include name="com.arm.oceansearch.**"/> </patternset> </package> <!--mybaites 相关的 mapper 混淆后,会造成 boot 项目启动失败 --> <class implements="com.arm.boot.core.base.BaseMapper"/> <!--mybaites 默认生成 sql 时是使用的实体类的类名,所以不能混淆--> <class implements="com.arm.oceansearch.entity.BaseEntity"/> <!-- 本包的 controller 混淆后,无法读取 mapping 映射,原因未知。--> <class> <patternset> <include name="com.arm.oceansearch.controller.*"/> </patternset> </class> <!-- service 的接口和实现都要暴露,不然 spring 的注入和 nacos 的服务发现都会存在问题。 --> <class> <patternset> <include name="com.arm.oceansearch.service.**"/> </patternset> </class> <!--main 方法配置--> <method name="void main(java.lang.String[])" class="com.arm.oceansearch.OceanSearchApplication"/> </keep>复制代码
简介 ProGuard 是一个开源的 Java 类文件收缩器、优化器、混淆器和预验证器。因此,ProGuard 处理的应用程序和库更小、更快,并且在一定程度上可以抵御逆向工程。
收缩步骤检测并删除未使用的类、字段、方法和属性。优化器 步骤优化字节码并删除未使用的指令。混淆步骤使用简短无意义的名称重命名剩余的类、字段和方法。最后的预验证步骤将预验证信息添加到类中,这是 Java Micro Edition 和 Java 6 及更高版本所必需的。对反射的处理反射和内省对于任何代码的自动处理都存在特殊的问题。在 ProGuard 中,代码中动态创建或调用(即按名称)的类或类成员也必须指定为入口点。例如,Class.forName()构造可以在运行时引用任何类。通常不可能计算必须保留哪些类(使用它们的原始名称),因为类名可能是从配置文件中读取的,例如。因此,您必须在 ProGuard 配置中指定它们,同样简单-keep 选项
Class.forName("SomeClass")SomeClass.classSomeClass.class.getField("someField")SomeClass.class.getDeclaredField("someField")SomeClass.class.getMethod("someMethod", null)SomeClass.class.getMethod("someMethod", new Class[] { A.class,... })SomeClass.class.getDeclaredMethod("someMethod", null)SomeClass.class.getDeclaredMethod("someMethod", new Class[] { A.class,... })AtomicIntegerFieldUpdater.newUpdater(SomeClass.class, "someField")AtomicLongFieldUpdater.newUpdater(SomeClass.class, "someField")AtomicReferenceFieldUpdater.newUpdater(SomeClass.class, SomeType.class, "someField")支持可单独使用。首先,下载一个 ProGuard 版本或者构建 ProGuard 从源头。然后可以通过调用目录中的脚本直接从命令行执行 ProGuard bin:linux/mac:bin/proguard.sh -injars path/to/my-application.jar \ -outjars path/to/obfuscated-application.jar \ -libraryjars path/to/java/home/lib/rt.jarwindows:bin\proguard.bat -injars path/to/my-applicati^ -outjars path/to/obfuscated-application.jar ^ -libraryjars path/to/java/home/lib/rt.jarGradle 模式 ant 模式 Maven 模式:(没有正式提供 maven 集成,也无法提供支持,但有可用的解决方案,但 Guardsquare 不保证它们提供的功能。)来源实现:github.com/wvengen/pro…github.com/dingxin/pro…错误解析[proguard] Error: The input doesn't contain any classes. Did you specify the proper '-injars' options?处理:<inFilter>com/ewa/pipe/**</inFilter>, inFilter 标签设置为包路径地址,把‘.’换成‘/’。 injar : 指定 target 中的一个目标地址:这里指定编译后的 classes 文件夹。 inFilter 指定的是 classes 的内部的文件夹(package)地址。<!-- 加载文件的过滤器,就是你的工程目录了--> <inFilter>com/arm/code/**</inFilter> <!-- 对什么东西进行加载,这里仅有 classes 成功,毕竟你也不可能对配置文件及 JSP 混淆吧--> <injar>classes</injar>复制代码以下是一个例子说明,如果你想更多的有用信息,请查看文档(www.guardsquare.com/manual/conf…)
<configuration> <!-- 是否将生成的 PG 文件安装部署--> <attach>false</attach> <!-- 是否混淆 --> <obfuscate>true</obfuscate> <!-- 指定生成文件分类 --> <!--<attachArtifactClassifier>pg</attachArtifactClassifier>--> <!-- 加载文件的过滤器,就是你的工程目录了--> <inFilter>com/arm/code/**</inFilter> <!-- 对什么东西进行加载,这里仅有 classes 成功,毕竟你也不可能对配置文件及 JSP 混淆吧--> <injar>classes</injar> <!-- 输出目录--> <outputDirectory>${project.build.directory}</outputDirectory> <outjar>${project.build.finalName}.${project.packaging}</outjar> <options> <!-- JDK 目标版本 1.7--> <option>-target 1.8</option> <!-- 不做收缩(删除注释、未被引用代码)--> <option>-dontshrink</option> <!-- 不做优化(变更代码实现逻辑)--> <option>-dontoptimize</option> <!-- 不忽略非公用类文件及成员--> <option>-dontskipnonpubliclibraryclasses</option> <option>-dontskipnonpubliclibraryclassmembers</option> <!-- 优化时允许访问并修改有修饰符的类和类的成员 --> <option>-allowaccessmodification</option> <!-- 确定统一的混淆类的成员名称来增加混淆,防止冲突--> <option>-useuniqueclassmembernames</option> <!-- 不混淆所有包名,Spring 配置中有大量固定写法的包名--> <option>-keeppackagenames</option> <!-- 不混淆所有特殊的类--> <option>-keepattributes Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,LocalVariable*Table,*Annotation*,Synthetic,EnclosingMethod</option> <!-- 不混淆所有的 set/get 方法,毕竟项目中使用的部分第三方框架(例如 Shiro)会用到大量的 set/get 映射--> <option>-keepclassmembers public class * {void set*(***);*** get*();}</option> <!-- 不混淆的 SpringBoot 类及其中的方法 --> <option>-keep class com.arm.code.mix.base.SpringBoot{ <methods>; }</option> <!-- 不混淆 job 包下的所有类名,且类中的方法也不混淆--> <!--<option>-keep class com.chinatelecom.gz.wy.zhukun.shiro_spring.job.** { <methods>; }</option> --> <!-- 不混淆 filter 包下的所有类名,这里主要是对 Shiro 的路踢人过滤器混淆,对类的属性和方法进行了混淆--> <!--<option>-keep class com.chinatelecom.gz.wy.zhukun.shiro_spring.filter.** </option> --> <!-- 不混淆 model 包中的所有类以及类的属性及方法,实体包,混淆了会导致 ORM 框架及前端无法识别--> <!--<option>-keep class com.chinatelecom.gz.wy.zhukun.shiro_spring.model.** {*;}</option>--> <!-- com.test.prog.util, util 包不混淆的类的属性及方法,实体包 --> <!--<option>-keep class com.test.prog.util.finals.Const{ *; }</option>--> <!-- 不混淆凭证包下的所有类名,但对类中的属性、方法进行混淆,原因是 Spring 配置中用到了这个类名--> <!--<option>-keep class com.test.prog.util.SecCode</option>--> <!--<option>-keep class com.test.prog.util.exception.HihSoftHandlerException</option>--> <!-- 不混淆 job 包下的所有类名,且类中的方法也不混淆--> <!--<option>-keep class com.test.prog.controller.** { <methods>; }</option>--> </options> <!-- 添加依赖,这里你可以按你的需要修改,这里测试只需要一个 JRE 的 Runtime 包就行了 --> <libs> <lib>${java.home}/lib/rt.jar</lib> <lib>${java.home}/lib/jce.jar</lib> <!--<lib>${java.home}/lib/security/local_policy.jar</lib>--> <!--<lib>${java.home}/lib/security/US_export_policy.jar</lib>--> <lib>${java.home}/lib/ext/sunjce_provider.jar</lib> </libs> </configuration>
评论