我们知道,利用 apktool 可以将 apk 反编译为 smali 文件,利用 dex2jar 也可以将 apk 反编译为 jar 文件。这样的话,破解者就可以根据关键代码(比如资源文件中的字符串),修改代码,然后再利用 apktool 重新编译,并运行 signapk.bat
重新签名打包为己所用,而你辛辛苦苦几个月的努力一下回到*!
最近看过《Android 软件安全与逆向分析》之后,又有了不少收获。
那么,怎样防止破解呢?其实之前介绍的利用 proguard 进行代码混淆就是一种方式,它可以有效增加利用 dex2jar 反编译后破解的难度。另外,也可以通过检测调试器、模拟器、签名的 hash 值和 classes.dex
文件的 crc 值来确定确认 apk 文件的完整性。
检测调试器
我们发布时将 AndroidManifest.xml
文件中 application
标签的 android:debuggable
属性设为 false
,程序运行时再去检测:
1
2
3
4
5
|
public void checkDebug(){
if((getApplicationInfo().flags&=ApplicationInfo.FLAG_DEBUGGABLE)!=0){
android.os.Process.killProcess(android.os.Process.myPid());
}
}
|
此外,Android SDK 还提供了一个专门检测 debugger 是否连接的方法:
1
|
android.os.Debug.isDebuggerConnected();
|
这样就不用在 AndroidManifest.xml
里配置字段了。
检测模拟器
通过 dab shell getprop
可以发现模拟器客真机这几个属性不一致:
ro.product.model
: 模拟器中为 sdk
,真机中为具体型号;
ro.build.tags
: 模拟器中为 test-keys
,真机中为 release-keys
;
ro.kernel.qemu
: 模拟器中为 1
,真机中不存在;
下面以第三个字段为例做检测:
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
|
public boolean isRunningInEmulator(){
boolean qemuKernel=false;
Process process=null;
DataOutpusStream os=null;
try{
process=Runtime.getRuntime().exec("get prop 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);
}catch(Exception e){
e.printStackTrace();
}finally{
try{
if(os!=null){
os.close();
}
}catch(Exception e){
e.printStackTrace();
}
}
return qemuKernel;
}
|
检查签名的 hash 值
Android 的 PackageManager
类提供了读取签名信息方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public int getSignature(Context context, String packageName){
PackageManager pm=context.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 e){
sig=0;
e.printStackTrace();
}
return sig;
}
|
打包发布前我们将这个 hash 值保存在 server 端,程序运行时再去比对。
检查 classes.dex 文件 CRC 值
apk 文件本质上是 zip 压缩文件,而 Android SDK 自带了读取 zip 压缩包 CRC 值的 API :
1
2
3
4
5
6
7
8
9
10
11
12
|
public long getDexCrc(Context context){
long crc=0;
ZipFile zf;
try{
zf=new ZipFile(context.getApplicationContext().getPackageCodePath());
ZipEntry ze=zf.getEntry("classes.dex");
crc=ze.getCrc();
}catch(Exception e){
e.printStackTrace();
}
return crc;
}
|
打包发布前我们也可以将这个 crc 值保存在 server 端,程序运行时再去比对。
将以上几种方式结合起来,就可以大大增强 apk 文件的破解难度。
最后,网上有些大神说还可以通过对 apktool 和 dex2jar 等反编译工具进行压力测试,以得到错误信息,而这些工具是开源的,这样我们就可以顺藤摸瓜地找到这些工具本身的漏洞,进而在我们的代码中加以利用,达到釜底抽薪的作用。例如对 dex2jar 运行这个批处理:
1
|
for %%i in (*.apk) do dex2jar %%i
|
这种思路理论上是行得通的,但我没有亲测过,这里就不再多说了。