现状:很多apk会被黑客反编译成smali文件,然后修改或植入恶意代码后重新编译成apk发布到市场。
解决要点:
1,代码混淆。可使用高级商用混淆工具DexGuard。(此法容易被攻破)
2,apk运行时进行签名验证和crc校验码验证。(此法的验证代码容易被黑客注销掉,使之不起作用)
个人总结的比较安全的解决方式:(这里不考虑网络通信被中间人拦截情况,后面会给出防止网络拦截的ssl验证方法)
1)服务器保存发布时apk的加密的签名和加密的crc校验码。
2)客户端的每次网络访问请求都需上传与服务器相同加密方式加密的apk签名和crc码。(这里使用so库获取加密的签名和crc码,比较安全。甚至可以考虑对so库加壳保护)
这样一来黑客不能注销掉验证,因为服务必须接收到正确的签名和crc码才允许继续通信。黑客也不能打印我们加密后的crc码,因为加入打印代码会使crc发生改变,从而得到错误的crc码。
漏洞:黑客获得apk原始crc码,并且攻破加壳的so库。(这难度非常大!!)
备注:crc指classes.dex的校验码。
部分实例代码:
package com.test;
import java.util.HashMap;
import java.util.Map;
import android.app.Activity;
import android.os.Bundle;
public class TestActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
LoginServelet sv=new LoginServelet();
Map<String,Object> params=new HashMap<String,Object>();
params.put("userName", "test");
params.put("password", "123");
params.put("appSignCiphered", ProguardNavtive.getCipheredAppSign());
params.put("crcCiphered",ProguardNavtive.getCipheredAppCrc());
sv.get(params);
}
}
package com.test;
import java.util.Map;
public class LoginServelet {
public void get(Map<String,Object> params){
String userName=(String) params.get("userName");
String password=(String) params.get("password");
String appSignCiphered=(String) params.get("appSignCiphered");
String crcCiphered=(String) params.get("crcCiphered");
/**
* upload to server check.
*/
}
}
package com.test;
/**
* 可以考虑对so库加壳,增加破解难度。
*
* @author lchli
*
*/
public class ProguardNavtive {
public static native String getCipheredAppSign();
public static native String getCipheredAppCrc();
}
package com.test;
import java.io.IOException;
import java.security.MessageDigest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
public class AppUtil {
public static String getAppSign(Context context){
PackageManager pm = context.getPackageManager();
try {
PackageInfo info = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
Signature sig = info.signatures[0];
return calcSHA1(sig.toByteArray());
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
private static String calcSHA1(byte[] sig) throws Exception{
MessageDigest digest = MessageDigest.getInstance("SHA1");
digest.update(sig);
byte[] sigHash = digest.digest();
return bytesToHex(sigHash);
}
private static String bytesToHex(byte[] bytes){
final char[] hexArray="0123456789ABCDEF".toCharArray();
char[] hexChars = new char[bytes.length * 2];
for ( int j = 0; j < bytes.length; j++ ) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
public static long getAppCrc(Context context){
try {
ZipFile zip = new ZipFile(context.getPackageCodePath());
ZipEntry entry = zip.getEntry("classes.dex");
return entry.getCrc();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
throw new RuntimeException(e);
}
}
}