Android平台由于自身的开放性,应用被反编译、二次打包、动态调试的情况屡见不鲜。Android Defense防御测试就是站在攻击者的角度,主动验证应用的安全防护能力是否真正有效。这类测试不同于普通的功能测试,它关注的是攻击面:代码层面能否被轻松逆向、数据层面是否存在明文存储、通信层面能否被抓包绕过、运行层面能否被注入hook。下面我们从攻击面分析、加固手段验证、运行时防护测试这几个角度,系统地讲解防御测试的完整流程。

一、常见的Android攻击面有哪些
做防御测试之前,首先要弄清楚攻击者能从哪些地方下手。第一个攻击面是APK逆向。Android安装包本质上是一个zip压缩包,攻击者拿到apk后可以直接用apktool解包,拿到smali代码、资源文件甚至AndroidManifest.xml的原文,再用jadx可以反编译出接近源码的Java代码。如果应用没有做任何混淆,攻击者几乎可以完整还原业务逻辑,找到加密密钥、接口签名算法等核心信息。
第二个攻击面是本地数据存储。很多开发者习惯把token、用户信息明文存进SharedPreferences或者SQLite,在有root权限的设备上,这些文件可以直接被读取。第三个攻击面是网络通信,如果应用没有做证书校验(SSL Pinning),攻击者通过安装自定义CA证书配合Charles或Burp Suite就能抓到全部明文请求。第四个攻击面是动态注入,攻击者可以用Frida在运行时hook任意Java方法或native函数,绕过登录校验、篡改支付金额,这类攻击对金融类应用威胁极大。
梳理攻击面的意义在于明确测试清单。一份完整的防御测试方案至少要覆盖:反编译难度评估、本地存储加密验证、通信抓包测试、动态注入防护测试、二次打包对抗测试这五项,缺一不可。
二、代码层加固:混淆与加壳的验证方法
代码层防护的第一道防线是混淆。开启ProGuard或R8后,类名、方法名会被替换成无意义的短字符,攻击者阅读反编译代码的成本会大幅提高。配置方式很简单,在build.gradle中设置即可:
android {
buildTypes {
release {
minifyEnabled true // 开启代码混淆
shrinkResources true // 开启资源压缩
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}验证混淆是否生效的方法很直接:用jadx打开release包,如果看到的类名是a、b、c这种形式,说明混淆已经生效。需要注意的是,混淆只是提高阅读成本,并不能阻止逆向,核心算法和密钥不能依赖混淆来保护。
第二道防线是加壳。加壳的原理是把原始dex文件加密,运行时由壳程序在内存中解密加载,常见的方案有腾讯乐固、360加固、梆梆安全等商业产品。验证加壳效果时,用apktool解包加壳后的apk,会发现classes.dex只是一个加载器,真正的业务dex并不存在,此时可以判定加壳生效。但加壳也不是万能的,市面上已经存在脱壳框架,所以关键逻辑建议放到native层用C++实现,配合OLLVM混淆,安全性会更好。
对抗二次打包也是代码层的重要测试项。攻击者修改smali后需要重新签名,因此应用可以在运行时校验自己的签名哈希值:
public static boolean checkSignature(Context context) {
try {
PackageInfo info = context.getPackageManager()
.getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
byte[] sig = info.signatures[0].toByteArray();
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] digest = md.digest(sig);
String hash = bytesToHex(digest);
// 与发布时计算的签名哈希比对,不一致说明被二次打包
return RELEASE_SIGN_HASH.equals(hash);
} catch (Exception e) {
return false;
}
}测试时可以用keytool生成一个新证书重新签名apk,如果应用启动后能检测到签名异常并退出,说明防护有效。当然签名校验本身也可能被hook掉,所以更稳妥的做法是把校验逻辑放到native层,并在多个位置交叉校验。
三、数据与通信层防御测试
本地存储方面,防御测试要验证敏感数据是否加密存储。推荐使用Android Keystore系统,它由硬件安全模块保护私钥不可导出。配合EncryptedSharedPreferences可以做到密文落盘:
MasterKey key = new MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build();
SharedPreferences sp = EncryptedSharedPreferences.create(
context, "secure_prefs", key,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM);
sp.edit().putString("user_token", token).apply();测试验证的方法是:在root设备上用cat命令直接查看/data/data目录下的xml文件,如果看到的是密文而不是明文token,说明存储加密生效。同理,SQLite数据库可以用SQLCipher加密,测试时尝试用sqlite3命令直接打开,如果提示文件加密或者乱码,防护即为合格。
通信层测试的重点是SSL Pinning。开启证书锁定后,即使攻击者安装了自定义CA证书,应用也会拒绝信任,抓包工具将无法解密流量。OkHttp的实现示例:
CertificatePinner pinner = new CertificatePinner.Builder()
.add("api.ipipp.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.build();
OkHttpClient client = new OkHttpClient.Builder()
.certificatePinner(pinner)
.build();测试时用Charles配置代理抓包,未做Pinning的应用可以直接看到明文请求;做了Pinning的应用会抛出SSLPeerUnverifiedException导致请求失败,这就证明防护生效。要注意证书到期更新的问题,建议锁定中间证书或配置多个备份pin,避免换证书导致全网用户不可用。
四、运行时防护:对抗Frida与动态调试
运行时防护是防御测试中最难也最关键的一环。Frida是目前最主流的动态注入工具,它通过ptrace注入目标进程,注入frida-agent后可以用JavaScript脚本hook任意函数。对抗Frida的第一个思路是检测特征:Frida默认监听27042端口,服务器线程名包含frida字符串,可以遍历/proc/self/task目录检查线程名:
#include <dirent.h>
#include <string.h>
int detect_frida() {
DIR* dir = opendir("/proc/self/task");
struct dirent* entry;
while ((entry = readdir(dir)) != NULL) {
// frida的线程名包含 gum-js-loop 或 pool-frida 等特征
if (strstr(entry->d_name, "frida") != NULL) return 1;
char path[256];
char name[256];
snprintf(path, sizeof(path), "/proc/self/task/%s/comm", entry->d_name);
FILE* f = fopen(path, "r");
if (f) {
fgets(name, sizeof(name), f);
fclose(f);
if (strstr(name, "gum-js-loop") != NULL) return 1;
}
}
closedir(dir);
return 0;
}除了线程名检测,还可以检测maps中是否加载了frida相关so、尝试连接27042端口、比对maps文件中内存段的起始地址和内核返回值是否一致(Frida注入会留下痕迹)等手段,多层检测结合使用效果更好。
反调试检测也是必要的防护。攻击者常用IDA或gdb附加进程,native层可以通过ptrace(PTRACE_TRACEME)自附加来阻止其他调试器挂载,因为一个进程只能被一个调试器trace。再加上检测TracerPid是否为0,就能覆盖大部分动态调试场景。
测试验证的方法是:用Frida注入目标应用,尝试hook一个登录校验方法或修改某个boolean返回值。如果应用能主动闪退或者进入风控逻辑,说明注入检测生效;如果hook成功且应用无任何反应,则说明防护缺失,需要补充native层的检测逻辑。
五、总结与测试清单
Android Defense防御测试的本质是攻防对抗,没有一劳永逸的方案。混淆、加壳、加密存储、SSL Pinning、反注入检测这些手段单独使用都有被绕过的可能,组合起来分层防御才能构建有效的安全纵深。一份可落地的测试清单应该包括:用jadx和apktool验证反编译难度、在root设备上检查本地文件是否明文存储、用Charles验证抓包是否被证书锁定拦截、用Frida验证注入是否被检测、重新签名验证二次打包防护是否生效。
建议将上述测试项纳入CI流程,每次发版前自动执行,形成安全门禁。同时持续关注新的攻击手法,比如基于eBPF的隐藏注入、对加壳应用的内存dump脱壳技术等,防护策略也需要随之迭代。安全不是一次性的工作,而是伴随应用整个生命周期的持续过程。
Android安全测试应用加固漏洞检测修改时间:2026-09-05 21:27:04