1. Frida Hook技术概述Frida是一款强大的动态代码插桩工具它允许开发者在运行时对应用程序进行修改和调试。与传统的静态分析工具不同Frida通过注入JavaScript代码到目标进程中实现对原生代码和Java代码的动态Hook。这种技术特别适合用于安全研究、逆向工程和自动化测试等领域。Frida的核心工作原理是基于进程注入技术。它通过将一个小型引擎frida-agent注入到目标进程中然后在引擎中执行JavaScript代码。这些JavaScript代码可以访问和修改进程的内存空间拦截函数调用甚至动态修改程序逻辑。Frida支持多种平台包括Windows、macOS、Linux、iOS和Android使其成为跨平台动态分析的理想选择。在Android平台上Frida可以同时Hook Java层和Native层的代码。对于Java层Frida提供了简洁的API来查找和替换类方法对于Native层Frida可以拦截和修改函数调用甚至修改内存数据。这种双层的Hook能力使得Frida在Android安全研究和逆向工程中特别有价值。提示Frida的JavaScript API设计得非常直观即使没有深入的逆向工程经验开发者也能快速上手基本的Hook操作。2. Frida环境搭建与配置2.1 安装Frida工具链要开始使用Frida首先需要在开发机上安装Frida工具链。对于Python环境可以使用pip安装pip install frida-tools同时还需要在目标设备上安装Frida-server。根据目标设备的架构下载对应版本的frida-server# 下载对应版本的frida-server wget https://github.com/frida/frida/releases/download/15.1.17/frida-server-15.1.17-android-arm64.xz # 解压并推送到设备 unxz frida-server-15.1.17-android-arm64.xz adb push frida-server-15.1.17-android-arm64 /data/local/tmp/frida-server adb shell chmod 755 /data/local/tmp/frida-server2.2 启动Frida-server在目标设备上启动frida-serveradb shell su cd /data/local/tmp ./frida-server 2.3 验证安装在开发机上运行以下命令验证Frida是否正常工作frida-ps -U如果能看到设备上运行的进程列表说明安装成功。2.4 常见问题排查error: jni not found这通常是因为目标应用的JNI库没有正确加载。确保在Hook前目标库已经加载。unable to create process检查Python环境路径是否正确特别是使用Anaconda等虚拟环境时。frida雷电模拟器在模拟器中使用Frida时可能需要特殊配置端口转发adb forward tcp:27042 tcp:27042 adb forward tcp:27043 tcp:270433. Frida Hook基础实战3.1 Hook Java层方法Frida提供了简洁的API来Hook Java层的方法。以下是一个基本的Hook示例Java.perform(function () { // 获取目标类 var targetClass Java.use(com.example.target.ClassName); // Hook目标方法 targetClass.targetMethod.implementation function (param1, param2) { console.log(目标方法被调用参数1: param1 , 参数2: param2); // 修改参数 param1 modified_ param1; // 调用原始方法 var result this.targetMethod(param1, param2); // 修改返回值 return modified_ result; }; });3.2 Hook Native层函数对于Native层的HookFrida提供了Interceptor API// 获取目标模块 var targetModule Process.getModuleByName(libtarget.so); // 获取目标函数地址 var targetFuncPtr targetModule.getExportByName(target_function); // Hook目标函数 Interceptor.attach(targetFuncPtr, { onEnter: function (args) { console.log(函数被调用参数0: args[0]); // 修改参数 args[0] ptr(0x1234); }, onLeave: function (retval) { console.log(函数返回: retval); // 修改返回值 retval.replace(ptr(0x5678)); } });3.3 动态修改内存数据Frida还可以直接修改进程内存中的数据// 查找内存中的字符串 var pattern 00 01 02 03 ?? 05 06 07; var results Memory.scanSync(Module.findBaseAddress(libtarget.so), Module.findSize(libtarget.so), pattern); // 修改找到的内存数据 results.forEach(function (match) { console.log(找到匹配项在: match.address); Memory.writeByteArray(match.address, [0xAA, 0xBB, 0xCC, 0xDD]); });4. 高级Hook技巧与实战案例4.1 绕过SSL PinningSSL Pinning是应用防止中间人攻击的常见技术使用Frida可以绕过这种保护Java.perform(function () { // Hook证书验证类 var X509TrustManager Java.use(javax.net.ssl.X509TrustManager); // 实现自定义TrustManager var MyTrustManager Java.registerClass({ name: com.example.MyTrustManager, implements: [X509TrustManager], methods: { checkClientTrusted: function (chain, authType) { console.log(Bypassing client cert check); }, checkServerTrusted: function (chain, authType) { console.log(Bypassing server cert check); }, getAcceptedIssuers: function () { return []; } } }); // Hook SSLContext.init方法 var SSLContext Java.use(javax.net.ssl.SSLContext); SSLContext.init.overload([Ljavax.net.ssl.KeyManager;, [Ljavax.net.ssl.TrustManager;, java.security.SecureRandom).implementation function (kms, tms, sr) { // 替换TrustManager var myTM MyTrustManager.$new(); this.init(kms, [myTM], sr); }; });4.2 反调试对抗许多应用会检测调试器使用Frida可以绕过这些检测// 绕过常见的反调试技术 var Debug Java.use(android.os.Debug); Debug.isDebuggerConnected.implementation function () { return false; }; // 修改系统属性 Java.perform(function () { var SystemProperties Java.use(android.os.SystemProperties); SystemProperties.get.overload(java.lang.String).implementation function (key) { if (key ro.debuggable) { return 0; } return this.get(key); }; });4.3 动态修改应用逻辑Frida可以动态修改应用的核心逻辑Java.perform(function () { // 修改应用中的关键判断 var AuthManager Java.use(com.example.app.AuthManager); AuthManager.checkPermission.implementation function (uid) { console.log(Bypassing permission check for UID: uid); return true; // 总是返回true }; // 修改应用中的业务逻辑 var PaymentManager Java.use(com.example.app.PaymentManager); PaymentManager.processPayment.implementation function (amount) { console.log(原始金额: amount); // 修改支付金额为0 return this.processPayment(0); }; });5. Frida脚本开发与调试技巧5.1 脚本模块化开发为了提高代码复用性可以将Frida脚本模块化// auth_bypass.js function bypassAuth() { Java.perform(function () { var AuthClass Java.use(com.example.app.Auth); AuthClass.login.implementation function (u, p) { console.log(Bypassing auth for user: u); return true; }; }); } // payment_bypass.js function bypassPayment() { Java.perform(function () { var PaymentClass Java.use(com.example.app.Payment); PaymentClass.verify.implementation function () { return {status: success, code: 200}; }; }); } // 主脚本 var scripts [./auth_bypass.js, ./payment_bypass.js]; scripts.forEach(function (script) { try { require(script); } catch (e) { console.error(加载脚本失败: script, e); } }); // 执行功能 bypassAuth(); bypassPayment();5.2 调试与日志技巧Frida提供了强大的调试功能// 设置全局异常处理 Process.setExceptionHandler(function (details) { console.error(异常发生:); console.error( type: details.type); console.error( address: details.address); console.error( memory: JSON.stringify(details.memory)); return true; // 继续执行 }); // 详细的日志记录 function logCall(className, methodName, args) { console.log(调用: className . methodName); for (var i 0; i args.length; i) { try { console.log( 参数 i : args[i]); } catch (e) { console.log( 参数 i : [无法打印]); } } } // 使用示例 Java.perform(function () { var TargetClass Java.use(com.example.Target); TargetClass.sensitiveMethod.implementation function () { logCall(com.example.Target, sensitiveMethod, arguments); return this.sensitiveMethod.apply(this, arguments); }; });5.3 性能优化技巧大型Hook脚本可能会影响目标应用性能以下是一些优化建议延迟加载只在需要时Hook特定类和方法条件Hook根据运行时条件决定是否Hook批量Hook使用Java.enumerateLoadedClasses批量处理类// 延迟Hook示例 function hookWhenReady(className, callback) { var interval setInterval(function () { Java.perform(function () { try { var clazz Java.use(className); clearInterval(interval); callback(clazz); } catch (e) { // 类尚未加载继续等待 } }); }, 1000); } // 使用示例 hookWhenReady(com.example.LazyClass, function (clazz) { clazz.targetMethod.implementation function () { console.log(LazyClass.targetMethod被调用); return this.targetMethod.apply(this, arguments); }; });6. Frida在实际安全测试中的应用6.1 敏感信息提取使用Frida可以提取应用中的敏感信息Java.perform(function () { // 提取SharedPreferences中的数据 var Context Java.use(android.content.Context); var SharedPreferences Java.use(android.content.SharedPreferences); var context Java.use(android.app.ActivityThread).currentApplication().getApplicationContext(); var prefs context.getSharedPreferences(user_prefs, Context.MODE_PRIVATE.value); var allEntries prefs.getAll(); console.log(SharedPreferences内容:); var entries allEntries.entrySet().toArray(); for (var i 0; i entries.length; i) { var entry entries[i]; console.log( entry.getKey() : entry.getValue()); } // 提取内存中的字符串 var MemoryScanner { scanStrings: function (pattern) { return Memory.scanSync(Module.findBaseAddress(libtarget.so), Module.findSize(libtarget.so), pattern); } }; var results MemoryScanner.scanStrings(password[^\\x00]); results.forEach(function (match) { console.log(找到敏感字符串: Memory.readUtf8String(match.address)); }); });6.2 API调用监控监控应用的关键API调用// 监控加密相关API Java.perform(function () { var Cipher Java.use(javax.crypto.Cipher); Cipher.getInstance.overload(java.lang.String).implementation function (algorithm) { console.log(Cipher.getInstance被调用算法: algorithm); return this.getInstance(algorithm); }; Cipher.doFinal.overload([B).implementation function (input) { console.log(加密数据 (hex): bytesToHex(input)); var result this.doFinal(input); console.log(加密结果 (hex): bytesToHex(result)); return result; }; function bytesToHex(bytes) { return Array.prototype.map.call(bytes, function(byte) { return (0 (byte 0xFF).toString(16)).slice(-2); }).join(); } });6.3 动态行为修改实时修改应用行为// 修改网络请求 Java.perform(function () { var OkHttpClient Java.use(okhttp3.OkHttpClient); var Builder OkHttpClient.$new().newBuilder(); // 添加拦截器 var Interceptor Java.use(okhttp3.Interceptor); var MyInterceptor Java.registerClass({ name: com.example.MyInterceptor, implements: [Interceptor], methods: { intercept: function (chain) { var request chain.request(); console.log(拦截请求: request.url()); // 修改请求 var newRequest request.newBuilder() .header(X-Injected, true) .build(); return chain.proceed(newRequest); } } }); Builder.addInterceptor(MyInterceptor.$new()); var newClient Builder.build(); // 替换应用的HTTP客户端 var AppClass Java.use(com.example.app.NetworkManager); AppClass.client.value newClient; });7. Frida与其他工具的结合使用7.1 与Burp Suite配合使用Frida可以与Burp Suite等代理工具配合使用实现更全面的安全测试// 设置全局代理 Java.perform(function () { var System Java.use(java.lang.System); System.setProperty.overload(java.lang.String, java.lang.String).implementation function (key, value) { // 防止应用覆盖我们的代理设置 if (key http.proxyHost || key https.proxyHost) { return; } return this.setProperty(key, value); }; // 强制设置代理 System.setProperty(http.proxyHost, 127.0.0.1); System.setProperty(http.proxyPort, 8080); System.setProperty(https.proxyHost, 127.0.0.1); System.setProperty(https.proxyPort, 8080); // 绕过证书验证 var TrustManager Java.use(javax.net.ssl.TrustManager); var X509TrustManager Java.use(javax.net.ssl.X509TrustManager); var MyTrustManager Java.registerClass({ name: com.example.MyTrustManager, implements: [X509TrustManager], methods: { checkClientTrusted: function (chain, authType) {}, checkServerTrusted: function (chain, authType) {}, getAcceptedIssuers: function () { return []; } } }); var SSLContext Java.use(javax.net.ssl.SSLContext); SSLContext.init.overload([Ljavax.net.ssl.KeyManager;, [Ljavax.net.ssl.TrustManager;, java.security.SecureRandom).implementation function (kms, tms, sr) { this.init(kms, [MyTrustManager.$new()], sr); }; });7.2 与Xposed模块的对比虽然Xposed和Frida都可以用于Android应用的Hook但它们有显著区别特性FridaXposed安装方式无需修改系统动态注入需要安装框架修改系统支持平台多平台(Android/iOS/Windows等)仅Android使用语言JavaScriptJava热更新能力支持无需重启应用需要重启应用性能影响较小较大逆向工程适用性更适合动态分析更适合持久化修改7.3 与r2frida的集成r2frida是Radare2与Frida的集成工具结合了两者的优势# 启动r2frida r2 frida://spawn/usb//com.example.app # 常用命令 # 列出模块 \i* # 分析二进制 \aa # 设置Hook \ Interceptor.attach(Module.findExportByName(null, open), { onEnter: function(args) { console.log(open: Memory.readUtf8String(args[0])); } });这种组合特别适合需要进行深入二进制分析的安全研究人员。8. Frida Hook的高级话题8.1 多线程环境下的Hook在多线程环境中使用Frida需要特别注意线程安全问题Java.perform(function () { // 确保在主线程执行 var Thread Java.use(java.lang.Thread); var currentThread Thread.currentThread(); console.log(当前线程: currentThread.getName()); // Hook线程创建 Thread.$init.overload(java.lang.Runnable).implementation function (runnable) { console.log(新线程创建: this.getName()); // 包装原始Runnable var WrappedRunnable Java.registerClass({ name: com.example.WrappedRunnable, implements: [Java.use(java.lang.Runnable)], fields: { original: java.lang.Runnable }, methods: { $init: function (original) { this.original.value original; }, run: function () { console.log(线程 Thread.currentThread().getName() 开始执行); try { this.original.value.run(); } finally { console.log(线程 Thread.currentThread().getName() 结束执行); } } } }); return this.$init(new WrappedRunnable.$new(runnable)); }; });8.2 处理混淆代码对于混淆过的代码Frida提供了几种应对策略模式匹配Hook通过方法签名而非名称来Hook枚举所有方法动态发现并Hook符合条件的方法行为特征识别根据方法的输入输出特征定位目标// 枚举类方法并Hook特定模式 Java.perform(function () { Java.enumerateLoadedClasses({ onMatch: function (className) { if (className.includes(a.b.c)) { // 部分已知包名 var clazz Java.use(className); var methods clazz.class.getDeclaredMethods(); for (var i 0; i methods.length; i) { var method methods[i]; if (method.toString().includes(String)) { // 查找处理String的方法 console.log(找到可能的目标方法: className . method.getName()); // 动态Hook clazz[method.getName()].overloads.forEach(function (overload) { overload.implementation function () { console.log(混淆方法被调用: className . method.getName()); return this[method.getName()].apply(this, arguments); }; }); } } } }, onComplete: function () {} }); });8.3 性能敏感型Hook对于性能敏感的场景需要优化Hook代码// 轻量级Hook示例 var lightHook { enabled: false, count: 0, threshold: 100, callback: function () { this.count; if (this.count this.threshold) { this.enabled false; console.log(采样完成禁用Hook); } } }; Java.perform(function () { var TargetClass Java.use(com.example.PerformanceCritical); TargetClass.criticalMethod.implementation function () { if (lightHook.enabled) { lightHook.callback(); } return this.criticalMethod.apply(this, arguments); }; }); // 按需启用 setTimeout(function () { lightHook.enabled true; console.log(开始性能采样); }, 5000);9. Frida的安全限制与对抗9.1 Frida自身的反调试技术Frida提供了一些反反调试功能// 检测和绕过常见的反Frida技术 function antiAntiFrida() { // 检查内存中的frida特征字符串 var fridaStrings [frida:rpc, LIBFRIDA, gum-js-loop]; fridaStrings.forEach(function (str) { var results Memory.scanSync(Module.findBaseAddress(null), Module.findSize(null), str); if (results.length 0) { console.log(发现Frida特征字符串: str); // 修改内存中的字符串 Memory.writeUtf8String(results[0].address, random_string); } }); // 检查/proc/self/maps中的frida特征 var File Java.use(java.io.File); var BufferedReader Java.use(java.io.BufferedReader); var InputStreamReader Java.use(java.io.InputStreamReader); var FileInputStream Java.use(java.io.FileInputStream); var mapsFile File.$new(/proc/self/maps); var fis FileInputStream.$new(mapsFile); var isr InputStreamReader.$new(fis); var br BufferedReader.$new(isr); var line; while ((line br.readLine()) ! null) { if (line.contains(frida) || line.contains(gum) || line.contains(fridaserver)) { console.log(发现可疑内存映射: line); } } br.close(); } antiAntiFrida();9.2 应对Frida检测一些应用会主动检测Frida的存在可以采取以下对抗措施// 重命名Frida-server var oldServer /data/local/tmp/frida-server; var newServer /data/local/tmp/random_name; Process.getModuleByName(linker).enumerateImports().forEach(function (imp) { if (imp.name fopen) { Interceptor.attach(imp.address, { onEnter: function (args) { var path Memory.readUtf8String(args[0]); if (path.includes(frida-server)) { Memory.writeUtf8String(args[0], newServer); } } }); } }); // 修改端口 var Server Java.use(com.example.app.Detection); Server.checkFridaPort.implementation function () { return false; // 总是返回未检测到 };9.3 隐蔽通信通道为了降低被检测的风险可以配置Frida使用更隐蔽的通信方式# 使用随机名称和Unix socket代替TCP ./frida-server --listenunix:/data/local/tmp/random.sock # 在客户端连接时 frida -U --listenunix/data/local/tmp/random.sock10. Frida在实际项目中的综合应用10.1 自动化测试中的应用Frida可以用于增强自动化测试能力// 自动化UI测试辅助 Java.perform(function () { var Activity Java.use(android.app.Activity); Activity.startActivity.overload(android.content.Intent).implementation function (intent) { console.log(启动Activity: intent.getComponent().getClassName()); return this.startActivity(intent); }; // 自动填充登录表单 var EditText Java.use(android.widget.EditText); EditText.setText.implementation function (text) { if (this.getHint() this.getHint().toString().toLowerCase().includes(password)) { return this.setText(testpassword); } return this.setText(text); }; // 模拟按钮点击 var Button Java.use(android.widget.Button); Button.performClick.implementation function () { console.log(按钮被点击: this.getText()); return this.performClick(); }; });10.2 安全评估中的应用全面的安全评估脚本示例function performSecurityAssessment() { // 1. 检查加密实现 checkCryptoImplementations(); // 2. 检查敏感信息存储 checkSensitiveDataStorage(); // 3. 检查网络通信安全 checkNetworkSecurity(); // 4. 检查权限使用 checkPermissionUsage(); } function checkCryptoImplementations() { Java.perform(function () { var SecureRandom Java.use(java.security.SecureRandom); SecureRandom.nextInt.implementation function () { console.warn(SecureRandom被调用注意是否使用强随机数); return this.nextInt(); }; var Cipher Java.use(javax.crypto.Cipher); Cipher.getInstance.overload(java.lang.String).implementation function (algorithm) { if (algorithm AES/ECB/PKCS5Padding) { console.error(不安全的加密模式: algorithm); } return this.getInstance(algorithm); }; }); } function checkSensitiveDataStorage() { Java.perform(function () { var SharedPreferences Java.use(android.content.SharedPreferences); var Editor SharedPreferences.Editor; Editor.putString.implementation function (key, value) { if (key.toLowerCase().includes(password) || key.toLowerCase().includes(token)) { console.error(明文存储敏感信息: key value); } return this.putString(key, value); }; }); } performSecurityAssessment();10.3 逆向工程中的应用逆向工程中的典型应用场景// 动态分析加密算法 Java.perform(function () { var cryptoClass Java.use(com.example.app.CryptoUtils); // Hook所有加密方法 var methods cryptoClass.class.getDeclaredMethods(); for (var i 0; i methods.length; i) { var method methods[i]; if (method.getName().startsWith(encrypt)) { cryptoClass[method.getName()].overloads.forEach(function (overload) { overload.implementation function () { console.log(加密方法 method.getName() 被调用); console.log(输入参数: JSON.stringify(arguments)); var result this[method.getName()].apply(this, arguments); console.log(加密结果: result); return result; }; }); } } // 跟踪密钥生成过程 var KeyGenerator Java.use(javax.crypto.KeyGenerator); KeyGenerator.generateKey.implementation function () { var key this.generateKey(); console.log(生成的密钥: bytesToHex(key.getEncoded())); return key; }; function bytesToHex(bytes) { return Array.prototype.map.call(bytes, function(byte) { return (0 (byte 0xFF).toString(16)).slice(-2); }).join(); } });