iOS Pentesting

使用 Trickest 轻松构建和 自动化工作流程,由世界上 最先进 的社区工具提供支持。 立即获取访问权限:

学习和实践 AWS 黑客技术:HackTricks 培训 AWS 红队专家 (ARTE) 学习和实践 GCP 黑客技术: HackTricks 培训 GCP 红队专家 (GRTE)

支持 HackTricks

iOS 基础

iOS Basics

测试环境

在此页面中,您可以找到有关 iOS 模拟器仿真器越狱的信息:

iOS Testing Environment

初步分析

基本 iOS 测试操作

在测试过程中 将建议进行几项操作(连接设备、读/写/上传/下载文件、使用一些工具...)。因此,如果您不知道如何执行这些操作,请 开始阅读页面

iOS Basic Testing Operations

对于以下步骤 应用程序应已安装 在设备上,并且应已获得 IPA 文件。 阅读 基本 iOS 测试操作 页面以了解如何做到这一点。

基本静态分析

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

识别 二进制文件中存在的保护

  • PIE(位置无关可执行文件):启用时,应用程序每次启动时加载到随机内存地址,使其初始内存地址更难预测。

otool -hv <app-binary> | grep PIE   # 应该包含 PIE 标志
  • 栈金丝雀:为了验证栈的完整性,在调用函数之前将一个“金丝雀”值放置在栈上,并在函数结束时再次验证。

otool -I -v <app-binary> | grep stack_chk   # 应该包含符号:stack_chk_guard 和 stack_chk_fail
  • ARC(自动引用计数):防止常见的内存损坏缺陷

otool -I -v <app-binary> | grep objc_release   # 应该包含 _objc_release 符号
  • 加密二进制文件:二进制文件应被加密

otool -arch all -Vl <app-binary> | grep -A5 LC_ENCRYPT   # cryptid 应该为 1

识别敏感/不安全函数

  • 弱哈希算法

# 在 iOS 设备上
otool -Iv <app> | grep -w "_CC_MD5"
otool -Iv <app> | grep -w "_CC_SHA1"

# 在 Linux 上
grep -iER "_CC_MD5"
grep -iER "_CC_SHA1"
  • 不安全的随机函数

# 在 iOS 设备上
otool -Iv <app> | grep -w "_random"
otool -Iv <app> | grep -w "_srand"
otool -Iv <app> | grep -w "_rand"

# 在 Linux 上
grep -iER "_random"
grep -iER "_srand"
grep -iER "_rand"
  • 不安全的 ‘Malloc’ 函数

# 在 iOS 设备上
otool -Iv <app> | grep -w "_malloc"

# 在 Linux 上
grep -iER "_malloc"
  • 不安全和易受攻击的函数

# 在 iOS 设备上
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"

# 在 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"

基本动态分析

查看 MobSF 执行的动态分析。您需要浏览不同的视图并与之互动,但它将在执行其他操作时挂钩多个类,并在完成后准备报告。

列出已安装的应用程序

使用命令 frida-ps -Uai 确定已安装应用程序的 包标识符

$ 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

基本枚举与钩子

学习如何枚举应用程序的组件以及如何使用objection轻松钩住方法和类

iOS Hooking With Objection

IPA结构

IPA文件的结构本质上是一个压缩包。通过将其扩展名重命名为.zip,可以解压缩以揭示其内容。在此结构中,Bundle表示一个完全打包的应用程序,准备安装。在其中,您会找到一个名为<NAME>.app的目录,封装了应用程序的资源。

  • Info.plist:此文件包含应用程序的特定配置详细信息。

  • _CodeSignature/:此目录包含一个plist文件,包含签名,确保包中所有文件的完整性。

  • Assets.car:一个压缩档案,存储图标等资产文件。

  • Frameworks/:此文件夹包含应用程序的本地库,可能以.dylib.framework文件的形式存在。

  • PlugIns/:这可能包括应用程序的扩展,称为.appex文件,尽管它们并不总是存在。 * Core Data:用于保存应用程序的永久数据以供离线使用,缓存临时数据,并为您的应用在单个设备上添加撤消功能。要在单个iCloud帐户中的多个设备之间同步数据,Core Data会自动将您的架构镜像到CloudKit容器中。

  • PkgInfoPkgInfo文件是指定应用程序或包的类型和创建者代码的另一种方式。

  • en.lproj, fr.proj, Base.lproj:是包含特定语言资源的语言包,以及在不支持某种语言时的默认资源。

  • 安全性_CodeSignature/目录通过数字签名验证所有打包文件的完整性,在应用程序的安全性中发挥关键作用。

  • 资产管理Assets.car文件使用压缩有效管理图形资产,对于优化应用程序性能和减少整体大小至关重要。

  • 框架和插件:这些目录强调了iOS应用程序的模块化,允许开发人员包含可重用的代码库(Frameworks/)并扩展应用程序功能(PlugIns/)。

  • 本地化:该结构支持多种语言,通过包含特定语言包的资源,促进全球应用程序的覆盖。

Info.plist

Info.plist作为iOS应用程序的基石,以键值对的形式封装了关键配置数据。此文件不仅是应用程序的必需品,也是捆绑在内的应用扩展和框架的必需品。它以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)和应用传输安全配置(NSAppTransportSecurity)。这些条目,以及其他如导出/导入的自定义文档类型(UTExportedTypeDeclarations / UTImportedTypeDeclarations),可以通过检查文件或使用简单的grep命令轻松找到:

$ grep -i <keyword> Info.plist

数据路径

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

由于 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

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

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中看到的应用程序包,它包含了基本的应用程序数据、静态内容以及应用程序的编译二进制文件。

  • 该目录对用户可见,但用户无法写入

  • 此目录中的内容不被备份

  • 此文件夹的内容用于验证代码签名

Data directory:

  • Documents/

  • 包含所有用户生成的数据。应用程序最终用户发起此数据的创建。

  • 对用户可见,用户可以写入

  • 此目录中的内容被备份

  • 应用程序可以通过设置NSURLIsExcludedFromBackupKey来禁用路径。

  • Library/

  • 包含所有非用户特定文件,例如缓存偏好设置cookies和属性列表(plist)配置文件。

  • iOS应用程序通常使用Application SupportCaches子目录,但应用程序可以创建自定义子目录。

  • Library/Caches/

  • 包含半持久的缓存文件

  • 对用户不可见,用户无法写入

  • 此目录中的内容不被备份

  • 当应用程序未运行且存储空间不足时,操作系统可能会自动删除此目录的文件。

  • Library/Application Support/

  • 包含运行应用程序所需的持久****文件

  • 用户不可见,用户无法写入。

  • 此目录中的内容被备份

  • 应用程序可以通过设置NSURLIsExcludedFromBackupKey来禁用路径。

  • Library/Preferences/

  • 用于存储即使在应用程序重新启动后也能持久的属性。

  • 信息以未加密的形式保存在应用程序沙箱中的一个名为[BUNDLE_ID].plist的plist文件中。

  • 使用NSUserDefaults存储的所有键/值对都可以在此文件中找到。

  • tmp/

  • 使用此目录来写入临时文件,这些文件在应用程序启动之间不需要持久化。

  • 包含非持久的缓存文件。

  • 对用户不可见

  • 此目录中的内容不被备份。

  • 当应用程序未运行且存储空间不足时,操作系统可能会自动删除此目录的文件。

让我们更仔细地看看iGoat-Swift的应用程序包(.app)目录,位于包目录内(/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

二进制逆向

<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

反汇编二进制文件

反汇编文本段:

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;
};

然而,反汇编二进制文件的最佳选项是:HopperIDA

使用 Trickest 轻松构建和 自动化工作流程,由世界上 最先进 的社区工具提供支持。 今天就获取访问权限:

数据存储

要了解 iOS 如何在设备中存储数据,请阅读此页面:

iOS Basics

以下存储信息的地方应在 安装应用程序后立即 检查,检查应用程序的所有功能后,甚至在 从一个用户注销并登录到另一个用户后。 目标是找到应用程序的 未保护敏感信息(密码、令牌)、当前用户和之前登录用户的信息。

Plist

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

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

NSUserDefaults 类提供了与默认系统交互的编程接口。默认系统允许应用程序根据 用户偏好 自定义其行为。通过 NSUserDefaults 保存的数据可以在应用程序包中查看。此类将 数据 存储在 plist 文件 中,但旨在用于少量数据。

此数据不能再通过受信任的计算机直接访问,但可以通过执行 备份 进行访问。

您可以使用 objection 的 ios nsuserdefaults get转储 使用 NSUserDefaults 保存的信息。

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

find ./ -name "*.plist"

要将文件从 XML 或二进制 (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 可以使用 SQLite 作为其持久存储,但该框架本身不是数据库。 CoreData 默认不加密其数据。然而,可以向 CoreData 添加额外的加密层。有关更多详细信息,请参见 GitHub Repo

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

如果您可以打开 SQLite 并访问敏感信息,那么您发现了一个配置错误。

Code from iGoat
-(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 的键值存储。 由于 Yap 数据库是 sqlite 数据库,您可以使用上一节中提供的命令找到它们。

其他 SQLite 数据库

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

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

Firebase 实时数据库

开发者可以通过 Firebase 实时数据库在 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 文件,您可以使用 这个 python 脚本 或使用 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将数据存储在Cache.db数据库中,例如HTTP请求和响应。如果令牌、用户名或任何其他敏感信息被缓存,则该数据库可能包含敏感数据。要查找缓存的信息,请打开应用的数据目录(/var/mobile/Containers/Data/Application/<UUID>),然后转到/Library/Caches/<Bundle Identifier>WebKit缓存也存储在Cache.db文件中。Objection可以使用命令sqlite connect Cache.db打开并与数据库交互,因为它是一个普通的SQLite数据库

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

  1. 建议在注销后删除缓存的响应。这可以通过Apple提供的方法removeAllCachedResponses来完成。您可以按如下方式调用此方法:

URLCache.shared.removeAllCachedResponses()

此方法将从Cache.db文件中删除所有缓存的请求和响应。 2. 如果您不需要使用cookie的优势,建议仅使用URLSession的.ephemeral配置属性,这将禁用保存cookie和缓存。

Apple文档:

一个短暂的会话配置对象类似于默认的会话配置(见默认),不同之处在于相应的会话对象不会将缓存、凭证存储或任何与会话相关的数据存储到磁盘上。相反,与会话相关的数据存储在RAM中。短暂会话写入数据到磁盘的唯一时间是当您告诉它将URL的内容写入文件时。 3. 通过将缓存策略设置为.notAllowed也可以禁用缓存。这将禁用以任何方式存储缓存,无论是在内存中还是在磁盘上。

Snapshots

每当您按下主屏幕按钮时,iOS 会拍摄当前屏幕的快照,以便能够以更平滑的方式过渡到应用程序。然而,如果当前屏幕上存在敏感 数据,它将被保存图像中(该图像在重启后仍然存在)。这些快照您也可以通过双击主屏幕在应用之间切换访问。

除非iPhone越狱,否则攻击者需要访问未被解锁设备才能查看这些屏幕截图。默认情况下,最后一个快照存储在应用的沙盒中,位于Library/Caches/Snapshots/Library/SplashBoard/Snapshots文件夹中(受信任的计算机无法从iOS 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。它防止敏感数据泄漏,因为 overlayImage.png 将始终覆盖当前视图。

Keychain

要访问和管理 iOS 密钥链,可以使用像 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 协议 提供了管理自动更正和安全文本输入的属性,这对于防止敏感信息缓存至关重要。例如,可以通过禁用自动更正和启用安全文本输入来实现:

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

此外,开发人员应确保文本字段,特别是用于输入敏感信息如密码和 PIN 的字段,通过将 autocorrectionType 设置为 UITextAutocorrectionTypeNosecureTextEntry 设置为 YES 来禁用缓存。

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

日志

调试代码通常涉及使用日志记录。这存在风险,因为日志可能包含敏感信息。在 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 按钮在新窗口中查看日志。

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

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

跟随命令观察日志活动,这对于诊断问题或识别日志中的潜在数据泄漏非常宝贵。


使用 Trickest 轻松构建和 自动化工作流程,由世界上 最先进 的社区工具提供支持。 今天获取访问权限:

备份

自动备份功能 集成在 iOS 中,通过 iTunes(最多到 macOS Catalina)、Finder(从 macOS Catalina 开始)或 iCloud 方便地创建设备数据副本。这些备份几乎涵盖所有设备数据,排除高度敏感的元素,如 Apple Pay 详细信息和 Touch ID 配置。

安全风险

已安装应用及其数据 包含在备份中,提出了潜在 数据泄漏 的问题,以及 备份修改可能改变应用功能 的风险。建议 不要在任何应用的目录或其子目录中以明文存储敏感信息 以降低这些风险。

从备份中排除文件

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

测试漏洞

要评估应用的备份安全性,首先使用 Finder 创建备份,然后根据 Apple 的官方文档 找到它。分析备份中可能影响应用行为的敏感数据或配置。

可以使用命令行工具或像 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>

对于处理加密备份,Python 脚本可在 DinoSec 的 GitHub 仓库 中找到,如 backup_tool.pybackup_passwd.py,可能会有用,但可能需要进行调整以兼容最新的 iTunes/Finder 版本。iOSbackup 工具 是访问密码保护备份中文件的另一种选择。

修改应用行为

通过备份修改来改变应用行为的一个例子是在 Bither 比特币钱包应用 中演示的,其中 UI 锁定 PIN 存储在 net.bither.plistpin_code 键下。将此键从 plist 中删除并恢复备份将移除 PIN 要求,从而提供无限制访问。

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

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

检索和分析内存转储

对于越狱和非越狱设备,工具如 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>

破损的加密技术

不良的密钥管理流程

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

使用不安全和/或过时的算法

开发者不应该使用过时的算法来执行授权检查存储发送数据。这些算法包括:RC4、MD4、MD5、SHA1……如果哈希用于存储密码,例如,应该使用抗暴力破解的哈希,并加盐。

检查

主要的检查是寻找是否可以在代码中找到硬编码的密码/秘密,或者这些是否是可预测的,以及代码是否使用某种加密算法。

有趣的是,你可以使用objection自动监控一些加密****库

ios monitor crypt

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

本地身份验证

本地身份验证 在保护通过加密方法访问远程端点方面发挥着至关重要的作用。关键在于,如果没有正确的实现,本地身份验证机制可能会被绕过。

苹果的 本地身份验证框架钥匙串 为开发者提供了强大的 API,以便分别促进用户身份验证对话框和安全处理秘密数据。安全隔 enclave 保护 Touch ID 的指纹 ID,而 Face ID 则依赖于面部识别而不妥协生物识别数据。

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

  • LocalAuthentication.framework 用于高层次的用户身份验证,而不访问生物识别数据。

  • Security.framework 用于低层次的钥匙串服务访问,通过生物识别身份验证保护秘密数据。各种 开源包装器 使钥匙串访问更简单。

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

实现本地身份验证

为了提示用户进行身份验证,开发者应利用 LAContext 类中的 evaluatePolicy 方法,选择:

  • deviceOwnerAuthentication:提示输入 Touch ID 或设备密码,如果两者都未启用则失败。

  • deviceOwnerAuthenticationWithBiometrics:仅提示输入 Touch ID。

成功的身份验证通过 evaluatePolicy 的布尔返回值指示,突显出潜在的安全缺陷。

使用钥匙串进行本地身份验证

在 iOS 应用中实现 本地身份验证 涉及使用 钥匙串 API 安全存储秘密数据,例如身份验证令牌。此过程确保数据只能由用户访问,使用他们的设备密码或生物识别身份验证,如 Touch ID。

钥匙串提供了设置带有 SecAccessControl 属性的项目的能力,该属性限制对该项目的访问,直到用户通过 Touch ID 或设备密码成功身份验证。此功能对于增强安全性至关重要。

以下是 Swift 和 Objective-C 中的代码示例,演示如何利用这些安全功能将字符串保存到钥匙串并从中检索。示例特别展示了如何设置访问控制以要求 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
}

目标-C

// 1. create AccessControl object that will represent authentication settings
CFErrorRef *err = nil;

SecAccessControlRef sacRef = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
kSecAccessControlUserPresence,
err);

// 2. define keychain services query. Pay attention that kSecAttrAccessControl is mutually exclusive with kSecAttrAccessible attribute
NSDictionary* query = @{
(_ _bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrLabel: @"com.me.myapp.password",
(__bridge id)kSecAttrAccount: @"OWASP Account",
(__bridge id)kSecValueData: [@"test_strong_password" dataUsingEncoding:NSUTF8StringEncoding],
(__bridge id)kSecAttrAccessControl: (__bridge_transfer id)sacRef
};

// 3. save item
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)query, nil);

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

现在我们可以从钥匙串请求保存的项目。钥匙串服务将向用户显示身份验证对话框,并根据是否提供了合适的指纹返回数据或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
}

目标-C

// 1. define query
NSDictionary *query = @{(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecReturnData: @YES,
(__bridge id)kSecAttrAccount: @"My Name1",
(__bridge id)kSecAttrLabel: @"com.me.myapp.password",
(__bridge id)kSecUseOperationPrompt: @"Please, pass authorisation to enter this area" };

// 2. get item
CFTypeRef queryResult = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &queryResult);

if (status == noErr){
NSData* resultData = ( __bridge_transfer NSData* )queryResult;
NSString* password = [[NSString alloc] initWithData:resultData encoding:NSUTF8StringEncoding];
NSLog(@"%@", password);
} else {
NSLog(@"Something went wrong");
}

检测

通过分析应用程序二进制文件的共享动态库列表,也可以检测到应用程序中使用的框架。这可以通过使用 otool 来完成:

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

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

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

如果使用了 Security.framework,则只会显示第二个。

本地身份验证框架绕过

Objection

通过位于 这个 GitHub 页面Objection 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 应用程序evaluatePolicy 使用示例:

+(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检查,拦截其回调以确保返回success=1。通过改变回调的行为,身份验证检查被有效绕过。

下面的脚本被注入以修改evaluatePolicy方法的结果。它将回调的结果更改为始终指示成功。

// 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 暴露

自定义 URI 处理程序 / 深度链接 / 自定义方案

iOS Custom URI Handlers / Deeplinks / Custom Schemes

通用链接

iOS Universal Links

UIActivity 共享

iOS UIActivity Sharing

UIPasteboard

iOS UIPasteboard

应用扩展

iOS App Extensions

WebViews

iOS WebViews

序列化和编码

iOS Serialisation and Encoding

网络通信

重要的是检查是否存在 未加密 的通信,并且应用程序是否正确 验证服务器的 TLS 证书。 要检查这些问题,可以使用像 Burp 这样的代理:

iOS Burp Suite Configuration

主机名检查

验证 TLS 证书的一个常见问题是检查证书是否由 受信任的 CA 签名,但 不检查 证书的主机名 是否是正在访问的主机名。 为了使用 Burp 检查此问题,在 iPhone 中信任 Burp CA 后,可以 为不同的主机名使用 Burp 创建新证书 并使用它。如果应用程序仍然可以正常工作,那么某些地方是脆弱的。

证书钉扎

如果应用程序正确使用 SSL 钉扎,则只有在证书是预期的证书时,应用程序才会正常工作。在测试应用程序时 这可能是一个问题,因为 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/* 中可以找到在将应用程序发送到后台之前对其进行的快照。

热补丁/强制更新

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

第三方

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

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

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

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

otool -L <application_path>

参考资料与更多资源

使用 Trickest 轻松构建和 自动化工作流程,由世界上 最先进 的社区工具提供支持。 今天就获取访问权限:

学习与实践 AWS Hacking:HackTricks 培训 AWS 红队专家 (ARTE) 学习与实践 GCP Hacking: HackTricks 培训 GCP 红队专家 (GRTE)

支持 HackTricks

Last updated