← API | 列表 | swift_SDK安全检测_IOSSecuritySuite
提示信息
# 同伴 App — IOSSecuritySuite 安全检测详解

> 纯 Swift 编写的 iOS 平台安全与防篡改库,覆盖 OWASP MASVS v8 标准。
> GitHub: https://github.com/securing/IOSSecuritySuite
> Pod: `pod 'IOSSecuritySuite'`

---

## 一、总览:8 大检测类别 × 21 个公开 API

| # | 类别 | API 数量 | 说明 |
|---|------|----------|------|
| 1 | 越狱检测 | 3 | 7 种子检测(URL Scheme / 文件 / 目录 / fork / 符号链接 / DYLD) |
| 2 | 调试器检测 | 3 | 检测 + 阻止 + 父进程异常 |
| 3 | 模拟器检测 | 1 | 真机 vs 模拟器 |
| 4 | 逆向工程检测 | 2 | 4 种子检测(Frida / 可疑端口 / DYLD / 进程标志) |
| 5 | 运行时 Hook 检测 | 4 | ObjC Hook / MSHook / fishhook |
| 6 | 完整性验证 | 2 | BundleID / MobileProvision / Mach-O 哈希 |
| 7 | 网络环境检测 | 2 | HTTP 代理 / 锁定模式 |
| 8 | 断点监视点检测 | 2 | 函数断点 / 内存监视点 |

**合计:21 个公开方法**

---

## 二、越狱检测(Jailbreak Detection)

### 2.1 API

```swift
// 简单判断
IOSSecuritySuite.amIJailbroken() -> Bool

// 带失败消息
IOSSecuritySuite.amIJailbrokenWithFailMessage() -> (jailbroken: Bool, failMessage: String)

// 带详细失败检查项(推荐用于日志上报)
IOSSecuritySuite.amIJailbrokenWithFailedChecks() -> (jailbroken: Bool, failedChecks: [FailedCheckType])
```

### 2.2 七种子检测

| # | 检测项 | FailedCheckType | 原理 |
|---|--------|-----------------|------|
| 1 | URL Scheme 探测 | `.urlSchemes` | 尝试 `canOpenURL` 检测越狱工具的 URL Scheme |
| 2 | 可疑文件存在性 | `.existenceOfSuspiciousFiles` | 检查 80+ 个已知越狱文件路径是否存在 |
| 3 | 可疑文件可打开 | `.suspiciousFilesCanBeOpened` | 尝试 `fopen` 打开 `/bin/bash`、`/usr/sbin/sshd` 等 |
| 4 | 受限目录可写 | `.restrictedDirectoriesWriteable` | 尝试写入 `/`、`/root/`、`/private/` 等 |
| 5 | fork() 调用 | `.fork` | 沙盒内 `fork()` 应返回失败,成功则说明越狱 |
| 6 | 符号链接异常 | `.symbolicLinks` | 检查系统目录是否存在非标准符号链接 |
| 7 | DYLD 动态库 | `.dyld` | 扫描已加载动态库中是否包含 `MobileSubstrate.dylib`、`Substrate`、`Electra` 等 |

### 2.3 URL Scheme 检测目标(需配置 Info.plist LSApplicationQueriesSchemes)

| URL Scheme | 对应工具 |
|------------|----------|
| `cydia://` | Cydia 越狱商店 |
| `undecimus://` | unc0ver 越狱工具 |
| `sileo://` | Sileo 越狱商店 |
| `zbra://` | Zebra 越狱商店 |
| `filza://` | Filza 文件管理器 |

### 2.4 可疑文件路径(部分列举,完整 80+ 个)

```
/Applications/Cydia.app
/Applications/Sileo.app
/Library/MobileSubstrate/MobileSubstrate.dylib
/usr/sbin/sshd
/etc/apt
/bin/bash
/usr/bin/ssh
/private/var/lib/apt/
/private/var/lib/cydia
/private/var/tmp/cydia.log
/private/var/stash
/var/cache/apt
/var/lib/dpkg
/usr/libexec/cydia
/usr/lib/TweakInject
...
```

### 2.5 使用示例

```swift
// 基础检测
if IOSSecuritySuite.amIJailbroken() {
    // 上报 + 降级处理
}

// 详细检测(推荐)
let result = IOSSecuritySuite.amIJailbrokenWithFailedChecks()
if result.jailbroken {
    for check in result.failedChecks {
        print("越狱检测命中: \(check)")
        // 上报具体触发项到服务端
    }
}
```

---

## 三、调试器检测(Debugger Detection)

### 3.1 API

```swift
// 检测调试器是否已附加
IOSSecuritySuite.amIDebugged() -> Bool

// 主动阻止调试器附加(调用后无法被 lldb/gdb 附加)
IOSSecuritySuite.denyDebugger()

// 检测父进程是否异常(非 LaunchD 启动 = 可能被调试器启动)
IOSSecuritySuite.isParentPidUnexpected() -> Bool
```

### 3.2 原理

| 方法 | 实现原理 |
|------|----------|
| `amIDebugged()` | 调用 `sysctl` 检查 `P_TRACED` 标志位 |
| `denyDebugger()` | 调用 `ptrace(PT_DENY_ATTACH, ...)` 拒绝调试器附加 |
| `isParentPidUnexpected()` | 检查 `getppid()` 是否为 `1`(LaunchD),非 1 则异常 |

### 3.3 使用示例

```swift
// 发布版主动阻止调试器
#if !DEBUG
IOSSecuritySuite.denyDebugger()
#endif

// 运行时检测
if IOSSecuritySuite.amIDebugged() {
    // 记录日志,可选择退出
}
```

---

## 四、模拟器检测(Emulator Detection)

### 4.1 API

```swift
IOSSecuritySuite.amIRunInEmulator() -> Bool
```

### 4.2 原理

检查 `TARGET_OS_SIMULATOR` 编译标志及运行时架构信息。

### 4.3 使用场景

```swift
if IOSSecuritySuite.amIRunInEmulator() {
    // 模拟器环境:禁用支付 / 限制敏感功能
}
```

---

## 五、逆向工程检测(Reverse Engineering Detection)

### 5.1 API

```swift
// 简单判断
IOSSecuritySuite.amIReverseEngineered() -> Bool

// 带详细失败检查项
IOSSecuritySuite.amIReverseEngineeredWithFailedChecks()
    -> (reverseEngineered: Bool, failedChecks: [FailedCheckType])
```

### 5.2 四种子检测

| # | 检测项 | FailedCheckType | 原理 |
|---|--------|-----------------|------|
| 1 | 可疑文件 | `.existenceOfSuspiciousFiles` | 检查 `/usr/sbin/frida-server` 等 Frida 相关文件 |
| 2 | DYLD 动态库 | `.dyld` | 扫描已加载库中是否包含 `FridaGadget`、`frida`、`cynject`、`libcycript` |
| 3 | 可疑端口 | `.openedPorts` | 尝试连接端口 `27042`(Frida)、`4444`、`22`(SSH)、`44` |
| 4 | 进程标志 | `.pSelectFlag` | 通过 `sysctl` 检查进程 P_SELECT 标志位 |

### 5.3 检测的逆向工具

| 工具 | 检测方式 |
|------|----------|
| **Frida** | 文件存在 + DYLD + 端口 27042 |
| **Cycript** | DYLD(libcycript) |
| **Cynject** | DYLD |
| **SSH** | 端口 22 |

### 5.4 使用示例

```swift
let result = IOSSecuritySuite.amIReverseEngineeredWithFailedChecks()
if result.reverseEngineered {
    for check in result.failedChecks {
        switch check {
        case .dyld:
            print("检测到可疑动态库注入")
        case .openedPorts:
            print("检测到可疑端口开放(可能运行 Frida)")
        case .existenceOfSuspiciousFiles:
            print("检测到逆向工具文件")
        case .pSelectFlag:
            print("进程标志异常")
        default:
            break
        }
    }
}
```

---

## 六、运行时 Hook 检测(Runtime Hook Detection)

### 6.1 API

```swift
// ObjC 方法 Hook 检测(Flex 等工具)
IOSSecuritySuite.amIRuntimeHooked(
    dyldAllowList: [String],        // 允许的 dylib 白名单
    detectionClass: AnyClass,       // 被检测的类
    selector: Selector,             // 被检测的方法
    isClassMethod: Bool             // 是否类方法
) -> Bool

// MSHook 函数 Hook 检测 (ARM64 only)
IOSSecuritySuite.amIMSHooked(_ functionAddress: UnsafeMutableRawPointer) -> Bool

// 获取被 MSHook 的原始函数地址 (ARM64 only)
IOSSecuritySuite.denyMSHook(_ functionAddress: UnsafeMutableRawPointer)
    -> UnsafeMutableRawPointer?

// 重绑定被 fishhook 的符号 (ARM64 only)
IOSSecuritySuite.denySymbolHook(_ symbol: String)
IOSSecuritySuite.denySymbolHook(_ symbol: String, at: UnsafePointer<mach_header>, imageSlide: Int)
```

### 6.2 原理

| 方法 | 检测原理 |
|------|----------|
| `amIRuntimeHooked` | 检查 ObjC 方法的 IMP 是否指向非白名单 dylib |
| `amIMSHooked` | 检查函数入口是否被替换为跳板(ARM64 指令分析) |
| `denyMSHook` | 读取跳板指令还原原始函数地址 |
| `denySymbolHook` | 使用 `rebind_symbols` 恢复被 fishhook 替换的符号 |

### 6.3 使用示例

```swift
// 检测关键方法是否被 Hook
let hooked = IOSSecuritySuite.amIRuntimeHooked(
    dyldAllowList: ["UIKitCore", "Foundation"],
    detectionClass: MyPaymentService.self,
    selector: #selector(MyPaymentService.processPayment),
    isClassMethod: false
)
if hooked {
    print("关键支付方法被 Hook!")
}
```

---

## 七、完整性验证(Integrity / Tamper Detection)

### 7.1 API

```swift
// 文件完整性检测
IOSSecuritySuite.amITampered(_ checks: [FileIntegrityCheck]) -> FileIntegrityCheckResult

// 获取 Mach-O 文件 SHA256 哈希 (ARM64 only)
IOSSecuritySuite.getMachOFileHashValue(_ target: IntegrityCheckerImageTarget) -> String?

// 列出所有已加载的动态库 (ARM64 only)
IOSSecuritySuite.findLoadedDylibs(_ target: IntegrityCheckerImageTarget) -> [String]?
```

### 7.2 FileIntegrityCheck 类型

| 检查类型 | 说明 | 示例 |
|----------|------|------|
| `.bundleID("com.tongban.app")` | 校验 Bundle ID 是否被篡改 | 防止重签名 |
| `.mobileProvision("sha256hash")` | 校验描述文件 SHA256 哈希 | 防止替换证书 |
| `.machO("sha256hash", "BinaryName")` | 校验 Mach-O 二进制 SHA256 | 防止代码注入 |

### 7.3 IntegrityCheckerImageTarget

| 目标 | 说明 |
|------|------|
| `.default` | 主可执行文件 |
| `.custom("DylibName")` | 指定的动态库 |

### 7.4 使用示例

```swift
// 校验 Bundle ID + 描述文件
let result = IOSSecuritySuite.amITampered([
    .bundleID("com.tongban.app"),
    .mobileProvision("expected_sha256_hash_here")
])

if result.result {
    print("应用被篡改!命中项: \(result.hitChecks)")
}

// 获取当前 Mach-O 哈希(可与服务端下发的值对比)
if let hash = IOSSecuritySuite.getMachOFileHashValue(.default) {
    print("当前二进制 SHA256: \(hash)")
}
```

---

## 八、网络环境检测

### 8.1 代理检测

```swift
// 检测 HTTP 代理(中间人攻击防护)
IOSSecuritySuite.amIProxied() -> Bool

// 可选:VPN 连接也视为代理
IOSSecuritySuite.amIProxied(considerVPNConnectionAsProxy: true) -> Bool
```

### 8.2 锁定模式检测 (iOS 16+)

```swift
IOSSecuritySuite.amIInLockdownMode() -> Bool
```

### 8.3 使用示例

```swift
if IOSSecuritySuite.amIProxied() {
    // 检测到代理,可能存在中间人攻击
    // 策略:禁用敏感 API 调用 / 弹窗提示
}
```

---

## 九、断点与监视点检测

### 9.1 API (ARM64 only)

```swift
// 检测指定函数是否被设置断点
IOSSecuritySuite.hasBreakpointAt(
    _ functionAddr: UnsafeRawPointer,
    functionSize: vm_size_t?
) -> Bool

// 检测是否存在活跃的内存监视点
IOSSecuritySuite.hasWatchpoint() -> Bool
```

### 9.2 使用场景

```swift
// 检测支付逻辑是否被断点调试
func processPayment() {
    let addr = unsafeBitCast(processPayment as () -> Void, to: UnsafeRawPointer.self)
    if IOSSecuritySuite.hasBreakpointAt(addr, functionSize: nil) {
        // 有人在调试支付流程
        return
    }
    // 正常执行支付...
}
```

---

## 十、FailedCheckType 枚举完整汇总

```swift
// 越狱检测返回的 FailedCheckType
public enum FailedCheckType: CaseIterable {
    case urlSchemes                        // URL Scheme 探测命中
    case existenceOfSuspiciousFiles        // 可疑文件存在
    case suspiciousFilesCanBeOpened        // 可疑文件可打开
    case restrictedDirectoriesWriteable    // 受限目录可写
    case fork                              // fork() 系统调用成功
    case symbolicLinks                     // 异常符号链接
    case dyld                              // 可疑动态库加载
    case openedPorts                       // 可疑端口开放
    case pSelectFlag                       // 进程标志异常
}
```

### 各检查项触发条件对照

| FailedCheckType | 越狱检测 | 逆向检测 | 说明 |
|-----------------|:--------:|:--------:|------|
| `.urlSchemes` | ✅ | - | 越狱工具 URL Scheme 可打开 |
| `.existenceOfSuspiciousFiles` | ✅ | ✅ | 越狱文件(80+) / 逆向工具(frida-server) |
| `.suspiciousFilesCanBeOpened` | ✅ | - | /bin/bash 等可被 fopen |
| `.restrictedDirectoriesWriteable` | ✅ | - | 沙盒外目录可写 |
| `.fork` | ✅ | - | fork() 不应在沙盒内成功 |
| `.symbolicLinks` | ✅ | - | 系统目录被创建符号链接 |
| `.dyld` | ✅ | ✅ | MobileSubstrate / FridaGadget 等 |
| `.openedPorts` | - | ✅ | 27042(Frida) / 22(SSH) 等 |
| `.pSelectFlag` | - | ✅ | sysctl 进程标志异常 |

---

## 十一、同伴 App 推荐集成策略

### 11.1 检测时机

| 时机 | 检测项 | 处理策略 |
|------|--------|----------|
| App 启动(PrivacyService.initSDKs) | 越狱 + 调试器 + 模拟器 | 上报 + 弹窗提示(不强制退出) |
| 进入支付流程 | 逆向 + Hook + 代理 + 断点 | 阻止支付 + 弹窗提示 |
| 登录成功后 | 完整性校验 | 静默上报到服务端 |
| 每次 API 请求 | amIProxied(可选) | Header 携带环境标记 |

### 11.2 建议代码

```swift
// SecurityChecker.swift
import IOSSecuritySuite

enum SecurityChecker {

    struct SecurityStatus {
        let isJailbroken: Bool
        let isDebugged: Bool
        let isEmulator: Bool
        let isReverseEngineered: Bool
        let isProxied: Bool
        let jailbreakDetails: [FailedCheckType]
        let reverseEngineeringDetails: [FailedCheckType]
    }

    static func fullCheck() -> SecurityStatus {
        let jbResult = IOSSecuritySuite.amIJailbrokenWithFailedChecks()
        let reResult = IOSSecuritySuite.amIReverseEngineeredWithFailedChecks()

        return SecurityStatus(
            isJailbroken:               jbResult.jailbroken,
            isDebugged:                 IOSSecuritySuite.amIDebugged(),
            isEmulator:                 IOSSecuritySuite.amIRunInEmulator(),
            isReverseEngineered:        reResult.reverseEngineered,
            isProxied:                  IOSSecuritySuite.amIProxied(),
            jailbreakDetails:           jbResult.failedChecks,
            reverseEngineeringDetails:  reResult.failedChecks
        )
    }

    /// 发布版阻止调试器
    static func denyDebuggerInRelease() {
        #if !DEBUG
        IOSSecuritySuite.denyDebugger()
        #endif
    }
}
```

---

## 十二、注意事项

1. **纯 Swift 编写**:方法不暴露给 ObjC 运行时,比 ObjC 方案更难被 Hook
2. **仍可被绕过**:攻击者可通过 `MSHookFunction` / `Frida Stalker` 修改执行流,建议配合代码混淆
3. **仅限真机**:大部分检测在模拟器上无效或误报,开发时应使用 `#if !DEBUG` 包裹
4. **iOS 16+ 锁定模式**:`amIInLockdownMode()` 需要 iOS 16+
5. **ARM64 限定**:`amIMSHooked`、`denyMSHook`、`denySymbolHook`、`hasBreakpointAt`、`hasWatchpoint`、`getMachOFileHashValue` 仅在 ARM64 真机上可用
6. **Info.plist 必须配置**:`LSApplicationQueriesSchemes` 中添加越狱工具的 URL Scheme,否则 URL Scheme 检测无效