反编译并不是为了去破解人家辛辛苦苦开发的app;混淆与加壳也是为了在一定程度上保护自己的开发成果。反编译、混淆、加壳是非常有用的技能。
记录一下个人日常使用
反编译
反编译工具
apktool
资源文件获取,可以提取出图片文件和布局文件进行使用查看:apktool.bat d -f [apk文件] [输出文件夹]
dex2jar
将apk反编译成Java源码(classes.dex转化成jar文件):dex2jar.bat [apk文件]
jd-gui
查看APK中classes.dex转化成出的jar文件,即源码文件:用jd-gui.exe打开上面生成的classes.dex即可
注:AndroidKiller是一个反编译工具,一步到位,看Java源码的时候,需要借助smail转java的工具。另外将test.apk的apk文件更换后缀名为test.zip,可以提取出资源图片文件等等。
未混淆
未混淆的app,反编译可以看到源码:
混淆
混淆app之后,反编译得到的源码:
混淆
开启App的混淆,在一定程度上保护自己辛苦开发的成果,即反编译出来的源码是a b c...
这样的,但是仍然可以被反编译看到AndroidManifest和资源文件。
开启混淆
在Android Studio的app工程目录里,release版本开启代码混淆,然后混淆的配置写在该目录里的proguard-rules.pro
文件里
1 | release { |
配置proguard-rules
配置该文件是为了防止某些功能的正常使用,代码过于混淆,会导致功能无法使用,这个时候就要保持不被混淆。比较全面的通用配置(常用的第三方框架)如下:
1 | # Add project specific ProGuard rules here. |
Proguard常用语法
- 关键字
关键字 | 描述 |
---|---|
keep | 保留类和类中的成员,防止它们被移除或被重命名 |
keepnames | 保留类和类中的成员,防止它们被重命名,但当成员没有被引用时会被移除 |
keepclassmembers | 只保留类中的成员,防止它们被移除或者被重命名 |
keepclassmembernames | 只保留类中的成员,防止它们被重命名,但当成员没有被引用时会被移除 |
keepclasseswithmembers | 防止拥有该成员的类和成员被移除或被重命名,前提是指明的类中的成员必须存在,如果不存在则还是会混淆 |
keepclasseswithmembernames | 防止拥有该成员的类和成员被重命名,但当成员没有被引用时会被移除,前提是指明的类中的成员必须存在,如果不存在则还是会混淆 |
- 通配符
通配符 | 描述 |
---|---|
<field> |
匹配类中的所有字段 |
<method> |
匹配类中的所有方法 |
<init> |
匹配类中的所有构造函数 |
* |
匹配任意长度字符,但不含包名分隔符(.);如完整包名:com.example.test.util,使用com.*、com.example.*都是无法匹配的,因为*无法匹配包名中的分隔符,正确的匹配方式是com.example.*.*、com.example.test.*;如果你不写任何其他内容,只有一个*,则表示匹配所有的字符 |
** |
匹配任意长度字符,并且包含包名分隔符(.);如混淆文件里的:dontwarn android.support.**就可以匹配android.support包下的所有内容,包括任意长度的子包 |
*** |
匹配任意参数类型;如void set(***) 匹配任意传入的参数类型,*** get() 就能匹配任意返回值的类型 |
... |
匹配任意长度的任意类型参数;如void test(…)匹配void test(String a) 或void test(int a, String b) 等等 |
- 常见规则
形如:
1 | [关键字][类]{ |
包:com.example.test
类:A
不混淆某个类
1
-keep public class com.example.test.A { *; }
不混淆某个包下所有的类
1
-keep class com.example.test.**{ *; }
不混淆某个类的子类
1
-keep public class * extends com.example.test.A { *; }
不混淆所有类名中包含了”关键字”的类及成员
1
-keep public class **.*model*.** { *; }
不混淆某个接口
1
-keep class * implements com.example.test.Interface { *; }
不混淆某个类的构造方法
1
2
3-keepclassmembers class com.example.test.A {
public <init>();
}不混淆某个类的特定的方法
1
2
3-keepclassmembers class com.example.test.A {
public void test(java.lang.String);
}不混淆某个类的内部类
1
2
3-keep class com.example.test.A$* {
*;
}
混淆注意
代码开启混淆之后,调试app或者遇到app异常时,打印里面显示的则是a b c...
这种的异常,定位不到异常的位置,这个时候在目录build\outputs\mapping\release
里,使用mapping.txt,结合SDK里的工具sdk\tools\proguard\bin\proguardgui.bat
->ReTrace,进行定位异常,基本上可以定位,然后解决异常错误。
加壳
加壳是在二进制的程序中植入一段代码,在运行的时候优先取得程序的控制权,做一些额外的工作。是应用加固的一种手法对原始二进制原文进行加密/隐藏/混淆。加壳的程序可以有效阻止对程序的反汇编分析,常用来保护软件版权,防止被软件破解。
Android Dex文件加壳原理
Android Dex文件大量使用引用给加壳带来了一定的难度,但是从理论上讲,Android APK加壳也是可行的。
加壳程序:加密源程序为解壳数据、组装解壳程序和解壳数据
解壳程序:解密解壳数据,并运行时通过DexClassLoader动态加载
源程序:需要加壳处理的被保护代码
加壳的利与弊
优势
保护自己核心代码算法,提高破解/盗版/二次打包的难度
还可以缓解代码注入/动态调试/内存注入攻击
劣势
影响兼容性
影响程序运行效率