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 模拟器模拟器jailbreaking 的信息:

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

识别敏感/不安全函数

  • Weak Hashing Algorithms
# 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"
  • Insecure Random Functions
# 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"
  • Insecure ‘Malloc’ Function
# On the iOS device
otool -Iv <app> | grep -w "_malloc"

# On linux
grep -iER "_malloc"
  • Insecure and Vulnerable Functions
# 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 相关的进程,例如 CydiaSubstratessh 的存在。
  • 内核漏洞:检查常用于越狱的内核漏洞是否存在。
  • 环境变量:检查环境变量是否有越狱迹象,例如 DYLD_INSERT_LIBRARIES
  • 库检查:检查加载到应用进程中的库。
  • 检查 schemes:例如 canOpenURL(URL(string: "cydia://"))

常见反调试检测方法

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

基本动态分析

查看 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

Basic Enumeration & Hooking

了解如何 enumerate the components of the application 以及如何使用 objection 轻松 hook methods and classes

iOS Hooking With Objection

IPA 结构

IPA file 的结构本质上就是一个 zipped package。将其扩展名改为 .zip 后,可以将其 解压 以查看其内容。在此结构中,Bundle 表示一个已打包好、可安装的完整应用。在其中,你会看到一个名为 <NAME>.app 的目录,封装了应用的资源。

  • Info.plist: 该文件保存应用程序的特定配置信息。
  • _CodeSignature/: 该目录包含一个 plist 文件,该文件包含签名,用于确保 bundle 中所有文件的完整性。
  • Assets.car: 一个压缩归档,存储诸如图标等资源文件。
  • Frameworks/: 此文件夹存放应用的本地库,可能以 .dylib.framework 文件的形式存在。
  • PlugIns/: 这里可能包含应用的扩展,称为 .appex 文件,但它们并不总是存在。 * Core Data: 它用于保存应用的永久数据以便离线使用、缓存临时数据,并在单个设备上为应用添加撤销功能。为了在单个 iCloud 帐户的多个设备之间同步数据,Core Data 会自动将你的模式镜像到 CloudKit 容器中。
  • PkgInfo: PkgInfo 文件是指定应用或 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 应用的基石,以键值对的形式封装了关键配置数据。此文件不仅是应用所必需的,对捆绑在其中的应用扩展和 frameworks 也同样适用。它可以是 XML 或二进制格式,包含从应用权限到安全配置等重要信息。有关可用键的详细说明,请参阅 Apple Developer Documentation

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

  • 针对 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 中的应用必须 sandbox,每个应用在 $HOME/Library/Containers 中也会有一个文件夹,文件夹名为应用的 CFBundleIdentifier

然而,这两个文件夹(data & container folders)都包含文件 .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/
  • 包含所有非用户特定的文件,例如 缓存(caches)偏好(preferences)cookies 和 property list (plist) 配置文件。
  • iOS 应用通常使用 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 段,可以使用:

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

以下用于存储信息的位置应当在安装应用后立即检查完应用所有功能后以及从一个用户登出并登录到另一个用户之后进行检查。
目标是找到应用、当前用户以及之前登录用户的未受保护的敏感信息(passwords, tokens)。

Plist

plist files are structured XML files that contains key-value pairs. It’s a way to store persistent data, so sometimes you may find sensitive information in these files. It’s recommended to check these files after installing the app and after using intensively it to see if new data is written.

The most common way to persist data in plist files is through the usage of NSUserDefaults. This plist file is saved inside the app sandbox in 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.

These data cannot be longer accessed directly via a trusted computer, but can be accessed performing a backup.

你可以使用 objection 的 ios nsuserdefaults get导出通过 NSUserDefaults 保存的信息。

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

find ./ -name "*.plist"

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

For macOS Users: 利用 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 是一个基于 SQLite 的 key/value 存储。
由于 Yap 数据库是 sqlite 数据库,你可以使用上一节中给出的命令来查找它们。

其他 SQLite 数据库

应用程序通常会创建自己的 sqlite 数据库。它们可能在上面 存储 敏感 数据 并且未加密。因此,检查应用目录中的每个数据库通常都很重要。请转到保存数据的应用目录(/private/var/mobile/Containers/Data/Application/{APPID}

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

Firebase 实时数据库

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

You can find how to check for misconfigured Firebase databases here:

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 file can be accessed in backups

要检查 cookies 文件,你可以使用 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"
}
]

缓存

By default NSURLSession stores data, such as HTTP requests and responses in the Cache.db database. 该数据库可能包含 敏感数据,如果 tokens、用户名或任何其他敏感信息已被缓存。要查找缓存的信息,请打开应用的数据目录 (/var/mobile/Containers/Data/Application/<UUID>) 并转到 /Library/Caches/<Bundle Identifier>WebKit cache is also being stored in the Cache.db 文件。Objection 可以使用命令 sqlite connect Cache.db 打开并操作该数据库,因为它是一个 normal SQLite database

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

  1. 建议在 logout 后移除缓存的响应。Apple 提供了名为 removeAllCachedResponses 的方法。你可以如下调用此方法:

URLCache.shared.removeAllCachedResponses()

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

  1. 如果你不需要使用 cookies 的好处,建议只使用 URLSession 的 .ephemeral 配置属性,这将禁用保存 cookies 和 Caches。

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,无论是在内存中还是在磁盘上。

快照

Whenever you press the home button, iOS takes a snapshot of the current screen 以便能够更平滑地进行应用切换。然而,如果当前屏幕上存在 敏感 数据,它将被 保存图像 中(这些图像 会在重启后保留)。这些就是你通过双击 home 屏幕切换应用时也可以访问到的快照。

除非 iPhone 已越狱,否则攻击者需要在设备解锁时才能查看这些截图。默认情况下,最后一个快照存储在应用的沙箱中 Library/Caches/Snapshots/Library/SplashBoard/Snapshots 文件夹(trusted computers 无法从 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。它可防止敏感数据 leaks,因为 overlayImage.png 会始终覆盖当前视图。

钥匙串

要访问和管理 iOS keychain,可使用像 Keychain-Dumper 这样的工具,适用于越狱设备。此外,Objection 提供了命令 ios keychain dump 来实现类似用途。

存储凭证

NSURLCredential 类非常适合将敏感信息直接保存在钥匙串中,避免使用 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 开始,用户可以安装自定义键盘扩展,可在 设置 > 通用 > 键盘 > 键盘 中管理。虽然这些键盘提供了扩展功能,但它们可能记录击键并将数据传输到外部服务器,尽管当键盘需要网络访问时会通知用户。应用可以且应当在敏感信息输入时限制使用自定义键盘。

安全建议:

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

防止文本字段缓存

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

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

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

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

日志

调试代码通常涉及使用 日志记录。存在风险,因为 日志可能包含敏感信息。在 iOS 6 及更早版本中,日志对所有应用可访问,这会导致敏感数据泄露的风险。现在,应用只能访问自身的日志

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

为降低风险,建议全面使用应用,探索其所有功能和输入,以确保不会无意间将敏感信息记录到日志中。

在审查应用源代码以查找潜在泄漏时,请查找 预定义自定义的日志语句,例如内置函数关键词 NSLog, NSAssert, NSCAssert, fprintf,以及出现 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 按钮在新窗口查看日志。

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

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

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

备份

自动备份功能 集成在 iOS 中,支持通过 iTunes(至 macOS Catalina)、Finder(自 macOS Catalina 起)或 iCloud 创建设备数据副本。这些备份包含几乎所有设备数据,但不包括 Apple Pay 详情和 Touch ID 配置等高度敏感的内容。

安全风险

备份中包含 installed apps and their data 会带来潜在的 数据泄漏 问题,并且 备份被修改可能会改变应用行为 的风险。建议 不要以明文形式在任何 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>

For dealing with encrypted backups, Python scripts available in DinoSec’s GitHub repo, like backup_tool.py and backup_passwd.py, may be useful, albeit potentially requiring adjustments for compatibility with the latest iTunes/Finder versions. The iOSbackup tool is another option for accessing files within password-protected backups.

Modifying App Behavior

An example of altering app behavior through backup modifications is demonstrated in the Bither bitcoin wallet app, where the UI lock PIN is stored within net.bither.plist under the pin_code key. Removing this key from the plist and restoring the backup removes the PIN requirement, providing unrestricted access.

Summary on Memory Testing for Sensitive Data

当处理存储在应用内存中的敏感信息时,关键是要尽量缩短这些数据的暴露时间。调查内存内容有两种主要方法:creating a memory dumpanalyzing the memory in real time。两种方法都有各自的挑战,包括在转储或分析过程中可能会错过关键数据。

Retrieving and Analyzing a 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]> /?
...

Runtime Memory Analysis

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

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

加密缺陷

密钥管理流程不当

有些开发者将敏感数据保存在本地存储中,并使用在代码中硬编码/可预测的密钥进行加密。这种做法不应该这样做,因为通过逆向工程,攻击者可能提取出机密信息。

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

开发者不应使用 已弃用的算法 来执行授权 检查存储发送 数据。其中一些算法包括:RC4, MD4, MD5, SHA1… 例如,如果使用 hashes 来存储密码,应使用具有抗 brute-force 能力的算法,并配合 salt。

检查

主要的检查包括查找代码中是否存在 硬编码 的密码/secrets,或这些是否 可预测,以及代码是否使用某种 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 为开发者分别提供了用于显示用户认证对话框和安全处理机密数据的强大 API。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 会向用户显示身份验证对话框,并根据是否提供了合适的指纹返回数据或 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
}

检测

也可以通过分析应用二进制文件的共享动态库列表来检测应用中使用的框架。可以使用 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

通过 Objection Biometrics Bypass,位于 this GitHub page,提供了一种绕过 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 脚本并绕过生物识别认证,可使用以下命令:

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

序列化和编码

iOS Serialisation and Encoding

网络通信

需要确认没有发生未加密的通信,并且应用正确地验证服务器的 TLS 证书
要检查这类问题,可以使用像 Burp 这样的代理:

iOS Burp Suite Configuration

主机名检查

验证 TLS 证书时一个常见的问题是确认证书是否由一个受信任的 CA 签发,但没有检查证书的主机名是否与正在访问的主机名匹配。
为了使用 Burp 检查此问题,在 iPhone 上信任 Burp CA 之后,你可以用 Burp 为不同的主机名创建一个新的证书并使用它。如果应用仍然工作,那么它就存在漏洞。

Certificate Pinning

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

你也可以使用 objection’s ios 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

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

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

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

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

otool -L <application_path>

有趣的漏洞与案例研究

Air Keyboard Remote Input Injection

Itunesstored Bookassetd Sandbox Escape

参考资料与更多资源

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