Android功守道-反编译VS混淆与加壳

Android

反编译并不是为了去破解人家辛辛苦苦开发的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
2
3
4
5
6
7
release {
// proguard files
minifyEnabled true

// add proguard cfg
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}

配置proguard-rules

配置该文件是为了防止某些功能的正常使用,代码过于混淆,会导致功能无法使用,这个时候就要保持不被混淆。比较全面的通用配置(常用的第三方框架)如下:

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in E:\AndroidTools\AndroidStudio\sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# Add any project specific keep options here:


###-----------指定代码的压缩级别------------
-optimizationpasses 5
###-----------是否使用大小写混合------------
-dontusemixedcaseclassnames
###-----------混淆时是否做预校验------------
-dontpreverify
###-----------混淆时是否记录日志------------
-verbose
###-----------忽略警告------------
-ignorewarnings
-keepattributes EnclosingMethod

###-----------保证异常时显示行号------------
-renamesourcefileattribute SourceFile
-keepattributes SourceFile,LineNumberTable

###-----------注解------------
-keepattributes *Annotation*

###-----------泛型------------
-keepattributes Signature

###-----------异常------------
-keepattributes Exceptions

###-----------去掉代码里的Log------------
-assumenosideeffects class android.util.Log {
public static boolean isLoggable(java.lang.String,int);
public static *** d(...);
public static *** v(...);
public static *** i(...);
public static *** w(...);
public static *** e(...);
}

###-----------混淆时所采用的算法------------
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*

###-----------保持Activity类不被混淆------------
-keep public class * extends android.app.Activity
###-----------保持AppCompatActivity类不被混淆------------
-keep public class * extends android.support.v7.app.AppCompatActivity
###-----------保持DialogFragment类不被混淆------------
-keep public class * extends android.app.DialogFragment
###-----------保持Application类不被混淆------------
-keep public class * extends android.app.Application
###-----------保持Service类不被混淆------------
-keep public class * extends android.app.Service
###-----------保持BroadcastReceiver类不被混淆------------
-keep public class * extends android.content.BroadcastReceiver
###-----------保持ContentProvider类不被混淆------------
-keep public class * extends android.content.ContentProvider
###-----------保持BackupAgentHelper类不被混淆------------
-keep public class * extends android.app.backup.BackupAgentHelper
###-----------保持Preference类不被混淆------------
-keep public class * extends android.preference.Preference
###-----------保持ILicensingService类不被混淆------------
-keep public class com.android.vending.licensing.ILicensingService

###-----------保持 native 方法不被混淆------------
-keepclasseswithmembernames class * {
native <methods>;
}

###-----------保持自定义控件类不被混淆------------
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
}

###-----------保持自定义控件类不被混淆------------
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet, int);
}

###-----------保持自定义控件类不被混淆------------
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}

###-----------保持枚举 enum 类不被混淆------------
-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 *;
}

###-----------保持 retrofit 不被混淆------------
-dontwarn retrofit2.**
-keep class retrofit2.** { *; }
-dontwarn javax.annotation.**

###-----------保持 okhttp 不被混淆------------
-dontwarn com.squareup.okhttp3.**
-keep class com.squareup.okhttp3.** { *;}
-dontwarn okio.**

###-----------保持 GreenDao 不被混淆------------
-keepclassmembers class * extends org.greenrobot.greendao.AbstractDao {
public static java.lang.String TABLENAME;
}
-keep class **$Properties

###-----------保持 Zbar 不被混淆------------
-keep class net.sourceforge.zbar.** { *; }
-keep interface net.sourceforge.zbar.** { *; }
-dontwarn net.sourceforge.zbar.**

###-----------保持 Glide 不被混淆------------
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public class * extends com.bumptech.glide.module.AppGlideModule
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
**[] $VALUES;
public *;
}

###-----------保持 eventbus 不被混淆------------
-keepattributes *Annotation*
-keepclassmembers class ** {
@org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
# Only required if you use AsyncExecutor
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
<init>(java.lang.Throwable);
}

###-----------保持 MPAndroidChart 不被混淆------------
-keep class com.github.mikephil.charting.** { *; }

###-----------保持 bugly 不被混淆------------
-dontwarn com.tencent.bugly.**
-keep public class com.tencent.bugly.**{*;}

###-----------保持 gson 不被混淆------------
-keep class sun.misc.Unsafe { *; }
-keep class com.google.gson.stream.** { *; }

###-----------保持 Rxjava RxAndroid 不被混淆------------
-dontwarn sun.misc.**
-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {
long producerIndex;
long consumerIndex;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef {
rx.internal.util.atomic.LinkedQueueNode producerNode;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef {
rx.internal.util.atomic.LinkedQueueNode consumerNode;
}

###-----------保持 volley 不被混淆------------
-keep class com.android.volley.** { *; }
-keep class com.android.volley.toolbox.** { *; }

###-----------保持 本项目的gson实体类 不被混淆------------
###-----------网络请求解析的实体类一定不要混淆------------
-keep class your-package.entity.** { *; }

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

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
2
3
[关键字][]{
[成员]
}

包: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动态加载

  • 源程序:需要加壳处理的被保护代码

加壳的利与弊

优势

  • 保护自己核心代码算法,提高破解/盗版/二次打包的难度

  • 还可以缓解代码注入/动态调试/内存注入攻击

劣势

  • 影响兼容性

  • 影响程序运行效率

常用的加壳方式

谢谢老板,请尽情用红包来蹂躏我吧!!!
0%