簡介
隨著 Xcode 7 的發(fā)布,蘋果為 Xcode 增加了一個新的特性 Bitcode [1]:
新的特性往往意味著新的攻擊面。本文首先介紹什么是 Bitcode 及 Bitcode 相關(guān)的工作流程,在熟悉了 Bitcode 的工作流程后,接下來是評估 Bitcode 相關(guān)的攻擊面,最后介紹針對各個攻擊面的測試方法及目前的測試結(jié)果。
什么是 Bitcode
簡單來說,Bitcode 是 LLVM-IR 在磁盤上的一種二進(jìn)制表示形式。關(guān)于 Bitcode 詳細(xì)描述,請參考[2],這里會用例子來讓大家對 Bitcode 有個感性認(rèn)識。
先寫一個簡單的 C 程序,功能是計(jì)算兩個數(shù)的和,代碼如下:
int add(int a, int b) { int c = a + b; return c; }將如上程序保存為 add.c,然后我們將源程序編譯成 Bitcode:
clang -emit-llvm -c add.c -o add.bc
執(zhí)行如上命令會生成 add.bc,我們使用二進(jìn)制編輯器打開生成的文件,查看文件內(nèi)容:
由于 Bitcode 是 LLVM-IR 的二進(jìn)制表示形式,如上圖,在不了解編碼方式的前提下基本不可讀。下面我們把 Bitcode 轉(zhuǎn)換成文本形式:
llvm-dis add.bc -o add.ll用文本編輯器打開 add.ll,可以看到 add 函數(shù)的 LLVM-IR 內(nèi)容如下:
; ModuleID = 'add.bc' target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64-apple-macosx10.11.0" ; Function Attrs: nounwind ssp uwtable ; 如下是 add() 對應(yīng)的 LLVM-IR ; 可以注意到這種表示形式會申請很多變量, ; 感興趣的同學(xué)可以了解下 Static Single Assignment (SSA) define i32 @add(i32 %a, i32 %b) #0 { %1 = alloca i32, align 4 ; 變量 1,4 字節(jié)空間,后續(xù)用來存放參數(shù) a %2 = alloca i32, align 4 ; 變量 2,4 字節(jié)空間,后續(xù)用來存放參數(shù) b %c = alloca i32, align 4 ; 變量 c,4 字節(jié)空間,后續(xù)用來存放結(jié)果 c store i32 %a, i32* %1, align 4 ; 將 a 保存到變量 1 中 store i32 %b, i32* %2, align 4 ; 將 b 保存到變量 2 中 %3 = load i32, i32* %1, align 4 ; 將立即數(shù) 1 保存到變量 3 中 %4 = load i32, i32* %2, align 4 ; 將立即數(shù) 2 保存到變量 4 中 %5 = add nsw i32 %3, %4 ; 將變量 3 與變量 4 的和保存到變量 5 中 store i32 %5, i32* %c, align 4 ; 將變量 5 保存到結(jié)果 c 中 %6 = load i32, i32* %c, align 4 ; 將結(jié)果 c 保存到變量 6 中 ret i32 %6 ; 返回變量 6 }
對比源碼與已經(jīng)注釋過的 add() 函數(shù)的 LLVM-IR 表示,大家應(yīng)該對 LLVM-IR 有個感性認(rèn)識了,下面我們一起看下 Bitcode 的工作流程。
工作流程
蘋果關(guān)于工作流程的描述:
“ 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.”
如上的工作流程可以分為兩個階段:
1.在將應(yīng)用上傳到 AppStore 時,Xcode 會將程序?qū)?yīng)的 Bitcode 一起上傳。 2.AppStore 會將 Bitcode 重新編譯為可執(zhí)行程序,供用戶下載。
下面會將 Bitcode 相關(guān)的完整的工作流程分解為如下幾個問題或子過程并分別做說明:
1.Where is the Bitcode? 2.嵌入 Bitcode 的方法 3.從 Bitcode 生成可執(zhí)行程序的方法
Where is the Bitcode?
參考蘋果的描述,只有在 Archive 時才會生成 Bitcode,于是建立了一個測試工程:
執(zhí)行 Archive,然后查看生成的包結(jié)構(gòu):
經(jīng)過分析在如上的目錄中并沒有直接找到 Bitcode,接下來檢查生成的 MachO。使用 MachOView 加載生成的 MachO,結(jié)果如下圖:
從上圖可以看到最終的可執(zhí)行程序中多了 LLVM 相關(guān)的 Segment 與 Section。繼續(xù)查看對應(yīng)的 Section 的信息:
如上圖,Section __bundle 中保存的是一個 xar 文檔,提取出 xar 文檔,然后使用如下命令解開文檔:
解開:xar -x -f XXX.xar
解開后,可以看到 Bitcode 文件。
總結(jié):程序?qū)?yīng)的 Bitcode 被 Xcode 打包成 xar 文檔,嵌入的 MachO 中。
下面我們看下在 MachO 中嵌入 Bitcode 的方法。
嵌入 Bitcode 的方法
方法一
通過對比 Archive 與非 Archive 時的編譯參數(shù),發(fā)現(xiàn)只要在如下圖所示的位置添加編譯參數(shù):-fembed-bitcode,即可讓 Xcode 普通編譯時也在 MachO 中嵌入 Bitcode:
方法二
方法一雖然很方便,但是 IDE 做了太多工作,不便于理解具體過程,接下來我們自己編譯可執(zhí)行文件。從源代碼生成可執(zhí)行程序主要分為:編譯、鏈接兩個過程,為了控制這兩個過程,下面會講解 Makefile 的配置,及這兩個過程用到的參數(shù)。
在使用 Makefile 編譯 iOS 程序時,有些通用的配置,如下的通用配置,供大家參考:
SDK_iOS := $(shell xcodebuild -version -sdk iphoneos Path) CC_iOS := $(shell xcrun --sdk iphoneos --find clang) LD_iOS := $(CC_iOS) SYS_ROOT = -isysroot $(SDK_iOS) SDK_SETTINGS_iOS = $(SYS_ROOT) -I$(SDK_iOS)/usr/include -I$(SDK_iOS)/usr/local/include MIN_VER_iOS = -miphoneos-version-min=8.0 ARCH_iOS = -arch arm64以 main.m 為例說明編譯需要的參數(shù):
CC_FLAGS_COMMON = -fblocks -std=gnu99 -fobjc-arc -g -fembed-bitcode CC_FLAGS=-x objective-c $(ARCH_iOS) $(CC_FLAGS_COMMON) COMPILE_iOS_OBJ=$(CC_iOS) $(MIN_VER_iOS) $(SDK_SETTINGS_iOS) $(CC_FLAGS) $(COMPILE_iOS_OBJ) -c main.m -o main.o將 main.o,AppDelegate.o,ViewController.o 鏈接成可執(zhí)行程序的參數(shù):
LDFLAGS=$(SYS_ROOT) \ -dead_strip \ -fembed-bitcode \ -fobjc-arc -fobjc-link-runtime LINK_iOS_BIN=$(LD_iOS) $(ARCH_iOS) $(MIN_VER_iOS) $(LDFLAGS) LDFLAGS_CUSTOM=-framework Foundation -framework UIKit $(LINK_iOS_BIN) $(LDFLAGS_CUSTOM) AppDelegate.o ViewController.o main.o -o XBCTest
大家把如上的 Makefile 片段稍加修改,整理到一個 Makefile 文件中,就可以通過 make 命令嵌入 Bitcode 到可執(zhí)行程序。
方法三
在這個方法中我們會將上面的步驟進(jìn)一步分解,具體過程為:
源碼→Bitcode→xar→可執(zhí)行程序
源碼→Bitcode
在這個過程中我們將 iOS 應(yīng)用的源碼編譯成 Bitcode,下面會 main.m 為例來說明使用的參數(shù):
CC_FLAGS_COMMON_BC = $(CC_FLAGS_COMMON) COMPILE_iOS_32_BC = $(CC_iOS) -cc1 -x objective-c $(CC_FLAGS_COMMON_BC) -triple thumbv7-apple-ios8.0.0 -disable-llvm-optzns -target-abi apcs-gnu -mfloat-abi soft $(SYS_ROOT) COMPILE_iOS_64_BC = $(CC_iOS) -cc1 -x objective-c $(CC_FLAGS_COMMON_BC) -triple arm64-apple-ios8.0.0 -disable-llvm-optzns -target-abi darwinpcs $(SYS_ROOT) $(COMPILE_iOS_64_BC) -emit-llvm-bc main.m -o main.bc
完成這個過程后,我們可以得到三個Bitcode 文件:
1.main.bc
2.AppDelegate.bc
3.ViewController.bc
Bitcode→xar
在這一步我們會將如上得到的三個 Bitcode 文件打包到一個 xar 文檔中。打包沒什么特別,需要注意的是需要與 Xcode 生成的 xar 保持兼容,具體參數(shù)如下:
生成:xar –toc-cksum none -c -f BC.xar main.bc AppDelegate.bc ViewController.bc
xar→可執(zhí)行程序
為了簡化過程,這里我們會跳出 Makefile,使用 Xcode,首先清除如下的編譯參數(shù):
將剛剛生成的 BC.xar 拷貝到測試工程的根目錄:
編輯工程設(shè)置的 Other Linker Flags,添加:-Wl,-sectcreate,__LLVM,__bundle,$(SRCROOT)/BC.xar,如下圖:
編譯程序,查看生成的 MachO 文件,可以看到 Bitcode 已經(jīng)被添加到了 MachO 中。
如上我們介紹了在 MachO 中嵌入 Bitcode 的方法,對應(yīng)的是第一個過程:“在將應(yīng)用上傳到 AppStore 時,Xcode 會將程序?qū)?yīng)的 Bitcode 一起上傳”,下面我們說明第二個過程。
從 Bitcode 生成可執(zhí)行程序的方法
第二個過程為:“AppStore 會將 Bitcode 重新編譯為可執(zhí)行程序,供用戶下載”。第二個過程是在蘋果的 Server 上進(jìn)行的,我們沒法直接獲得細(xì)節(jié),但是應(yīng)該都是基于相同的工具鏈,我們可以模擬這個過程。
從 MachO 中提取 Bitcode
AppStore 拿到我們上傳的 IPA 后,首先需要從 IPA 內(nèi)的 MachO 文件中提取出包含 Bitcode 的 xar 文檔。在 Xcode 的工具鏈中有個工具 segedit 可以用來從 MachO 提取 Section,提取 xar 的具體參數(shù)如下:
segedit ./XBCTest -extract "__LLVM" "__bundle" Embedded-BC.xar提取到 xar 后,解開 xar:
解開:xar -x -f Embedded-BC.xar
得到如下幾個 Bitcode 文件:
還可以使用 llvm-dis 工具將如上文件處理成可讀形式,從而了解每個文件的內(nèi)容。
生成可執(zhí)行程序
在有了 Bitcode 后,接下來需要將 Bitcode 編譯成可執(zhí)行程序,分為兩個過程:將 Bitcode 編譯成 Object 文件;鏈接 Object 文件到可執(zhí)行程序。
將 Bitcode 編譯成 Object 文件
Makefile 片段如下:
COMPILE_iOS_BC_2_OBJ=$(CC_iOS) $(MIN_VER_iOS) $(SYS_ROOT) $(ARCH_iOS) $(COMPILE_iOS_BC_2_OBJ) -c 1 -o 1.o $(COMPILE_iOS_BC_2_OBJ) -c 2 -o 2.o $(COMPILE_iOS_BC_2_OBJ) -c 3 -o 3.o $(COMPILE_iOS_BC_2_OBJ) -c 4 -o 4.o
鏈接 Object 文件到可執(zhí)行程序
Makefile 片段如下:
LDFLAGS=$(SYS_ROOT) \ -dead_strip \ -fobjc-arc -fobjc-link-runtime LINK_iOS_BIN=$(LD_iOS) $(ARCH_iOS) $(MIN_VER_iOS) $(LDFLAGS) LDFLAGS_CUSTOM=-framework Foundation -framework UIKit $(LINK_iOS_BIN) $(LDFLAGS_CUSTOM) 1.o 2.o 3.o 4.o -o XBCTest
如上我們已經(jīng)從 Bitcode 重新生成了可執(zhí)行程序 XBCTest。
攻擊面
我們先回顧下 Bitcode 在本地的工作流程:Xcode 將嵌入了 Bitcode 的 MachO 上傳到 AppStore。通過分析可以發(fā)現(xiàn)這里存在兩個問題:
1.MachO 與其中嵌入的 Bitcode 的一致性問題。即:能否把 程序B 的 Bitcode 嵌入到 程序A中。
2.AppStore 是否信任了 Xcode,而沒有檢查一致性問題,從而允許將 Malformed MachO 上傳到 AppStore。
在分析了可能存在的問題后,我們認(rèn)為如果 Bitcode 流程與功能存在缺陷,便可以對兩個目標(biāo)形成威脅:普通用戶、蘋果。
普通用戶
由于 Bitcode 對普通用戶是透明的,因此無法通過其弱點(diǎn)直接攻擊用戶。但是一致性問題是可能對普通用戶造成威脅的,試想:如果提交 AppStore 審核的 程序A 中嵌入了含有惡意代碼的Bitcode,普通用戶就有可能從AppStore 下載到含有惡意代碼的程序。
對于這種攻擊方式我們將其叫做 Bitcode Injection,下文會詳細(xì)介紹這種攻擊的實(shí)施方法,及我們的測試結(jié)果。
蘋果
如果 Malformed MachO 可以被上傳到蘋果的服務(wù)器,蘋果的服務(wù)器相較于之前,主要需要進(jìn)行兩個額外的操作:解開 xar;編譯 Bitcode。如果這兩個過程出現(xiàn)問題,輕則可以在蘋果的服務(wù)器上造成 DoS,重則可以在蘋果的服務(wù)器上造成任意代碼執(zhí)行。
另外,Bitcode 原本是 LLVM-IR 的一種序列化形式,而 LLVM-IR 是一種中間形式,之前沒有被直接暴露出來,現(xiàn)在完全開放了,而且又是二進(jìn)制格式,這是很容易出問題的。從 Bitcode 生成可執(zhí)行文件的過程主要由如下幾個子過程組成:
1.基于平臺無關(guān)的 IR的代碼優(yōu)化。
2.IR的平臺相關(guān)化、合法化。
3.平臺相關(guān)的優(yōu)化、代碼生成。
這些原本是編譯器的內(nèi)部過程,由于各種原因,傳統(tǒng)的對編譯器的測試主要集中在前端的 Parser 與 Lexer,現(xiàn)在借由 Bitcode 如上的一些中間或者后端過程也暴露了出來,如果如上的過程出現(xiàn)問題最糟糕的結(jié)果是可以控制編譯器的指令生成。
以上是關(guān)于攻擊面的分析,后文會介紹測試 xar 及 Bitcode 的思路,以及發(fā)現(xiàn)的問題。
Bitcode Injection
上文在介紹 Bitcode 工作流程時已經(jīng)介紹了實(shí)施 Bitcode Injection 的方法,但是上面提到的方法不夠簡練,這里我們再介紹一種更簡單的方法,主要的思路就是最大限度的利用 Xcode,這個方法的具體實(shí)施步驟為:
1. 用 Xcode 建立工程XBCTest:
2. 復(fù)制工程XBCTest,得到工程XBCTest2:
3. 修改工程XBCTest2的源碼,嵌入惡意代碼:
4. Archive 工程XBCTest2:
5. 獲得 MachO,利用 segedit 從 MachO 中提取出含有 Bitcode 的 xar:
提取xar: segedit ./XBCTest -extract "__LLVM" "__bundle" BC.xar
6. 修改工程XBCTest的鏈接標(biāo)記,將提取的 xar: BC.xar 嵌入到工程XBCTest的 MachO文件中。
7. 禁用工程XBCTest的 Bitcode 特性,Archive 并上傳 AppStore:
我們在測試的過程中并沒有嵌入惡意代碼,而是從網(wǎng)上找個兩個完全不同的應(yīng)用,將其中一個的 Bitcode 嵌入到另一個的 MachO 中,并提交到AppStore。
在將應(yīng)用提交到 AppStore 的過程中主要會進(jìn)行兩個方面的檢查:Xcode 在本地進(jìn)行靜態(tài)分析;提交后,蘋果的服務(wù)器還會進(jìn)行檢查。但是使用 Bitcode Injection 構(gòu)造的應(yīng)用可以通過這兩項(xiàng)檢查:
經(jīng)過漫長的審核后,我們的應(yīng)用被拒了,理由是:我們的應(yīng)用與描述不符。在描述中我們的應(yīng)用應(yīng)該長成如下樣子:
但是蘋果的審核人員安裝后,程序卻長成這個樣子:
這至少可以說明三個問題:
1.我們使用的 Bitcode Injection 方法沒有問題。 2.蘋果的審核人員審核的是從 Bitcode 編譯出來的程序。 3.一致性是靠人肉區(qū)分的。如果嵌入對 UI 沒有影響的惡意代碼,還是有可能繞過審核的。
測試xar
思路
對 xar 進(jìn)行模糊測試,生成數(shù)據(jù)的方法是基于標(biāo)準(zhǔn)的 xar 文檔進(jìn)行變異。
測試結(jié)果
目前主要 Fuzz 出一些空指針解引用問題。
測試 clang
思路
對 clang 中的Bitcode 到 Object 的功能進(jìn)行模糊測試,也是采用變異的方法生成測試數(shù)據(jù)。
測試結(jié)果
通過對 clang 的 Fuzz 我們發(fā)現(xiàn)了一些堆損壞相關(guān)的問題。
總結(jié)
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. 在上文中我們詳細(xì)介紹了所考慮到的攻擊面,及針對每個攻擊面的測試思路,希望這些對大家研究 Bitcode 相關(guān)的攻擊面及安全性有幫助。
參考資料
[1] What’s New in Xcode
[2] LLVM Bitcode File Format
*作者:360NirvanTeam(企業(yè)賬號),轉(zhuǎn)載請注明來自FreeBuf黑客與極客(FreeBuf.COM)