原文:blog.quarkslab.com

原作者:Quarkslab

譯:Holic (知道創宇404安全實驗室)

現在有許多搭載三星 Exynos 智能手機使用了 SBOOT 專屬 bootLoader。目前三星 Galaxy S7,Galaxy S6 和 Galaxy A3 即是如此,當然在三星 Exynos Showcase [1] 上可能還會有更多的智能手機采用,我也有一次在審計各種 TEE 實現的時候逆向了一部分 BootLoader 。本文是 SBoot 系列的第一篇,它回顧了 ARMv8 的一些概念,闡述了我的方法論,正確或錯誤的假設,同時在沒有文檔的情況下分析了一堆三星 S6 的一堆東西。

來龍去脈

最近,作為日常工作,我有幸在幾次可信執行環境(TEE)的應用中審計出了幾個 bug。在項目之余,我開始挖掘更多的 TEE 應用,特別是在我的智能手機上,個人用或在工作中,巧合的是,他們來自同一個軟件編輯器,即由 ARM,G&D 和 Gemalto 共同創立的Trustonic [2]。目前我手上的智能手機的共同點就是他們都基于 Exynos 。

Trustonic 的 TEE,名為 <t-base ,由 Mobicore, G&D 的 TEE 原型演變而來。據我所知,這個 TEE 及以前的版本中很少有公開的技術信息。分析變得富有挑戰性,超出我的預想范圍。我們針對三星 Galaxy S6 進一步調查吧!

然而在文件系統上識別受信任的應用是最困難的挑戰,而據我分析在 Exynos 智能手機上查找 TEE OS 相當于大海撈針。實際上,你可以在一些智能手機上找到的(例如基于 Qualcomm 的 SoC)專門存儲 TEE OS 鏡像的特定分區,在這里已經無法找到了。它一定是存儲在其他地方,也許是在 bootloader 自身,這就我要逆向 SBOOT 的原因了。本文是系列首篇,敘述了我的 TEE OS 逆向之旅,主要講在 IDA 中確定三星 S6 SBOOT 的基地址的方法。

ARMv8 相關概念

在啟動 IDA Pro 之前,我們來回顧一下 ARMv8 的一些基本原理。我在這里先介紹幾個概念,對 ARMv8 新手但已經使用過 ARMv7 的人來說是有用的。有關更準確完整的文檔,參考 ARMv8 程序員指南[3]。由于我不是 ARMv8 專家,如果你看到任何錯誤和不準確的之處,請隨時評論。

Exception Levels

ARMv8 通過定義異常級別的概念引入一個新的異常模型。異常級別確定運行軟件組件的權限級別(PL0到PL3)和運行它的處理器模式(非安全和安全)。在 ELn 處執行對應權限 PLn,并且 n 越大,執行級別具有的特權越多。

異常向量表

當異常發生時,處理器分到異常向量表運行響應的處理程序。在 ARMv8 上,每個異常級別都有自己的異常向量表。習慣與逆向 ARMv7 bootloader 的人,你會注意到它的格式與 ARMv7 完全不同:

機智的讀者可能已經注意,異常向量表對應的條目為 128(0x80)字節長度,而在 ARMv7 上每條僅為 4 字節寬,并且每條保持一些了異常處理指令。雖然在 ARMv7 上異常向量表的位置由的 VTOR(Vector Table Offset Registe)確定,但在 ARMv8 上使用了三個 VBAR(Vector Based Address Registers)VBAR_EL3,VBAR_EL2,VBAR_EL1。這里注意,對于特定級別將執行的處理程序(或者表項)的運行取決于:

  • 異常類型(SError,FIQ,IRQ 或 Synchronous)
  • 如果異常在相等的異常級別發生,則使用堆棧指針(SP0或SPx)。
  • 如果異常在較低的異常級別發生,則切換下一個較低級別(AArch64 或 AArch32)的執行狀態。

運行在特定級別的組件可以使用專用指令在與底層異常級別上運行的軟件交互。比如例如,用戶模式的進程(EL0)通過發起 Supervisor 調用(SVC),來執行由內核(EL1)處理的系統調用,內核便能使用 Hypervisor 調用(HVC)與 hypervisor(EL2) 交互,或者直接通過安全監控(EL3)的安全監視調用(SMC)等方法進行交互。這些服務調用生成的同步異常,由異常向量表同步處理器處理。

反匯編 SBOOT

據我所知,SBOOT 使用了尚未公開記錄的專有格式。

SBOOT 加載到 IDA Pro

三星 Galaxy S6 由 1.5GHz 64八核 Exynos 7420 CPU 驅動。回想到 ARMv8 可以運行由 AArch32 和 AArch64 構建的應用程序。因此,可以試著將 SBOOT 加載為32位或者64位的 ARM 二進制文件。

我猜 BootROM 還沒有切換到 AArch32 狀態,并將其作為64位的二進制文件加載到 IDA Pro 中,保留默認選項:

  • Processor Type: ARM Little Endian [ARM]
  • Disassemble as 64-bit code: Yes

許多 AArch64 指令被自動識別。當涉及到反匯編指令,基本塊還是有意義的,讓我想到我真的在處理 AArch64 代碼。

確定基址

我花了幾天時間來確定正確的基地址。先直接給你解決方案毫無意義的,我會先詳細說明我所嘗試的東西,直到假設正確,給我正確的基地址。正如諺語所說:“授之以魚,不如授之以漁”。

我開始在各個搜索引擎搜索三星 bootloader 和 SBOOT 相關內容。很不幸,結果很少,相關內容只有 2015年三月 reverseengineering.stackexchange.com 上的一條帖子[5]。

這個帖子主要給我兩個提示。 J-Cho 感覺 bootloader 從文件偏移 0x3F000 處開始加載,這點挺有用,而它實際從 0x10 處開始。

正如我想證實的假設,bootloader 的基地址是 0x00000000,它的代碼總是從 0x10 開始,我開始在其他 Exynos 智能手機使用的 bootloader。魅族的 SBOOT 并沒有在 0x10 給出有效指令,澄清了我懷疑之處:

我還分析了其它bootloader中是否留有調試字符串,這會對找到 SBOOT 在內存中加載的位置有所提示。不幸:( 但是還有一條線索:在魅族 SBOOT 的一些字符串說明使用了 U-Boot 。即使 U-Boot 不使用三星 Galaxy S6,這是一個值得探索的路線,我開始進一步探索。

U-Boot Respsitory

U-Boot 是開源的,支持多個 Exynos 芯片。例如,Exynos 4 和 Exynos 5 已經支持 5 年多了。Exynos 7 的支持尚未完全登錄主線[6],Exynos 7 ESPRESSO 開發板還存在一些補丁。

我可能錯過了這一點,但縱覽 ESPRESSO 開發板的補丁,并沒有得到相應結果:(我的 Exynos 7 上試過多個 Exynos 4已知的的基地址,無果。是時候換個姿勢了。

ARM Literal Pools(文字池)

如果你熟悉 ARM 逆向,你一定注意到了大量的文字池來保存要加載到寄存器的常量。此屬性可以幫助我們查找在何處加載 SBOOT,特別是從文字池加載目標分支地址時。

我搜索了 IDA Pro 在操作數中所有標記有錯誤的分支指令(以紅色突出顯示)。由于 bootloader 代碼是自包含的,我可以大膽猜想大多數分支目標代碼的最終必須指向 bootloader 本身。帶著這個假設,我可以估計 bootloader 的基地址。

這些代碼段有個很有意思的地方:

  1. 分支指令 BR(跳轉分支到寄存器)是無條件跳轉,意味著沒有返回。
  2. 兩個分支操作數相同(0x2104010),并且位于 bootloader 前面一部分。
  3. 最后一個字節 0x10,正是 bootloader 代碼開始的偏移量。

我猜地址 0x2104010 是個復位地址,我試著在 0x2104010 加載 SBOOT 二進制文件,帶著下列選項:

  • Processor Type: ARM Little Endian [ARM]
  • Start ROM Address: 0x2104000
  • Loading Address: 0x2104000
  • Disassemble as 64-bit code: Yes
ARM 系統寄存器

現在我有了潛在可能的基地址,繼續逆向 SBOOT ,希望代碼流中沒有異常。

由于我想找到 TEE OS,我開始搜索在安全監視器中執行的代碼片段。找到安全監視器有一個相當簡單的技巧,就是尋找設置或讀取相關指令,該指令只能從安全監視器設置或者讀取寄存器。如前所述,安全監視器運行與 EL3 中。VBAR_EL3 是一個很好的找到 EL3 代碼的候選方案,因為它保存了 EL3 的異常向量表的基址并指向 SMC 處理程序。

你還記得本文開頭介紹的異常向量表的格式嗎?它由 16 個 0x80 字節的條目組成,保存異常處理程序的代碼。在搜索結果中,0x2111000處的代碼似乎指向一個有效的異常向量表:

即便如此,所選擇的基地址仍不是正確的:( 當驗證設置 VBAR_EL3 的其它指令時,可以注意一下 0x210F000 在函數當中:

這些異常表明 0x2104000 不是正確的基地址。

我們試試別的東西吧。

Service Descriptors

三星 Galaxy S6 SBOOT 部分基于 ARM 可信固件[7]。ARM 可信固件是開源的,作為 ARMv8-A 提供了安全世界軟件的參考實現,包括在異常級別3(EL3)執行的安全監視器。安全監視器對應的匯編代碼與 ARM 可信固件中的匯編代碼完全相同。好消息,因為它節省了我不少時間,節省了不少逆向的工夫。

我試著在反匯編中找到另一個錨點,以確定 SBOOT 的基地址。在結構體中的 char * 類型的成員特別有趣,因為它們指向在編譯時就定義其地址的字符串。在比較 SBOOT 反匯編代碼和 ARM 可信固件的源碼時,我確定了一個結構,rt_svc_desc_t,它具有我正在在找的屬性。

typedef struct rt_svc_desc {
    uint8_t start_oen;
    uint8_t end_oen;
    uint8_t call_type;
    const char *name;
    rt_svc_init_t init;
    rt_svc_handle_t handle;
} rt_svc_desc_t;

根據 ARM 可信固件的源碼,rt_svc_descs 是一個 rt_svc_desc_t 數組,用于保存服務導出的運行時服務描述符。它使用于函數 runtime_svc_init 中,通過調用函數 bl31_main 中的調試字符串可輕易將其放置在 SBOOT 中:

我想把二進制映射到不同的地址,并檢測是否可以找到 rt_svc_desc.name 條目的有效字符串。這是一段挺小的暴破腳本:

import sys
import string
import struct

RT_SVC_DESC_FORMAT  = "BBB5xQQQ"
RT_SVC_DESC_SIZE    = struct.calcsize(RT_SVC_DESC_FORMAT)
RT_SVC_DESC_OFFSET  = 0xcb50
RT_SVC_DESC_ENTRIES = (0xcc10 - 0xcb50) / RT_SVC_DESC_SIZE

if len(sys.argv) != 2:
    print("usage: %s <sboot.bin>" % sys.argv[0])
    sys.exit(1)

sboot_file = open(sys.argv[1], "rb")
sboot_data = sboot_file.read()

rt_svc_desc = []
for idx in range(RT_SVC_DESC_ENTRIES):
    start = RT_SVC_DESC_OFFSET + (idx << 5)
    desc = struct.unpack(RT_SVC_DESC_FORMAT,
                         sboot_data[start:start+RT_SVC_DESC_SIZE])
    rt_svc_desc.append(desc)

strlen = lambda x: 1 + strlen(x[1:]) if x and x[0] in string.printable else 0

for base_addr in range(0x2100000, 0x21fffff, 0x1000):
    names = []
    print("[+] testing base address %08x" % base_addr)
    for desc in rt_svc_desc:
        offset = desc[3] - base_addr
        if offset < 0:
            sys.exit(0)
        name_len = strlen(sboot_data[offset:])
        if not name_len:
            break
        names.append(sboot_data[offset:offset+name_len])
    if len(names) == RT_SVC_DESC_ENTRIES:
        print("[!] w00t!!! base address is %08x" % base_addr)
        print("    found names: %s" % ", ".join(names))

在要分析的 SBOOT 上運行此腳本,給出以下輸出:

$ python bf_sboot.py sboot.bin
[+] testing base address 02100000
[+] testing base address 02101000
[+] testing base address 02102000
[!] w00t!!! base address is 02102000
    found names: mon_smc, std_svc, tbase_dummy_sip_fastcall,
                 tbase_oem_fastcall, tbase_smc, tbase_fastcall
[...]

成功!三星 Galaxy S6 SBOOT 的基址為 0x02102000 。用這個基址重新加載二進制到 IDA Pro,似乎糾正了我之前看到的所有反匯編代碼中的奇怪之處。我們現在一定可以獲得正確的結果。

增強反匯編

逆向工程像是解謎一樣。試著將一些信息放到一起來理解軟件的運行原理。因此,你擁有的信息越多,解謎的難度就越大。這里有一些小技巧,幫助我找到了正確的基地址。

缺少的函數

盡管 IDA Pro 在反匯編常用文件格式方面做得很出色,但在逆向未知格式的二進制可能會漏掉很多函數。這種情況下,通常是寫腳本查找起始指令,并聲明函數存在于此。一個簡單的 AArch64 函數序列如下:

// AArch64 PCS assigns the frame pointer to x29
sub     sp, sp, #0x10
stp     x29, x30, [sp]
mov     x29, sp

指令mov x29,sp是 AArch64 起始語句相當可靠的標記。找到函數入口點的思路在于搜索這個標記,并在發現公共起始語句的時候進行反匯編。在 IDA Python 中搜索 AArch64 函數入口點的函數如下:

import idaapi

def find_sig(segment, sig, callback):
    seg = idaapi.get_segm_by_name(segment)
    if not seg:
        return
    ea, maxea = seg.startEA, seg.endEA
    while ea != idaapi.BADADDR:
        ea = idaapi.find_binary(ea, maxea, sig, 16, idaapi.SEARCH_DOWN)
        if ea != idaapi.BADADDR:
            callback(ea)
            ea += 4

def is_prologue_insn(ea):
    idaapi.decode_insn(ea)
    return idaapi.cmd.itype in [idaapi.ARM_stp, idaapi.ARM_mov, idaapi.ARM_sub]

def callback(ea):
    flags = idaapi.getFlags(ea)
    if idaapi.isUnknown(flags):
        while ea != idaapi.BADADDR:
            if is_prologue_insn(ea - 4):
                ea -= 4
            else:
                print("[*] New function discovered at %#lx" % (ea))
                idaapi.add_func(ea, idaapi.BADADDR)
                break
    if idaapi.isData(flags):
        print("[!] %#lx needs manual review" % (ea))

mov_x29_sp = "fd 03 00 91"
find_sig("ROM", mov_x29_sp, callback)
ARM64 IDA Plugins

AArch64 mov simplifier

編譯器有時會優化代碼,使其不易讀。使用 IDA Pro 的 API ,可以變形特定架構的代碼簡化器。我發現由 @xerub 分享的 AArch64 代碼簡化器非常管用:

ROM:0000000002104200                 BL              sub_2104468
ROM:0000000002104204                 MOV             X19, #0x814
ROM:0000000002104208                 MOVK            X19, #0x105C,LSL#16
ROM:000000000210420C                 MOV             X0, X19

@xerub 的“ AArch64 mov 簡化器”將反匯編更改成下面的樣子:

ROM:0000000002104200                 BL              sub_2104468
ROM:0000000002104204                 MOVE            X19, #0x105C0814
ROM:000000000210420C                 MOV             X0, X19

機智的讀者應該注意到了,MOVE 不是有效的 ARM64 指令。MOVE 只是一個標間,告訴逆向工程師當前的指令已經被簡化并被這條指令取代。

FRIEND

在 IDA Pro 中逆向 ARM 的低級代碼總是無聊的。確定與系統控制處理器相關的指令是一次可怕的經理,因為 IDA Pro 在沒有寄存器別名的情況下進行反匯編。如果可以選擇,你喜歡讀哪一個:

msr vbar_el3, x0

或者

msr #6, c12, c0, #0, x0

ARM 的幫助插件有助于改進 IDA Pro 的反匯編,來自Stefan Esser(@ i0n1c)的 IDA AArch64 Helper插件[9]是一個這樣的插件。不幸的是,它并不公開。Alex Hude(@getorix)為MacOS寫了一個類似的插件FRIEND [10]。如果你密切關注這個項目,我最近提交修改[11],上周已經合并,支持跨平臺。現在可以在 Windows,Linux和MacOS上使用 FRIENDs 了 :)。

Signatures

前面說過,SBOOT部分基于ARM可信固件[12]。由于源代碼可用,可以通過瀏覽源代碼,重新編譯它并做二進制差分(或簽名匹配)來節省大量的逆向工作,以便盡可能多恢復符號。

我通常結合多個二進制 diffing 工具來增強二進制的符號識別:

  • Rizzo [13] from Craig Heffner (devttys0)
  • Bindiff [14] from Zymanics
  • Diaphora [15] from Joxean Koret (@matalaz)

它們通常具有互補的效果。

結論

在本文中,我描述了如何確定三星Galaxy S6 SBOOT的基地址以及如何加載到IDA Pro。這里描述的方法應該適用于其他三星的智能手機,并可能適用于使用 Exynos SoC 的其他制造商的產品。

參考

  • [1] http://www.samsung.com/semiconductor/minisite/Exynos/w/showcase/smartphones/
  • [2] https://www.trustonic.com/about-trustonic
  • [3] http://infocenter.arm.com/help/topic/com.arm.doc.den0024a/DEN0024A_v8_architecture_PG.pdf
  • [4] http://quoteinvestigator.com/2015/08/28/fish/
  • [5] http://reverseengineering.stackexchange.com/questions/10995/problem-with-ida-pro-6-8-disassemble-galaxy-s6-sboot
  • [6] http://lists.denx.de/mailman/listinfo/u-boot
  • [7] https://github.com/ARM-software/arm-trusted-firmware
  • [8] https://gist.github.com/xerub/c6936d219db8e6635d25
  • [9] https://youtu.be/dg6byIiAwtc
  • [10] https://github.com/alexhude/FRIEND
  • [11] https://github.com/alexhude/FRIEND/pull/6
  • [12] https://github.com/ARM-software/arm-trusted-firmware
  • [13] https://github.com/devttys0/ida/blob/master/plugins/rizzo/rizzo.py
  • [14] https://www.zynamics.com/software.html
  • [15] https://github.com/joxeankoret/diaphora

Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/242/