跳转到主内容
极星编程网:以代码为星,赴技术山海!

Android 代码混淆 混淆方案

seeds.txt

列出没有被混淆的类和成员

usage.txt

列出被移除的代码 我们可以根据

seeds.txt

文件检查未被混淆的类和成员中是否已包含所有期望保留的,再根据

usage.txt

文件查看是否有被误移除的代码。

另一方面,需要从测试方面检查。

将混淆过的包进行全方面测试,检查是否有 bug 产生。

解出混淆栈 ===== 混淆后的类、方法名等等难以阅读,这固然会增加逆向工程的难度,但对追踪线上 crash 也造成了阻碍。

我们拿到 crash 的堆栈信息后会发现很难定位,这时需要将混淆反解。

在

/tools/proguard/

路径下有附带的的反解工具(Window 系统为

proguardgui.bat

,Mac 或 Linux 系统为

proguardgui.sh

)。

这里以 Window 平台为例。

双击运行 proguardgui.bat 后,可以看到左侧的一行菜单。

点击 ReTrace,选择该混淆包对应的 mapping 文件(混淆后在 /build/outputs/mapping/release/ 路径下会生成 mapping.txt 文件,它的作用是提供混淆前后类、方法、类成员等的对照表),再将 crash 的 stack trace 黏贴进输入框中,点击右下角的 ReTrace ,混淆后的堆栈信息就显示出来了。

以上使用 GUI 程序进行操作,另一种方式是利用该路径下的 retrace 工具通过命令行进行反解,命令是

retrace.bat|retrace.sh [-verbose] mapping.txt []

例如:

retrace.bat -verbose mapping.txt obfuscated_trace.txt

注意事项: 所有在

AndroidManifest.xml

涉及到的类已经自动被保持,因此不用特意去添加这块混淆规则。

(很多老的混淆文件里会加,现在已经没必要)

proguard-android.txt

已经存在一些默认混淆规则,没必要在 proguard-rules.pro 重复添加 混淆简介 ==== Android中的“混淆”可以分为两部分,一部分是

Java

代码的优化与混淆,依靠

proguard

混淆器来实现;另一部分是资源压缩,将移除项目及依赖的库中未被使用的资源(资源压缩严格意义上跟混淆没啥关系,但一般我们都会放一起用)。

代码压缩

代码混淆是包含了代码压缩、优化、混淆等一系列行为的过程。

如上图所示,混淆过程会有如下几个功能: 压缩。

移除无效的类、类成员、方法、属性等; 优化。

分析和优化方法的二进制代码;根据

proguard-android-optimize.txt

中的描述,优化可能会造成一些潜在风险,不能保证在所有版本的Dalvik上都正常运行。

混淆。

把类名、属性名、方法名替换为简短且无意义的名称; 预校验。

添加预校验信息。

这个预校验是作用在Java平台上的,Android平台上不需要这项功能,去掉之后还可以加快混淆速度。

这四个流程默认开启。

在

Android

项目中我们可以选择将“优化”和“预校验”关闭,对应命令是

-dontoptimize、-dontpreverify

(当然,默认的

proguard-android.txt

文件已包含这两条混淆命令,不需要开发者额外配置)。

资源压缩 ==== 资源压缩将移除项目及依赖的库中未被使用的资源,这在减少

apk

包体积上会有不错的效果,一般建议开启。

具体做法是在

build.grade

文件中,将

shrinkResources

属性设置为

true

。

需要注意的是,只有在用

minifyEnabled true

开启了代码压缩后,资源压缩才会生效。

资源压缩包含了“合并资源”和“移除资源”两个流程。

“合并资源”流程中,名称相同的资源被视为重复资源会被合并。

需要注意的是,这一流程不受

shrinkResources

属性控制,也无法被禁止,

gradle

必然会做这项工作,因为假如不同项目中存在相同名称的资源将导致错误。

gradle

在四处地方寻找重复资源:

src/main/res/

路径 不同的构建类型(

debug

、

release

等等) 不同的构建渠道 项目依赖的第三方库 合并资源时按照如下优先级顺序 依赖 -> main -> 渠道 -> 构建类型 假如重复资源同时存在于main文件夹和不同渠道中,gradle 会选择保留渠道中的资源。

同时,如果重复资源在同一层次出现,比如

src/main/res/ 和 src/main/res2/

,则

gradle

无法完成资源合并,这时会报资源合并错误。

“移除资源”流程则见名知意,需要注意的是,类似代码,混淆资源移除也可以定义哪些资源需要被保留,这点在下文给出。

自定义混淆规则 ======= 在上文“混淆配置”中有这样一行代码 proguardFiles getDefaultProguardFile(‘proguard-android.txt’), ‘proguard-rules.pro’ 这行代码定义了混淆规则由两部分构成:位于 SDK 的 tools/proguard/ 文件夹中的 proguard-android.txt 的内容以及默认放置于模块根目录的 proguard-rules.pro 的内容。

前者是 SDK 提供的默认混淆文件,后者是开发者自定义混淆规则的地方。

常见的混淆指令 ======= optimizationpasses dontoptimize dontusemixedcaseclassnames dontskipnonpubliclibraryclasses dontpreverify dontwarn verbose optimizations keep keepnames keepclassmembers keepclassmembernames keepclasseswithmembers keepclasseswithmembernames 更多详细的请到 官网 需要特别介绍的是与保持相关元素不参与混淆的规则相关的几种命令: | 命令 | 作用 | | — | — | | -keep | 防止类和成员被移除或者被重命名 | | -keepnames | 防止类和成员被重命名 | | -keepclassmembers | 防止成员被移除或者被重命名 | | -keepnames | 防止成员被重命名 | | -keepclasseswithmembers | 防止拥有该成员的类和成员被移除或者被重命名 | | -keepclasseswithmembernames | 防止拥有该成员的类和成员被重命名 | 保持元素不参与混淆的规则 [保持命令] [类] { [成员] } “类”代表类相关的限定条件,它将最终定位到某些符合该限定条件的类。

它的内容可以使用: 具体的类 访问修饰符(public、protected、private) 通配符*,匹配任意长度字符,但不含包名分隔符(.) 通配符**,匹配任意长度字符,并且包含包名分隔符(.) extends,即可以指定类的基类 implement,匹配实现了某接口的类 $,内部类 “成员”代表类成员相关的限定条件,它将最终定位到某些符合该限定条件的类成员。

它的内容可以使用: 匹配所有构造器 匹配所有域 匹配所有方法 通配符*,匹配任意长度字符,但不含包名分隔符(.) 通配符**,匹配任意长度字符,并且包含包名分隔符(.) 通配符***,匹配任意参数类型 …,匹配任意长度的任意类型参数。

比如void test(…)就能匹配任意 void test(String a) 或者是 void test(int a, String b) 这些方法。

访问修饰符(public、protected、private) 举个例子,假如需要将com.biaobiao.test包下所有继承Activity的public类及其构造函数都保持住,可以这样写: -keep public class com.biaobiao.test.** extends Android.app.Activity { } 常用自定义混淆规则 不混淆某个类 -keep public class com.biaobiao.example.Test { *; } 不混淆某个包所有的类 -keep class com.biaobiao.test.** { *; } } 不混淆某个类的子类 -keep public class * extends com.biaobiao.example.Test { *; } 不混淆所有类名中包含了“model”的类及其成员 -keep public class * extends com.biaobiao.example.Test { *; } 不混淆某个接口的实现 -keep class * implements com.biaobiao.example.TestInterface { *; } 不混淆某个类的构造方法 -keepclassmembers class com.biaobiao.example.Test { public (); } 不混淆某个类的特定的方法 -keepclassmembers class com.biaobiao.example.Test { public void test(java.lang.String); } } 不混淆某个类的内部类 -keep class com.biaobiao.example.Test$* { *; } 自定义资源保持规则 1. keep.xml 用

shrinkResources true

开启资源压缩后,所有未被使用的资源默认被移除。

假如你需要定义哪些资源必须被保留,在

res/raw/

路径下创建一个 xml 文件,例如

keep.xml

。

通过一些属性的设置可以实现定义资源保持的需求,可配置的属性有:

keep

定义哪些资源需要被保留(资源之间用“,”隔开)

discard

定义哪些资源需要被移除(资源之间用“,”隔开)

shrinkMode

开启严格模式 当代码中通过

Resources.getIdentifier()

用动态的字符串来获取并使用资源时,普通的资源引用检查就可能会有问题。

例如,如下代码会导致所有以“img_”开头的资源都被标记为已使用。

当代码中通过

Resources.getIdentifier()

用动态的字符串来获取并使用资源时,普通的资源引用检查就可能会有问题。

例如,如下代码会导致所有以“img_”开头的资源都被标记为已使用。

String name = String.format(“img_%1d”, angle + 1); res = getResources().getIdentifier(name, “drawable”, getPackageName()); 我们可以设置

tools:shrinkMode

为

strict

来开启严格模式,使只有确实被使用的资源被保留。

以上就是自定义资源保持规则相关的配置,举个例子:

tools:keep=“@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*” tools:discard=“@layout/unused2” tools:shrinkMode=“strict”/> 移除替代资源 一些替代资源,例如多语言支持的 strings.xml,多分辨率支持的 layout.xml 等,在我们不需要使用又不想删除掉时,可以使用资源压缩将它们移除。

我们使用 resConfig 属性来指定需要支持的属性,例如 一些替代资源,例如多语言支持的

strings.xml

,多分辨率支持的

layout.xml

等,在我们不需要使用又不想删除掉时,可以使用资源压缩将它们移除。

我们使用

resConfig

属性来指定需要支持的属性,例如 android { defaultConfig { … resConfigs “en”, “fr” } } 其他未显式声明的语言资源将被移除。

最后附上一个我在实际项目中的混淆方案 ==================

proguard-android.txt

文件内容 代码混淆压缩比,在0~7之间 -optimizationpasses 5 混合时不使用大小写混合,混合后的类名为小写 -dontusemixedcaseclassnames 指定不去忽略非公共库的类 -dontskipnonpubliclibraryclasses 不做预校验,preverify是proguard的四个步骤之一,Android不需要preverify,去掉这一步能够加快混淆速度。

-dontpreverify -verbose 避免混淆泛型 -keepattributes Signature 保留Annotation不混淆 -keepattributes Annotation ,InnerClasses #google推荐算法 -optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/ ,!class/merging/ 避免混淆Annotation、内部类、泛型、匿名类 -keepattributes Annotation ,InnerClasses,Signature,EnclosingMethod 重命名抛出异常时的文件名称 -renamesourcefileattribute SourceFile 抛出异常时保留代码行号 -keepattributes SourceFile,LineNumberTable 处理support包 -dontnote android.support.** -dontwarn android.support.** 保留继承的 -keep public class * extends android.support.v4.** -keep public class * extends android.support.v7.** -keep public class * extends android.support.annotation.** 保留R下面的资源 -keep class * .R$ {*;} 保留四大组件,自定义的Application等这些类不被混淆 -keep public class * extends android.app.Activity -keep public class * extends android.app.Appliction -keep public class * extends android.app.Service -keep public class * extends android.content.BroadcastReceiver -keep public class * extends android.content.ContentProvider -keep public class * extends android.preference.Preference -keep public class com.android.vending.licensing.ILicensingService 保留在Activity中的方法参数是view的方法, 这样以来我们在layout中写的onClick就不会被影响 -keepclassmembers class * extends android.app.Activity{ public void *(android.view.View); } 对于带有回调函数的onXXEvent、**On*Listener的,不能被混淆 -keepclassmembers class * { void (**On Event); void (**On Listener); } 保留本地native方法不被混淆 -keepclasseswithmembernames class * { native ; } 保留枚举类不被混淆 -keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); } 保留Parcelable序列化类不被混淆 -keep class * implements android.os.Parcelable { public static final android.os.Parcelable$Creator *; } -keepclassmembers class * implements java.io.Serializable { static final long serialVersionUID; private static final java.io.ObjectStreamField[] serialPersistentFields; private void writeObject(java.io.ObjectOutputStream); private void readObject(java.io.ObjectInputStream); java.lang.Object writeReplace(); java.lang.Object readResolve(); } #assume no side effects:删除android.util.Log输出的日志 -assumenosideeffects class android.util.Log { public static *** v(…); public static *** d(…); public static *** i(…); public static *** w(…); public static *** e(…); } #保留Keep注解的类名和方法 -keep,allowobfuscation @interface android.support.annotation.Keep -keep @android.support.annotation.Keep class * -keepclassmembers class * { @android.support.annotation.Keep *; } #3D 地图 V5.0.0之前: -dontwarn com.amap.api.** -dontwarn com.autonavi.** -keep class com.amap.api.**{*;} -keep class com.autonavi.**{*;} -keep class com.amap.api.maps.**{*;} -keep class com.autonavi.amap.mapcore. { ;} -keep class com.amap.api.trace.**{*;} #3D 地图 V5.0.0之后: -keep class com.amap.api.maps.**{*;} -keep class com.autonavi.**{*;} -keep class com.amap.api.trace.**{*;} #定位 -keep class com.amap.api.location.**{*;} 总结 Android架构学习进阶是一条漫长而艰苦的道路,不能靠一时激情,更不是熬几天几夜就能学好的,必须养成平时努力学习的习惯。

所以:贵在坚持!

上面分享的字节跳动公司2020年的面试真题解析大全,笔者还把一线互联网企业主流面试技术要点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。

就先写到这,码字不易,写的很片面不好之处敬请指出,如果觉得有参考价值的朋友也可以关注一下我 ①「Android面试真题解析大全」PDF完整高清版+②「Android面试知识体系」学习思维导图压缩包阅读下载 ,最后觉得有帮助、有需要的朋友可以点个赞

参考docs.qq.com/doc/DSkNLaERkbnFoS0ZF p class com.autonavi.amap.mapcore. { ;} -keep class com.amap.api.trace.**{*;} #3D 地图 V5.0.0之后: -keep class com.amap.api.maps.**{*;} -keep class com.autonavi.**{*;} -keep class com.amap.api.trace.**{*;} #定位 -keep class com.amap.api.location.**{*;} 总结 Android架构学习进阶是一条漫长而艰苦的道路,不能靠一时激情,更不是熬几天几夜就能学好的,必须养成平时努力学习的习惯。

所以:贵在坚持!

上面分享的字节跳动公司2020年的面试真题解析大全,笔者还把一线互联网企业主流面试技术要点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。

就先写到这,码字不易,写的很片面不好之处敬请指出,如果觉得有参考价值的朋友也可以关注一下我 ①「Android面试真题解析大全」PDF完整高清版+②「Android面试知识体系」学习思维导图压缩包阅读下载 ,最后觉得有帮助、有需要的朋友可以点个赞 [外链图片转存中…(img-1Y4uUK3b-1724306210847)] [外链图片转存中…(img-GgZpYAaV-1724306210848)] [外链图片转存中…(img-lEQyy0mn-1724306210848)] 参考docs.qq.com/doc/DSkNLaERkbnFoS0ZF

相关文章