Android 程序的反破解技术

我们知道,利用 apktool 可以将 apk 反编译为 smali 文件,利用 dex2jar 也可以将 apk 反编译为 jar 文件。这样的话,破解者就可以根据关键代码(比如资源文件中的字符串),修改代码,然后再利用 apktool 重新编译,并运行 signapk.bat 重新签名打包为己所用,而你辛辛苦苦几个月的努力一下回到解放前!

最近看过《Android 软件安全与逆向分析》之后,又有了不少收获。

那么,怎样防止破解呢?其实之前介绍的利用 proguard 进行代码混淆就是一种方式,它可以有效增加利用 dex2jar 反编译后破解的难度。另外,也可以通过检测调试器、模拟器、签名的 hash 值和 classes.dex 文件的 crc 值来确定确认 apk 文件的完整性。

检测调试器

我们发布时将 AndroidManifest.xml 文件中 application 标签的 android:debuggable 属性设为 false ,程序运行时再去检测:

public void checkDebug(){
	if((getApplicationInfo().flags&=ApplicationInfo.FLAG_DEBUGGABLE)!=0){
		android.os.Process.killProcess(android.os.Process.myPid());
	}
}

此外,Android SDK 还提供了一个专门检测 debugger 是否连接的方法:

android.os.Debug.isDebuggerConnected();

这样就不用在 AndroidManifest.xml 里配置字段了。

检测模拟器

通过 dab shell getprop 可以发现模拟器客真机这几个属性不一致:

  • ro.product.model : 模拟器中为 sdk ,真机中为具体型号;

  • ro.build.tags : 模拟器中为 test-keys ,真机中为 release-keys ;

  • ro.kernel.qemu : 模拟器中为 1 ,真机中不存在;

下面以第三个字段为例做检测:

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 类提供了读取签名信息方法:

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 :

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 运行这个批处理:

for %%i in (*.apk) do dex2jar %%i

这种思路理论上是行得通的,但我没有亲测过,这里就不再多说了。