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
Read the Basic iOS Testing Operations page to learn how to do this.

基本静态分析

一些有趣的 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"

常见的越狱检测方法

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

常见的反调试检测方法

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

基本动态分析

查看 MobSF 执行的动态分析。你需要在不同视图间导航并与之交互,但它会挂钩若干类并在完成后生成报告。

列出已安装应用

使用命令 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

基本枚举 & Hooking

了解如何枚举应用程序的组件以及如何使用 objection 轻松hook 方法和类

iOS Hooking With Objection

IPA 结构

IPA file 的结构本质上类似于一个压缩包。通过将其扩展名重命名为 .zip,可以将其解压以查看内容。在该结构中,Bundle 表示一个准备安装的完整打包应用。在内部,你会看到一个名为 <NAME>.app 的目录,它封装了应用的资源。

  • Info.plist:此文件包含应用的特定配置细节。
  • _CodeSignature/:此目录包含一个 plist 文件,该文件包含签名,用于确保 bundle 中所有文件的完整性。
  • Assets.car:一个压缩存档,用于存储诸如图标之类的资源文件。
  • Frameworks/:此文件夹包含应用的本地库,可能以 .dylib.framework 文件的形式存在。
  • PlugIns/:这里可能包含应用的扩展,即 .appex 文件,但它们并不总是存在。 * Core Data:用于保存应用的永久数据以供离线使用、缓存临时数据,并为单设备上的应用添加撤销功能。要在同一 iCloud 帐户的多个设备间同步数据,Core Data 会自动将你的模式镜像到 CloudKit 容器中。
  • PkgInfoPkgInfo 文件是指定应用或 bundle 的类型和创建者代码的另一种方式。
  • en.lproj, fr.proj, Base.lproj:这些是语言包,包含针对特定语言的资源,以及在某语言不受支持时使用的默认资源。
  • 安全性_CodeSignature/ 目录通过数字签名验证所有打包文件的完整性,在应用安全中起着关键作用。
  • 资源管理Assets.car 文件使用压缩来高效管理图形资源,这对于优化应用性能和减小整体体积至关重要。
  • Frameworks 和 PlugIns:这些目录强调了 iOS 应用的模块化,允许开发者包含可重用的代码库(Frameworks/)并扩展应用功能(PlugIns/)。
  • 本地化:该结构支持多语言,通过包含针对特定语言的资源来促进应用的全球覆盖。

Info.plist

Info.plist 是 iOS 应用的基石,以键-值对的形式封装关键配置数据。此文件不仅是应用所必需的,也适用于捆绑其中的应用扩展和 framework。它可以采用 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 文件可 divulge 的众多信息中,值得注意的条目包括应用权限字符串(UsageDescription)、自定义 URL schemes(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 tool 提供了一个有用的命令 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,包含应用的核心数据、静态内容以及已编译的应用二进制文件。
  • 该目录对用户可见,但 用户无法写入
  • 此目录中的内容不被备份
  • 此文件夹的内容用于验证代码签名

Data directory:

  • Documents/
  • 包含所有用户生成的数据。应用的最终用户会触发这些数据的创建。
  • 对用户可见且 用户可以写入
  • 此目录中的内容会被备份
  • 应用可以通过设置 NSURLIsExcludedFromBackupKey 来禁用某些路径。
  • Library/
  • 包含所有非用户特定的文件,例如缓存偏好cookies,以及 property list (plist) 配置文件。
  • iOS apps usually use the Application Support and Caches subdirectories, but the app can create custom subdirectories.
  • Library/Caches/
  • 包含半持久性的缓存文件
  • 对用户不可见且 用户无法写入
  • 此目录中的内容不被备份
  • 当应用未运行且存储空间不足时,OS 可能会自动删除该目录下的文件。
  • Library/Application Support/
  • 包含运行应用所需的持久性****文件
  • 对用户不可见,且用户无法写入。
  • 此目录中的内容会被备份
  • 应用可以通过设置 NSURLIsExcludedFromBackupKey 来禁用某些路径。
  • Library/Preferences/
  • 用于存储可以在应用重启后仍然保留的属性。
  • 信息以未加密形式保存在应用沙箱内的一个名为 [BUNDLE_ID].plist 的 plist 文件中。
  • 使用 NSUserDefaults 存储的所有键/值对都可以在此文件中找到。
  • tmp/
  • 使用此目录写入不需要在应用重新启动间持久化的临时文件
  • 包含非持久性的缓存文件。
  • 对用户不可见
  • 此目录中的内容不被备份。
  • 当应用未运行且存储空间不足时,OS 可能会自动删除该目录下的文件。

Let’s take a closer look at iGoat-Swift’s Application Bundle (.app) directory inside the Bundle directory (/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

二进制逆向

Inside the <application-name>.app folder you will find a binary file called <application-name>. This is the file that will be 执行。You can perform a basic inspection of the binary with the tool 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;
};

However, the best options to disassemble the binary are: Hopper and IDA.

数据存储

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

iOS Basics

Warning

下列存储信息的位置应在 安装应用程序之后立即检查完应用所有功能后 以及甚至在 从一个用户登出并以另一个用户登录后 进行检查。
目标是查找应用(密码、tokens)中 未受保护的敏感信息,包括当前用户和先前登录用户的敏感信息。

Plist

plist 文件是结构化的 XML 文件,包含键值对。它是一种存储持久数据的方式,因此有时你可能会在 这些文件中发现敏感信息。建议在安装应用后以及密集使用后检查这些文件,以查看是否写入了新数据。

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

The NSUserDefaults class provides a programmatic interface for interacting with the default system. The default system allows an application to customize its behaviour according to user preferences. Data saved by NSUserDefaults can be viewed in the application bundle. This class stores data in a plist file, but it’s meant to be used with small amounts of data.

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

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

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

find ./ -name "*.plist"

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

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

$ plutil -convert xml1 Info.plist

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

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

在 Objection Session 中: 对于分析移动应用程序,可使用特定命令直接转换 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 信息

如果你能够打开 SQLite 并访问到敏感信息,那么你发现了一个配置错误。

-(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 是一个构建在 SQLite 之上的键/值存储。
由于 YapDatabase 是基于 SQLite 的数据库,你可以使用上一节中提供的命令来查找它们。

其他 SQLite 数据库

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

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

Firebase 实时数据库

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

你可以在此处查看如何检查配置错误的 Firebase 数据库:

Firebase Database

Realm 数据库

Realm Objective-CRealm 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 文件可以在备份中被访问

要检查 cookies 文件,你可以使用 this python script 或使用 objection 的 ios cookies get.
You can also use objection to 将这些文件转换为 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"
}
]

缓存

默认情况下 NSURLSession 会存储数据,例如 HTTP requests and responses in the Cache.db 数据库。该数据库可能包含 敏感数据,如果令牌、用户名或任何其他敏感信息被缓存。要查找缓存的信息,请打开应用的数据目录(/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 来禁用缓存。它会禁止以任何方式在内存或磁盘上存储缓存。

快照

每当按下 home 键时,iOS 会截取当前屏幕的快照,以便在切换到应用时实现更平滑的过渡。然而,如果当前屏幕上存在敏感****数据,这些信息将被保存在图像中(并在重启后仍然存在)。这些就是通过双击 home 键切换应用时可以访问到的快照。

除非 iPhone 已越狱,攻击者 需要在设备未锁定的情况下获得访问权限才能查看这些截图。默认情况下,最后的快照存储在应用的沙箱中的 Library/Caches/Snapshots/Library/SplashBoard/Snapshots 文件夹中(trusted computers can’ t access the filesystem from 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 来实现类似功能。

存储凭据

NSURLCredential 类非常适合将敏感信息直接保存到 keychain,绕过 NSUserDefaults 或其他封装。要在登录后存储凭据,可使用以下 Swift 代码:

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

要提取这些存储的凭证,可使用 Objection 的命令 ios nsurlcredentialstorage dump

自定义键盘与键盘缓存

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

安全建议:

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

防止文本字段缓存

UITextInputTraits protocol 提供了用于管理自动更正和安全文本输入的属性,这对于防止敏感信息被缓存至关重要。例如,可以通过以下方式禁用自动更正并启用安全文本输入:

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

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

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

Logs

调试代码通常涉及使用 logging。这存在风险,因为 logs 可能包含敏感信息。在 iOS 6 及更早版本中,logs 对所有应用可见,存在敏感数据 leak 的风险。现在,应用被限制为只能访问它们自己的 logs

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

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

在审查应用源代码以查找潜在 leak 时,应查看 预定义自定义 logging 语句,查找内置函数关键字如 NSLog, NSAssert, NSCAssert, fprintf,以及自定义实现中出现的 LoggingLogfile 等。

监控系统 Logs

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

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 按钮在新窗口中查看日志。

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

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

随后是用于观察日志活动的命令,这对于诊断问题或识别日志中的潜在 data leakage 非常有价值。

备份

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

安全风险

备份中包含 installed apps and their data 会引发潜在的 data leakage 问题,并存在 backup modifications could alter app functionality 的风险。建议 不要以明文在任何 app 的目录或其子目录中存储敏感信息,以降低这些风险。

从备份中排除文件

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

测试漏洞

要评估应用的备份安全性,首先使用 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.plistpin_code 键下。从 plist 中移除该键并恢复备份即可移除 PIN 要求,从而获得不受限制的访问。

关于敏感数据内存测试的总结

在处理存储于应用内存中的敏感信息时,至关重要的是要限制这些数据暴露的时间。调查内存内容的两种主要方法是:创建内存转储实时分析内存。这两种方法都有各自的挑战,例如在转储或分析过程中可能会错过关键数据。

检索和分析内存转储

对于 jailbroken 和 non-jailbroken 设备,像 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 提供了一种强大的替代方法,用于实时检查应用程序的内存,而无需内存转储。该工具允许直接在正在运行的应用程序内存中执行搜索命令:

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

加密缺陷

密钥管理流程不当

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

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

开发者不应使用 deprecated algorithms 来执行授权 checksstoresend 数据。其中一些算法包括:RC4, MD4, MD5, SHA1… 如果例如使用 hashes 来存储 passwords/secrets,应使用对 brute-force resistanthashes 并配合 salt

检查

主要需要检查的是是否能在代码中找到 hardcoded passwords/secrets,或它们是否 predictable,以及代码是否使用某种 weak cryptography 算法。

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

ios monitor crypt

For 更多信息 about iOS cryptographic APIs and libraries access https://mobile-security.gitbook.io/mobile-security-testing-guide/ios-testing-guide/0x06e-testing-cryptography

本地认证

本地认证 在通过加密方法保护远程端点访问时发挥着关键作用。关键在于,如果实现不当,本地认证机制可能会被规避。

Apple 的 Local Authentication frameworkkeychain 分别为开发者提供了用于显示用户认证对话框和安全处理秘密数据的强大 APIs。Secure Enclave 为 Touch 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 二进制文件的共享动态库列表来检测。可以使用 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.

本地认证框架绕过

Objection

通过位于 this GitHub pageObjection Biometrics Bypass,可以使用一种技术来绕过 LocalAuthentication 机制。该方法的核心是利用 Frida 操作 evaluatePolicy 函数,使其始终返回 True,而不考虑实际认证是否成功。这在绕过有缺陷的生物识别认证流程时特别有用。

要启用此绕过,请使用以下命令:

...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"];
});
}
}

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

下面注入的脚本用于修改 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 脚本并 bypass 生物识别认证,使用以下命令:

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

Network Communication

检查应用是否存在“未加密通信”以及应用是否正确“验证服务器的 TLS 证书”非常重要。
要检测这类问题,你可以使用像 Burp 这样的代理:

iOS Burp Suite Configuration

Hostname check

在验证 TLS 证书时常见的问题是检查证书是否由受信任的 CA 签发,但没有检查证书的主机名(hostname) 是否与正在访问的主机名一致。
为了在 Burp 中检测此问题,在 iPhone 中信任 Burp CA 后,你可以用 Burp 为另一个主机名创建新的证书并使用它。如果应用仍然能工作,那么说明它存在漏洞。

Certificate Pinning

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

你也可以使用 objectionios sslpinning disable

Misc

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

Hot Patching/Enforced Updateing

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

Third Parties

使用 第三方 SDK (3rd party SDKs) 的一个显著挑战是对其功能缺乏细粒度控制。开发者面临选择:要么集成 SDK 并接受其所有功能,包括潜在的安全漏洞和隐私问题,要么完全放弃其带来的好处。通常,开发者无法自己修补这些 SDK 中的漏洞。此外,随着 SDK 在社区内获得信任,有些 SDK 可能开始包含恶意软件。

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

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

要识别应用使用的库,可以使用 otool 命令。该工具应针对应用及其使用的每个 shared library 运行,以发现额外的库。

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