簡介
隨著 Xcode 7 的發布,蘋果為 Xcode 增加了一個新的特性 Bitcode [1]:
新的特性往往意味著新的攻擊面。本文首先介紹什麼是 Bitcode 及 Bitcode 相關的工作流程,在熟悉了 Bitcode 的工作流程後,接下來是評估 Bitcode 相關的攻擊面,最後介紹針對各個攻擊面的測試方法及目前的測試結果。
什麼是 Bitcode
簡單來說,Bitcode 是 LLVM-IR 在磁盤上的一種二進制表示形式。關於 Bitcode 詳細描述,請參考[2],這裡會用例子來讓大家對 Bitcode 有個感性認識。
先寫一個簡單的 C 程序,功能是計算兩個數的和,代碼如下:
int add(int a, int b) { int c = a + b; return c; }
將如上程序保存為 add.c,然後我們將源程序編譯成 Bitcode:
clang -emit-llvm -c add.c -o add.bc
執行如上命令會生成 add.bc,我們使用二進制編輯器打開生成的文件,查看文件內容:
由於 Bitcode 是 LLVM-IR 的二進制表示形式,如上圖,在不了解編碼方式的前提下基本不可讀。下面我們把 Bitcode 轉換成文本形式:
llvm-dis add.bc -o add.ll
用文本編輯器打開 add.ll,可以看到 add 函數的 LLVM-IR 內容如下:
對比源碼與已經注釋過的 add() 函數的 LLVM-IR 表示,大家應該對 LLVM-IR 有個感性認識了,下面我們一起看下 Bitcode 的工作流程。
工作流程
蘋果關於工作流程的描述:
“ When you archive for submission to the App Store, Xcode compiles your app into an intermediate representation. The App Store then compiles the bitcode down into the 64- or 32-bit executables as necessary.”
如上的工作流程可以分為兩個階段:
在將應用上傳到 AppStore 時,Xcode 會將程序對應的 Bitcode 一起上傳。
AppStore 會將 Bitcode 重新編譯為可執行程序,供用戶下載。
下面會將 Bitcode 相關的完整的工作流程分解為如下幾個問題或子過程並分別做說明:
Where is the Bitcode?
嵌入 Bitcode 的方法
從 Bitcode 生成可執行程序的方法
Where is the Bitcode?
參考蘋果的描述,只有在 Archive 時才會生成 Bitcode,於是建立了一個測試工程:
執行 Archive,然後查看生成的包結構:
經過分析在如上的目錄中並沒有直接找到 Bitcode,接下來檢查生成的 MachO。使用 MachOView 加載生成的 MachO,結果如下圖:
從上圖可以看到最終的可執行程序中多了 LLVM 相關的 Segment 與 Section。繼續查看對應的 Section 的信息:
如上圖,Section __bundle 中保存的是一個 xar 文檔,提取出 xar 文檔,然後使用如下命令解開文檔:
解開:xar -x -f XXX.xar
解開後,可以看到 Bitcode 文件。
總結:程序對應的 Bitcode 被 Xcode 打包成 xar 文檔,嵌入的 MachO 中。
下面我們看下在 MachO 中嵌入 Bitcode 的方法。
嵌入 Bitcode 的方法
方法一
通過對比 Archive 與非 Archive 時的編譯參數,發現只要在如下圖所示的位置添加編譯參數:-fembed-bitcode,即可讓 Xcode 普通編譯時也在 MachO 中嵌入 Bitcode:
方法二
方法一雖然很方便,但是 IDE 做了太多工作,不便於理解具體過程,接下來我們自己編譯可執行文件。從源代碼生成可執行程序主要分為:編譯、鏈接兩個過程,為了控制這兩個過程,下面會講解 Makefile 的配置,及這兩個過程用到的參數。
在使用 Makefile 編譯 iOS 程序時,有些通用的配置,如下的通用配置,供大家參考:
以 main.m 為例說明編譯需要的參數:
將 main.o,AppDelegate.o,ViewController.o 鏈接成可執行程序的參數:
大家把如上的 Makefile 片段稍加修改,整理到一個 Makefile 文件中,就可以通過 make 命令嵌入 Bitcode 到可執行程序。
方法三
在這個方法中我們會將上面的步驟進一步分解,具體過程為:
源碼→Bitcode→xar→可執行程序
源碼→Bitcode
在這個過程中我們將 iOS 應用的源碼編譯成 Bitcode,下面會 main.m 為例來說明使用的參數:
完成這個過程後,我們可以得到三個Bitcode 文件:
main.bc
AppDelegate.bc
ViewController.bc
Bitcode→xar
在這一步我們會將如上得到的三個 Bitcode 文件打包到一個 xar 文檔中。打包沒什麼特別,需要注意的是需要與 Xcode 生成的 xar 保持兼容,具體參數如下:
生成:xar –toc-cksum none -c -f BC.xar main.bc AppDelegate.bc ViewController.bc
xar→可執行程序
為了簡化過程,這裡我們會跳出 Makefile,使用 Xcode,首先清除如下的編譯參數:
將剛剛生成的 BC.xar 拷貝到測試工程的根目錄:
編輯工程設置的 Other Linker Flags,添加:-Wl,-sectcreate,__LLVM,__bundle,$(SRCROOT)/BC.xar,如下圖:
編譯程序,查看生成的 MachO 文件,可以看到 Bitcode 已經被添加到了 MachO 中。
如上我們介紹了在 MachO 中嵌入 Bitcode 的方法,對應的是第一個過程:“在將應用上傳到 AppStore 時,Xcode 會將程序對應的 Bitcode 一起上傳”,下面我們說明第二個過程。
從 Bitcode 生成可執行程序的方法
第二個過程為:“AppStore 會將 Bitcode 重新編譯為可執行程序,供用戶下載”。第二個過程是在蘋果的 Server 上進行的,我們沒法直接獲得細節,但是應該都是基於相同的工具鏈,我們可以模擬這個過程。
從 MachO 中提取 Bitcode
AppStore 拿到我們上傳的 IPA 後,首先需要從 IPA 內的 MachO 文件中提取出包含 Bitcode 的 xar 文檔。在 Xcode 的工具鏈中有個工具 segedit 可以用來從 MachO 提取 Section,提取 xar 的具體參數如下:
提取到 xar 後,解開 xar:
得到如下幾個 Bitcode 文件:
還可以使用 llvm-dis 工具將如上文件處理成可讀形式,從而了解每個文件的內容。
生成可執行程序
在有了 Bitcode 後,接下來需要將 Bitcode 編譯成可執行程序,分為兩個過程:將 Bitcode 編譯成 Object 文件;鏈接 Object 文件到可執行程序。
將 Bitcode 編譯成 Object 文件
Makefile 片段如下:
鏈接 Object 文件到可執行程序
如上我們已經從 Bitcode 重新生成了可執行程序 XBCTest。
攻擊面
我們先回顧下 Bitcode 在本地的工作流程:Xcode 將嵌入了 Bitcode 的 MachO 上傳到 AppStore。通過分析可以發現這裡存在兩個問題:
1.MachO 與其中嵌入的 Bitcode 的一致性問題。即:能否把 程序B 的 Bitcode 嵌入到 程序A中。
2.AppStore 是否信任了 Xcode,而沒有檢查一致性問題,從而允許將 Malformed MachO 上傳到 AppStore。
在分析了可能存在的問題後,我們認為如果 Bitcode 流程與功能存在缺陷,便可以對兩個目標形成威脅:普通用戶、蘋果。
普通用戶
由於 Bitcode 對普通用戶是透明的,因此無法通過其弱點直接攻擊用戶。但是一致性問題是可能對普通用戶造成威脅的,試想:如果提交 AppStore 審核的 程序A 中嵌入了含有惡意代碼的Bitcode,普通用戶就有可能從AppStore 下載到含有惡意代碼的程序。
對於這種攻擊方式我們將其叫做 Bitcode Injection,下文會詳細介紹這種攻擊的實施方法,及我們的測試結果。
蘋果
如果 Malformed MachO 可以被上傳到蘋果的服務器,蘋果的服務器相較於之前,主要需要進行兩個額外的操作:解開 xar;編譯 Bitcode。如果這兩個過程出現問題,輕則可以在蘋果的服務器上造成 DoS,重則可以在蘋果的服務器上造成任意代碼執行。
另外,Bitcode 原本是 LLVM-IR 的一種序列化形式,而 LLVM-IR 是一種中間形式,之前沒有被直接暴露出來,現在完全開放了,而且又是二進制格式,這是很容易出問題的。從 Bitcode 生成可執行文件的過程主要由如下幾個子過程組成:
1.基於平台無關的 IR的代碼優化。
2.IR的平台相關化、合法化。
3.平台相關的優化、代碼生成。
這些原本是編譯器的內部過程,由於各種原因,傳統的對編譯器的測試主要集中在前端的 Parser 與 Lexer,現在借由 Bitcode 如上的一些中間或者後端過程也暴露了出來,如果如上的過程出現問題最糟糕的結果是可以控制編譯器的指令生成。
以上是關於攻擊面的分析,後文會介紹測試 xar 及 Bitcode 的思路,以及發現的問題。
Bitcode Injection
上文在介紹 Bitcode 工作流程時已經介紹了實施 Bitcode Injection 的方法,但是上面提到的方法不夠簡練,這裡我們再介紹一種更簡單的方法,主要的思路就是最大限度的利用 Xcode,這個方法的具體實施步驟為:
用 Xcode 建立工程XBCTest:
2. 復制工程XBCTest,得到工程XBCTest2:
3. 修改工程XBCTest2的源碼,嵌入惡意代碼:
4. Archive 工程XBCTest2:
5. 獲得 MachO,利用 segedit 從 MachO 中提取出含有 Bitcode 的 xar:
提取xar: segedit ./XBCTest -extract "__LLVM" "__bundle" BC.xar
6. 修改工程XBCTest的鏈接標記,將提取的 xar: BC.xar 嵌入到工程XBCTest的 MachO文件中。
7. 禁用工程XBCTest的 Bitcode 特性,Archive 並上傳 AppStore:
我們在測試的過程中並沒有嵌入惡意代碼,而是從網上找個兩個完全不同的應用,將其中一個的 Bitcode 嵌入到另一個的 MachO 中,並提交到AppStore。
在將應用提交到 AppStore 的過程中主要會進行兩個方面的檢查:Xcode 在本地進行靜態分析;提交後,蘋果的服務器還會進行檢查。但是使用 Bitcode Injection 構造的應用可以通過這兩項檢查:
經過漫長的審核後,我們的應用被拒了,理由是:我們的應用與描述不符。在描述中我們的應用應該長成如下樣子:
但是蘋果的審核人員安裝後,程序卻長成這個樣子:
這至少可以說明三個問題:
1.我們使用的 Bitcode Injection 方法沒有問題。
2.蘋果的審核人員審核的是從 Bitcode 編譯出來的程序。
3.一致性是靠人肉區分的。如果嵌入對 UI 沒有影響的惡意代碼,還是有可能繞過審核的。
測試xar
思路
對 xar 進行模糊測試,生成數據的方法是基於標准的 xar 文檔進行變異。
測試結果
目前主要 Fuzz 出一些空指針解引用問題。
測試 clang
思路
對 clang 中的Bitcode 到 Object 的功能進行模糊測試,也是采用變異的方法生成測試數據。
測試結果
通過對 clang 的 Fuzz 我們發現了一些堆損壞相關的問題。
總結
1. The Xcode 7 bitcode feature has opened a huge attacking surface, Apple should do something to narrow it, for example: checking the bitcode is identical to the related MachO file.
2. 在上文中我們詳細介紹了所考慮到的攻擊面,及針對每個攻擊面的測試思路,希望這些對大家研究 Bitcode 相關的攻擊面及安全性有幫助。
參考資料
[1] What’s New in Xcode
[2] LLVM Bitcode File Format
作者:360NirvanTeam(企業賬號)