首页
社区
课程
招聘
[原创] 浅谈ios的风控检测与对抗
发表于: 2026-10-6 20:46 1343

[原创] 浅谈ios的风控检测与对抗

2026-10-6 20:46
1343

开始分析一个 iOS 应用时,最先遇到的往往不是复杂算法,而是几件很具体的事:进程能不能正常启动,Frida 能不能附加,脚本执行以后,业务还能不能继续。

接着,问题会变得有些反直觉。

越狱路径已经查不到,为什么环境判断还会异常?Frida 的名字已经藏起来,为什么一安装 Hook 就出问题?手动调用检测函数,返回值明明正常,为什么下一次请求仍然带着旧记录?

它们指向同一个关键问题:你改变的位置,是否就是检测器观察的位置;这次改变,又有没有传到最终使用结果的地方。

iOS 风控并不只有一个“是否越狱”的开关。它可以从文件查到服务,从镜像名查到函数指针,再从运行时环境延伸到触摸记录。理解这些检测,比维护一份越来越长的 Hook 清单更有用:知道了每条查询在看什么,才能解释对抗为什么有效,以及它的作用范围在哪里。

本文以越狱环境、Frida 和反 Hook 为主线,随后讨论触摸与结果缓存;字体和资源保护放在补充部分。所有场景都按通用机制展开,不依赖某个商业应用的类名或接口。

下面会直接给出对抗代码:从精确隐藏一条路径,到截断本机协议握手,再到改变一个反 Hook 检测方法的返回。每个例子都说明它改了哪一层,以及怎样确认这次改动生效。

可以先把检测器拆成四个问题,而不是立即寻找一个返回布尔值的方法。

下面是一个简化的数据流。具体实现会选择其中一部分,但每项对抗都可以放到这张图上定位。

隐藏路径,主要改变系统查询看到的内容;替换检测返回,改变某个调用者得到的值;修改输出字段,则已经到了链路后部。它们可能得到相似的界面表现,却有不同的覆盖范围。

还有一个贯穿全文的前提:观察主体必须一致。 越狱文件管理工具能读到某个对象,不代表普通沙盒应用也能读到;检测函数能执行,不代表它一定命中;界面继续运行,也不代表没有生成风险记录。

本文的代码与运行说明全部放在本页。选取一个小节的 Frida 代码保存为 demo.js,使用 CLI 加载即可;不同实验分开运行。§2.2 的 Foundation 片段是同节路径规则的可选扩展,接在路径片段之后。普通 JavaScript 演示会单独注明,无需 Frida。

将包名换成自己的实验 App。需要先准备文件或保存 IMP 基线的例子,先启动 Demo 做准备,再使用 frida -U -n IOSRiskDemo -l demo.js attach;重启进程后再换下一组实验。Objective-C 代码则放进自有 App 的 .m 文件,在实验按钮中触发。

涉及原生函数替换的片段直接使用 SystemFunction 转发未匹配调用,并保留返回值与 errno。原生替换函数中的 this.errno 来自 Frida 的调用上下文[10]。如果实验进程主动拒绝附加,先按 §3.7 确认处理时机。

示例用于自有或授权环境,按 Frida 17 CLI 编写;CLI 提供本文使用的 ObjC bridge。本次只有本地代码检查,没有 iOS 真机实测,后文“预期”均是需要验证的代码行为。

越狱检测最容易想到的是文件:包管理器、注入框架、启动配置是否存在。这类检查直观,但同一个环境往往不只留下一个文件。

rootless 让路径问题更明显。旧清单可能只盯着 /Library/MobileSubstrate,新环境则使用不同的 bootstrap 布局、重定位目录和链接关系。适配工作不能机械地给每条旧路径加上 /var/jb。

这里的 rootless 主要描述越狱的安装布局与对系统根卷的处理方式,不代表“没有越狱”。进程以哪个 UID 运行、是否受到沙盒限制、是否获得额外 entitlement、是否允许某种代码加载,也是不同维度。只看 getuid() 或某个目录,无法替代对这些实际能力的判断。

Substrate、Substitute、libhooker、ElleKit 等名称有助于整理生态对象。不过,增加四个框架名,未必增加了四种检测原理:它们可能最终都经过同一条文件查询。

对抗研究应当先分清两种变化:对象真的变了,还是观察方收到的结果变了。 移走文件与过滤一次查询,不一定同样影响已启动服务、已加载代码和历史缓存。

假设应用容器里有一个符号链接,它指向的文件已经被删除。lstat 能看到链接本体,stat 跟随链接后却失败。两者不一致,完全可以发生在正常环境里。

这就是为什么文件探针不能只保存一串 true/false。

把所有查询改成失败,可能连应用自己的正常文件都一起“藏”掉。只处理 NSFileManager,又可能漏掉直接使用 C 接口的读者。

检测方可以多路观察,但多路不等于独立:三个包装接口如果最终共用同一个被控制的下层,得到一致结果并不奇怪。应先画出调用关系,再判断增加了多少有效观察。

最直接的对抗,是让指定路径返回“不存在”,其余查询照常执行。例如规则明确查询下面两条路径,就按完整路径匹配,避免把任何包含 jb 的字符串都误伤。

这段代码在原调用之前判断路径。尤其是 fopen,不能先真实打开文件,再简单把返回指针改成空,否则可能泄漏句柄,写模式还可能已经创建或截断文件。命中时直接返回失败,调用者也不应继续使用 stat 的输出缓冲。

同一个实验中,若还要处理 Foundation 查询,将下面代码接在前一段后面。它复用同一份 paths,并处理带 isDirectory 输出参数的形式:

在 App 中先创建 Documents/ios-risk-demo-marker 与另一个正常文件,确认二者都能读取;再 attach 加载脚本,重复相同查询。实验所需的文件准备与观察可以直接写在按钮里:

其他 POSIX 接口按同样方式分别保存返回和错误码,预期如下:

它绕过的是这些接口上的精确路径规则。 open/openat、目录枚举、路径别名、其他符号变体和已加载镜像仍需分别研究。若检测器实际走的是 openat,给这份脚本多加十条路径也不会补上调用路径的缺口。

一张黑名单只能问“这些对象在不在”。加入自己掌握答案的对照,才能进一步检查查询通道。

文件实验可以准备一个刚创建并确认内容的文件,再准备一个不存在的路径。全部返回 false 会破坏阳性样本,全部返回 true 会破坏阴性样本。

Scheme 也能做类似实验:用自有接收 App 注册一个 Scheme,观察 App 正确声明并查询它;再查询一个已声明、测试环境中没有处理者的 Scheme。

Apple 对 canOpenURL: 的查询声明有明确要求[2]。未声明而返回 false,不能直接归因于 Hook;名字叫 dummy:// 的 Scheme,也没有永远无人注册的保证。

这组对照能暴露粗粒度改写,却未必发现只处理指定输入的修改。它把对抗问题从“返回一个期望值”,推进到“保持正常接口语义的同时影响指定查询”。

对应的代码只处理选定 Scheme,阳性与阴性对照仍走原实现:

实验时用自有接收 App 注册 riskdemo-jb 与另一个正常 Scheme,观察 App 在 LSApplicationQueriesSchemes 中声明两者。先确认两个查询都返回 YES,再安装脚本:目标应变为 NO,正常 Scheme 应保持 YES。这样才证明选择性改写,而不是碰巧查询了一个本来不存在或没有声明的 Scheme。

这能避开“全部返回 NO”造成的简单对照异常,却不会卸载处理者,也不覆盖实际打开 URL 的路径或其他证据。

这是路径检测之外很值得研究的一层:安装文件被隐藏了,已经启动的守护进程却可能继续提供服务。

观察可以拆成三个步骤:当前命名空间能否查询到名称,能否取得可用端口,使用约定协议后能否得到有效应答。

plist 内容能把这几个观察面连接起来。

配置声明不等于服务已经运行,服务运行也不等于任意 App 都有权访问。bootstrap 命名空间、沙盒规则、entitlements 和接口可用性,共同决定结果。macOS 上的具名 XPC 示例不能直接套成普通 iOS App 的能力。

配置读取还有一层容易遗漏:原始数据可以经过系统 plist 解析器,也可以先由文本扫描提取 XML 片段。NSScanner 一类路径与属性列表解析器不是同一个修改位置。只改变某个解析器返回,未必覆盖实际读者;文本扫描本身则仍要处理格式、转义和二进制 plist 等问题。

一个有价值的对抗实验,是在同一观察进程里保持服务运行,只改变文件查询,再比较服务通道。这样得到的结论比“路径已隐藏,所以环境已隐藏”具体得多。

若已经确认检测使用 bootstrap_look_up 查询某个服务,可以在这一层处理名称与输出端口。下面用自有服务名演示,按实验中实际查询的名称替换即可。

这与 POSIX 文件查询有个关键区别:1102 是 bootstrap 返回码,不能拿 ENOENT 填进去充数。由于命中时没有调用原函数,也就没有先获取一个 Mach send right 再丢弃的问题。

它可以改变通过该函数发起的后续名称查询,但拿不到已经缓存的端口,也不会关闭已建立的 XPC 连接。验证必须从“相同观察进程原本能查到服务”开始;受签名或命名空间限制,本来就查询失败的环境不构成成功绕过。

前面的检测都在问“某个已知对象在不在”。写入探针换了一个问题:我现在能不能创建文件、写进数据,再把它映射到内存? 隐藏包管理器路径,没有直接改变这组操作。

这里必须分开两种场景:向当前应用本来无权写入的位置尝试创建文件,以及在自己的 Documents 中完成文件读写。两者可以使用相似的 API,却不能得出同一个结论。

沙箱外也不能简单按 /private 这个前缀判断,因为应用容器的规范化路径本来就可能位于 /private/var/...。要核对的是目标与当前容器、访问授权的关系。

一条典型的文件映射采样链如下:

因此,即便某个实现把“映射成功”写进了风险位,也要分开描述程序如何编码结果和这个结果是否能证明越狱。没有正常设备的对照,不能把 Documents 可写称为“最强越狱判据”或“无法绕过”。

以下代码可放入自有 App 的 .m 文件。它保留创建、写入、映射这条链;为了不覆盖旧文件,使用 open(O_EXCL) 创建,再交给 fdopen 取得文件流。只有本次创建成功的文件才会被清理。

正常容器里,这组读写映射应具有成功的条件;若失败,要保留阶段和 errno,例如旧文件未清理导致的 EEXIST,不能混成“检测到沙箱限制”。fflush 保证映射前的内容已经从 stdio 缓冲写到文件,避免把缓冲问题误读成安全机制。

对于通过 mmap 返回值决定该项记录的实现,可以在映射这一层介入。下面代码面向 64 位 iOS:根据文件描述符取得真实路径,仅处理自有 Demo 的共享读写映射。

这里有三个容易写错的细节。mmap 失败返回 MAP_FAILED,不是空指针;fcntl 是变参函数,Frida 的参数列表中显式放了 ...;辅助的 F_GETPATH 查询不能污染未匹配调用的错误状态[10]。路径也先解析符号链接,减少 /var 与 /private/var 别名带来的不匹配。

先运行探针确认成功,再加载这段脚本重新触发,预期从 stage=1 变为 stage=-4 errno=13,创建与写入阶段仍然经过原实现。这证明的是指定映射结果被改变,并没有改变系统沙箱权限,也没有验证任何服务端风控结果。

如果目标使用 fopen("w+") 起步,也可以沿用 §2.2 的精确路径过滤,在打开阶段返回空指针。但两种介入不能混为一谈:拒绝打开会跳过后续步骤,拒绝映射则保留先前的写入和清理路径。失败究竟代表“未命中”还是另一种异常,要根据具体分支决定。Foundation 的 writeToFile:…、openat 或其他调用路径也不能默认都被同一个 Hook 覆盖。

文件路径看不到了,不代表启动时带入进程的信息也消失了。加载配置、工具工作目录、运行模式等,都可能通过环境变量提供线索。

这里有一个容易漏掉的区别:getenv("某个名称") 是查询函数,environ 是环境变量字符串表。修改查询函数的返回,并没有自动改掉表里的内容;检测器还可能在更早的时候复制过它。

以下自建示例只使用 IOS_DEMO_FLAG,不对应任何实际工具或应用。把代码放进实验 App 的 .m 文件,所有操作由同一串行实验入口触发,避免一边遍历、一边修改环境变量表。

准备完成后,三个读者的预期结果都是 1。再通过 attach 安装下面这段:

这会让指定查询返回空指针,其他名称仍走原实现。然后在 App 的实验入口中移除这个虚构变量,再观察一次:

表中是代码预期,需要设备验证。unsetenv 改变的是当前进程的环境状态,已经复制出去的数据不会因此消失;它也不会卸载此前根据环境配置加载的动态库。

因此,对抗要根据实际读者选择位置:过滤查询适用于走该接口的检测;改变环境状态影响后续读取;旧副本则要确认正常刷新条件。不要为了隐藏一个线索无差别清空环境,也不要在运行中的其他线程可能读取它时随意重写 environ 数组。

假设某次实验只记录到文件查询,隐藏路径后却突然出现了环境变量或写入探针。先别急着判断检测“升级”了:原来的函数可能命中第一项就返回,后面的代码此前根本没机会执行。

越狱检测常见两种控制流。首项命中即返回只报告当前顺序中最早的结果;逐项累积则继续执行后续探针,最后再组合。相同的路径、环境和权限检查,放进这两种控制流,实验日志会很不一样。

以下普通 JavaScript 用两个虚构因素展示区别,可以直接放进 Node.js 或浏览器控制台。名称只表示教学模型,没有实际查询设备:

第二行并不意味着文件 Hook 失效。在首项返回模型里,它成功让程序越过了第一道分支;在累积模型里,它只改变了一项,而最终的“任一命中”结果仍可能为真。§6.4 还会展示合并结果不变时,为什么连新记录都可能没有。

实际分析时,至少把一次探针的状态分成 未执行、执行失败、执行且未命中、执行且命中。配置关闭、平台不支持和被前一项短路,都属于没有取得本项结论;不能用一个 false 把它们与明确未命中混在一起。

§2.5 保留了创建、写入和映射的失败阶段,因为“让 API 失败”与“让检测通过”之间,还隔着调用者的分支。

以一次文件创建为例,相同的 -1/EACCES 可以被不同实现解释成三件事:目标确实受到访问限制;这条探针无法取得有效结论;进入备用检测。甚至还可能直接产生一条“检查异常”的记录。错误码本身没有替调用者作出这些选择。

还要区分成功取得映射与成功访问映射内容。mmap 返回地址,并没有证明随后读写每个字节都安全;例如访问完全超出文件末尾的映射页,可能触发 SIGBUS。因此,映射探针应记录文件长度、偏移、保护位、映射类型和是否真正访问过内容。

前面的 Demo 在映射前执行 fflush,文件长度覆盖了申请映射的示例内容,成功后立即解除映射,没有再访问其中的字节。它展示的是取得文件映射这一层的可干预性。不能把普通共享读写映射、可执行内存权限与沙盒外写入,合并成一个“沙箱可写”结论。

这一层的对抗重点是保留没有改变的操作语义:目标调用失败时返回正确的失败值,未匹配调用保留原错误状态,已获得的文件句柄、端口和映射仍按真实生命周期释放。否则,第二次运行产生的残留文件或资源泄漏,可能比第一条检测更早暴露问题。

越狱检测有时先做平台或签名判断,再决定运行哪些探针。这种前置分支必须一起看:在模拟器中得到“没有越狱风险”,可能只是相关检查被跳过,并没有对真机环境作出任何验证。

Apple Silicon 上的 iOS Simulator 可以执行 arm64 代码;开发签名、TestFlight、Mac 上运行 iOS App 等也有正常用途。判断必须结合该应用预期的分发与运行方式。

对抗时,修改机器名或收据查询可能改变前置分支,让某条检测不再执行。但它也可能触发另一套平台规则。正确的观察是“哪条分支改变、哪些探针跳过、调用者怎样解释结果”,而不是把一个平台属性伪装成功当成越狱环境已经消失。

前面的路径实验还有一个前提:查询确实经过了被 Hook 的函数。如果检测器自行准备参数,再用 svc #0x80 进入内核,替换 access、open 或 ptrace 的 C 包装入口就可能完全收不到这次调用。

这就是 Direct Syscall 值得单独讨论的地方。它绕开的是某条用户态包装路径,仍然受内核的权限与沙盒规则约束。 在 iOS 上还应确认实际的 libSystem/libsystem_kernel 入口与符号别名,不能把一个导出名当成所有调用的必经之处。

公开 XNU 源码里的 ARM64 BSD 调用路径使用 x16 传递调用号,参数由约定寄存器传入;svc 的立即数 0x80 表示这条陷入入口,不是 open 或 ptrace 的编号[12]。不能照搬 Linux AArch64 使用 x8 的示例,也不能把其他架构的调用号编码直接移过来。

stat、stat64、open、ptrace 等名称可以帮助找到候选,但要落到目标系统的调用表、实际调用号、参数和结构体布局。C 包装函数还可能选择不同变体,或处理取消、错误转换等细节。仅在二进制里找到一条 svc,并不能证明它执行了哪项检测。

错误返回尤其容易混淆。XNU 的 ARM64 BSD 返回路径在常规失败时设置 carry flag,并把错误号放进 x0;系统库包装随后才把它转换成调用者熟悉的 -1 与线程局部 errno[13]。直接执行 SVC 不会自动完成这次转换。把裸返回当作 POSIX 返回读取,或只在指令边界改 x0 却忽略条件标志,都可能使后续分支与输出互相矛盾。

这里继续使用 §2.2 的自有 marker 文件。下面的汇编只查询它的可访问性,不创建沙盒外文件,也不调用拒绝附加操作。

在自有 Demo 工程中加入一个 DemoRawAccess.S,内容全部如下。它面向 iOS 设备的 ARM64 BSD ABI;SYS_access 从当前 SDK 头文件取得,并应与目标系统核对。裸系统调用不属于可假定跨版本稳定的 iOS SDK 接口,本例未进行 Apple SDK 编译或设备运行。

在 §2.2 的同一个 .m 文件中加入以下观察入口:

第二行解释了“Hook 明明命中,另一条检测仍然发现文件”:前一条读者看到了被修改的包装函数返回,后一条读者仍在请求内核查询真实对象。这是待设备验证的调用路径对照,不是某个商业实现的复现。

反过来,如果文件本来就不存在,两条路径都可能失败。本例将裸调用失败转换成 -错误号,没有设置 POSIX errno;必须先统一错误语义,再比较是否一致。路径、权限、采样时机和结构体布局不一致造成的差异,也不能直接归为 Hook。

确认直接系统调用后,继续增加 open、stat 的路径关键词并不能扩大覆盖。用户态仍可以研究承载 SVC 的实际封装函数、它的调用者,以及返回后形成判断的分支;在授权环境中,指令级跟踪也可以用来观察调用号、实参和结果。这里要定位的是实际执行路径,而非仅扫描所有 SVC 后统一改写。§3.9 的原生函数入口示例说明了同一原则。

如果要在更下层交叉核对,内核态观测可以回答:当前进程究竟提交了哪些 BSD 系统调用、从哪里陷入、内核实际返回了什么。通过系统库和自有 SVC 发起的请求,可以在共同的内核分派路径汇合;按进程、线程、调用号、参数、返回状态和时间关联,才有机会分清用户态日志缺失与请求确实没有发生。

不过,XNU 源码里存在跟踪或审计路径,不等于普通 iOS App 可以使用它们。调试内核、受支持的研究设备或虚拟化环境,需要各自具备相应观测条件;越狱或 root 身份也不自动意味着能够任意插桩内核。观测到原始结果,和改变原始结果,更是两种能力。本文没有实现内核 Hook,也不把 macOS 上的跟踪工具直接当成 iPhone 上可用的方案。

Direct Syscall 因而补全了前面示例的边界:替换一个导出函数,只覆盖到达该入口的调用。 它能够提高部分用户态对抗的成本,但不能仅凭采用 SVC 就宣称检测不可绕过,更不能据此推断商业风控的普遍采用比例。

到这里,越狱检测已经可以分成对象可见性、实际能力、查询路径和控制流四层。接下来把视线移进进程,Frida 检测还会继续问:工具留下了什么,代码又发生了什么变化。

讨论“能不能检测 Frida”,最好先说清楚测试的运行方式。同一个工具,在不同加载方式和时机下,留下的痕迹并不相同。

attach 与 spawn 首先是介入时机的区别,不是两套天然不同的“隐身能力”。Gadget 也不能直接套用某个 server 默认端点的检测假设。

这一步看似只是交代环境,实际上决定了后面的阴性结果有没有意义:探针没有发现它本来就没有机会观察的对象,不能据此证明整体不可检测。

frida-server、gum-js-loop、gmain、gdbus 等名称,是最容易理解的一类入口。它们来自具体实现与配置,适合定位线索,却不适合作为全部结论。

对抗到这一层,最容易产生一个错觉:某份脚本查不到 Frida 字样,便认为已经处理完检测。事实上,这只验证了那份脚本的名称规则。下一层完全可以不关心工具叫什么,直接观察函数有没有被改变。

检测方也需要正常对照。GLib 不是 Frida 专属,诊断库可以来自 Xcode,合法 SDK 也可能改写方法。来自同一次注入的文件、镜像和线程线索,还应保留共同原因,避免重复计成多份独立证据。

一个地址属于系统库,不能独立证明这次调用没有被改变。先把常见调用方式画出来:

方法名不变,IMP 可以被替换;dlsym 找到原始实现,业务调用使用的导入槽却可能指向别处;地址和指针都没变,原实现入口的指令也可能已经改变。

由此可以解释一类常见现象:dladdr 报告系统镜像,而函数执行却已经转向其他位置。传入的原地址确实在系统镜像里,地址来源检查没有回答它之后执行了什么。

对抗也要与位置匹配。只处理 IMP 检查,不等于处理了入口指令检查;只改变镜像枚举,不会自然恢复调用方指针。验证时,把“解析到谁、调用点指向谁、实际执行到谁”对应起来,比单独打印一个函数地址有用。

这里仍需要区分正常行为:开发者可以合法交换方法,系统包装函数与编译器跳板可以包含分支。首条指令是跳转,并不是完整的 Hook 判据。

面对 IMP、导入槽、Inline 三种观察,先选择一个已经定位的检测器。下面是一个可以直接放进自有 App 的完整小类:先保存 IMP,再替换方法,最后检查当前映射。

这里的最小对抗可以直接发生在检测返回处:

先保存基线,再用 Demo 的方法替换制造一个已知变化,确认检测返回 YES;安装这段 Hook 后,同一次检测的调用者应得到 NO。这就是一个具体的返回值绕过,代码没有假装把原 IMP 恢复了。

但例子故意保留了 demoCachedResult = changed。原方法先执行并写入缓存,onLeave 才改返回,所以还要同时读取缓存。§6.2 会展示为什么“检测返回 NO”和“历史风险记录清空”是两回事。

若另一个检测器检查此检测方法自身的代码,这段 Hook 也可能被看到。把 dladdr 全部伪造成系统库来源,或把所有 IMP 都替换成同一个地址,不会解决独立的指针与代码比较。针对哪条检测改哪层,并记录剩余观察,才是这类示例可迁移的部分。

某些检测会通过动态解析取得系统函数,而不是一直使用某个固定导入槽。更深入的实现还可能研究私有 dyld 入口,例如 __dyld_func_lookup 一类路径。

它们的价值在于提供另一条取址方式:如果只修改了调用方的导入槽,另一条解析路径可能不受同一修改影响。

但如果多条解析路径最终都到达同一份被修改的代码,换解析器仍会碰到共同目标。这就是为什么“动态解析可以避开某种重绑定”与“动态解析不受 Hook 影响”不是同一个结论。

私有入口的名称、可用性与语义需要按系统版本验证;ASLR、共享缓存和 arm64e 指针认证,也会影响实际地址处理。未验证的地址掩码与旧式导入表遍历,不适合直接当成跨版本实现。

如果调用方进一步直接执行 SVC,问题就从“通过哪条路径取得函数地址”,变成了“是否还经过系统库包装”。两者的对抗位置不同,详见 §2.10。

Mach-O 的初始化入口提供了另一个研究方向。传统布局可以包含 __mod_init_func 数组;检查时应按实际 Mach-O 的段、节和加载信息定位,不能固定假设它总在 __DATA,也不能假设所有初始化都通过同一种布局表达。随后才能核对入口指向哪些代码、所属镜像是否符合预期。

这里的增量是把观察从某个目标函数扩展到加载与初始化过程。对抗是否覆盖一个业务函数,与是否覆盖初始化相关观察,是不同问题。

正常动态库本来就有初始化函数。要写成有效检测,还需要给出实际遍历、比较条件、执行时机与正常基线。本节只提出观察位置,没有给出经过验证的识别器;仅发现初始化相关名称或框架符号,不足以判断注入。

假设工具的文件名和进程名都已经改变,但它仍在原来的地址接收连接,并按原来的协议回答请求。此时,查询名字的规则可能失效,主动连接的规则却仍有机会取得线索。

127.0.0.1 是设备自己的 IPv4 回环地址。向它发起连接,是在询问本机服务。这里要分清三层:端口能连上、服务会回答、回答符合某种协议特征。越往后,判断越具体,需要验证的条件也越多。

以 Frida 排查中常见的认证协商思路为例,可以研究带行结束符的 AUTH 请求与 REJECT 类响应标记。重点是观察一次有上下文的交互:连接是否成功、发送了哪些字节、回包是否完整、哪条规则成立。认证被拒绝不妨碍探针获得协议线索;它要识别的是应答方式,未必需要登录成功。

不过,AUTH 与拒绝应答不是 Frida 独占的词。静态材料里出现 REJECT,也没有证明真实回包就只有这个词,更没有给出完整认证流程。将它写成 Frida 检测,需要补上具体版本、端点配置、请求与响应的对应关系,并检查其他协议兼容服务是否也会命中。不同 Frida 部署方式,包括不同配置的 server 与 Gadget,也不能默认暴露相同端点。

HTTP 探测则应单独理解。GET / HTTP/1.0 配合本机 Host,可以用来观察是否存在 HTTP 响应者;请求某个控制接口路径,可以进一步观察功能特征。但返回一个网页不等于发现 Frida,路径里带有录制或控制字样也不足以确定服务身份。检测对象应由实际响应与协议行为确定。

从对抗角度看,这几层也对应不同的作用范围:

因此,这一节适合做一个直观对照:在自有实验环境中,先记录原始连接与回包,再仅改变名称,最后改变端点配置。每一步分别展示名称查询、端点可达性与协议匹配结果。若只改名后应答仍相同,就能解释为什么改名没有覆盖这条检测;连接失败则只能说明本次未取得应答,不能推出进程中没有 Frida。

对于“连上端点 → 发送 AUTH 或 HTTP → 比较回包”的检测,最小对抗不必伪造每一种响应。若它依赖 connect,可以先让选定端点的连接返回拒绝,握手就不会通过这次连接发生。

关键在地址解析。Darwin 的 sockaddr_in 前两个字节是 sin_len、sin_family;不能照搬 Linux 的字段布局。端口是网络字节序,也不能直接按主机字节序读取。

这里直接改变的是指定连接的结果。 代码命中时不执行原 connect,依赖这次连接的握手也就拿不到应答;其他端点保留原返回与错误码。只有上层将这种失败解释为未命中,这次改动才构成该项规则的绕过。27042 是示例端口,实际应以服务的监听配置为准。

验证时至少比较目标回环端点、另一个正常回环端点和普通网络连接。目标应在安装前确实可达,安装后返回 -1/61;其余连接按原行为执行。再观察上层怎样解释失败:如果它把失败记成异常,或者改走另一条探测路径,阻止握手也不等于整体风控通过。

这个例子没有覆盖 IPv6 的 ::1、Unix socket、connectx、直接系统调用或已有连接。也不建议全局改写 strcmp 去吞掉 REJECT:那会影响大量无关比较,而且真正的协议判据可能根本不经过该函数。


[招生]科锐逆向工程师培训(2026年11月13日实地,远程教学同时开班, 第57期)

收藏
点赞 ・1
打赏
分享
最新回复 (2)
雪    币: 110
活跃值: (9656)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2 楼
tql
2026-10-7 10:15
0
雪    币: 364
活跃值: (437)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3 楼
cool!
2026-10-10 15:51
0
游客
登录 | 注册 方可回帖
返回