当前位置: 首页 > news >正文

Frida实战:绕过安卓APP抓包检测的5种核心姿势

1. 项目概述:当抓包工具遇上“隐身”的APP

作为一名在移动安全领域摸爬滚打了十来年的老手,我处理过太多“抓不到包”的棘手情况。你兴冲冲地打开Burp Suite或Charles,配置好代理,准备对目标安卓APP进行安全测试或协议分析,结果却发现APP要么直接网络错误,要么数据流空空如也,请求根本发不出去。这背后,就是APP内置的各种抓包检测与防护机制在起作用。它们像给APP套上了一层“隐身衣”,让传统的代理抓包手段瞬间失效。

今天要聊的,就是如何用Frida这件“神器”,亲手把这层“隐身衣”给扒下来。Frida是一个动态代码插桩框架,简单说,它能在APP运行时,像做手术一样精准地修改其内存和逻辑。我们这次实战的核心目标非常明确:针对APP用于检测代理、证书的5种最常见“姿势”,逐一找到其检测逻辑的关键点,并用Frida脚本进行“绕过”或“欺骗”。我会提供经过实战检验的代码模板,你拿到手稍作修改就能用。无论你是从事安全测试、逆向分析,还是单纯对APP通信协议感到好奇的开发者,掌握这些方法,都能让你在分析道路上扫清一大障碍。

2. 核心思路与工具准备:为什么是Frida?

在开始“手术”之前,我们必须先理解“病因”。APP阻止抓包,本质上是检测到了运行环境与正常用户环境的不同。其主要“侦查”手段无外乎以下几类:检测系统是否设置了代理、检测是否安装了“额外”的证书(即我们抓包工具安装的CA证书)、检测证书链是否被“劫持”(SSL Pinning)。传统的对抗方法,比如尝试用Postern等工具做全局代理转发、或者寻找低版本无需证书的APP,都越来越乏力,因为防护手段在持续升级。

这时,Frida的优势就凸显出来了。与静态反编译修改Smali/字节码再重打包相比,Frida的动态Hook(挂钩)方式有三大不可替代的优势:

  1. 无需重打包与签名:直接附加到运行中的进程进行修改,避免了繁琐且易出错的脱壳、修改、回编译、签名对齐流程。
  2. 实时交互与快速迭代:脚本可以随时修改、重新加载,调试和验证逻辑的效率极高。
  3. 精准打击:可以精确到某个类、某个方法的某一行进行逻辑修改或信息打印,实现“指哪打哪”。

2.1 基础环境搭建清单

工欲善其事,必先利其器。以下是经过无数次环境配置后总结的最稳当的清单:

  • 测试设备:一台已经Root的安卓手机或模拟器。推荐使用官方Android Studio自带的x86/x86_64镜像模拟器(性能好,快照功能方便),或者实体机如Google Pixel系列(对Root支持友好)。注意:高版本安卓(特别是Android 7.0以上)对证书安装和系统安全性有更严格限制,建议从Android 9或10开始练习,其机制具有代表性。
  • Frida环境
    • PC端:通过Python的pip安装:pip install frida-tools。这会将fridafrida-psfrida-ls-devices等命令行工具一并安装。
    • 设备端:需要安装与PC端frida-tools版本匹配的frida-server。去Frida官网的GitHub Releases页面,根据设备CPU架构(通常是arm64)下载对应的frida-server-xx.x.x-android-arm64.xz,解压后得到二进制文件,通过adb push推送到设备/data/local/tmp/目录,并赋予可执行权限(chmod 755 frida-server),然后在后台运行(./frida-server &)。
  • 抓包工具:Burp Suite Professional/Community版或Charles。确保其代理已开启(如Burp默认为127.0.0.1:8080),并且已将抓包工具的CA证书安装到安卓设备的系统证书目录(/system/etc/security/cacerts/)或用户证书目录(对于Android 7.0+,系统级Hook往往需要将证书移至系统目录)。安装到系统目录需要Root权限。
  • 逆向辅助工具:Jadx-GUI或Ghidra。用于静态分析APP,快速定位我们感兴趣的类和方法名,为编写Frida Hook脚本提供“坐标”。

注意frida-server的版本务必与PC端fridafrida-tools的版本保持一致或尽可能接近,否则在连接时会出现协议错误。这是新手最常踩的坑之一。

2.2 核心思路拆解

我们的作战计划是“侦察-定位-打击”:

  1. 侦察:通过静态分析(Jadx)或动态探索(Frida的枚举API),找到APP中可能负责网络库初始化、代理检查、证书验证的相关类和方法。常见嫌疑对象包括OkHttpClient.BuilderX509TrustManagerHostnameVerifierProxySystem.getProperty等。
  2. 定位:编写试探性的Frida脚本,Hook这些可疑方法,打印出它们的调用栈、参数和返回值,观察在开启抓包和关闭抓包时,这些方法的执行逻辑有何不同。从而精确定位到触发“检测”的关键代码行。
  3. 打击:针对定位到的关键方法,编写最终的Hook脚本。我们的目标不是“修复”或“删除”代码,而是“欺骗”——让方法返回我们期望的值(如返回一个空的代理列表、让证书验证总是成功),或者直接阻止某些检测函数的执行。

3. 姿势一:Hook系统属性与Proxy类检测

这是最基础、也最常见的一种检测方式。APP会读取系统的某些属性,或者检查Proxy类的相关方法,来判断是否设置了代理。

3.1 检测原理分析

APP通常会通过以下方式检测代理:

  1. 读取系统属性System.getProperty("http.proxyHost")System.getProperty("https.proxyHost")。如果这些属性不为空,则说明设置了代理。
  2. 使用ProxySelector:Java标准库中的ProxySelector会尝试获取系统默认的代理配置。APP可能通过ProxySelector.getDefault().select(...)来获取代理列表。
  3. 网络库内置检测:如OkHttp的OkHttpClient.Builder在构建时,如果没有显式设置proxy(Proxy.NO_PROXY),它可能会使用系统默认代理。

3.2 Frida Hook实战与代码模板

我们的策略是:Hook这些读取属性的方法或Proxy选择逻辑,让它们返回“空”或“无代理”的结果。

Java.perform(function () { console.log("[*] 开始Hook系统代理检测..."); // 1. Hook System.getProperty, 针对代理相关属性返回null var System = Java.use("java.lang.System"); System.getProperty.overload('java.lang.String').implementation = function (key) { var result = this.getProperty(key); // 关键:拦截代理相关属性的查询 if (key && (key.indexOf("proxy") !== -1 || key.indexOf("PROXY") !== -1)) { console.log("[+] 拦截 System.getProperty('" + key + "'), 原值: " + result + ", 返回: null"); return null; // 或者返回空字符串 "" } return result; }; // 2. Hook ProxySelector.getDefault().select, 返回空列表(表示无代理) var ProxySelector = Java.use("java.net.ProxySelector"); var defaultSelector = ProxySelector.getDefault(); if (defaultSelector) { // 注意:这里需要获取原始类的引用,并替换其select方法 var OriginalProxySelectorClass = Java.use(defaultSelector.$className); OriginalProxySelectorClass.select.overload('java.net.URI').implementation = function (uri) { console.log("[+] 拦截 ProxySelector.select() for URI: " + uri); // 返回一个空的Java List,表示没有可用的代理 var ArrayList = Java.use("java.util.ArrayList"); var emptyList = ArrayList.$new(); return emptyList; }; } // 3. Hook OkHttpClient.Builder 的 proxy 方法(如果存在) try { var OkHttpClient_Builder = Java.use("okhttp3.OkHttpClient$Builder"); OkHttpClient_Builder.proxy.overload('java.net.Proxy').implementation = function (proxy) { console.log("[+] 拦截 OkHttpClient.Builder.proxy(), 强制设置为 Proxy.NO_PROXY"); var Proxy = Java.use("java.net.Proxy"); return this.proxy(Proxy.NO_PROXY); }; } catch (e) { console.log("[-] 未找到OkHttpClient.Builder, 可能未使用OkHttp或版本不同: " + e); } console.log("[*] 系统代理检测Hook完成。"); });

3.3 实操心得与注意事项

  • 顺序很重要:有时APP会有多层检测,先检查系统属性,再检查ProxySelector。我们的Hook脚本最好能覆盖所有层面。
  • 注意OverloadSystem.getProperty有多个重载方法,我们通常Hook的是String参数的那个。使用overload关键字可以精确指定。
  • 兼容性处理:像HookOkHttpClient.Builder的代码,需要用try-catch包裹,因为不是所有APP都使用OkHttp,或者使用的版本可能不同。脚本应具备一定的容错能力。
  • 验证方法:注入脚本后,可以打开APP的网络设置页面(如果有),或者触发一个网络请求,同时观察Frida的控制台输出,看我们的Hook是否被触发。

4. 姿势二:绕过证书绑定(SSL Pinning)

这是对抗抓包最核心、也是最复杂的一环。SSL Pinning(证书绑定)是指APP在代码中“硬编码”了它信任的服务端证书或公钥哈希。当建立TLS连接时,APP会将自己内置的证书与服务器返回的证书进行比对,如果不匹配(比如我们的抓包工具返回的是Burp/Charles的证书),就直接断开连接。

4.1 SSL Pinning的常见实现方式

  1. 网络库层Pinning:OkHttp、Retrofit等库提供了便捷的证书绑定API,开发者可以配置CertificatePinner
  2. 自定义TrustManager:APP实现自己的X509TrustManager接口,在checkServerTrusted方法中实现自定义的验证逻辑,包括比对证书。
  3. 自定义HostnameVerifier:实现HostnameVerifier接口,在verify方法中严格校验主机名。
  4. Native层Pinning:将验证逻辑写在C/C++层(JNI/NDK),增加逆向难度。

4.2 Frida Hook方案与代码模板

我们的目标是“瓦解”这些验证逻辑。

Java.perform(function () { console.log("[*] 开始Hook SSL Pinning相关组件..."); // 1. 万能钥匙:Hook X509TrustManager, 让所有证书都通过验证 var X509TrustManager = Java.use("javax.net.ssl.X509TrustManager"); // 注意:这里需要找到APP实际使用的TrustManager类,可能是自定义的。以下是一种通用但可能不完美的尝试。 // 更好的方法是通过枚举或静态分析找到具体类名。 Java.choose("javax.net.ssl.X509TrustManager", { "onMatch": function (instance) { console.log("[+] 发现 X509TrustManager 实例: " + instance); // Hook checkServerTrusted 方法 var clazz = Java.use(instance.$className); clazz.checkServerTrusted.overload('[Ljava.security.cert.X509Certificate;', 'java.lang.String').implementation = function (chain, authType) { console.log("[+] 绕过 X509TrustManager.checkServerTrusted, authType: " + authType); // 什么都不做,直接通过,或者打印一下证书信息 for (var i = 0; i < chain.length; i++) { console.log(" Cert[" + i + "]: " + chain[i].getSubjectDN()); } return; }; clazz.checkClientTrusted && clazz.checkClientTrusted.overload('[Ljava.security.cert.X509Certificate;', 'java.lang.String').implementation = function (chain, authType) { console.log("[+] 绕过 X509TrustManager.checkClientTrusted"); return; }; clazz.getAcceptedIssuers && clazz.getAcceptedIssuers.overload().implementation = function () { console.log("[+] 绕过 X509TrustManager.getAcceptedIssuers"); return []; }; }, "onComplete": function () { console.log("[*] X509TrustManager 实例搜索完成。"); } }); // 2. Hook HostnameVerifier, 让所有主机名都验证通过 var HostnameVerifier = Java.use("javax.net.ssl.HostnameVerifier"); HostnameVerifier.verify.overload('java.lang.String', 'javax.net.ssl.SSLSession').implementation = function (hostname, session) { console.log("[+] 绕过 HostnameVerifier.verify for hostname: " + hostname); return true; // 总是返回true }; // 3. Hook OkHttp的CertificatePinner(如果使用) try { var CertificatePinner = Java.use("okhttp3.CertificatePinner"); CertificatePinner.check.overload('java.lang.String', 'java.util.List').implementation = function (hostname, pins) { console.log("[+] 绕过 OkHttp CertificatePinner.check for: " + hostname); // 直接跳过检查,不抛异常 return; }; // 或者Hook其构造方法,使其不添加任何Pin规则 CertificatePinner.$init.overload('okhttp3.CertificatePinner$Builder').implementation = function (builder) { console.log("[+] 拦截 CertificatePinner 初始化, 返回一个空的(无pinning)的实例"); // 调用原始构造,但传入一个空的Builder?更直接的方法是替换整个对象。 // 这里提供一个思路:可以尝试在OkHttpClient.Builder构建时替换其certificatePinner。 }; } catch (e) { console.log("[-] 未找到OkHttp CertificatePinner: " + e); } console.log("[*] SSL Pinning Hook完成。"); });

4.3 高级技巧:定位自定义TrustManager

上面的代码通过Java.choose来寻找X509TrustManager实例,这有时可能不够精确或找不到。更可靠的方法是静态分析:

  1. 用Jadx搜索checkServerTrusted方法。
  2. 查看哪些类实现了X509TrustManager接口。
  3. 找到在SSLContext.init或网络库初始化时被使用的那个具体类。
  4. 在Frida脚本中直接Hook这个具体的类名。
// 假设通过静态分析发现APP使用了一个叫`com.example.app.CustomTrustManager`的类 var CustomTrustManager = Java.use("com.example.app.CustomTrustManager"); CustomTrustManager.checkServerTrusted.overload('[Ljava.security.cert.X509Certificate;', 'java.lang.String').implementation = function (chain, authType) { console.log("[+] 绕过 CustomTrustManager.checkServerTrusted"); return; // 静默通过 };

5. 姿势三:对抗证书链完整性校验

除了SSL Pinning,一些更严格的APP会校验整个证书链的完整性,或者检测证书是否由系统已知的CA签发(而我们的Burp证书是用户安装的)。它们可能通过TrustManagerFactoryKeyStore相关的API来实现。

5.1 检测原理

APP可能加载一个预设的、仅包含官方CA的密钥库(KeyStore),然后基于此生成TrustManager。这样,非系统预设的CA证书(如Burp证书)将不被信任。

5.2 Frida Hook策略

我们的策略是Hook密钥库的加载过程,或者直接替换掉整个TrustManagerFactory的初始化。

Java.perform(function () { console.log("[*] 开始Hook证书链与KeyStore相关逻辑..."); // 1. Hook KeyStore的load方法, 尝试阻止其加载特定文件,或加载一个空的KeyStore var KeyStore = Java.use("java.security.KeyStore"); KeyStore.load.overload('java.io.InputStream', '[C').implementation = function (stream, password) { console.log("[+] 拦截 KeyStore.load()"); // 我们可以选择: // A. 让加载失败?不,这可能导致APP崩溃。 // B. 调用原方法,但之后清空它?比较复杂。 // C. 更常见的做法是Hook TrustManagerFactory.init,见下文。 return this.load(stream, password); // 暂时先正常执行 }; // 2. Hook TrustManagerFactory的init方法, 这是更关键的入口 var TrustManagerFactory = Java.use("javax.net.ssl.TrustManagerFactory"); TrustManagerFactory.init.overload('java.security.KeyStore').implementation = function (ks) { console.log("[+] 拦截 TrustManagerFactory.init(KeyStore)"); // 关键:传入一个null或空的KeyStore, 这样工厂会使用默认的系统信任库。 // 但我们的Burp证书如果已安装到系统目录,就会被信任。 // 然而,如果APP就是想用自定义KeyStore排除系统CA,我们这样做可能破坏其逻辑。 // 另一种思路:获取默认的系统KeyStore传给它。 try { var defaultKs = KeyStore.getInstance(KeyStore.getDefaultType()); defaultKs.load(null, null); // 加载默认的(系统)KeyStore console.log(" -> 替换为系统默认KeyStore"); return this.init(defaultKs); } catch (e) { console.log(" -> 替换失败, 使用原始KeyStore: " + e); return this.init(ks); } }; // 3. Hook SSLContext的init方法, 直接替换TrustManager var SSLContext = Java.use("javax.net.ssl.SSLContext"); SSLContext.init.overload('[Ljavax.net.ssl.KeyManager;', '[Ljavax.net.ssl.TrustManager;', 'java.security.SecureRandom').implementation = function (keyManagers, trustManagers, secureRandom) { console.log("[+] 拦截 SSLContext.init()"); // 创建一个“信任所有”的TrustManager数组 var TrustAllManager = Java.registerClass({ name: 'com.bypass.TrustAllManager', implements: [X509TrustManager], methods: { checkClientTrusted: function (chain, authType) { }, checkServerTrusted: function (chain, authType) { console.log(" TrustAllManager: Accepting server cert for: " + (chain && chain[0] ? chain[0].getSubjectDN() : 'unknown')); }, getAcceptedIssuers: function () { return []; } } }); var dummyTrustManagers = [TrustAllManager.$new()]; console.log(" -> 替换为自定义的TrustAllManager"); return this.init(keyManagers, dummyTrustManagers, secureRandom); }; console.log("[*] 证书链完整性校验Hook完成。"); });

5.4 实操心得

  • 风险较高:直接替换SSLContext.initTrustManagerFactory.init是比较“暴力”的方法,可能会影响APP其他需要严格SSL校验的功能(如支付),需谨慎测试。
  • 顺序问题:这些Hook需要在APP网络库初始化之前执行。确保Frida脚本在APP启动早期就注入(例如使用frida -U -f com.example.app --no-pause -l script.js在启动时注入)。
  • 组合使用:姿势二和姿势三经常需要组合使用,因为APP可能同时采用多种证书校验机制。

6. 姿势四:应对基于网络状态/连接属性的检测

有些APP不直接检测代理,而是检测当前活跃网络的属性,例如判断网络是否是通过VPN或代理连接(NetworkInfo/ConnectivityManager),或者检查LinkProperties

6.1 检测原理

在Android中,可以通过ConnectivityManager.getActiveNetwork()获取当前网络,然后通过NetworkCapabilitiesLinkProperties来查询网络详细信息。如果LinkProperties中包含了代理相关的ProxyInfo,或者NetworkCapabilities表明网络是VPN,APP就可能拒绝在此网络下发送敏感请求。

6.2 Frida Hook代码模板

Java.perform(function () { console.log("[*] 开始Hook网络连接属性检测..."); // Hook ConnectivityManager 的相关方法 var ConnectivityManager = Java.use("android.net.ConnectivityManager"); // 方法1:Hook getActiveNetworkInfo (旧API,deprecated但可能还在用) try { ConnectivityManager.getActiveNetworkInfo.implementation = function () { var originalResult = this.getActiveNetworkInfo(); if (originalResult) { console.log("[+] 拦截 getActiveNetworkInfo, 类型: " + originalResult.getTypeName()); // 可以尝试修改返回对象的属性,但更简单的是Hook返回后APP调用的方法。 } return originalResult; }; } catch (e) { /* 方法可能不存在 */ } // 方法2:Hook getActiveNetwork (新API) 和 getNetworkCapabilities / getLinkProperties try { ConnectivityManager.getActiveNetwork.implementation = function () { var originalNetwork = this.getActiveNetwork(); console.log("[+] 拦截 getActiveNetwork"); return originalNetwork; // 返回原始网络对象,但后续可以Hook对它的查询 }; } catch (e) { /* 方法可能不存在 */ } // 方法3:Hook LinkProperties 的 getHttpProxy 方法(关键!) var LinkProperties = Java.use("android.net.LinkProperties"); LinkProperties.getHttpProxy.implementation = function () { console.log("[+] 拦截 LinkProperties.getHttpProxy(), 返回 null"); return null; // 强制返回null,告诉APP此网络没有HTTP代理 }; // 方法4:Hook NetworkCapabilities 的 hasTransport 或 hasCapability, 避免被识别为VPN var NetworkCapabilities = Java.use("android.net.NetworkCapabilities"); NetworkCapabilities.hasTransport.overload('int').implementation = function (transportType) { var result = this.hasTransport(transportType); // TRANSPORT_VPN 的值通常是 17 (Android 10+) 或 4 (旧版本?需要查常量)。这里是一个示例。 // 更稳妥的做法是获取常量值:var TRANSPORT_VPN = Java.use("android.net.NetworkCapabilities").TRANSPORT_VPN.value; if (transportType === 17 || transportType === 4) { // 假设是VPN类型 console.log("[+] 拦截 NetworkCapabilities.hasTransport(VPN), 原结果: " + result + ", 返回 false"); return false; } return result; }; console.log("[*] 网络连接属性检测Hook完成。"); });

6.3 注意事项

  • 版本适配:Android网络API变化较大,不同版本(尤其是Android 5.0, 7.0, 10.0)的检测方式可能不同。需要根据目标APP的targetSdkVersion来调整Hook策略。
  • 常量值:像TRANSPORT_VPN这样的常量,其整数值可能随版本变化。最准确的方式是在Hook脚本中动态获取:Java.use("android.net.NetworkCapabilities").TRANSPORT_VPN.value

7. 姿势五:处理Native层(JNI/NDK)检测

最高级的防护会将关键检测逻辑下沉到Native层(C/C++),通过JNI调用。这增加了逆向分析的门槛,因为需要分析so库文件。

7.1 检测原理

APP的Java代码通过System.loadLibrary加载so库,并调用其中的native方法。这些方法可能执行:

  • 检测frida-server进程或端口(27042)。
  • 检测/proc/self/maps中是否包含frida-agent等字符串。
  • 执行更底层的网络套接字操作,绕过Java层的代理设置。
  • 实现自己的TLS逻辑(如BoringSSL),进行证书绑定。

7.2 Frida Hook Native函数

Frida同样可以Hook Native层的函数,这需要一些C/C++的基础。

Java.perform(function () { console.log("[*] 开始处理Native层检测..."); // 首先,确保APP已经加载了目标so库 // 我们可以Hook System.loadLibrary var System = Java.use("java.lang.System"); System.loadLibrary.overload('java.lang.String').implementation = function (libname) { console.log("[+] Java层加载Native库: " + libname); var result = this.loadLibrary(libname); // 库加载后,我们可以开始Hook其中的Native函数 if (libname.indexOf("security") !== -1 || libname.indexOf("net") !== -1) { // 假设库名包含关键词 setTimeout(hookNativeFunctions, 500); // 延迟执行,确保库完全加载 } return result; }; // 也可以直接枚举已加载的模块 Process.enumerateModules({ onMatch: function (module) { console.log("模块: " + module.name + " 基址: " + module.base); // 如果发现可疑模块,如`libnetguard.so`, `libssl.so`等,可以针对性Hook }, onComplete: function () { console.log("[*] 模块枚举完成。"); } }); }); // 单独的Native Hook函数 function hookNativeFunctions() { console.log("[*] 尝试Hook Native函数..."); // 示例1:Hook libc 的 `getaddrinfo` 函数,这是域名解析的核心函数,有些检测会在这里做手脚。 var libc = Module.findBaseAddress('libc.so'); if (libc) { // 注意:函数签名和偏移量因Android版本和设备架构(arm/arm64/x86)而异。这里是一个示例。 // 在实际操作中,你需要用IDA/Ghidra反编译so库,找到目标函数的偏移量或符号。 var getaddrinfo = Module.findExportByName('libc.so', 'getaddrinfo'); if (getaddrinfo) { Interceptor.attach(getaddrinfo, { onEnter: function (args) { // args[0]是node, args[1]是service, args[2]是hints, args[3]是res var hostname = Memory.readCString(args[0]); console.log("[Native] getaddrinfo called for hostname: " + hostname); // 可以在这里修改hostname,或者记录信息 }, onLeave: function (retval) { // console.log("[Native] getaddrinfo returned: " + retval); } }); console.log("[+] Hook libc.getaddrinfo 成功"); } } // 示例2:Hook一个自定义的JNI函数(假设我们知道它的符号) // 通过静态分析so库,发现一个函数 `Java_com_example_app_NativeHelper_checkProxy` var targetSymbol = "Java_com_example_app_NativeHelper_checkProxy"; var targetAddress = Module.findExportByName(null, targetSymbol); // 在已加载的所有模块中查找 if (targetAddress) { Interceptor.attach(targetAddress, { onEnter: function (args) { console.log("[Native] 进入自定义检测函数: " + targetSymbol); // 这个函数可能返回一个布尔值或整数表示是否检测到代理。 // 我们可以修改其返回值。 }, onLeave: function (retval) { console.log("[Native] 函数原始返回值: " + retval); // 强制返回0(假)或1(真),取决于函数语义。假设返回0表示安全。 retval.replace(ptr(0)); console.log("[+] 修改返回值为 0 (安全)"); } }); console.log("[+] Hook " + targetSymbol + " 成功"); } else { console.log("[-] 未找到符号: " + targetSymbol); } // 示例3:对抗Frida检测(如果Native代码在检测Frida) // 常见的检测:扫描maps文件,查找frida-agent。我们可以Hook文件读取相关函数。 var fopen = Module.findExportByName('libc.so', 'fopen'); if (fopen) { Interceptor.attach(fopen, { onEnter: function (args) { var filename = Memory.readCString(args[0]); var mode = Memory.readCString(args[1]); if (filename && filename.indexOf('/proc/self/maps') !== -1) { console.log("[Native] 检测到 maps 文件读取: " + filename); // 可以在这里做手脚,但比较复杂。一种思路是Hook后续的fgets/fread,过滤掉包含'frida'的行。 } } }); } }

7.3 高级技巧与排查

  • 符号剥离:发布版的so库经常被剥离符号,你找不到像Java_com_example_app_NativeHelper_checkProxy这样清晰的函数名。这时需要通过反汇编工具分析so,找到关键函数的偏移地址(offset),然后通过Module.base.add(offset)来获取函数地址进行Hook。
  • 参数与返回值:Hook Native函数需要了解其调用约定(ARM/ARM64/x86)和参数类型。使用FridaMemory.readCStringMemory.readByteArray等API来读取内存中的数据。
  • 动态注册JNI:有些JNI函数是动态注册的(通过JNIEnv->RegisterNatives),其符号名可能不是标准的Java_开头。需要在JNI_OnLoad函数或RegisterNatives调用处下钩子,来获取这些函数的地址。

8. 实战整合与问题排查

在实际对抗中,一个成熟的APP往往会同时采用上述多种姿势。因此,一个完整的绕过脚本,需要将上述多个Hook点整合起来,形成一个“组合拳”。

8.1 脚本整合策略

  1. 分模块编写:将针对不同检测姿势的代码写成独立的函数或模块,如hookProxyDetection(),hookSSLPinning(),hookNativeChecks()
  2. 顺序执行:在Java.perform的主函数中,按顺序调用这些模块。通常,先处理Java层的通用代理和SSL Pinning,再处理网络属性,最后处理Native层。
  3. 错误处理:每个Hook点都用try-catch包裹,避免因为某个类或方法不存在导致整个脚本崩溃。
  4. 日志分级:使用console.log输出不同级别的信息(如[*]信息,[+]成功,[-]失败/未找到,[!]警告),便于调试。

8.2 常见问题排查实录

即使脚本写好了,注入后也可能遇到APP崩溃、Hook不生效、网络依然不通等问题。以下是我踩过无数坑后总结的排查清单:

问题现象可能原因排查思路与解决方案
注入后APP立即闪退1. Frida版本不匹配。
2. Hook了错误的方法或类,导致无限递归或内存访问错误。
3. Native Hook触发了反调试。
1. 检查frida-server与PC端版本。使用frida --versionadb shell /data/local/tmp/frida-server --version比对。
2. 注释掉部分Hook代码,采用二分法定位导致崩溃的Hook点。特别注意implementation函数中是否错误地调用了原方法导致循环。
3. 尝试先不进行Native Hook,只进行Java层Hook,看是否稳定。
Hook脚本执行无日志输出,检测依然生效1. 脚本未成功注入或执行。
2. Hook的类名/方法签名不正确。
3. 检测逻辑发生在脚本注入之前。
1. 在脚本开头加一句console.log(“脚本开始执行”);,确认Frida连接和脚本加载正常。
2. 使用frida -U -f com.example.app -l script.js --no-pause在APP启动时注入。确保在检测逻辑初始化前执行Hook。
3. 使用Java.available()检查Java运行时是否就绪。使用Java.enumerateLoadedClasses()Frida-D参数打印所有类,确认目标类已加载。
部分网络请求通了,部分还是失败1. APP使用了多个网络库或连接方式(如HttpURLConnection,OkHttp,WebView,Socket直连)。
2. Native层实现了独立的网络栈。
3. 证书绑定针对不同域名有不同的策略。
1. 检查失败请求的库。Hook更底层的类,如java.net.Socketjavax.net.ssl.SSLSocketFactory
2. 对libssl.solibcrypto.so等系统库的SSL_read/SSL_writeSSL_CTX_new等函数进行Hook,观察流量。
3. 分析证书绑定的代码,看是否是“动态Pinning”(从服务器获取pin列表),需要Hook其获取和更新的逻辑。
Frida自身被检测到APP有反Frida机制,如检测frida-server进程、端口、内存特征、文件特征等。1. 重命名frida-server二进制文件并运行。
2. 修改Frida默认端口(frida-server -l 0.0.0.0:8080)。
3. 使用FridaD-Bus协议而非默认的listen模式(更复杂)。
4. 使用objection等基于Frida的工具,它可能有一些反反制措施。
5. 在非Root环境下尝试使用frida-gadget注入,但难度较高。

8.3 一个综合性的脚本启动命令

# 在APP启动时注入,并等待(--no-pause),同时允许子进程派生(--enable-child-gating),这对于多进程APP有用 frida -U -f com.example.targetapp --no-pause --enable-child-gating -l bypass_all.js # 附加到已运行的APP进程 frida -U com.example.targetapp -l bypass_all.js # 开启调试输出,可以看到更详细的Frida内部通信和错误信息 frida -U com.example.targetapp -l bypass_all.js -D

绕过抓包检测是一场持续的“攻防战”。今天有效的技术,明天可能因为APP更新或系统升级而失效。核心不在于记住所有脚本,而在于理解其原理和Frida这个工具的强大能力——动态修改运行时。掌握了“侦察-定位-打击”这个基本方法论,你就能应对层出不穷的新防护手段。最后,所有的技术都应用于合法授权的安全测试与学习研究,这是从业者不可逾越的底线。

http://www.gsyq.cn/news/1634805.html

相关文章:

  • MPCM-Net云图分割网络架构与优化实践
  • 3步创建梦想岛屿:Happy Island Designer 终极免费设计指南
  • 无人机航拍目标检测优化:YOLOv12实战与性能提升
  • 文生图模型选择指南:从潜空间到训练数据的三层决策逻辑
  • Python+CNN蔬菜识别系统开发全流程解析
  • SRC漏洞实战:从信息收集到报告撰写的完整挖洞指南
  • 零样本学习与提示工程的实践指南
  • 精确计时系统:CS2200-CP与STM32F756ZG硬件架构与配置
  • 移动设备远程控制攻击链深度解析与防御实战指南
  • 生成模型选型三维评估法:粒度、鲁棒性与集成成本
  • MIC1557与STM32F215ZG高精度定时系统设计指南
  • MC6470与MKV42F256VLH16的运动控制方案详解
  • 基于YOLOv11的水稻病害AI检测系统开发实践
  • 2023深度学习笔记本选型指南:硬件、场景与稳定性实战
  • Wwise音频工具终极指南:3分钟掌握游戏音频文件解包与定制技巧
  • 国产大模型选型实战指南:按任务场景匹配GLM-5、Kimi、通义千问等5款模型
  • 从信息搜集到攻击面分析:漏洞赏金实战中的自动化侦察与弱点关联
  • T5、BERT、Stable Diffusion等10大AI模型选型实战指南
  • 多维聚合实战:从数据立方体到动态分组的四层架构
  • 从零构建AI Agent:技术选型与实战指南
  • AI驱动网络安全实战:从行为基线检测到自适应防御体系构建
  • 【JAVA毕设源码分享】基于springboot幼儿园管理系统的设计与实现(程序+文档+代码讲解+一条龙定制)
  • CISSP证书维持指南:16个免费官方CPE渠道与高效续证策略
  • 工业机器人ML实战:从算法到落地的全链路指南
  • 机器学习模型上线后如何持续存活:监控、弹性与可观测性实战
  • LabVIEW控制东佑达TC100步进电缸的RS485通信实现
  • PIC18F66K40与SLO2016的工业通信优化方案
  • 从Notebook到生产:构建可靠机器学习服务的实战指南
  • 为什么化学研究者都在用Ketcher?这款免费分子编辑器如何改变科研绘图体验
  • CIML 2026会议投稿与参会全攻略