1. 为什么需要跨平台加密算法动态分析
在移动安全研究领域,加密算法的分析一直是个头疼的问题。我遇到过不少开发者,他们经常抱怨:每次分析不同平台的加密实现都要重写一套工具,Android和iOS的代码完全不通用,光是环境切换就浪费大量时间。更麻烦的是,同一个算法在不同系统版本上可能有多种实现方式,比如Android 7.0之后的AES加密就和之前版本有所不同。
这时候Frida的价值就体现出来了。它就像个万能钥匙,能同时打开Android和iOS的加密黑盒。我去年分析某金融类APP时,就靠一个脚本同时抓取了iOS的RSA签名和Android的AES加密数据,省去了反复切换工具的麻烦。这种跨平台能力在分析混合开发框架(如Flutter)的应用时尤其有用,因为它们的加密模块往往共用同一套实现。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Frida环境快速搭建指南
2.1 基础环境配置
先说说我的标准工作环境:一台Ubuntu 20.04的主机跑Frida服务端,测试机准备Android 9和iOS 12.4两个设备。这里有个坑要注意——Frida的Python包版本必须和服务端严格匹配,我有次因为版本不兼容折腾了半天。
安装其实很简单:
bash复制pip install frida-tools==12.8.0
adb push frida-server-12.8.0-android-arm64 /data/local/tmp/
iOS设备稍微麻烦些,需要先越狱然后通过Cydia安装Frida。建议新手直接使用checkra1n越狱,成功率比较高。记得在设备上测试连接:
javascript复制frida-ps -U
2.2 通用脚本框架设计
我习惯把脚本分成三个模块:算法识别、数据捕获和格式转换。下面这个模板是我经过十几个项目迭代出来的:
javascript复制Java.perform(function () {
// 通用工具函数
const utils = {
toHex: bytes => Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join(':'),
printStack: function() {
const Exception = Java.use("java.lang.Exception");
console.log(Exception.$new().getStackTrace().join('\n'));
}
};
// 算法处理器
const processors = {
AES: function(cipher) {
// 具体实现后面会讲
}
};
});
3. 五大加密算法的通杀实现
3.1 AES算法的动态Hook
AES在移动端最常见的有三种模式:ECB、CBC和GCM。通过Hook Cipher类的init和doFinal方法可以通杀所有变种:
javascript复制const cipher = Java.use("javax.crypto.Cipher");
cipher.init.overload('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec').implementation = function (opmode, key, params) {
this.mode = opmode; // 1加密 2解密
this.algorithm = this.getAlgorithm();
// 提取密钥和IV
const keyBytes = key.getEncoded();
let iv = null;
if (params) {
iv = Java.cast(params, Java.use("javax.crypto.spec.IvParameterSpec")).getIV();
}
console.log(`[AES] 模式: ${this.algorithm}`);
console.log(`[AES] 密钥: ${utils.toHex(keyBytes)}`);
if (iv) console.log(`[AES] IV: ${utils.toHex(iv)}`);
return this.init.apply(this, arguments);
};
3.2 RSA算法的关键点捕获
RSA主要关注密钥生成和签名验证两个环节。这里有个技巧:Android上OpenSSL实现的私钥需要特殊处理:
javascript复制const rsaKey = Java.use("java.security.KeyPair");
rsaKey.getPrivate.implementation = function () {
const key = this.getPrivate.apply(this);
// 过滤OpenSSL私钥
if (!key.getClass().getName().contains("OpenSSL")) {
console.log(`[RSA] 私钥: ${utils.toHex(key.getEncoded())}`);
}
return key;
};
3.3 哈希算法的统一处理
MD5、SHA1这些哈希算法虽然简单,但hook时要注意多态调用。MessageDigest的update有四种重载方式:
javascript复制const digest = Java.use("java.security.MessageDigest");
['[B', 'java.nio.ByteBuffer', 'byte', '[BII'].forEach(type => {
digest.update.overload(type).implementation = function (data) {
const alg = this.getAlgorithm();
if (type === '[B') {
console.log(`[${alg}] 输入数据: ${utils.toHex(data)}`);
}
return this.update.apply(this, arguments);
};
});
4. 实战中的疑难问题解决
4.1 系统版本兼容性处理
Android 8.0之后引入的API变化最让人头疼。比如KeyStore的密钥获取方式就变了,我的解决方案是双重hook:
javascript复制if (Android.version >= 26) {
const keyStore = Java.use("android.security.keystore.KeyStore");
keyStore.getKey.overload('java.lang.String', '[C').implementation = function () {
const key = this.getKey.apply(this, arguments);
console.log(`[KeyStore] 获取密钥: ${key}`);
return key;
};
}
4.2 数据流关联分析
当遇到多层加密时,建议给每次操作生成唯一ID方便追踪:
javascript复制let operationId = 0;
cipher.doFinal.implementation = function () {
const id = ++operationId;
console.log(`[操作${id}] 开始 ${this.algorithm}`);
const result = this.doFinal.apply(this, arguments);
console.log(`[操作${id}] 结果: ${utils.toHex(result)}`);
return result;
};
5. 效率优化与安全防护
5.1 脚本性能调优
大量日志输出会拖慢运行速度,我通常添加过滤条件:
javascript复制const targetPackages = ["com.bank.app", "com.payment.service"];
if (!targetPackages.includes(Java.use("android.app.ActivityThread").currentApplication().getPackageName())) {
return; // 非目标应用直接跳过
}
5.2 反调试对抗措施
有些应用会检测Frida,常见的手段是检查端口或进程名。可以通过这些方式绕过:
javascript复制// 重命名Frida线程
const threadClass = Java.use("java.lang.Thread");
threadClass.setName.overload('java.lang.String').implementation = function (name) {
if (name.includes("frida")) {
return this.setName.call(this, "system_thread");
}
return this.setName.apply(this, arguments);
};
这套方案在最近三个金融类APP的分析中都取得了不错的效果,平均能节省40%的分析时间。最关键的是,一次编写的脚本可以复用在多个项目,长期来看性价比很高。
