二进制漏洞分析-5.华为安全监控漏洞(SMC MNTN OOB 访问)
二进制漏洞分析-10.华为TrustZone TEE_SERVICE_VOICE_REC漏洞
二进制漏洞分析-19.华为TrustZone TCIS漏洞
二进制漏洞分析-21.华为TrustZone TALoader信息泄露
二进制漏洞分析-22.华为TrustZone TA_uDFingerPrint漏洞
二进制漏洞分析-23.华为TrustZone TA_SensorInfo漏洞
二进制漏洞分析-24.华为TrustZone TA_HuaweiWallet漏洞
二进制漏洞分析-25.华为TrustZone TA_SignTool OOB Read
二进制漏洞分析-26.华为华为TrustZone IfaaKey_TA漏洞
Huawei TrustZone HuaweiNfcActiveCard 漏洞
此通报包含有关以下漏洞的信息:
CVE-2021-39996 漏洞CVE-2021-39996 SplitAidStrtok 中的缓冲区溢出
HuaweiNfcActiveCard TA符合GlobalPlatform TEE内部核心接口。因此,它从位于正常环境中的客户端应用程序接收命令。这些命令由函数处理。TA_InvokeCommandEntryPoint
该 TA 实现了 13 个命令,但仅调用 (0x20002) 就足以触发漏洞。CmdTeeHardwareSyncAidList
ParseAidList
¶函数中存在信息泄漏,可用于泄漏堆栈上分配的缓冲区的地址。结合第二个漏洞,可以用来获取TA基址。此信息泄漏来自对 in 的调用:ParseAidList
SLog
ParseAidList
int ParseAidList() {
char *list_elem;
int list_elem_len;
char stack_buf[0x1400];
// [...]
if (g_aidCount > 0) {
for (list_elem = &g_aidList; ; list_elem += 0x100) {
SLog("%s: ParseAidList Enter, g_aidList[%d]=%s\n", "[Trace]", index, list_elem);
list_elem_len = strlen(list_elem);
tmpCnt = SplitAidStrtok(stack_buf, list_elem, list_elem_len, ",");
SLog("%s: ParseAidList Enter, tmpCnt=%d\n", "[Trace]", tmpCnt);
if (tmpCnt < 2) {
SLog("%s: ParseAidList : at least two property:type and aid\n", "[Error]");
} else if (!strncmp(&stack_buf[0x100], "501", 3)) {
// [...]
}
// [...] many more else ifs [...]
else {
SLog("%s: ParseAidList unknown card aidList=%s, type=%d\n", "[Error]", &stack_buf[0x100], stack_buf);
}
// [...]
}
}
// [...]
}
每个 in 都经过解析,用于拆分分隔符上的字符串并将生成的子字符串放入堆栈缓冲区中。该函数需要 2 个子字符串和 .如果堆栈缓冲区中的 , at offset 0x100与任何预期值(等)不匹配,则它将进入默认情况,该情况使用 和 as 参数进行调用。但是由于 的格式说明符是 而不是 ,它将打印字符串地址而不是字符串本身:list_elem
g_aidList
SplitAidStrtok
","
stack_buf
type
aid
aid
"501"
SLog
type
aid
aid
%d
%s
[TA_HuaweiNfcActiveCard-1] [Error]: ParseAidList unknown card aidList=A, type=110212648
SplitAidStrtok
¶该函数中存在一个漏洞,允许超出其第一个参数的边界写入。根据调用此函数的位置,它可用于在 BSS 中写入用户控制的数据,或者更有趣的是,在堆栈上写入。此缓冲区溢出是由于缺少对函数的检查。SplitAidStrtok
SplitAidStrtok
int SplitAidStrtok(char *out_list, char *aidList, int aidLen, const char *separator) {
// [...]
const char *list_elem;
size_t list_elem_len;
int next_elem_ptr;
int count;
int ptr;
// [...]
// sanity checks of the arguments
list_elem = strtok_s(aidList, separator, &ptr);
if (list_elem) {
list_elem_len = strlen(list_elem);
if (strncpy_s(out_list, 0x100, list_elem, list_elem_len))
return 0xFFFF0006;
next_elem_ptr = out_list + 0x100;
count = 1;
while (1) {
list_elem = strtok_s(0, separator, &ptr);
if (!list_elem)
break;
list_elem_len = strlen(list_elem);
++count;
if (strncpy_s(next_elem_ptr, 0x100, list_elem, list_elem_len))
return 0xFFFF0006;
next_elem_ptr += 0x100;
}
return count;
}
// [...]
}
该函数在循环中调用作为参数给出的字符串,以检索以分隔的子字符串。对于每个子字符串,它调用以每次递增 0x100 的偏移量将其复制到其中。SplitAidStrtok
strtok_s
aidList
separator
strncpy_s
out_list
例如,if is ,将被复制到 +0x0、+0x100 和 +0x200。但正如你所看到的,可以复制的子字符串数量没有限制。这意味着给定足够长的字符串,我们可以溢出缓冲区。aidList
"A,B,C"
"A"
out_list
"B"
out_list
"C"
out_list
aidList
out_list
SplitAidStrtok
在 2 个地方调用:
int GetAidList(const char *aidList, int aidLen) {
// [...]
g_aidCount = SplitAidStrtok(&g_aidList, aidList, aidLen, "|");
// [...]
}
在 中,它可用于溢出 BSS 中分配的大小为 0x1400 的缓冲区(该参数完全由用户控制)。GetAidList
g_aidList
aidList
int ParseAidList() {
char stack_buf[0x1400];
// [...]
list_elem = &g_aidList;
for (index = 0; index < g_aidCount; ++index) {
list_elem_len = strlen(list_elem);
tmpCnt = SplitAidStrtok(stack_buf, list_elem, list_elem_len, ",");
// [...]
list_elem += 0x100;
}
}
在 中,它可用于溢出堆栈上分配的大小为 0x1400 的缓冲区(该参数由用户控制,但限制为 255 字节)。ParseAidList
stack_buf
list_elem
unsigned int CmdTeeHardwareSyncAidList(void *session, unsigned int paramTypes, int *params) {
// [...]
// checking the parameters types
// [...]
buffer = params[0].memref.buffer;
length = params[0].memref.length;
WriteAidListToFile(buffer, length);
GetAidList(buffer, length);
ParseAidList();
}
该命令需要单个参数:包含单个字符串的输入缓冲区。它将使用此字符串调用函数。 将拆分它并将子字符串存储到全局缓冲区中。然后调用将每个子字符串拆分为 on ,将结果放入堆栈缓冲区并对其进行处理。CmdTeeHardwareSyncAidList
GetAidList
GetAidList
"|"
g_aidList
CmdTeeHardwareSyncAidList
ParseAidList
g_aidList
","
触发崩溃的输入字符串示例如下:A,A,A,A,A,A,A,A,A,A,A,A,A,A,A,A,A,A,A,A,AAAAAAAAAAAAAAAAAAAAAAAA
[HM] ESR_EL1: 82000006, ELR_EL1: 41414140, FAR is not valid
[HM] TA_HuaweiNfcAct vm fault prefetch abort: 41414140
[HM] fault: 82000006 tcb cref 2200000028
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <ParseAidList>+0x384/0x41c
[HM] [ERROR][2171]vmem_as_ondemand_prepare failed
[HM] [ERROR][2496]process 2200000028 (tid: 40) data abort:
[HM] [ERROR][2498]Bad memory access on address: 0x41414140, fault_code: 0x82000006
[HM]
[HM] Dump task states for tcb
[HM] ----------
[HM] name=[TA_HuaweiNfcAct] tid=40 is-idle=0 is-curr=0
[HM] state=BLOCKED@MEMFAULT sched.pol=0 prio=46 queued=1
[HM] aff[0]=ff
[HM] flags=1000 smc-switch=0 ca=8625 prefer-ca=8625
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <ParseAidList>+0x384/0x41c
[HM] Dump task states END
[HM]
[HM] [TRACE][1212]pid=48 exit_status=130
虽然 trustlet 中有堆栈 cookie,但令人惊讶的是,函数中没有。如上所述,可以覆盖堆栈上的返回地址,我们现在将看到如何利用它。ParseAidList
我们开发漏洞利用的设备是运行固件更新的P40 Pro。trustlet 二进制 MD5 校验和如下:ELS-LGRP4-OVS_11.0.0.196
HWELS:/ ## md5sum /vendor/bin/5fce3ea5-fa71-4152-a30f-f46be8c924bf.sec
318662fa357d2701941706d775e1115f /vendor/bin/5fce3ea5-fa71-4152-a30f-f46be8c924bf.sec
华为TEE OS iTrustee实现了白名单机制,只允许特定的客户端应用(原生二进制文件或APK)与可信应用通信。
在我们的例子中,HuaweiNfcActiveCard TA 只能被 2 个原生二进制文件和一个 APK 调用:
/vendor/bin/nfcgetcplc
(uid 0);
/vendor/bin/hw/[email protected]
(uid 0 或 1068);
com.android.nfc
(证书以 开头)。c2851d1c
身份验证机制分为 3 个部分:
- 守护进程,实现 TEE 客户端 API,检查哪个原生二进制文件/APK 正在与它通信,并将该信息发送到内核驱动程序;
- 内核驱动程序确保它正在与 通信,并将它收到的信息转发给 TEE OS;
- TEE OS 验证客户端应用是否在 TA 的白名单中。teecd
teecd
由于我们不想费心在这些二进制文件之一中注入代码,因此我们选择通过修补内核驱动程序来规避身份验证,以添加模拟任何原生二进制文件/APK 的功能。
要获取 trustlet 的基址,第一步是泄露 in 的地址。为此,我们通过发送包含未知(在漏洞利用中)的输入字符串来触发上一节中介绍的信息泄漏。stack_buf
ParseAidList
aid
"A,A"
第二步是在 trustlet 部分泄露地址。由于第二个漏洞,我们能够溢出缓冲区周围的堆栈布局如下:.text
stack_buf
0x1A40 ┌────────────────┐
│ LR ──┼──► .text + 0x1364
0x1A3C ├────────────────┤
│ R11 │
0x1A38 ├────────────────┤
│ R7 │
0x1A34 ├────────────────┤
│ R6 ──┼──► saved length
0x1A30 ├────────────────┤
│ R5 ──┼──► saved buffer
0x1A2C ├────────────────┤
│ R4 │
0x1A28 ├────────────────┤
│ │
│ │
│ │
│ stack_buf │
│ │
│ │
│ │
0x0628 └────────────────┘
后面的第二个 dword 是在 开头推送的 R5 寄存器的值。此值将在执行返回到 之前的 right 末尾弹出。在 中,R5 寄存器将用作调用 的参数,带有格式说明符,允许我们泄漏此地址的字节(最多第一个空字节)。由于我们对泄露代码地址感兴趣,因此我们可以选择用输入时保存的 LR 地址覆盖保存的 R5。stack_buf
ParseAidList
ParseAidList
CmdTeeHardwareSyncAidList
CmdTeeHardwareSyncAidList
SLog
%s
ParseAidList
通过覆盖保存的 LR 而不是保存的 R5,我们可以执行不同的 ROP 链,允许我们执行任意读取和写入。在我们的 ROP 链中,我们需要注意不要在堆栈上溢出太多值,因为我们希望保留先前堆栈帧的已保存寄存器,以便以后可以将它们弹出。为此,我们选择使用以下小工具将 pivot 堆栈到从正常世界映射到的输入缓冲区中:
pop {r11,pc}
---
sub sp, r11, #4
pop {r11,pc}
我们根据经验并利用过去漏洞利用的知识获得了输入缓冲区地址(0x70003000)。
任意写入的 ROP 链是围绕以下小工具构建的:
str r3, [r4,#4]
sub sp, r11, #0xc
pop {r4,r5,r11,pc}
任意读取的 ROP 链更复杂,因为我们找不到任何从 trustlet 内的寄存器执行加载的小工具。相反,我们决定将 to 的调用与对 的调用链接起来。这实际上等同于调用 。WriteAidListToFile(src_addr, 4)
ReadAidListFromFile(dst_addr, &{4})
memcpy(dst_addr, src_addr, 4)
因为第一个参数是输入缓冲区而不是输入输出缓冲区,所以它不会在返回时被 TEEOS 复制回正常世界,因此我们不能使用它来检索我们的值。相反,我们选择做的是将值写入堆栈,将其弹出到带有小工具的寄存器中,然后将其移动到 R0 中。这样,它将成为命令的返回码。
在漏洞利用中,我们通过写入该部分来演示我们的 write 原语,并通过转储 trustlet 内存来演示我们的 read 原语:.data
adb wait-for-device shell su root sh -c 0 "/data/local/tmp/ta_huaweinfcactivecard"
stack_leak_addr = 69ce628
code_leak_addr = 3ee0608
ta_base_addr = 3ede000
got_addr = 69c6000
bss_start = 12345678
bss_start = deadbeef
Trustlet memory dump:
0x03ede000: f0 4f 2d e9 54 c1 9f e5 20 b0 8d e2 84 d0 4d e2 .O-.T... .....M.
0x03ede010: 00 30 9c e5 00 00 51 e3 98 00 0b e5 28 30 0b e5 .0....Q.....(0..
0x03ede020: 49 00 00 0a 12 00 52 e3 3d 00 00 ca 30 31 9f e5 I.....R.=...01..
0x03ede030: c2 9f a0 e1 92 03 c3 e0 43 92 79 e0 21 00 00 4a ........C.y.!..J
虽然在漏洞中没有演示,但也可以使用共享库(、、等)中的小工具,因为它们也映射在 trustlet 的地址空间中。可以通过阅读 GGOT 的内容找到它们的基址。libc_shared_a32.so
libtee_shared_a32.so
libvendor_shared_a32.so
例如,这可用于进行任意系统调用,并可能进一步提升权限。
我们已验证该漏洞是否影响了以下设备:
麒麟990:P40 专业版 (ELS)
请注意,其他型号可能已受到影响。
名字 | 严厉 | CVE漏洞 | 补丁 |
---|---|---|---|
缓冲区溢出SplitAidStrtok | 危急 | CVE-2021-39996 漏洞CVE-2021-39996 | 2021 年 12 月 |
2021年10月06日 - 向华为PSIRT发送漏洞报告。
2021年10月25日 - 华为PSIRT确认该漏洞报告。
2021年12月01日 - 华为PSIRT表示,这些问题已在2021年12月的更新中修复。
从 2022 年 11 月 30 日至 2023 年 7 月 19 日 - 我们定期交换有关公告发布的信息。
二进制漏洞(更新中)
其它课程
windows网络安全防火墙与虚拟网卡(更新完成)
windows文件过滤(更新完成)
USB过滤(更新完成)
游戏安全(更新中)
ios逆向
windbg
恶意软件开发(更新中)
还有很多免费教程(限学员)
更多详细内容添加作者微信