iOS Pentesting

Tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE) 学习和实践 Azure 黑客技术:HackTricks Training Azure Red Team Expert (AzRTE)

支持 HackTricks

iOS 基础

iOS Basics

测试环境

在此页面你可以找到有关 iOS simulatoremulatorsjailbreaking: 的信息。

iOS Testing Environment

初始分析

基本 iOS 测试操作

在测试过程中,会建议执行若干操作(连接设备、读取/写入/上传/下载文件、使用一些工具等)。因此,如果你不知道如何执行这些操作,请先阅读该页面

iOS Basic Testing Operations

Tip

在接下来的步骤中,应用应已安装 在设备上,并且应已获得该应用的 IPA file
阅读 Basic iOS Testing Operations 页面以了解如何操作。

基本静态分析

一些有趣的 iOS - IPA 文件反编译器:

建议使用工具 MobSF 对 IPA 文件执行自动静态分析。

识别二进制中存在的保护措施

  • PIE (Position Independent Executable):启用时,应用每次启动都会加载到随机内存地址,从而使预测其初始内存地址更困难。
otool -hv <app-binary> | grep PIE   # It should include the PIE flag
  • Stack Canaries:为验证栈的完整性,会在调用函数前在栈上放置一个“canary”值,并在函数结束时再次验证。
otool -I -v <app-binary> | grep stack_chk   # It should include the symbols: stack_chk_guard and stack_chk_fail
  • ARC (Automatic Reference Counting):用于防止常见的内存破坏缺陷
otool -I -v <app-binary> | grep objc_release   # It should include the _objc_release symbol
  • Encrypted Binary:二进制应为加密状态
otool -arch all -Vl <app-binary> | grep -A5 LC_ENCRYPT   # The cryptid should be 1

识别敏感/不安全函数

  • 弱哈希算法
# On the iOS device
otool -Iv <app> | grep -w "_CC_MD5"
otool -Iv <app> | grep -w "_CC_SHA1"

# On linux
grep -iER "_CC_MD5"
grep -iER "_CC_SHA1"
  • 不安全的随机函数
# On the iOS device
otool -Iv <app> | grep -w "_random"
otool -Iv <app> | grep -w "_srand"
otool -Iv <app> | grep -w "_rand"

# On linux
grep -iER "_random"
grep -iER "_srand"
grep -iER "_rand"
  • 不安全的 ‘Malloc’ 函数
# On the iOS device
otool -Iv <app> | grep -w "_malloc"

# On linux
grep -iER "_malloc"
  • 不安全和易受攻击的函数
# On the iOS device
otool -Iv <app> | grep -w "_gets"
otool -Iv <app> | grep -w "_memcpy"
otool -Iv <app> | grep -w "_strncpy"
otool -Iv <app> | grep -w "_strlen"
otool -Iv <app> | grep -w "_vsnprintf"
otool -Iv <app> | grep -w "_sscanf"
otool -Iv <app> | grep -w "_strtok"
otool -Iv <app> | grep -w "_alloca"
otool -Iv <app> | grep -w "_sprintf"
otool -Iv <app> | grep -w "_printf"
otool -Iv <app> | grep -w "_vsprintf"

# On linux
grep -R "_gets"
grep -iER "_memcpy"
grep -iER "_strncpy"
grep -iER "_strlen"
grep -iER "_vsnprintf"
grep -iER "_sscanf"
grep -iER "_strtok"
grep -iER "_alloca"
grep -iER "_sprintf"
grep -iER "_printf"
grep -iER "_vsprintf"

常见 Jailbreak 检测方法

  • 文件系统检查:查找常见 jailbreak 文件和目录的存在,例如 /Applications/Cydia.app 或 /Library/MobileSubstrate/MobileSubstrate.dylib。
  • 沙箱违规:尝试访问文件系统中的受限区域,这些区域在未越狱设备上应被阻止。
  • API 检查:检查是否可以使用禁止的调用,如 fork() 创建子进程或 system() 以查看 /bin/sh 是否存在。
  • 进程检查:监视已知与 jailbreak 相关的进程,例如 Cydia、Substrate 或 ssh。
  • 内核利用检测:检查常用于越狱的内核漏洞的存在。
  • 环境变量:检查环境变量是否有越狱迹象,例如 DYLD_INSERT_LIBRARIES。
  • 库检查:检查加载到应用进程中的 libs。
  • 检查 schemes:例如 canOpenURL(URL(string: “cydia://”))。

常见反调试检测方法

  • 检查调试器存在:使用 sysctl 或其他方法检查是否附加了调试器。
  • 反调试 API:查找对反调试 API 的调用,如 ptrace 或 SIGSTOP,例如 ptrace(PT_DENY_ATTACH, 0, 0, 0)。
  • 时间检查:测量某些操作所需时间,并查找可能表明正在调试的差异。
  • 内存检查:检查内存中是否存在已知的调试器工件或修改。
  • 环境变量:检查可能表明调试会话的环境变量。
  • Mach 端口:检测 mach exception ports 是否被调试器使用。

反调试与反篡改技术(分层检测)

真实应用通常在 pre-exec、on-attach 和持续检查上叠加多种检测。常见的模式以及在测试中如何中和它们:

  • Private API 侧信道指纹识别:滥用私有启动 API(例如 SBSLaunchApplicationWithIdentifierAndURLAndLaunchOptions)通过返回码/日志探测已安装的 bundle ID(例如 com.opa334.TrollStoreorg.coolstar.SileoStorecom.tigisoftware.Filza 等)。hook 该调用并清理参数/返回值以模拟干净设备。
  • 通过代码签名状态进行自我证明:使用 csops() 搭配 CS_OPS_ENTITLEMENTS_BLOB 读取 entitlements;异常值会触发退出。将此与完整性检查(资源的 CRC32/MD5、证书验证、Mach-O 元数据如 LC_ENCRYPTION_INFO_64)配对以检测重新签名或打补丁。对这些例程进行插装并在分析期间强制返回“期望”的结果。
  • 附加即杀死ptrace(PT_DENY_ATTACH) 结合在附加时调用的 abort()/exit()。通过中和终止路径或 hook ptrace 使其成功但不强制拒绝,从而绕过。
  • 崩溃取证破坏:在崩溃前覆盖 CPU 寄存器以破坏回溯。更倾向于在检测路径的更早阶段设置断点/hook,而不是依赖崩溃日志。
  • 基于 Jetsam 的终止:有意造成内存压力以触发 jetsam,这不会产生正常的崩溃日志。查看检测逻辑周围的大量分配并对其进行限制/短路以保留日志。
  • 带延迟强制执行的持续检查:心跳定时器会重新运行检测并在稍后执行强制操作。追踪定时器/dispatch source 并通过绕过延迟杀死路径来保持进程存活。

基本动态分析

查看 MobSF 执行的动态分析。你需要浏览不同的视图并与之交互,但它会在执行其他操作时 hook 若干类,并在完成后生成报告。

列出已安装的应用

使用命令 frida-ps -Uai 来确定已安装应用的 bundle identifier

$ frida-ps -Uai
PID  Name                 Identifier
----  -------------------  -----------------------------------------
6847  Calendar             com.apple.mobilecal
6815  Mail                 com.apple.mobilemail
-  App Store            com.apple.AppStore
-  Apple Store          com.apple.store.Jolly
-  Calculator           com.apple.calculator
-  Camera               com.apple.camera
-  iGoat-Swift          OWASP.iGoat-Swift

基本 Enumeration & Hooking

学习如何 enumerate the components of the application,以及如何使用 objection 轻松地 hook methods and classes

iOS Hooking With Objection

IPA 结构

一个 IPA file 的结构本质上是一个 zipped package。通过将扩展名重命名为 .zip,可以将其 decompressed 来查看内容。在该结构中,Bundle 表示一个已打包完成、可供安装的应用程序。内部会包含一个名为 <NAME>.app 的目录,该目录封装了应用的资源。

  • Info.plist: 此文件保存应用的特定配置信息。
  • _CodeSignature/: 该目录包含一个 plist 文件,里面有签名,用于确保 bundle 中所有文件的完整性。
  • Assets.car: 一个压缩存档,用来存储诸如图标之类的 asset 文件。
  • Frameworks/: 此文件夹包含应用的本地库,可能以 .dylib.framework 的形式存在。
  • PlugIns/: 可能包含应用的扩展,称为 .appex 文件,但它们并不总是存在。 * Core Data: It is used to save your application’s permanent data for offline use, to cache temporary data, and to add undo functionality to your app on a single device. To sync data across multiple devices in a single iCloud account, Core Data automatically mirrors your schema to a CloudKit container.
  • PkgInfo: The PkgInfo file is an alternate way to specify the type and creator codes of your application or bundle.
  • en.lproj, fr.proj, Base.lproj: 是包含特定语言资源的语言包,以及在某语言不受支持时使用的默认资源。
  • Security: _CodeSignature/ 目录在应用安全性中起着关键作用,通过数字签名验证所有打包文件的完整性。
  • Asset Management: Assets.car 文件使用压缩来高效管理图形资源,这对优化应用性能和减小总体体积至关重要。
  • Frameworks and PlugIns: 这些目录强调了 iOS 应用的模块化,允许开发者包含可重用的代码库(Frameworks/)并扩展应用功能(PlugIns/)。
  • Localization: 该结构支持多语言,通过包含特定语言包的资源来促进应用的全球覆盖。

Info.plist

Info.plist 是 iOS 应用的基石,以 键值 对的形式封装了关键的配置数据。该文件是应用、应用扩展以及捆绑在其中的框架所必需的。它可以采用 XML 或二进制格式,包含从应用权限到安全配置等关键信息。有关可用键的详细说明,请参阅 Apple Developer Documentation

对于希望以更易读格式处理此文件的人员,可以在 macOS 上使用 plutil(在 10.2 及更高版本上原生可用)或在 Linux 上使用 plistutil 将其转换为 XML。转换命令如下:

  • For macOS:
$ plutil -convert xml1 Info.plist
  • 适用于 Linux:
$ apt install libplist-utils
$ plistutil -i Info.plist -o Info_xml.plist

Info.plist 文件可能泄露的大量信息中,值得注意的条目包括应用权限字符串 (UsageDescription)、自定义 URL 方案 (CFBundleURLTypes) 以及 App Transport Security 的配置 (NSAppTransportSecurity)。这些条目,以及像导出/导入的自定义文档类型 (UTExportedTypeDeclarations / UTImportedTypeDeclarations) 等其他项,都可以通过检查该文件或使用简单的 grep 命令轻松定位:

$ grep -i <keyword> Info.plist

数据路径

在 iOS 环境中,目录被专门划分用于 系统应用用户安装的应用。系统应用位于 /Applications 目录,而用户安装的应用位于 /var/mobile/containers/Data/Application/ 下。这些应用会被分配一个称为 128-bit UUID 的唯一标识符,由于目录名称的随机性,手动定位某个应用的文件夹变得很有挑战性。

Warning

由于 iOS 应用必须处于沙箱中,每个应用在 $HOME/Library/Containers 内还会有一个以应用 CFBundleIdentifier 为文件夹名的文件夹。

不过,这两个文件夹(数据与容器文件夹)都有文件 .com.apple.mobile_container_manager.metadata.plist,该文件在键 MCMetadataIdentifier 中将两者关联。)

为了便于发现用户安装应用的安装目录,objection 工具 提供了一个有用的命令 env。该命令会显示该应用的详细目录信息。下面是如何使用该命令的示例:

OWASP.iGoat-Swift on (iPhone: 11.1.2) [usb] # env

Name               Path
-----------------  -------------------------------------------------------------------------------------------
BundlePath         /var/containers/Bundle/Application/3ADAF47D-A734-49FA-B274-FBCA66589E67/iGoat-Swift.app
CachesDirectory    /var/mobile/Containers/Data/Application/8C8E7EB0-BC9B-435B-8EF8-8F5560EB0693/Library/Caches
DocumentDirectory  /var/mobile/Containers/Data/Application/8C8E7EB0-BC9B-435B-8EF8-8F5560EB0693/Documents
LibraryDirectory   /var/mobile/Containers/Data/Application/8C8E7EB0-BC9B-435B-8EF8-8F5560EB0693/Library

或者,可以在 /private/var/containers 内使用 find 命令搜索应用名称:

find /private/var/containers -name "Progname*"

诸如 pslsof 的命令也可分别用于识别应用进程并列出打开的文件,从而提供有关应用活动目录路径的洞见:

ps -ef | grep -i <app-name>
lsof -p <pid> | grep -i "/containers" | head -n 1

Bundle directory:

  • AppName.app
  • 这是在 IPA 中看到的 Application Bundle,包含应用的核心数据、静态资源以及应用编译后的二进制文件。
  • 该目录对用户可见,但 用户无法写入它
  • 此目录中的内容 不被备份
  • 该文件夹的内容用于 验证 code signature

Data directory:

  • Documents/
  • 包含所有用户生成的数据。由应用最终用户主动创建这些数据。
  • 对用户可见并且 用户可以写入它
  • 此目录中的内容 会被备份
  • 应用可以通过设置 NSURLIsExcludedFromBackupKey 来排除路径。
  • Library/
  • 包含所有 非用户特定的文件,例如 cachespreferencescookies 和属性列表 (plist) 配置文件。
  • iOS apps 通常使用 Application SupportCaches 子目录,但应用可以创建自定义子目录。
  • Library/Caches/
  • 包含 半持久的缓存文件。
  • 对用户不可见且 用户无法写入它
  • 此目录中的内容 不被备份
  • 当应用未运行且存储空间紧张时,操作系统可能会自动删除该目录的文件。
  • Library/Application Support/
  • 包含运行应用所需的 持久性 文件
  • 对用户不可见,且用户无法写入。
  • 此目录中的内容 会被备份
  • 应用可以通过设置 NSURLIsExcludedFromBackupKey 来排除路径。
  • Library/Preferences/
  • 用于存储可以 在应用重启后仍然持久保留 的属性。
  • 信息以未加密形式保存在应用沙盒内的一个名为 [BUNDLE_ID].plist 的 plist 文件中。
  • 使用 NSUserDefaults 存储的所有键/值对都可以在该文件中找到。
  • tmp/
  • 使用此目录写入不需要在应用启动间持久保存的 临时文件
  • 包含非持久性缓存文件。
  • 对用户不可见
  • 此目录中的内容不被备份。
  • 当应用未运行且存储空间紧张时,操作系统可能会自动删除该目录的文件。

让我们更仔细地查看 iGoat-Swift 的 Application Bundle (.app) 目录,位于 Bundle 目录内 (/var/containers/Bundle/Application/3ADAF47D-A734-49FA-B274-FBCA66589E67/iGoat-Swift.app):

OWASP.iGoat-Swift on (iPhone: 11.1.2) [usb] # ls
NSFileType      Perms  NSFileProtection    ...  Name
------------  -------  ------------------  ...  --------------------------------------
Regular           420  None                ...  rutger.html
Regular           420  None                ...  mansi.html
Regular           420  None                ...  splash.html
Regular           420  None                ...  about.html

Regular           420  None                ...  LICENSE.txt
Regular           420  None                ...  Sentinel.txt
Regular           420  None                ...  README.txt

Binary Reversing

<application-name>.app 文件夹内,你会找到一个名为 <application-name> 的二进制文件。这个文件将被 执行。你可以使用工具 otool 对该二进制文件进行基本检查:

otool -Vh DVIA-v2 #Check some compilation attributes
magic  cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
MH_MAGIC_64    ARM64        ALL  0x00     EXECUTE    65       7112   NOUNDEFS DYLDLINK TWOLEVEL WEAK_DEFINES BINDS_TO_WEAK PIE

otool -L DVIA-v2 #Get third party libraries
DVIA-v2:
/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 400.9.1)
/usr/lib/libsqlite3.dylib (compatibility version 9.0.0, current version 274.6.0)
/usr/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.11)
@rpath/Bolts.framework/Bolts (compatibility version 1.0.0, current version 1.0.0)
[...]

检查应用是否被加密

查看是否有任何输出:

otool -l <app-binary> | grep -A 4 LC_ENCRYPTION_INFO

反汇编二进制文件

反汇编 text 段:

otool -tV DVIA-v2
DVIA-v2:
(__TEXT,__text) section
+[DDLog initialize]:
0000000100004ab8    sub    sp, sp, #0x60
0000000100004abc    stp    x29, x30, [sp, #0x50]   ; Latency: 6
0000000100004ac0    add    x29, sp, #0x50
0000000100004ac4    sub    x8, x29, #0x10
0000000100004ac8    mov    x9, #0x0
0000000100004acc    adrp    x10, 1098 ; 0x10044e000
0000000100004ad0    add    x10, x10, #0x268

要打印示例应用程序的 Objective-C segment,可以使用:

otool -oV DVIA-v2
DVIA-v2:
Contents of (__DATA,__objc_classlist) section
00000001003dd5b8 0x1004423d0 _OBJC_CLASS_$_DDLog
isa        0x1004423a8 _OBJC_METACLASS_$_DDLog
superclass 0x0 _OBJC_CLASS_$_NSObject
cache      0x0 __objc_empty_cache
vtable     0x0
data       0x1003de748
flags          0x80
instanceStart  8

为了获得更紧凑的 Objective-C 代码,你可以使用 class-dump

class-dump some-app
//
//     Generated by class-dump 3.5 (64 bit).
//
//     class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard.
//

#pragma mark Named Structures

struct CGPoint {
double _field1;
double _field2;
};

struct CGRect {
struct CGPoint _field1;
struct CGSize _field2;
};

struct CGSize {
double _field1;
double _field2;
};

不过,反汇编二进制文件的最佳选择是: HopperIDA

数据存储

要了解 iOS 如何在设备上存储数据,请阅读本页:

iOS Basics

Warning

以下存储信息的位置应该在刚安装应用后检查完应用所有功能后,甚至在从一个用户登出并以不同用户登录后立即检查。
目标是寻找应用、当前用户及先前登录用户的未受保护的敏感信息(passwords, tokens)。

Plist

plist files are structured XML files that contains key-value pairs。这是一种存储持久化数据的方式,所以有时你可能会在这些文件中发现敏感信息。建议在安装应用后以及高频使用后检查这些文件,查看是否有新数据被写入。

持久化数据到 plist 文件的最常见方式是通过使用 NSUserDefaults。该 plist 文件保存在应用沙箱的 Library/Preferences/<appBundleID>.plist 里。

The NSUserDefaults class provides a programmatic interface for interacting with the default system。默认系统允许应用根据user preferences自定义其行为。通过 NSUserDefaults 保存的数据可以在应用包中查看。该类将数据存储在一个plist file 中,但它适用于少量数据。

这些数据不能再通过受信任的电脑直接访问,但可以通过执行一次backup来访问。

你可以使用 objection 的 ios nsuserdefaults getdump 使用 NSUserDefaults 保存的信息。

要找到应用使用的所有 plist,你可以访问 /private/var/mobile/Containers/Data/Application/{APPID} 并运行:

find ./ -name "*.plist"

要将 XML or binary (bplist) 格式的文件转换为 XML,可以根据你的操作系统使用不同的方法:

对于 macOS 用户: 使用 plutil 命令。它是 macOS (10.2+) 的内置工具,专为此目的设计:

$ plutil -convert xml1 Info.plist

对于 Linux 用户: 先安装 libplist-utils,然后使用 plistutil 将你的文件转换:

$ apt install libplist-utils
$ plistutil -i Info.plist -o Info_xml.plist

在 Objection 会话中: 在分析移动应用程序时,有一个特定命令允许您直接转换 plist 文件:

ios plist cat /private/var/mobile/Containers/Data/Application/<Application-UUID>/Library/Preferences/com.some.package.app.plist

Core Data

Core Data 是一个用于管理应用中对象模型层的框架。Core Data can use SQLite as its persistent store,但该框架本身并不是一个数据库。
CoreData 默认不会对其数据进行加密。但是,可以为 CoreData 添加额外的加密层。有关详细信息,请参见 GitHub Repo

你可以在路径 /private/var/mobile/Containers/Data/Application/{APPID}/Library/Application Support 找到应用的 SQLite Core Data 信息

If you can open the SQLite and access sensitive information, then you found a miss-configuration.

-(void)storeDetails {
AppDelegate * appDelegate = (AppDelegate *)(UIApplication.sharedApplication.delegate);

NSManagedObjectContext *context =[appDelegate managedObjectContext];

User *user = [self fetchUser];
if (user) {
return;
}
user = [NSEntityDescription insertNewObjectForEntityForName:@"User"
inManagedObjectContext:context];
user.email = CoreDataEmail;
user.password = CoreDataPassword;
NSError *error;
if (![context save:&error]) {
NSLog(@"Error in saving data: %@", [error localizedDescription]);

}else{
NSLog(@"data stored in core data");
}
}

YapDatabase

YapDatabase is a key/value store built on top of SQLite.
由于 Yap 数据库是 sqlite 数据库,你可以使用上一节中提到的命令找到它们。

Other SQLite Databases

应用程序通常会创建自己的 sqlite 数据库。它们可能在其中 存储 敏感 数据 并以未加密的形式保存。因此,检查应用目录中的每个数据库总是很有意义。前往保存数据的应用目录 (/private/var/mobile/Containers/Data/Application/{APPID})

find ./ -name "*.sqlite" -or -name "*.db"

Firebase Real-Time Databases

开发者可以通过 Firebase Real-Time Databases 在 NoSQL 云托管数据库存储并同步数据。数据以 JSON 格式存储,并会实时同步到所有连接的客户端。

你可以在这里找到如何检查配置错误的 Firebase 数据库:

Firebase Database

Realm databases

Realm Objective-C and Realm Swift 提供了一个强大的数据存储替代方案,不是由 Apple 提供的。默认情况下,它们会 以未加密形式存储数据,但可以通过特定配置启用加密。

数据库位于:/private/var/mobile/Containers/Data/Application/{APPID}。要查看这些文件,可以使用如下命令:

iPhone:/private/var/mobile/Containers/Data/Application/A079DF84-726C-4AEA-A194-805B97B3684A/Documents root# ls
default.realm  default.realm.lock  default.realm.management/  default.realm.note|

$ find ./ -name "*.realm*"

建议使用 Realm Studio 工具来查看这些数据库文件。

要在 Realm 数据库中实现加密,可以使用以下代码片段:

// Open the encrypted Realm file where getKey() is a method to obtain a key from the Keychain or a server
let config = Realm.Configuration(encryptionKey: getKey())
do {
let realm = try Realm(configuration: config)
// Use the Realm as normal
} catch let error as NSError {
// If the encryption key is wrong, `error` will say that it's an invalid database
fatalError("Error opening realm: \(error)")
}

Couchbase Lite 数据库

Couchbase Lite 被描述为一个 轻量级嵌入式 的数据库引擎,遵循 面向文档的 (NoSQL) 方法。它为 iOSmacOS 原生设计,提供无缝同步数据的能力。

要在设备上识别潜在的 Couchbase 数据库,应检查以下目录:

ls /private/var/mobile/Containers/Data/Application/{APPID}/Library/Application Support/

Cookies

iOS 将应用的 cookies 存储在每个应用文件夹内的 Library/Cookies/cookies.binarycookies。然而,开发者有时会将它们保存在 keychain 中,因为上述 cookie 文件可以通过备份访问

要检查 cookie 文件,你可以使用 this python script 或使用 objection 的 ios cookies get.
你也可以使用 objection 来 将这些文件转换为 JSON 格式并检查数据。

...itudehacks.DVIAswiftv2.develop on (iPhone: 13.2.3) [usb] # ios cookies get --json
[
{
"domain": "highaltitudehacks.com",
"expiresDate": "2051-09-15 07:46:43 +0000",
"isHTTPOnly": "false",
"isSecure": "false",
"name": "username",
"path": "/",
"value": "admin123",
"version": "0"
}
]

Cache

默认情况下 NSURLSession 会存储数据,例如 HTTP 请求和响应 存储在 Cache.db 数据库中。该数据库可能包含 敏感数据,如果 tokens、用户名或任何其他敏感信息已被缓存。要查找缓存的信息,请打开应用的数据目录 (/var/mobile/Containers/Data/Application/<UUID>) 并进入 /Library/Caches/<Bundle Identifier>WebKit cache 也被存储在 Cache.db 文件中。Objection 可以使用命令 sqlite connect Cache.db 打开并操作该数据库,因为它是一个普通的 SQLite 数据库

建议禁用对此类数据的缓存,因为请求或响应中可能包含敏感信息。下面的列表展示了几种实现方法:

  1. 建议在登出后移除缓存的响应。可以使用 Apple 提供的方法 removeAllCachedResponses 来完成。你可以如下调用此方法:

URLCache.shared.removeAllCachedResponses()

此方法会从 Cache.db 文件中移除所有缓存的请求和响应。

  1. 如果不需要使用 cookies 的优势,建议直接使用 URLSession 的 .ephemeral 配置属性,这将禁用保存 cookies 和缓存。

Apple documentation:

An ephemeral session configuration object is similar to a default session configuration (see default), except that the corresponding session object doesn’t store caches, credential stores, or any session-related data to disk. Instead, session-related data is stored in RAM. The only time an ephemeral session writes data to disk is when you tell it to write the contents of a URL to a file.

  1. 也可以通过将 Cache Policy 设置为 .notAllowed 来禁用 Cache。这会禁止以任何方式在内存或磁盘上存储 Cache。

Snapshots

每当按下 Home 按钮时,iOS 会对当前屏幕进行快照,以便在应用切换时实现更平滑的过渡。然而,如果当前屏幕上存在敏感****数据,它们会被保存图像中(该图像在重启后仍会保留)。这些就是通过双击 Home 屏幕切换应用时可以访问的快照。

除非 iPhone 已越狱,否则 attacker 需要能够解锁设备才能查看这些屏幕截图。默认情况下,最后的快照存储在应用的沙盒中,位于 Library/Caches/Snapshots/Library/SplashBoard/Snapshots 文件夹(受信任的计算机从 iOX 7.0 开始无法访问文件系统)。

一种防止这种行为的方法是在进入后台前使用 ApplicationDidEnterBackground() 函数将屏幕置空或移除敏感数据。

下面是一个示例修复方法,它会设置默认截图。

Swift:

private var backgroundImage: UIImageView?

func applicationDidEnterBackground(_ application: UIApplication) {
let myBanner = UIImageView(image: #imageLiteral(resourceName: "overlayImage"))
myBanner.frame = UIScreen.main.bounds
backgroundImage = myBanner
window?.addSubview(myBanner)
}

func applicationWillEnterForeground(_ application: UIApplication) {
backgroundImage?.removeFromSuperview()
}

Objective-C:

@property (UIImageView *)backgroundImage;

- (void)applicationDidEnterBackground:(UIApplication *)application {
UIImageView *myBanner = [[UIImageView alloc] initWithImage:@"overlayImage.png"];
self.backgroundImage = myBanner;
self.backgroundImage.bounds = UIScreen.mainScreen.bounds;
[self.window addSubview:myBanner];
}

- (void)applicationWillEnterForeground:(UIApplication *)application {
[self.backgroundImage removeFromSuperview];
}

这会在应用进入后台时将背景图像设置为 overlayImage.png。它可以防止敏感数据 leak,因为 overlayImage.png 将始终覆盖当前视图。

Keychain

要访问和管理 iOS keychain,可以使用像 Keychain-Dumper 这样的工具,适用于 jailbroken devices。另一个工具 Objection 提供了命令 ios keychain dump 用于类似目的。

存储凭据

The NSURLCredential class is ideal for saving sensitive information directly in the keychain, bypassing the need for NSUserDefaults or other wrappers. To store credentials after login, the following Swift code is used:

NSURLCredential *credential;
credential = [NSURLCredential credentialWithUser:username password:password persistence:NSURLCredentialPersistencePermanent];
[[NSURLCredentialStorage sharedCredentialStorage] setCredential:credential forProtectionSpace:self.loginProtectionSpace];

To extract these stored credentials, Objection’s command ios nsurlcredentialstorage dump is utilized.

自定义键盘和键盘缓存

从 iOS 8.0 开始,用户可以安装自定义键盘扩展,这些扩展可在 Settings > General > Keyboard > Keyboards 中管理。虽然这些键盘提供了扩展功能,但它们存在 keystroke logging 的风险并可能将数据传输到外部服务器,尽管系统会在键盘需要网络访问时提醒用户。应用应当(并且可以)限制在输入敏感信息时使用自定义键盘。

安全建议:

  • 建议为增强安全性禁用第三方键盘。
  • 注意默认 iOS 键盘的 autocorrect 和自动建议功能,这些功能可能会将敏感信息存储在位于 Library/Keyboard/{locale}-dynamic-text.dat/private/var/mobile/Library/Keyboard/dynamic-text.dat 的缓存文件中。应定期检查这些缓存文件以查找敏感数据。建议通过 Settings > General > Reset > Reset Keyboard Dictionary 重置键盘字典以清除缓存数据。
  • 拦截网络流量可以揭示自定义键盘是否在远程传输 keystrokes。

防止文本字段缓存

The UITextInputTraits protocol offers properties to manage autocorrection and secure text entry, essential for preventing sensitive information caching. For example, disabling autocorrection and enabling secure text entry can be achieved with:

textObject.autocorrectionType = UITextAutocorrectionTypeNo;
textObject.secureTextEntry = YES;

此外,开发人员应确保文本字段,尤其是用于输入敏感信息(如 passwords 和 PINs)的字段,通过将 autocorrectionType 设置为 UITextAutocorrectionTypeNo 并将 secureTextEntry 设置为 YES 来禁用缓存。

UITextField *textField = [[UITextField alloc] initWithFrame:frame];
textField.autocorrectionType = UITextAutocorrectionTypeNo;

日志

调试代码通常涉及使用 日志记录 (logging)。存在风险,因为 日志可能包含敏感信息。以前,在 iOS 6 及更早版本中,日志对所有应用可访问,存在敏感数据泄露的风险。现在,应用程序被限制只能访问它们自己的日志

尽管有这些限制,具有物理访问权限的攻击者仍然可以通过将解锁的设备连接到电脑并读取日志来利用这一点。需要注意的是,即使应用被卸载,日志仍会保留在磁盘上。

为降低风险,建议充分与应用交互,探索其所有功能和输入,确保没有敏感信息被无意中记录。

在审查应用源代码以查找潜在泄露时,查找 预定义自定义日志语句,使用诸如 NSLogNSAssertNSCAssertfprintf 等内置函数的关键字,以及任何提到 LoggingLogfile 的自定义实现。

监控系统日志

应用会记录各种可能包含敏感信息的数据。要监控这些日志,可以使用如下工具和命令:

idevice_id --list   # To find the device ID
idevicesyslog -u <id> (| grep <app>)   # To capture the device logs

是有用的。此外,Xcode 提供了一种收集控制台日志的方法:

  1. 打开 Xcode
  2. 连接 iOS 设备。
  3. 导航到 Window -> Devices and Simulators
  4. 选择你的设备。
  5. 触发你正在调查的问题。
  6. 使用 Open Console 按钮在新窗口中查看日志。

对于更高级的日志记录,连接到 device shell 并使用 socat 可以提供实时日志监控:

iPhone:~ root# socat - UNIX-CONNECT:/var/run/lockdown/syslog.sock

随后可以运行命令来观察日志活动,这对于诊断问题或识别日志中的潜在数据泄露非常有价值。

Backups

Auto-backup features 集成在 iOS 中,便于通过 iTunes(直到 macOS Catalina)、Finder(自 macOS Catalina 起)或 iCloud 创建设备数据副本。这些备份包含几乎所有设备数据,但不包括 Apple Pay 详细信息和 Touch ID 配置等高度敏感的内容。

Security Risks

包含 已安装的应用及其数据 在备份中,会带来潜在的 数据泄露 问题,以及备份被修改后可能改变应用行为的风险。建议 不要在任何 app 的目录或其子目录中以明文形式存储敏感信息,以降低这些风险。

Excluding Files from Backups

默认会备份 Documents/Library/Application Support/ 中的文件。开发者可以使用 NSURL setResourceValue:forKey:error: 并设置 NSURLIsExcludedFromBackupKey 来将特定文件或目录从备份中排除。此做法对于防止敏感数据被包含在备份中至关重要。

Testing for Vulnerabilities

要评估应用的备份安全性,可以先使用 Finder 创建备份,然后按照 Apple’s official documentation 的指引找到该备份。分析备份中是否存在敏感数据或配置,这些内容可能被篡改以影响应用行为。

可以使用命令行工具或像 iMazing 这样的应用来查找敏感信息。对于加密备份,可以通过检查备份根目录中的 “Manifest.plist” 文件里的 “IsEncrypted” 键来确认是否启用了加密。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
...
<key>Date</key>
<date>2021-03-12T17:43:33Z</date>
<key>IsEncrypted</key>
<true/>
...
</plist>

对于处理加密备份,可以使用 DinoSec’s GitHub repo 中的 Python 脚本,例如 backup_tool.pybackup_passwd.py,但可能需要针对最新的 iTunes/Finder 版本进行兼容性调整。iOSbackup tool 是另一个用于访问受密码保护备份中文件的选项。

修改应用行为

通过修改备份来改变应用行为的一个例子可以在 Bither bitcoin wallet app 中看到,UI 锁定 PIN 存储在 net.bither.plist 的 pin_code 键下。从 plist 中删除该键并恢复备份会移除 PIN 要求,从而获得不受限制的访问。

敏感数据内存测试摘要

当处理存储在应用内存中的敏感信息时,必须尽量缩短这些数据的暴露时间。调查内存内容的两种主要方法是:创建内存转储(memory dump)和实时分析内存。两种方法都有各自的挑战,例如在转储或分析过程中可能错过关键数据。

检索与分析内存转储

对于越狱和未越狱设备,都可以使用 objectionFridump 等工具来导出应用进程的内存。一旦得到内存转储,分析这些数据需要根据要查找的信息类型使用不同的工具。

要从内存转储中提取字符串,可以使用 stringsrabin2 -zz 等命令:

# Extracting strings using strings command
$ strings memory > strings.txt

# Extracting strings using rabin2
$ rabin2 -ZZ memory > strings.txt

对于更详细的分析,包括搜索特定数据类型或模式,radare2 提供了广泛的搜索功能:

$ r2 <name_of_your_dump_file>
[0x00000000]> /?
...

运行时内存分析

r2frida 提供了一个强大的替代方案,用于实时检查应用的内存,而无需 memory dump。该工具可以直接在运行中的应用内存上执行搜索命令:

$ r2 frida://usb//<name_of_your_app>
[0x00000000]> /\ <search_command>

加密缺陷

密钥管理流程不当

一些开发者将敏感数据保存在本地存储,并使用在代码中 hardcoded/predictable 的 key 对其加密。这样做不应该,因为通过 reversing,攻击者可能提取这些机密信息。

使用不安全和/或已弃用的算法

开发者不应该使用 deprecated algorithms 来执行授权 checksstoresend 数据。这些算法包括:RC4, MD4, MD5, SHA1… 如果 hashes 被用于存储密码,例如,应使用对暴力破解更具 resistant 的 hashes,并使用 salt。

Check

主要的检查包括是否能在代码中找到 hardcoded 密码/密钥,或者这些是否 predictable,以及代码是否使用某种 weak cryptography 算法。

值得注意的是,你可以使用 objection 自动 monitor 某些 crypto libraries,命令如下:

ios monitor crypt

For 更多信息 关于 iOS 密码学 API 和 库,请访问 https://mobile-security.gitbook.io/mobile-security-testing-guide/ios-testing-guide/0x06e-testing-cryptography

本地认证

本地认证 在保护通过密码学方法访问远端端点时起着关键作用。关键在于,如果实现不当,本地认证机制可能会被绕过。

Apple 的 Local Authentication frameworkkeychain 分别为开发者提供了用于显示用户认证对话框和安全处理秘密数据的强大 API。Secure Enclave 为 Touch ID 的指纹 ID 提供保护,而 Face ID 则依赖面部识别,同时不会泄露生物特征数据。

要集成 Touch ID/Face ID,开发者有两种 API 可选:

  • LocalAuthentication.framework 用于高层次的用户认证,且不访问生物特征数据。
  • Security.framework 用于底层的 keychain 服务访问,通过生物认证保护秘密数据。各种 open-source wrappers 可以简化 keychain 的访问。

Caution

然而,LocalAuthentication.frameworkSecurity.framework 都存在漏洞,因为它们主要返回布尔值而不传递用于认证过程的数据,这使得它们容易被绕过(参见 Don’t touch me that way, by David Lindner et al)。

实现本地认证

要提示用户进行认证,开发者应在 LAContext 类中使用 evaluatePolicy 方法,可在以下选项间选择:

  • deviceOwnerAuthentication:提示使用 Touch ID 或设备解锁密码;如果两者都未启用则失败。
  • deviceOwnerAuthenticationWithBiometrics:仅提示使用 Touch ID。

成功的认证由 evaluatePolicy 的布尔返回值表示,这凸显了一个潜在的安全缺陷。

使用 Keychain 的本地认证

在 iOS 应用中实现 本地认证 涉及使用 keychain APIs 来安全存储诸如认证令牌之类的秘密数据。该过程确保数据只有在用户使用设备解锁密码或 Touch ID 等生物认证成功时才能被访问。

keychain 提供了使用 SecAccessControl 属性设置项目的功能,该属性会限制对该项目的访问,直到用户通过 Touch ID 或设备解锁密码成功认证为止。此功能对于提升安全性至关重要。

下面给出 Swift 和 Objective-C 的代码示例,演示如何利用这些安全特性将字符串保存到 keychain 并从中检索。这些示例特别展示了如何设置访问控制以要求 Touch ID 认证,并确保数据仅在设置该数据的设备上可用,前提是设备已配置解锁密码。

// From https://github.com/mufambisi/owasp-mstg/blob/master/Document/0x06f-Testing-Local-Authentication.md

// 1. create AccessControl object that will represent authentication settings

var error: Unmanaged<CFError>?

guard let accessControl = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
SecAccessControlCreateFlags.biometryCurrentSet,
&error) else {
// failed to create AccessControl object

return
}

// 2. define keychain services query. Pay attention that kSecAttrAccessControl is mutually exclusive with kSecAttrAccessible attribute

var query: [String: Any] = [:]

query[kSecClass as String] = kSecClassGenericPassword
query[kSecAttrLabel as String] = "com.me.myapp.password" as CFString
query[kSecAttrAccount as String] = "OWASP Account" as CFString
query[kSecValueData as String] = "test_strong_password".data(using: .utf8)! as CFData
query[kSecAttrAccessControl as String] = accessControl

// 3. save item

let status = SecItemAdd(query as CFDictionary, nil)

if status == noErr {
// successfully saved
} else {
// error while saving
}

现在我们可以从 keychain 请求已保存的条目。Keychain services 会向用户显示身份验证对话框,并根据是否提供了合适的指纹返回 data 或 nil。

// 1. define query
var query = [String: Any]()
query[kSecClass as String] = kSecClassGenericPassword
query[kSecReturnData as String] = kCFBooleanTrue
query[kSecAttrAccount as String] = "My Name" as CFString
query[kSecAttrLabel as String] = "com.me.myapp.password" as CFString
query[kSecUseOperationPrompt as String] = "Please, pass authorisation to enter this area" as CFString

// 2. get item
var queryResult: AnyObject?
let status = withUnsafeMutablePointer(to: &queryResult) {
SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0))
}

if status == noErr {
let password = String(data: queryResult as! Data, encoding: .utf8)!
// successfully received password
} else {
// authorization not passed
}

检测

可以通过分析 app 二进制文件的共享动态库列表来检测应用中使用的 frameworks。这可以使用 otool 完成:

$ otool -L <AppName>.app/<AppName>

如果在应用中使用了 LocalAuthentication.framework,输出将包含以下两行(请记住 LocalAuthentication.framework 在底层使用了 Security.framework):

/System/Library/Frameworks/LocalAuthentication.framework/LocalAuthentication
/System/Library/Frameworks/Security.framework/Security

If Security.framework is used, only the second one will be shown.

Local Authentication Framework Bypass

Objection

通过位于 this GitHub pageObjection Biometrics Bypass,可以获得一种绕过 LocalAuthentication 机制的技术。该方法的核心是利用 Frida 操纵 evaluatePolicy 函数,使其始终返回 True,无论实际身份验证是否成功。这对于规避存在缺陷的生物识别认证流程特别有用。

To activate this bypass, the following command is employed:

...itudehacks.DVIAswiftv2.develop on (iPhone: 13.2.3) [usb] # ios ui biometrics_bypass
(agent) Registering job 3mhtws9x47q. Type: ios-biometrics-disable
...itudehacks.DVIAswiftv2.develop on (iPhone: 13.2.3) [usb] # (agent) [3mhtws9x47q] Localized Reason for auth requirement: Please authenticate yourself
(agent) [3mhtws9x47q] OS authentication response: false
(agent) [3mhtws9x47q] Marking OS response as True instead
(agent) [3mhtws9x47q] Biometrics bypass hook complete

该命令触发一系列操作,Objection 注册了一个任务,该任务有效地将 evaluatePolicy 检查的结果更改为 True

Frida

来自 DVIA-v2 applicationevaluatePolicy 使用示例:

+(void)authenticateWithTouchID {
LAContext *myContext = [[LAContext alloc] init];
NSError *authError = nil;
NSString *myLocalizedReasonString = @"Please authenticate yourself";

if ([myContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&authError]) {
[myContext evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
localizedReason:myLocalizedReasonString
reply:^(BOOL success, NSError *error) {
if (success) {
dispatch_async(dispatch_get_main_queue(), ^{
[TouchIDAuthentication showAlert:@"Authentication Successful" withTitle:@"Success"];
});
} else {
dispatch_async(dispatch_get_main_queue(), ^{
[TouchIDAuthentication showAlert:@"Authentication Failed !" withTitle:@"Error"];
});
}
}];
} else {
dispatch_async(dispatch_get_main_queue(), ^{
[TouchIDAuthentication showAlert:@"Your device doesn't support Touch ID or you haven't configured Touch ID authentication on your device" withTitle:@"Error"];
});
}
}

为了实现对 Local Authentication 的 bypass,编写了一个 Frida 脚本。该脚本针对 evaluatePolicy 检查,拦截其 callback 以确保返回 success=1。通过改变 callback 的行为,认证检查会被有效地 bypass。

下面注入的脚本用于修改 evaluatePolicy 方法的结果。它将 callback 的返回值改为始终表示成功。

// from https://securitycafe.ro/2022/09/05/mobile-pentesting-101-bypassing-biometric-authentication/
if(ObjC.available) {
console.log("Injecting...");
var hook = ObjC.classes.LAContext["- evaluatePolicy:localizedReason:reply:"];
Interceptor.attach(hook.implementation, {
onEnter: function(args) {
var block = new ObjC.Block(args[4]);
const callback = block.implementation;
block.implementation = function (error, value)  {

console.log("Changing the result value to true")
const result = callback(1, null);
return result;
};
},
});
} else {
console.log("Objective-C Runtime is not available!");
}

要注入 Frida 脚本并绕过生物识别认证,请使用以下命令:

frida -U -f com.highaltitudehacks.DVIAswiftv2 --no-pause -l fingerprint-bypass-ios.js

通过 IPC 暴露敏感功能

iOS Custom URI Handlers / Deeplinks / Custom Schemes

iOS Universal Links

UIActivity Sharing

iOS UIActivity Sharing

UIPasteboard

iOS UIPasteboard

App Extensions

iOS App Extensions

WebViews

iOS WebViews

Serialisation and Encoding

iOS Serialisation and Encoding

网络通信

检查是否存在未加密(without encryption)的通信以及应用是否正确地校验服务器的 TLS 证书(validating the TLS certificate)非常重要。
要检测此类问题,你可以使用像 Burp 这样的代理:

iOS Burp Suite Configuration

主机名校验

在校验 TLS 证书时,一个常见的问题是检查证书是否由受信任的 CA 签发,但并未检查证书上的 hostname 是否与正在访问的主机名一致。
使用 Burp 检查此问题时,在 iPhone 中信任 Burp CA 之后,你可以使用 Burp 为不同的 hostname 创建一个新的证书 并使用它。如果应用仍然正常工作,则该应用存在漏洞。

Certificate Pinning

如果应用正确使用了 SSL Pinning,那么应用只有在证书与预期一致时才会正常工作。在测试应用时,这可能成为一个问题,因为 Burp 会提供它自己的证书。
为了在已越狱的设备上绕过此保护,你可以安装应用 SSL Kill Switch 或安装 Burp Mobile Assistant

你也可以使用 objectionios sslpinning disable

其他

  • /System/Library 中可以找到系统应用使用的框架
  • 用户从 App Store 安装的应用位于 /User/Applications
  • /User/Library 包含用户级应用保存的数据
  • 你可以访问 /User/Library/Notes/notes.sqlite 来读取应用中保存的笔记
  • 在已安装应用的文件夹(/User/Applications/<APP ID>/)中可以找到一些有意思的文件:
  • iTunesArtwork:应用使用的图标
  • iTunesMetadata.plist:用于 App Store 的应用信息
  • /Library/*:包含偏好设置和缓存。在 /Library/Cache/Snapshots/* 中可以找到将应用送入后台前的快照。

Hot Patching/Enforced Updateing

开发者可以远程即时修补其应用的所有安装版本,而无需重新提交应用到 App Store 并等待批准。
为此通常使用 JSPatch。但也有其他选项,例如 Sirenreact-native-appstore-version-checker
这是一个危险的机制,可能被恶意的第三方 SDK 滥用,因此建议检查用于自动更新的方法(如果有)并进行测试。 你可以尝试为此目的下载应用的旧版本。

Third Parties

使用 第三方 SDKs 的一个重大挑战是对其功能缺乏细粒度控制。开发者必须在集成该 SDK 并接受其所有功能(包括潜在的安全漏洞和隐私问题)与完全放弃其好处之间做出选择。通常,开发者无法自行修补这些 SDK 中的漏洞。此外,随着某些 SDK 在社区中获得信任,可能会开始包含恶意软件。

第三方 SDK 提供的服务可能包括用户行为跟踪、广告展示或用户体验增强。然而,这会带来风险,因为开发者可能无法完全了解这些库执行的代码,从而导致潜在的隐私和安全风险。至关重要的是将与第三方服务共享的信息限制为必要范围,确保不会暴露敏感数据。

第三方服务的实现通常有两种形式:独立库或完整 SDK。为保护用户隐私,任何与这些服务共享的数据都应进行匿名化,以防止泄露个人可识别信息(Personal Identifiable Information, PII)。

要识别应用使用的库,可以使用 otool 命令。该工具应针对应用及其使用的每个共享库运行,以发现更多库。

otool -L <application_path>

有趣的漏洞与案例研究

Air Keyboard Remote Input Injection

Itunesstored Bookassetd Sandbox Escape

Zero Click Messaging Image Parser Chains

参考资料与更多资源

Tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE) 学习和实践 Azure 黑客技术:HackTricks Training Azure Red Team Expert (AzRTE)

支持 HackTricks