在移动应用安全测试领域,加密函数的逆向分析一直是技术难点。传统静态分析方法需要反编译APK/IPA文件,不仅耗时耗力,还容易受到代码混淆和加固方案的影响。而Frida的动态插桩技术,则为我们提供了一把"万能钥匙"。
我曾在一次金融APP的安全评估中,仅用3小时就完成了原本需要2天工作量的加密逻辑分析。通过Frida实时Hook,我们发现了该APP存在三个关键安全问题:
这种效率提升主要得益于Frida的四大核心能力:
不同于静态分析只能看到代码逻辑,Frida可以在运行时捕获:
这相当于在加密算法的"输入端"和"输出端"都安装了监控探头。
更强大的是,我们可以:
这种能力在测试签名验证、证书校验等安全机制时尤为有用。
无论是Android的Java层、Native层,还是iOS的Objective-C/Swift,甚至是C/C++编写的底层库,Frida都能无缝Hook。我曾用同一套脚本同时测试过Android的Bouncy Castle和iOS的CommonCrypto加密实现。
对于使用ProGuard、DexProtector等工具混淆的APP,Frida可以通过以下方式突破限制:
实战经验:在Hook高防护等级的APP时,建议先用Frida枚举所有类和方法,再通过输入输出特征筛选目标,而不是直接搜索特定类名。
需要准备:
pip install frida-tools)避坑提示:Android 8.0以上设备需注意SELinux策略,建议临时设置为permissive模式:
adb shell setenforce 0
对于未混淆的APP,可以直接通过类名定位:
javascript复制Java.perform(() => {
const CryptoClass = Java.use("com.example.security.CryptoUtils");
});
遇到混淆时,可采用特征搜索法:
javascript复制Java.enumerateLoadedClasses({
onMatch: function(className) {
if (className.includes("cipher") || className.includes("encrypt")) {
console.log("[*] Found potential crypto class: " + className);
}
},
onComplete: function() {}
});
javascript复制Java.perform(() => {
const TargetClass = Java.use("com.example.CryptoUtils");
// Hook加密方法
TargetClass.encrypt.overload('java.lang.String').implementation = function(input) {
console.log("\n[+] Encryption Triggered");
console.log("[-] Plaintext: " + input);
// 打印调用栈
console.log("[-] Call Stack:");
console.log(Java.use("android.util.Log").getStackTraceString(
Java.use("java.lang.Exception").$new()
));
// 调用原方法并打印结果
const result = this.encrypt(input);
console.log("[-] Ciphertext: " + result);
return result;
};
});
javascript复制TargetClass.getSecretKey.implementation = function() {
const key = this.getSecretKey();
// 打印密钥信息
console.log("[!] Key Retrieved:");
console.log("[-] Key Hex: " + bytesToHex(key));
console.log("[-] Key Length: " + key.length + " bytes");
// 测试篡改密钥
// return Java.array('byte', [0x01, 0x02...]);
return key;
};
function bytesToHex(bytes) {
return Array.from(bytes).map(b =>
b.toString(16).padStart(2, '0')
).join('');
}
bash复制frida -U -f com.target.app -l hook_crypto.js --no-pause
建议使用以下优化参数:
bash复制frida -U \
-f com.target.app \
-l hook_crypto.js \
--runtime=v8 \
--enable-jit \
--kill-on-disconnect
性能提示:对于高频调用的加密函数,建议在脚本中添加调用频率限制,避免日志刷屏:
javascript复制let callCount = 0; const MAX_CALLS = 10; TargetClass.encrypt.implementation = function(input) { if (callCount++ < MAX_CALLS) { // 打印日志 } return this.encrypt(input); };
建议将输出重定向到文件并添加时间戳:
bash复制frida -U ... | ts '[%Y-%m-%d %H:%M:%S]' > crypto_hook.log
可以使用awk快速分析日志:
bash复制# 统计加密调用次数
grep "Encryption Triggered" crypto_hook.log | wc -l
# 提取所有明文-密文对
awk '/Plaintext:/{p=$0} /Ciphertext:/{print p "\n" $0 "\n"}' crypto_hook.log
对于深度混淆的APP,可以通过以下特征识别加密方法:
示例:识别AES加密方法
javascript复制Java.enumerateMethods('*!*encrypt*')
.filter(m => m.argumentTypes.length === 1)
.forEach(m => {
console.log(`Found potential encrypt method: ${m.className}.${m.methodName}`);
});
Hook系统加密基类:
javascript复制const Cipher = Java.use("javax.crypto.Cipher");
Cipher.doFinal.overload('[B').implementation = function(input) {
console.log(`Cipher used: ${this.getAlgorithm()}`);
console.log(`Input: ${bytesToHex(input)}`);
const result = this.doFinal(input);
console.log(`Output: ${bytesToHex(result)}`);
return result;
};
javascript复制const SM4Cipher = Java.use("org.bouncycastle.crypto.engines.SM4Engine");
SM4Cipher.processBlock.implementation = function(input, inOff, output, outOff) {
console.log("[SM4 Block Cipher]");
console.log(`Input Block: ${bytesToHex(input.slice(inOff, inOff+16))}`);
const result = this.processBlock(input, inOff, output, outOff);
console.log(`Output Block: ${bytesToHex(output.slice(outOff, outOff+16))}`);
return result;
};
javascript复制const SM3Digest = Java.use("org.bouncycastle.crypto.digests.SM3Digest");
SM3Digest.update.implementation = function(input, offset, length) {
console.log(`[SM3 Update] Data: ${bytesToHex(input.slice(offset, offset+length))}`);
return this.update(input, offset, length);
};
SM3Digest.doFinal.implementation = function(output, offset) {
const result = this.doFinal(output, offset);
console.log(`[SM3 Final] Hash: ${bytesToHex(output.slice(offset, offset+32))}`);
return result;
};
javascript复制const openssl = Process.findModuleByName("libcrypto.so");
// Hook EVP加密函数
const EVP_CipherUpdate = openssl.getExportByName("EVP_CipherUpdate");
Interceptor.attach(EVP_CipherUpdate, {
onEnter(args) {
this.ctx = args[0];
this.in = args[1];
this.inl = args[2];
this.out = args[3];
this.outl = args[4];
console.log(`[OpenSSL] Cipher Update:`);
console.log(`- Input: ${hexdump(this.in, { length: parseInt(this.inl) })}`);
},
onLeave(retval) {
console.log(`- Output: ${hexdump(this.out, { length: parseInt(this.outl) })}`);
}
});
对于非标准加密实现,可以通过内存特征定位:
javascript复制// 搜索内存中的S盒(AES特征)
const sboxPattern = "637c777bf26b6fc53001672bfed7ab76";
const sboxAddress = Memory.scanSync(
Process.findModuleByName("libnativecrypto.so"),
sboxPattern
)[0].address;
console.log(`Found SBox at ${sboxAddress}`);
javascript复制const SignUtils = Java.use("com.example.SignatureUtils");
SignUtils.generateSign.implementation = function(params) {
console.log("[Signature Params]");
Object.keys(params).forEach(k => {
console.log(`- ${k}: ${params[k]}`);
});
const sign = this.generateSign(params);
console.log(`- Signature: ${sign}`);
// 测试参数顺序变化
const tamperedParams = JSON.parse(JSON.stringify(params));
tamperedParams.timestamp = "0";
const tamperedSign = this.generateSign(tamperedParams);
console.log(`[Tamper Test] Modified signature: ${tamperedSign}`);
return sign;
};
javascript复制const Cipher = Java.use("javax.crypto.Cipher");
Cipher.getInstance.overload('java.lang.String').implementation = function(algorithm) {
console.log(`[!] Cipher Algorithm: ${algorithm}`);
// 检测不安全的模式
if (algorithm.includes("ECB") || !algorithm.includes("/")) {
console.warn("Insecure cipher mode detected!");
}
return this.getInstance(algorithm);
};
javascript复制const OkHttpClient = Java.use("okhttp3.OkHttpClient");
OkHttpClient.$init.implementation = function(builder) {
console.log("[OkHttpClient Initialization]");
// 检测证书固定配置
const certPinner = builder.certificatePinner;
if (certPinner) {
console.log("Certificate Pinning Enabled:");
certPinner.pins.forEach(pin => {
console.log(`- ${pin.pattern}: ${pin.hashAlgorithm}/${pin.hash}`);
});
} else {
console.warn("No certificate pinning configured!");
}
return this.$init(builder);
};
javascript复制const X509TrustManager = Java.use("javax.net.ssl.X509TrustManager");
X509TrustManager.checkServerTrusted.implementation = function(chain, authType) {
console.log("[SSL Certificate Verification]");
chain.forEach(cert => {
console.log(`- Subject: ${cert.getSubjectDN()}`);
console.log(`- Issuer: ${cert.getIssuerDN()}`);
console.log(`- Serial: ${cert.getSerialNumber()}`);
});
// 继续原验证流程
return this.checkServerTrusted(chain, authType);
};
javascript复制const KeyGenerator = Java.use("javax.crypto.KeyGenerator");
KeyGenerator.init.implementation = function(params) {
console.log("[Key Generation]");
console.log(`- Algorithm: ${this.getAlgorithm()}`);
console.log(`- Key Size: ${params.getKeySize()}`);
// 检测弱密钥
if (params.getKeySize() < 256 && this.getAlgorithm() === "AES") {
console.error("Weak AES key size detected!");
}
return this.init(params);
};
javascript复制const SecureRandom = Java.use("java.security.SecureRandom");
SecureRandom.nextBytes.implementation = function(bytes) {
const result = this.nextBytes(bytes);
// 记录随机数生成
console.log(`[SecureRandom] Generated ${bytes.length} bytes:`);
console.log(hexdump(bytes));
return result;
};
Failed to spawn: unable to connect to remote frida-serveradb forward tcp:27042 tcp:27042是否执行Error: unable to find method overloadjavascript复制// 1. 检查方法签名
console.log(Java.use("com.example.Class").methodName.overloads);
// 2. 尝试模糊匹配
Java.choose("com.example.Class", {
onMatch: function(instance) {
console.log(instance.methodName());
}
});
javascript复制// 在脚本开头预加载常用类
const preloadClasses = [
"java.lang.String",
"javax.crypto.Cipher",
"android.util.Log"
];
preloadClasses.forEach(cls => {
try { Java.use(cls); } catch (e) {}
});
javascript复制// 使用批量Hook减少性能开销
const batchHook = (className, methodNames) => {
const clazz = Java.use(className);
methodNames.forEach(method => {
clazz[method].implementation = function() {
console.log(`[BatchHook] ${className}.${method} called`);
return this[method].apply(this, arguments);
};
});
};
batchHook("com.example.Crypto", ["encrypt", "decrypt", "getKey"]);
javascript复制// 绕过常见反调试检查
const Debug = Java.use("android.os.Debug");
Debug.isDebuggerConnected.implementation = function() {
return false;
};
const SystemProperties = Java.use("android.os.SystemProperties");
SystemProperties.get.overload('java.lang.String').implementation = function(key) {
if (key.includes("debug") || key.includes("trace")) {
return "";
}
return this.get(key);
};
javascript复制// 使用低侵入式Hook
function stealthHook(className, methodName) {
const target = Java.use(className);
const original = target[methodName];
target[methodName] = function() {
// 不打印日志,仅记录到内存
const result = original.apply(this, arguments);
return result;
};
}
在实际项目中,我发现最有效的Hook策略是"渐进式深入":先进行最小化的必要Hook,确认核心加密逻辑位置后,再逐步增加监控点。这样既能避免触发反调试机制,又能保持脚本的高效运行。