Xcode 7 Bitcode的工作流程及安全性評估

wyzsk發表於2020-08-19
作者: 360NirvanTeam · 2015/12/16 14:42

Author:Proteas of 360 Nirvan Team

0x00 簡介


隨著 Xcode 7 的釋出,蘋果為 Xcode增加了一個新的特性 Bitcode 【1】:

p1

新的特性往往意味著新的攻擊面。本文首先介紹什麼是Bitcode及Bitcode相關的工作流程,在熟悉了 Bitcode的工作流程後,接下來是評估Bitcode相關的攻擊面,最後介紹針對各個攻擊面的測試方法及目前的測試結果。

0x01 什麼是Bitcode


簡單來說,Bitcode是LLVM-IR在磁碟上的一種二進位制表示形式。關於Bitcode詳細描述,請參考【2】,這裡會用例子來讓大家對 Bitcode 有個感性認識。

先寫一個簡單的 C程式,功能是計算兩個數的和,程式碼如下:

#!c
intadd(int a, int b)
{
    int c = a + b;
    return c;
}

將如上程式儲存為 add.c,然後我們將源程式編譯成 Bitcode:

#!bash
clang -emit-llvm -c add.c-o add.bc

執行如上命令會生成 add.bc,我們使用二進位制編輯器開啟生成的檔案,檢視檔案內容:

p2

由於Bitcode是LLVM-IR的二進位制表示形式,如上圖,在不瞭解編碼方式的前提下基本不可讀。下面我們把 Bitcode轉換成文字形式:

#!bash
llvm-dis add.bc -o add.ll

用文字編輯器開啟 add.ll,可以看到 add 函式的 LLVM-IR內容如下:

#!c
; 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: nounwindsspuwtable
; 如下是 add() 對應的 LLVM-IR
; 可以注意到這種表示形式會申請很多變數,
; 感興趣的同學可以瞭解下  Static Single Assignment (SSA) 
define i32 @add(i32 %a, i32 %b) #0 {
  %1 = alloca i32, align 4          ; 變數 1,4 位元組空間,後續用來存放引數 a
  %2 = alloca i32, align 4          ; 變數 2,4 位元組空間,後續用來存放引數 b
  %c = alloca i32, align 4          ; 變數 c,4 位元組空間,後續用來存放結果 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   ; 將立即數 1 儲存到變數 3 中
  %4 = load i32, i32* %2, align 4   ; 將立即數 2 儲存到變數 4 中
  %5 = addnsw i32 %3, %4            ; 將變數 3 與變數 4 的和儲存到變數 5 中
store i32 %5, i32* %c, align 4      ; 將變數 5 儲存到結果 c 中
  %6 = load i32, i32* %c, align 4   ; 將結果 c 儲存到變數 6 中
ret i32 %6                          ; 返回變數 6
}

對比原始碼與已經註釋過的 add() 函式的 LLVM-IR 表示,大家應該對 LLVM-IR 有個感性認識了,下面我們一起看下Bitcode的工作流程。

0x02 工作流程


蘋果關於工作流程的描述:

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. 在將應用上傳到 AppStore時,Xcode會將程式對應的 Bitcode一起上傳。
  2. AppStore會將 Bitcode重新編譯為可執行程式,供使用者下載。

下面會將 Bitcode相關的完整的工作流程分解為如下幾個問題或子過程並分別做說明:

  1. WhereistheBitcode?
  2. 嵌入Bitcode 的方法
  3. 從 Bitcode生成可執行程式的方法

WhereistheBitcode?

參考蘋果的描述,只有在 Archive時才會生成 Bitcode,於是建立了一個測試工程:

p3

執行Archive,然後檢視生成的包結構:

p4

經過分析在如上的目錄中並沒有直接找到 Bitcode,接下來檢查生成的 MachO。使用MachOView載入生成的 MachO,結果如下圖:

p5

從上圖可以看到最終的可執行程式中多了 LLVM 相關的 Segment 與 Section。繼續檢視對應的Section的資訊:

p6

如上圖,Section__bundle 中儲存的是一個 xar文件,提取出 xar文件,然後使用如下命令解開文件:

#!bash
解開:
xar -x -f XXX.xar

解開後,可以看到 Bitcode檔案。

總結:程式對應的 Bitcode被Xcode打包成 xar文件,嵌入的 MachO中。

下面我們看下在 MachO中嵌入 Bitcode的方法。

嵌入Bitcode 的方法

方法一

透過對比 Archive與非Archive時的編譯引數,發現只要在如下圖所示的位置新增編譯引數:-fembed-bitcode,即可讓 Xcode普通編譯時也在 MachO中嵌入 Bitcode:

p7

方法二

方法一雖然很方便,但是IDE做了太多工作,不便於理解具體過程,接下來我們自己編譯可執行檔案。從原始碼生成可執行程式主要分為:編譯、連結兩個過程,為了控制這兩個過程,下面會講解Makefile的配置,及這兩個過程用到的引數。

在使用Makefile編譯iOS程式時,有些通用的配置,如下的通用配置,供大家參考:

#!bash
SDK_iOS := $(shell xcodebuild -version -sdkiphoneos Path)
CC_iOS := $(shell xcrun --sdkiphoneos --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為例說明編譯需要的引數:

#!bash
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連結成可執行程式的引數:

#!bash
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.oViewController.omain.o -oXBCTest

大家把如上的 Makefile片段稍加修改,整理到一個 Makefile檔案中,就可以透過 make命令嵌入Bitcode到可執行程式。

方法三

在這個方法中我們會將上面的步驟進一步分解,具體過程為:

原始碼-->Bitcode-->xar-->可執行程式

  • 原始碼-->Bitcode

    在這個過程中我們將 iOS應用的原始碼編譯成 Bitcode,下面會 main.m為例來說明使用的引數:

    #!bash
    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-abiapcs-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-abidarwinpcs $(SYS_ROOT)
    
    $(COMPILE_iOS_64_BC) -emit-llvm-bcmain.m -o main.bc
    

    完成這個過程後,我們可以得到三個Bitcode檔案:

    1. main.bc
    2. AppDelegate.bc
    3. ViewController.bc
  • Bitcode-->xar

    在這一步我們會將如上得到的三個 Bitcode檔案打包到一個 xar文件中。打包沒什麼特別,需要注意的是需要與 Xcode生成的 xar保持相容,具體引數如下:

    #!bash
    生成:
    xar --toc-cksum none -c -f BC.xarmain.bcAppDelegate.bcViewController.bc
    
  • xar-->可執行程式

    為了簡化過程,這裡我們會跳出 Makefile,使用Xcode,首先清除如下的編譯引數:

    p8

    將剛剛生成的 BC.xar複製到測試工程的根目錄:

    p9

    編輯工程設定的 Other Linker Flags,新增:-Wl,-sectcreate,__LLVM,__bundle,$(SRCROOT)/BC.xar,如下圖:

    p10

    編譯程式,檢視生成的 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的具體引數如下:

#!bash
segedit ./XBCTest -extract "__LLVM" "__bundle" Embedded-BC.xar

提取到 xar後,解開xar:

#!bash
解開:
xar -x -f Embedded-BC.xar

得到如下幾個Bitcode檔案:

p11

還可以使用 llvm-dis 工具將如上檔案處理成可讀形式,從而瞭解每個檔案的內容。

生成可執行程式

在有了 Bitcode後,接下來需要將 Bitcode編譯成可執行程式,分為兩個過程:將Bitcode編譯成 Object檔案;連結Object檔案到可執行程式。

  • 將Bitcode編譯成 Object檔案

    Makefile片段如下:

    #!bash
    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檔案到可執行程式

    Makefile片段如下:

    #!bash
    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 -oXBCTest
    

如上我們已經從Bitcode重新生成了可執行程式 XBCTest。

0x03 攻擊面


我們先回顧下 Bitcode 在本地的工作流程:Xcode將嵌入了 Bitcode的MachO上傳到 AppStore。透過分析可以發現這裡存在兩個問題:

  1. MachO與其中嵌入的 Bitcode的一致性問題。即:能否把程式B的 Bitcode嵌入到程式A中。
  2. AppStore是否信任了 Xcode,而沒有檢查一致性問題,從而允許將MalformedMachO上傳到 AppStore。

在分析了可能存在的問題後,我們認為如果Bitcode流程與功能存在缺陷,便可以對兩個目標形成威脅:普通使用者、蘋果。

普通使用者

由於 Bitcode對普通使用者是透明的,因此無法透過其弱點直接攻擊使用者。但是一致性問題是可能對普通使用者造成威脅的,試想:如果提交 AppStore稽核的 程式A 中嵌入了含有惡意程式碼的Bitcode,普通使用者就有可能從AppStore下載到含有惡意程式碼的程式。

對於這種攻擊方式我們將其叫做 Bitcode Injection,下文會詳細介紹這種攻擊的實施方法,及我們的測試結果。

蘋果

果MalformedMachO可以被上傳到蘋果的伺服器,蘋果的伺服器相較於之前,主要需要進行兩個額外的操作:解開xar;編譯Bitcode。如果這兩個過程出現問題,輕則可以在蘋果的伺服器上造成 DoS,重則可以在蘋果的伺服器上造成任意程式碼執行。

另外,Bitcode原本是LLVM-IR的一種序列化形式,而LLVM-IR是一種中間形式,之前沒有被直接暴露出來,現在完全開放了,而且又是二進位制格式,這是很容易出問題的。從 Bitcode生成可執行檔案的過程主要由如下幾個子過程組成:

  1. 基於平臺無關的 IR的程式碼最佳化。
  2. IR的平臺相關化、合法化。
  3. 平臺相關的最佳化、程式碼生成。

這些原本是編譯器的內部過程,由於各種原因,傳統的對編譯器的測試主要集中在前端的 Parser 與Lexer,現在藉由 Bitcode如上的一些中間或者後端過程也暴露了出來,如果如上的過程出現問題最糟糕的結果是可以控制編譯器的指令生成。

以上是關於攻擊面的分析,後文會介紹測試xar及Bitcode的思路,以及發現的問題。

0x04 BitcodeInjection


上文在介紹 Bitcode工作流程時已經介紹了實施BitcodeInjection的方法,但是上面提到的方法不夠簡練,這裡我們再介紹一種更簡單的方法,主要的思路就是最大限度的利用 Xcode,這個方法的具體實施步驟為:

  1. 用 Xcode建立工程XBCTest:

    p12

  2. 複製工程XBCTest,得到工程XBCTest2:

    p13

  3. 修改工程XBCTest2的原始碼,嵌入惡意程式碼:

    p14

  4. Archive 工程XBCTest2:

    p15

    p16

  5. 獲得MachO,利用segedit從MachO中提取出含有Bitcode的xar:

    p17

    #!bash
    提取xar:
    segedit ./XBCTest -extract "__LLVM" "__bundle" BC.xar
    
  6. 修改工程XBCTest的連結標記,將提取的xar:BC.xar嵌入到工程XBCTest的MachO檔案中。

  7. 禁用工程XBCTest的 Bitcode特性,Archive並上傳 AppStore:

    p18

我們在測試的過程中並沒有嵌入惡意程式碼,而是從網上找個兩個完全不同的應用,將其中一個的 Bitcode嵌入到另一個的 MachO中,並提交到AppStore。

在將應用提交到 AppStore的過程中主要會進行兩個方面的檢查:Xcode在本地進行靜態分析;提交後,蘋果的伺服器還會進行檢查。但是使用 Bitcode Injection 構造的應用可以透過這兩項檢查:

p18

p19

經過漫長的稽核後,我們的應用被拒了,理由是:我們的應用與描述不符。在描述中我們的應用應該長成如下樣子:

p20

但是蘋果的稽核人員安裝後,程式卻長成這個樣子:

p21

這至少可以說明三個問題:

  1. 我們使用的 BitcodeInjection方法沒有問題。
  2. 蘋果的稽核人員稽核的是從 Bitcode編譯出來的程式。
  3. 一致性是靠人肉區分的。如果嵌入對UI沒有影響的惡意程式碼,還是有可能繞過稽核的。

0x05 測試xar


思路

對 xar進行模糊測試,生成資料的方法是基於標準的 xar文件進行變異。

測試結果

目前主要 Fuzz出一些空指標解引用問題。

0x06 測試clang


思路

對 clang中的Bitcode到Object 的功能進行模糊測試,也是採用變異的方法生成測試資料。

測試結果

透過對 clang的Fuzz我們發現了一些堆損壞相關的問題。

0x07 總結


  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 relatedMachO file.
  2. 在上文中我們詳細介紹了所考慮到的攻擊面,及針對每個攻擊面的測試思路,希望這些對大家研究 Bitcode相關的攻擊面及安全性有幫助。

0x08 參考資料


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

相關文章