保证apk安全是非常重要的工作,这篇文件文件我们来总结如何防逆向工程。
- Java代码混淆
- 资源文件混淆
- 使用HTTPS,校验密钥
- URL签名
- DEX加固
- so文件校验签名
Java代码混淆
Android 工程本身就已经有代码混淆的方法,我们只需要按照要求对代码进行混淆即可。建议开发者除了对自己编写的代码混淆,也要对引入的第三方代码进行混淆,这样可以大大降低代码被破解的风险。
资源文件混淆
推荐使用微信开源的 AndResGuard 对资源文件进行混淆,不但可以降低资源文件被盗用的风险,还可以降低APP的大小,实际测试使用 AndResGuard 后,apk降低了2M大小。
使用HTTPS,校验证书
使用HTTPS可以非常有效防止被抓包,避免APP请求的内容被破解,但是单纯引入HTTPS是不足够的,还是可以被抓包的,必须要对证书进行校验。
《Android 逆向工程:Charles + Android 实现 HTTPS 抓包》
《Android 网络安全:如何避免 Okhttp 的 HTTPS 请求被抓包》
URL签名
使用HTTPS可以避免被抓包,但是如果别人知道了请求的URL和参数,就可以直接请求URL获取数据,所以我们非常有必要在请求的URL增加签名,服务器确认这个请求是从APP发起了,而不是别人伪造的。
《Android 网络安全:URL签名验证的实现API防篡改》
DEX加固
加固也避免dex文件直接暴露在apk压缩文件中,但是加固也有明确的缺点,可能会影响启动的速度,apk体积增大,无法使用补丁,依然可以脱壳逆向。所以加固已经不再被推荐使用,基本上大公司的apk都不会使用加固技术。
so文件校验签名
这个是针对apk中有编写了C++代码的项目,一些重要的算法我们通常是写在C++中,比如URL签名算法,因为写在Java就意味着容易被破解。但是so文件也通常存在被盗用的风险,我们有必要在C++层对apk的签名进行校验,避免apk被重新打包,so被盗用。
可以从这个方法中读取Application:ActivityThread.currentActivityThread().getApplication()
不用直接通过传入context到C++层校验,因为context是可以伪造的。
int get_apk_signature_hash(JNIEnv *env) {
// 不直接使用传入的context,避免是mock出来的,通过ActivityThread.currentActivityThread().getApplication()方式获取Application
jclass ActivityThreadClass = env->FindClass("android/app/ActivityThread");
jmethodID currentActivityThreadMethodID = env->GetStaticMethodID(ActivityThreadClass,"currentActivityThread","()Landroid/app/ActivityThread;");
jobject ActivityThreadObject = env->CallStaticObjectMethod(ActivityThreadClass,
currentActivityThreadMethodID);
jmethodID getApplication = env->GetMethodID(ActivityThreadClass, "getApplication","()Landroid/app/Application;");
jobject context = env->CallObjectMethod(ActivityThreadObject, getApplication);
jclass ContextWrapperClass = env->FindClass("android/content/ContextWrapper");
//this.getPackageManager();
jmethodID getPackageManagerMethodID = env->GetMethodID(ContextWrapperClass, "getPackageManager","()Landroid/content/pm/PackageManager;");
jobject packageManagerObject = env->CallObjectMethod(context, getPackageManagerMethodID);
//this.getPackageName();
jmethodID getPackageNameMethodID = env->GetMethodID(ContextWrapperClass, "getPackageName",
"()Ljava/lang/String;");
jstring packageName = (jstring) env->CallObjectMethod(context, getPackageNameMethodID);
const char *tmp = env->GetStringUTFChars(packageName, NULL);
std::string packageName_ = tmp;
// 校验apk的包名
int findPackage = packageName_.find("com.xxx.xxx");
if(findPackage < 0){
return -4;
}
// packageManager->getPackageInfo(packageName, GET_SIGNATURES);
jclass PackageManagerClass = env->FindClass("android/content/pm/PackageManager");
jclass PackageInfoClass = env->FindClass("android/content/pm/PackageInfo");
jmethodID getPackageInfoMethodID = env->GetMethodID(PackageManagerClass, "getPackageInfo",
"(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
jobject packageInfo = env->CallObjectMethod(packageManagerObject, getPackageInfoMethodID,
packageName, 0x40); //GET_SIGNATURES = 64;
jfieldID fid = env->GetFieldID(PackageInfoClass, "signatures",
"[Landroid/content/pm/Signature;");
jobjectArray signatures = (jobjectArray) env->GetObjectField(packageInfo, fid);
jobject sig = env->GetObjectArrayElement(signatures, 0);
ContextWrapperClass = env->GetObjectClass(sig);
getPackageInfoMethodID = env->GetMethodID(ContextWrapperClass, "hashCode", "()I");
int sig_value = (int) env->CallIntMethod(sig, getPackageInfoMethodID);
return sig_value;
}
上面的C++代码对应的Java代码(简化思路)
int signaturesHash = getApplication().getPackageManager()
.getPackageInfo("com.xxx.xxx",0).signatures.hashCode();