Android软件的反破解技术

本文最后更新于:2020年3月19日 下午

Android逆向可以通过四个步骤:反编译、静态分析、动态调试、重编译。

对抗反编译

如何对抗反编译工具

寻找反编译工具的缺陷,使其反编译失败。

阅读反编译工具源码

需要较强的代码分析能力

压力测试

通过脚本测试大量apk,查找反编译工具的缺陷

对抗dex2jar

dex2jar可将dex文件转换为jar文件。

对抗静态分析

代码混淆技术

使用ProGuard进行混淆。

  1. 在project.properties文件中添加proguard.config=proguard.cfg
  2. 在proguard.cfg中设置需要混淆和保留的类和方法。

现在在AS里会有这个文件:

image-20200319161948039

配置 build.gradle,

1
2
3
4
5
6
7
8
9
10
11
12
13
release {
// 不显示log
buildConfigField "boolean", "LOG_DEBUG", "false"
// 混淆
minifyEnabled true
// Zipalign优化
zipAlignEnabled true
// 移除无用的resource
shrinkResources true
// 混淆配置
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}

我用debug模式混淆的时候,apk打不开,release版的成功了。

proguard

proguard有以下功能:

  • 压缩(Shrink):检测并移除代码中无用的类、字段、方法和特性(Attribute)。
  • 优化(Optimize):对字节码进行优化,移除无用的指令。
  • 混淆(Obfuscate):使用a,b,c,d这样简短而无意义的名称,对类、字段和方法进行重命名。
  • 预检(Preveirfy):在Java平台上对处理后的代码进行预检,确保加载的class文件是可执行的。

常用配置

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
######################## 公共 ########################

#指定代码的压缩级别
-optimizationpasses 5

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

#记录生成的日志数据,gradle build时在本项目根目录输出
#apk 包内所有 class 的内部结构
-dump class_files.txt
#未混淆的类和成员
-printseeds seeds.txt
#列出从 apk 中删除的代码
-printusage unused.txt
#混淆前后的映射
-printmapping mapping.txt

#移除log代码
-assumenosideeffects class android.util.Log {
public static *** v(...);
public static *** i(...);
public static *** d(...);
public static *** w(...);
public static *** e(...);
}

#不混淆反射用到的类
-keepattributes Signature
-keepattributes EnclosingMethod

#保持继承自系统类的class不被混淆
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-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.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep interface android.support.v4.app.** { *; }
-keep class android.support.v4.** { *; }
-keep public class * extends android.support.v4.**
-keep interface android.support.v7.app.** { *; }
-keep class android.support.v7.** { *; }
-keep public class * extends android.support.v7.**
-keep public class * extends android.app.Fragment
-keep class * extends android.**{*;}

#不混淆Serializable接口的子类中指定的某些成员变量和方法
-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();
}

######################## Module自定义 ########################

############ 不混淆引用的jar ############

#不混淆butterknife
-keepclasseswithmembernames class * {
@butterknife.* <fields>;
}
-keepclasseswithmembernames class * {
@butterknife.* <methods>;
}
-dontwarn butterknife.internal.**

#不混淆AndroidBootstrap
-keep class com.beardedhen.androidbootstrap.**{*;}
-dontwarn com.beardedhen.androidbootstrap.**

#不混淆应用宝自更新jar
-keep class com.qq.**
-dontwarn com.qq.**
-keep class com.tencent.**
-dontwarn com.tencent.**

############ 保持自定义控件不被混淆 ############

-keepclasseswithmembernames class * {
public <init>(android.content.Context);
}
-keepclasseswithmembernames class * {
public <init>(android.content.Context, android.util.AttributeSet);
}
-keepclasseswithmembernames class * {
public <init>(android.content.Context, android.util.AttributeSet, int);
}
-keepclasseswithmembernames class * {
public <init>(android.content.Context, android.util.AttributeSet, int, int);
}

############ 项目内部类的混淆配置 ############

#不混淆整个包
#-keep class com.test.test.**{*;}

#不混淆对外接口的public类名和成员名,否则外部无法调用
#-keep public interface com.test.test.**{*;}
#-keep public enum com.test.test.**{*;}
#-keep public class com.test.test.**{
# public *;
#}

#忽略项目中其他Module的警告 ############
#-dontwarn com.test.test.**

参考:https://blog.csdn.net/wangwangli6/article/details/79800520

NDK保护

使用NDK开发可增加逆向难度。

外壳保护

加壳加固。

对抗动态调试

检测调试器

通过检查debuggable的值是否被修改过判断是否被调试。

image-20200319163846893

检测调试器是否连接:

image-20200319164003984

检测模拟器

通过命令adb shell getprop对比模拟器和真机。

真机:

image-20200319164906736

模拟器:

image-20200319175344380

这不没区别吗。。。现在模拟器都这么先进了,书上有点落后了。。。

就贴一下旧代码吧,虽然不一定能用,以后也能作参考。

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
检测代码:
boolean isRunningInEmualtor() {
boolean qemuKernel = false;
Process process = null;
DataOutputStream os = null;
try{
process = Runtime.getRuntime().exec("getprop ro.kernel.qemu");
os = new DataOutputStream(process.getOutputStream());
BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream(),"GBK"));
os.writeBytes("exit\n");
os.flush();
process.waitFor();
qemuKernel = (Integer.valueOf(in.readLine()) == 1);
Log.d("com.droider.checkqemu", "检测到模拟器:" + qemuKernel);
} catch (Exception e){
qemuKernel = false;
Log.d("com.droider.checkqemu", "run failed" + e.getMessage());
} finally {
try{
if (os != null) {
os.close();
}
process.destroy();
} catch (Exception e) {

}
Log.d("com.droider.checkqemu", "run finally");
}
return qemuKernel;
}

public static String getProp(Context context, String property) {
try {
ClassLoader cl = context.getClassLoader();
Class SystemProperties = cl.loadClass("android.os.SystemProperties");
Method method = SystemProperties.getMethod("get", String.class);
Object[] params = new Object[1];
params[0] = new String(property);
return (String)method.invoke(SystemProperties, params);
} catch (Exception e) {
return null;
}
}

防止重编译

检查签名

检查签名的hashcode是否一致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public int getSignature(String packageName) {      
PackageManager pm = this.getPackageManager();
PackageInfo pi = null;
int sig = 0;
try {
pi = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
Signature[] s = pi.signatures;
sig = s[0].hashCode();
} catch (Exception e1) {
sig = 0;
e1.printStackTrace();
}
return sig;
}

校验保护

检测classes.dex的校验值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private boolean checkCRC() {
boolean beModified = false;
long crc = Long.parseLong(getString(R.string.crc));
ZipFile zf;
try {
zf = new ZipFile(getApplicationContext().getPackageCodePath());
ZipEntry ze = zf.getEntry("classes.dex");
Log.d("com.droider.checkcrc", String.valueOf(ze.getCrc()));
if (ze.getCrc() == crc) {
beModified = true;
}
} catch (IOException e) {
e.printStackTrace();
beModified = false;
}
return beModified;
}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!