目录
背景
前段时间MOSEC上盘古关于MTK BootROM Exploit的议题非常精彩,所以我画了一些时间对议题内容进行分析,并结合手边能找到的一些材料做了逆向分析,也感谢同事@C0ss4ck在会场拍下了完整的Slide :)
配合MOSEC官方的微博食用更佳 :)
议题学习
MTK Based Boot flow
在进行研究之前需要搞明白MTK方案的设备的冷启动流程,议题中提供的图简洁明了:
按照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才可以。
根据大佬的议题内容可知,漏洞是一个整数溢出,是在判断读/写命令地址范围的时候出现的:
因为MTK的方案有很多开发板,所以基线代码基本上都很容易找到,比如使用了MT6737的香橙派-4G-IOT这个开发板(好像停产了,现存的巨贵),有个大哥把代码放github了
根据这份代码,分析这个漏洞其实很简单了
/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: .... } ... } |
支持的命令也很多:
直接定位到 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 } |
这里执行了两个检查:
- 判断你要操作的是不是物理外设所在的内存
- 判断你要操作的外设是不是在黑名单里,有部分外设不能操作
- 这里可能是因为方案不同,大佬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(®ion, &(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; } |
这里就要祭出datasheet里的memory map
根据memory map,利用这漏洞就可以把BROM dump出来了
BROM部分
基本分析
MTK的话BROM Exp满天飞,多搜一搜可以找到,或者按照dissecting-a-mediatek-bootrom-exploit中的办法,应该也可以,或者对于没开SecurityBoot的设备搞个mini DA进去也可以(参考这里 bypass_utility/main.py at master · MTK-bypass/bypass_utility · GitHub
)。
这里以某个SoC的BROM为例作分析,推荐使用Ghirda来做,选ARMv7就行。
1 2 3 4 | DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 67676 0x1085C Mediatek bootloader 72020 0x11954 SHA256 hash constants, little endian |
前面还是喜闻乐见的中断向量表,根据reset handler,能定位到类似main的位置,但是我们的目的是分析usb dl的逻辑,这里我看了下已知的文章,可以通过handshake来确定,直接暴搜一波 A0 0A 50 05
,但是这里需要注意,有两个handshake,uart和usb的,需要做好区分,然后就可以定位到 process_cmd() 里了。
然后可以还原出来 相关标志位,如 security boot & SLA & DAA。
不过这显然不是这次的目的,这次是想找到盘古议题中提到的两个BROM的漏洞 :)
议题中的漏洞
vuln1
根据MTK的公告可知和议题内容,这个应该是那个Issue1,即 Endpoint processing vulnerability
的这枚漏洞 :)
我这里根据几个地方来确认函数位置的
- 少的可怜的两个字符串
[USBDL]
开头的,和timeout相关 - 根据
GitHub - 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]; |
- 议题中漏洞特征
最终让我找到了这个漏洞,和我最开始预想的差不多,处理USB协议相关的逻辑,不过是在标准的流程后面
[TBD]vuln2
说来也比较巧合,rrr拍的图里似乎没有标题为MTK BootROM Vul #2
的slide,所以我目前还没有分析出来,只找到了一些相关的资料辅助分析:
- USB通信设备类CDC简介 - USB中文网
https://yhsnlkm.github.io/2019/08/14/USB相关/应用层遍历所有接入的USB设备-1/
mtktest/ mtktest --username qq413187589/N65/N65_V1/usb/src/usbacm_drv.c at master · mtek-hack-hack/mtktest · GitHub
- 【实战经验】USB CDC类入门培训 - STM32团队 ST意法半导体中文论坛
比较有意思的是链接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是否合法
所以,对于开了SecurityBoot的设备,就不能用公版DA了,大佬的议题中也是以开了SecurityBoot的设备为例讲的,通过前面的漏洞disable sla & daa,从而实现加载自定义的DA,然后通过这个DA来读写任意分区,从而实现加载任意代码的目的 :)
Attacking DA
大佬在议题中对MTK的DA做了详细的介绍,这里主要涉及了
- DA如何被加载
- DA的执行阶段
- stage1
- stage2
- 如何攻击DA实现任意分区读写
MTK的SP Flash Tool里带的这个公版DA其实是个DA的合集,SP FlashTool根据读到的chip id选对应的DA用来交互
DA stage1
这里提到了一个EMI file,stage1会根据这个EMI file来初始化DRAM,既然可以从preloader里后去,那么前面的基线代码里妥妥也会有了
当然也可以借助工具来解析出来,比如这个 GitHub - mfdl/MTKPreloaderParser: MTK Preloader Parser
, 相关内容就不展开了,为了理解议题内容的话,只需要了解这个东西的作用以及在哪里就行了:)
DA stage2
stage2是比较关键的内容了,它被stage1加载到了dram里执行(前面初始化dram这里要用)
这里列举了secure enable的情况,DA的能力将受到限制,即一部分功能无法使用,作者通过之前的BROM exploit disable了daa,然后加载自己patch过的da,从而使用这个patch过的da来实现全分区的读写,以及使用da中全部的功能。
policy_part_map?
这部分感觉PPT顺序有点问题,不过也不是特别影响理解吧,主要是启动过程中对加载的镜像完整性校验相关的介绍,这块和后面大佬讲攻击流程能对上。
github随便搜了下,就能看明白这个东西了 :)
主要是有这么个结构体来描述对应的镜像的安全配置,是否受到保护、能不能刷这个分区等等啥的。
相关的部分代码,这是在加载镜像之前,加载这个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
当然,所需要覆盖的变量也比较好找,把cmd是 0xd8
的 CMD_GET_TARGET_CONFIG
为入口就可以找到需要的东西了
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的一个特殊的测试模式,也算是一个之前没见过的攻击面
在这个模式下,可以做很多事情:
- Obtain/Modify EFUSE/RPMB Info
- Load Customized OS
- USERDATA Decrypt
- Obtain/Modify Hardware Key
- Unlock Bootloader
- …
巧了,咱手里正好有个某个MTK方案的设备的完整镜像 :-) 根据PPT中的信息,可以check下相关的逻辑
我这个设备没有找到相关的逻辑,应该是删除了这个模式,不过幸运的是 meta_tst
没有删除:),而且根据PPT里的内容,这个服务应该是比较核心的,MTK设计了私有协议做一些交互
分析的难度也不大,而且有趣的是如果你在github上搜一些特定的字符串,会发现很多有意思的repo :) 这对理解一些逻辑很有帮助
more exploit
这没什么可说的,既然从源头破坏掉了信任链,那么自然可以做任何事 😎
基本上一些很成熟的“取证”工具都能干- 。- 比如这一篇
support-for-mediatek-devices-in-oxygen-forensic-detective
感兴趣的话可以阅读一下
后记
这次虽然过程艰辛又带着一些遗憾,不过个人起码了解了MTK方案BROM Exploit的思路,vuln#2还没找到,后面等不忙了时间多了再尝试看看好了 :)
参考
MT6737/linux at master · SoCXin/MT6737 · GitHub
GitHub - chaosmaster/bypass_payloads
https://tinyhack.com/2021/01/31/dissecting-a-mediatek-bootrom-exploit/
https://www.cnblogs.com/wen123456/p/14034493.html
[MT6765]Preloader_流程分析--基于android 10_preloader lk-CSDN博客
https://github.com/rn2/ven/blob/db95d7f096/hardware/meta/common/README