news 2026/10/7 9:56:19

OWASP MASTG 实战:用 Frida 验证 iOS 应用的虚拟设备检测逻辑(MASTG-DEMO-0131)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
OWASP MASTG 实战:用 Frida 验证 iOS 应用的虚拟设备检测逻辑(MASTG-DEMO-0131)
  • 文档
  • 教程
  • 网络安全

【免费下载链接】mastg

The OWASP Mobile Application Security Testing Guide (MASTG) is a comprehensive manual for mobile app security testing and reverse engineering. It describes technical processes for verifying the OWASP Mobile Security Weakness Enumeration (MASWE) weaknesses, which are in alignment with the OWASP MASVS.

项目地址:https://gitcode.com/gh_mirrors/ow/mastg
点击查看免费下载

本文基于 demos/ios/MASVS-RESILIENCE/MASTG-DEMO-0131 编写。该 Demo 演示了如何构建一个自包含的 iOS 示例应用:应用在运行时主动查询设备属性、Metal GPU 可用性以及 Corellium 虚拟化引擎守护进程文件,作为虚拟设备(virtual device)检测指示器;测试人员则借助 Frida 插桩捕获这些底层 API 调用与调用栈,验证应用是否确实执行了虚拟设备检测逻辑,从而通过 MASTG-TEST-0367 动态测试。读完本文,你将掌握三类典型 iOS 虚拟设备指示器的实现写法、用 Frida 挂钩 C 函数并记录回溯(backtrace)的完整流程,以及如何依据运行输出判定测试通过与否。

一、背景:iOS 虚拟设备检测为何重要

在反逆向(anti-reversing)语境下,模拟器/虚拟设备检测的目标是提高攻击者在模拟或虚拟化环境中运行应用的难度。当检测被部署后,逆向工程师要么必须绕过这些检查,要么只能改用物理设备,从而限制了大规模设备分析所需的访问途径(参见 MASTG-KNOW-0135 的 Overview 部分)。

所谓虚拟设备,是指虚拟化了 iOS 应用所期望的硬件与操作系统环境、且能够直接执行 iOS 设备二进制的环境。它与 iOS Simulator(运行模拟器构建产物)有本质区别,后者属于 MASTG-KNOW-0088 讨论的范畴。自 MASTG-TOOL-0108(Corellium) 发布以来,iOS 虚拟化得以商用化——Corellium 使用真正的 ARM 型 1 型虚拟机监视器(type 1 hypervisor),其虚拟设备是 ARM 原生的,可以在不做代码修改的情况下运行生产代码,这让在虚拟设备上开展 iOS 应用的逆向与安全测试成为现实;与此同时,近期的 vPhone 与 Super Tart 等研究项目也进一步扩展了虚拟设备检测的对抗面(见 MASTG-KNOW-0135)。

检测的核心策略是识别常见虚拟化方案的特性与局限。典型指示器包括:

  • 硬件能力探测(Hardware Capability Probes):Corellium 官方文档明确指出其 iOS 设备缺少 GPU/Metal 支持,且当前不支持 NFC 与 Bluetooth。因此可通过MTLCreateSystemDefaultDevice()(Metal GPU)、NFCReaderSession.readingAvailable(NFC)、CBCentralManager(蓝牙)等系统 API 查询硬件存在性;
  • 设备属性查询(Device Properties):通过sysctlbyname("hw.machine")获取硬件机型标识,再与运行时观察到的硬件能力做交叉校验——一个宣称的机型若与真实应具备的能力不匹配,便是虚拟设备的有力线索;
  • 虚拟化引擎文件存在性(Presence of Specific Virtualization Engine Files):Corellium 会在设备中加入名为corelliumd的守护进程(其标准路径为/usr/libexec/corelliumd),该文件不属于标准 iOS 设备。

需要注意的是,MASTG-KNOW-0135 同时给出安全警示:虚拟设备检测本质上是猫鼠游戏,检测方法与绕过手段持续演化,有充分时间与资源的攻击者可通过挂钩(hooking)或修补(patching)检测逻辑来规避;文件类检查也容易被隐藏、重命名、删除或拦截访问。因此这些技术应作为纵深防御(defense-in-depth)的一部分,而非独立解决方案。

二、示例应用:三类指示器检查的完整实现

MASTG-DEMO-0131 的示例应用(Swift 实现)位于 demos/ios/MASVS-RESILIENCE/MASTG-DEMO-0131/MastgTest.swift。应用执行虚拟设备指示器检查,并在 UI 中报告查询值与指示器结果(代码中注释为 "This sample demonstrates iOS virtual device detection by executing virtual device indicator checks and reporting a final verdict in the UI")。

2.1 指示器分级模型与结果结构

示例先定义了一个四级指示器枚举IndicatorLevel:

private enum IndicatorLevel { case expected // 符合物理设备预期 case suspicious // 可疑 case confirmed // 已确认(强证据) case inconclusive // 无法得出结论 }

以及承载单条检查结果的结构体IndicatorResult,包含检查顺序order、指示器名称name、观测值observed、分级level与原因说明reason:

private struct IndicatorResult { let order: Int let name: String let observed: String let level: IndicatorLevel let reason: String }

2.2 三项检查的逐项解析

检测器核心类为VirtualDeviceDetector,其run()依次串联三类检查:

func run() -> String { let machineIdentifier = currentMachineIdentifier() let results = [ machineIndicator(for: machineIdentifier), metalIndicator(), corelliumIndicator() ] return buildReport(from: results) }

第一项:设备机型标识检查(machineIndicator)。通过sysctlbyname("hw.machine")查询硬件机型标识:

private func currentMachineIdentifier() -> String { var size: size_t = 0 guard sysctlbyname("hw.machine", nil, &size, nil, 0) == 0, size > 1 else { return "unknown" } var buffer = CChar) let result = buffer.withUnsafeMutableBufferPointer { pointer in sysctlbyname("hw.machine", pointer.baseAddress, &size, nil, 0) } guard result == 0 else { return "unknown" } return String(cString: buffer) }

实现采用标准的两段式sysctlbyname调用:先传空缓冲区获取所需size(同时校验调用成功且size > 1),再分配缓冲区第二次调用取回 C 字符串。随后machineIndicator(for:)依据返回值分级——若返回"unknown"判为inconclusive("The app could not resolve the device model."),否则判为expected,原因统一为:"The reported model can be cross-checked against the hardware capabilities observed at runtime."(源码中标注PASS: [MASTG-TEST-0367])。

第二项:Metal GPU 可用性检查(metalIndicator)。调用MTLCreateSystemDefaultDevice()探测默认 Metal 设备:

if MTLCreateSystemDefaultDevice() != nil { return IndicatorResult(order: 2, name: "Metal GPU", observed: "available", level: .expected, reason: "A Metal device is available, which is consistent with a physical iOS device.") } return IndicatorResult(order: 2, name: "Metal GPU", observed: "missing", level: .suspicious, reason: "No Metal device is available, which is unusual for a supported physical iOS device.")

逻辑非常直观:Metal 设备可用 →expected(与物理设备一致);不可用 →suspicious(受支持的物理 iOS 设备上通常都应有 Metal 设备)。这与 MASTG-KNOW-0135 中 Corellium iOS 设备"缺乏 GPU/Metal 支持"的官方说明相对应。

第三项:Corellium 守护进程文件检查(corelliumIndicator)。用stat检查/usr/libexec/corelliumd是否存在:

private func corelliumDaemonExists() -> Bool { var fileInfo = stat() return "/usr/libexec/corelliumd".withCString { path in stat(path, &fileInfo) == 0 } }

若文件存在则判为confirmed并给出强证据理由("The file /usr/libexec/corelliumd exists, which is a strong Corellium indicator.");不存在则判为expected("The file /usr/libexec/corelliumd was not found.")。注意 MASTG-KNOW-0135 同时提醒:Corellium 也支持创建不带corelliumd守护进程的 iOS 设备,因此文件缺失不足以排除 Corellium 虚拟设备——文件类检查只是指示器,且易于被绕过。

2.3 报告与最终判定(buildReport)

buildReport(from:)先将结果按order排序,再统计confirmed与suspicious结果,生成多行文本报告:

private func buildReport(from results: [IndicatorResult]) -> String { let orderedResults = results.sorted { $0.order < $1.order } let confirmedResults = orderedResults.filter { $0.level == .confirmed } let suspiciousResults = orderedResults.filter { $0.level == .suspicious } var lines = [ "Virtual device detection results:", "", "Indicators:" ] for result in orderedResults { lines.append("- \(result.name): \(result.observed) [\(result.level.label)] - \(result.reason)") } lines.append("") lines.append("Verdict:") if !confirmedResults.isEmpty { lines.append("- Likely virtual device.") for result in confirmedResults { lines.append(" - \(result.name): \(result.reason)") } } else if !suspiciousResults.isEmpty { lines.append("- No strong virtual-device verdict yet. Review the suspicious indicator below.") for result in suspiciousResults { lines.append(" - \(result.name): \(result.reason)") } } else { lines.append("- Likely physical device. No strong virtual-device indicators were observed.") } return lines.joined(separator: "\n") }

判定逻辑形成三级结论:存在confirmed结果 → "Likely virtual device."(并逐条列出确认依据);仅有suspicious→ "No strong virtual-device verdict yet."(要求人工复核可疑项);否则 → "Likely physical device."。最后MastgTest.mastgTest(completion:)通过闭包把报告字符串交给 UI 展示。

2.4 示例的刻意取舍(设计说明)

原文档明确了两点设计取舍:

  • 回避 NFC 与蓝牙检查:这两类检查可能需要额外的 entitlements、用途描述字符串(usage descriptions)或依赖用户可控状态(如蓝牙开关),会削弱 Demo 的确定性(determinism);
  • 回避 App Attest 检查:App Attest 需要服务端校验流程,破坏 MASTG Demo 自包含(self-contained)的要求。

因此示例聚焦于设备属性、Metal GPU 可用性与 Corellium 文件这三类确定性强、无需额外配置的指示器。

三、Frida 插桩:如何捕获虚拟设备检测调用

示例配套的 Frida 脚本 demos/ios/MASVS-RESILIENCE/MASTG-DEMO-0131/script.js(配合 MASTG-TOOL-0039(Frida,iOS))挂钩三个底层 C 函数,精确对应示例的三项检查。

3.1 通用挂钩基础设施

脚本先定义了两块基础设施:

const BACKTRACE_LIMIT = 6; const hookedAddresses = new Set(); function printBacktrace(context) { console.log("Backtrace:"); const frames = Thread.backtrace(context, Backtracer.ACCURATE) .map(DebugSymbol.fromAddress) .slice(0, BACKTRACE_LIMIT); for (const frame of frames) { console.log(frame); } } function logEvent(message, context) { console.log(message); printBacktrace(context); console.log(""); } function hookGlobalExport(symbolName, callbacks) { const address = Module.findGlobalExportByName(symbolName); if (address === null) { console.log(`[skip] ${symbolName} not found`); return; } const addressKey = address.toString(); if (hookedAddresses.has(addressKey)) { return; } hookedAddresses.add(addressKey); Interceptor.attach(address, callbacks); }

要点:Thread.backtrace+Backtracer.ACCURATE采集精确调用栈,DebugSymbol.fromAddress将地址解析为符号名,最多保留前 6 帧(BACKTRACE_LIMIT);hookGlobalExport通过Module.findGlobalExportByName定位全局导出符号,并用Set去重避免重复挂钩同一地址。

3.2 挂钩MTLCreateSystemDefaultDevice

hookGlobalExport("MTLCreateSystemDefaultDevice", { onEnter(args) { this.contextCopy = this.context; }, onLeave(retval) { const outcome = retval.isNull() ? "missing" : "available"; logEvent(`MTLCreateSystemDefaultDevice() => ${outcome}`, this.contextCopy); } });

在onEnter中保存上下文副本(用于回溯),在onLeave中根据返回指针是否为 null 输出available/missing。

3.3 挂钩sysctlbyname(过滤hw.machine)

hookGlobalExport("sysctlbyname", { onEnter(args) { this.contextCopy = this.context; this.name = args[0].readCString(); this.outputPtr = args[1]; this.shouldLog = this.name === "hw.machine" && !this.outputPtr.isNull(); }, onLeave(retval) { if (!this.shouldLog || retval.toInt32() !== 0) { return; } const value = this.outputPtr.readCString(); logEvent(`sysctlbyname("hw.machine") => ${value}`, this.contextCopy); } });

注意两个过滤条件:仅当查询名称为hw.machine且输出缓冲区指针非空(即第二次取值调用,而非第一次求 size 的调用)时记录;仅当retval == 0(调用成功)时读取并输出结果。

3.4 挂钩stat(过滤 Corellium 守护进程路径)

function hookStatSymbol(symbolName) { hookGlobalExport(symbolName, { onEnter(args) { this.contextCopy = this.context; this.path = args[0].readCString(); this.shouldLog = this.path === "/usr/libexec/corelliumd"; }, onLeave(retval) { if (!this.shouldLog) { return; } const outcome = retval.toInt32() === 0 ? "present" : "missing"; logEvent(`${symbolName}("/usr/libexec/corelliumd") => ${outcome}`, this.contextCopy); } }); } hookStatSymbol("stat");

同样用路径过滤,只记录目标路径/usr/libexec/corelliumd,并以返回值为 0 与否判定present/missing。

四、运行步骤与复现

原文档给出的运行步骤(安装应用见 MASTG-TECH-0056,Frida 安装与使用见 MASTG-TOOL-0039):

  1. 在设备上安装应用(org.owasp.mastestapp.MASTestApp-iOS);
  2. 确保本机已安装 Frida,且设备上已运行frida-server(在越狱设备上通过 Sileo 等渠道安装后自动以 root 运行,默认仅监听本地 USB 接口,可通过frida-ps -U验证连接);
  3. 运行 run.sh 以 Frida 启动应用:
    #!/bin/bash frida -U -f org.owasp.mastestapp.MASTestApp-iOS -l ./script.js -o output.txt

    -U连接 USB 设备,-f以 spawn 方式冷启动目标应用(应用启动前即注入),-l加载脚本,-o将输出写入output.txt;

  4. 点击应用中的Start按钮,触发检测流程;
  5. 按Ctrl+C停止脚本。

五、运行输出解读(Observation)

Demo 仓库附带的 output.txt 记录了真实运行捕获。完整输出为:

sysctlbyname("hw.machine") => iPhone10,6 Backtrace: 0x1c2ac4368 CoreFoundation!___CFGetProductName_block_invoke 0x1c9794780 libdispatch.dylib!_dispatch_client_callout 0x1c9764ddc libdispatch.dylib!_dispatch_once_callout 0x1c2a0fe1c CoreFoundation!_CFBundleInfoPlistProcessInfoDictionary 0x1c2a39e30 CoreFoundation!_CFBundleCopyInfoDictionaryInDirectoryWithVersion 0x1c2a2e588 CoreFoundation!_CFBundleRefreshInfoDictionaryAlreadyLocked sysctlbyname("hw.machine") => iPhone10,6 Backtrace: 0x104e29fb0 MASTestApp.debug.dylib!closure #1 in VirtualDeviceDetector.currentMachineIdentifier() 0x104e2a000 MASTestApp.debug.dylib!partial apply for closure #1 in VirtualDeviceDetector.currentMachineIdentifier() 0x104e2a108 MASTestApp.debug.dylib!$sSa30withUnsafeMutableBufferPointeryqd__qd__SryxGzqd_0_YKXEqd_0_YKs5ErrorRd_0_r0_lF 0x104e287cc MASTestApp.debug.dylib!VirtualDeviceDetector.currentMachineIdentifier() 0x104e28090 MASTestApp.debug.dylib!VirtualDeviceDetector.run() 0x104e28040 MASTestApp.debug.dylib!static MastgTest.mastgTest(completion:) MTLCreateSystemDefaultDevice() => available Backtrace: 0x104e28b4c MASTestApp.debug.dylib!VirtualDeviceDetector.metalIndicator() 0x104e28104 MASTestApp.debug.dylib!VirtualDeviceDetector.run() 0x104e28040 MASTestApp.debug.dylib!static MastgTest.mastgTest(completion:) 0x104e2d018 MASTestApp.debug.dylib!closure #1 in closure #1 in closure #1 in ContentView.body.getter 0x1c686b35c SwiftUI!0x7d735c (0x18ac6f35c) 0x1c686b844 SwiftUI!0x7d844 (0x18ac6f844) stat("/usr/libexec/corelliumd") => missing Backtrace: 0x104e2a5c4 MASTestApp.debug.dylib!closure #1 in VirtualDeviceDetector.corelliumDaemonExists() 0x104e2a5fc MASTestApp.debug.dylib!partial apply for closure #1 in VirtualDeviceDetector.corelliumDaemonExists() 0x1bc937a1c libswiftCore.dylib!String.withCString<A>(_:) 0x104e29dec MASTestApp.debug.dylib!VirtualDeviceDetector.corelliumDaemonExists() 0x104e28d60 MASTestApp.debug.dylib!VirtualDeviceDetector.corelliumIndicator() 0x104e28124 MASTestApp.debug.dylib!VirtualDeviceDetector.run()

对捕获结果的逐条解读(点击Start按钮后触发):

  • sysctlbyname("hw.machine")返回iPhone10,6:共出现两次调用。第一次的调用栈位于CoreFoundation/libdispatch.dylib,属于框架层(Framework-level)的早期调用(___CFGetProductName_block_invoke等);第二次的调用栈定位到应用自身,即VirtualDeviceDetector.currentMachineIdentifier()及其闭包(closure),证实示例在运行时查询了设备机型标识;
  • MTLCreateSystemDefaultDevice()返回available:调用栈清晰显示VirtualDeviceDetector.metalIndicator()→VirtualDeviceDetector.run()→MastgTest.mastgTest(completion:)→ContentView.body.getter(SwiftUI 视图),证实示例执行了 Metal GPU 可用性检查,且当前环境存在 Metal 设备;
  • stat("/usr/libexec/corelliumd")返回missing:调用栈显示VirtualDeviceDetector.corelliumDaemonExists()(内部经String.withCString调用)→VirtualDeviceDetector.corelliumIndicator()→VirtualDeviceDetector.run(),证实示例检查了 Corellium 守护进程文件,且该文件在当前环境不存在。

run.sh将脚本输出写入output.txt,回传的console.log即上述逐行记录;每个事件后都附带了 6 帧以内的符号化回溯,足以区分"框架自身行为"与"应用主动调用"。

六、测试判定:MASTG-TEST-0367 通过

本 Demo 对应动态测试 tests-beta/ios/MASVS-RESILIENCE/MASTG-TEST-0367.md(Runtime Use of Virtual Device Detection Techniques,关联 MASWE-0053)。该测试通过尝试挂钩常见虚拟设备检测机制,验证应用是否实现了对 iOS 虚拟设备(如 Corellium)存在性的运行时检测;其判定规则为:若未观察到任何虚拟设备检测检查实例,则测试失败(fail)。

MASTG-DEMO-0131 的测试结果为pass,依据是运行输出确认应用在运行时实现了三类检测:

  • sysctlbyname("hw.machine")调用(设备属性检查):应用查询了设备机型标识;报告的机型可以与运行时观察到的硬件能力交叉校验(cross-check);
  • MTLCreateSystemDefaultDevice()调用(硬件能力检查):应用检查了是否存在 Metal GPU;
  • stat("/usr/libexec/corelliumd")调用(虚拟化引擎文件检查):应用检查了 Corellium 守护进程文件是否存在。

测试边界与预期误报(Expected False Negatives)

MASTG-TEST-0367 明确指出该测试不覆盖检测机制的健壮性与有效性——其效果往往难以仅靠自动化测试评估,可能需要手动逆向与自定义插桩(相关最佳实践见 MASTG-BEST-0053)。同时存在预期误报场景:

  • 应用使用了未被挂钩/追踪机制覆盖的虚拟设备检测机制;
  • 检测逻辑通过混淆、动态代码加载或反插桩(anti-instrumentation)技术规避了追踪。

在这些情况下,未发现检测行为并不保证应用没有实施虚拟设备检测,仍需额外的手动逆向或自定义插桩来识别与分析。

七、相关资源

  • 示例完整源码:MastgTest.swift、script.js、run.sh、output.txt
  • 知识背景:MASTG-KNOW-0135(Virtual Devices Detection)
  • 关联测试:MASTG-TEST-0367
  • 相关工具:Corellium(MASTG-TOOL-0108)、Frida iOS(MASTG-TOOL-0039)、应用安装(MASTG-TECH-0056)
  • 最佳实践:MASTG-BEST-0053

如需在真实目标上验证,可将 script.js 的挂钩目标(sysctlbyname、MTLCreateSystemDefaultDevice、stat)与过滤条件替换为目标应用可能使用的检测 API 与文件路径,并将run.sh中的应用标识换成目标应用的 Bundle ID,即可复现相同的插桩观察流程。

  • 文档
  • 教程
  • 网络安全

【免费下载链接】mastg

The OWASP Mobile Application Security Testing Guide (MASTG) is a comprehensive manual for mobile app security testing and reverse engineering. It describes technical processes for verifying the OWASP Mobile Security Weakness Enumeration (MASWE) weaknesses, which are in alignment with the OWASP MASVS.

项目地址:https://gitcode.com/gh_mirrors/ow/mastg
点击查看免费下载
上一篇:免费也能下Steam模组?WorkshopDL Steam创意工坊下载器零基础完整指南
下一篇:魔兽争霸3兼容速修:三步解除4MB地图限制、解锁144帧的免费插件实测

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 9:55:11

GPT-SoVITS hps 未定义报错完整修复指南

GPT-SoVITS hps 未定义报错完整修复指南 【免费下载链接】GPT-SoVITS 1 min voice data can also be used to train a good TTS model! (few shot voice cloning) 项目地址: https://gitcode.com/GitHub_Trending/gp/GPT-SoVITS GPT-SoVITS 这个少样本语音克隆项目&…

作者头像 李华
网站建设 2026/10/7 9:54:45

荣耀手机与iPhone互传文件:碰一碰NFC传输全攻略

荣耀MagicOS 和 iPhone 之间怎么互传文件&#xff0c;这个问题我最近被问了很多次。以前大家默认跨系统传文件就是微信发原图、QQ 传文件&#xff0c;或者干脆用网盘中转&#xff0c;但荣耀 MagicOS 8.0 之后&#xff0c;"荣耀分享"这个功能已经可以做到用 iPhone 直…

作者头像 李华
网站建设 2026/10/7 9:54:00

Spark+Scala+Hive高校三源异构数据清洗聚类实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/7 9:54:00

(十六)openclaw-公众号自动发布技能:一篇文章推到草稿箱,群发只留一个按钮

适合读者:有公众号、也在用 openclaw(开源 Agent 框架)写稿的人。你手里已经有一篇稿子,想让它自动进公众号草稿箱——标题、正文、封面、产品橱窗一次到位,最后群发那一下自己点。全文跟着做,每个文件、每条命令都给全,可以完整复现出这个技能。源码详见文末链接。 技能…

作者头像 李华
网站建设 2026/10/7 9:53:25

Ktlint 自定义 RuleSet 开发指南:从模板项目到自研规则实战

开发工具代码质量Lint格式化 【免费下载链接】ktlint An anti-bikeshedding Kotlin linter with built-in formatter 项目地址&#xff1a; https://gitcode.com/gh_mirrors/kt/ktlint 点击查看 免费下载 导读 本文面向需要为 Ktlint 定制团队代码规范的开发者&#xff0c;系…

作者头像 李华
网站建设 2026/10/7 9:51:34

ArkTS 表单工程:保养录入页的换行胶囊与四字段表单

ArkTS 表单工程&#xff1a;保养录入页的换行胶囊与四字段表单 App 58「车辆保养提醒」保养页&#xff08;Func1Tab&#xff09;&#xff0c;主题色 #008080 青蓝。本页是保养记录的"录入页"——白色单行 Header&#xff08;"记录保养"20 号加粗&#xff0…

作者头像 李华