华为TrustZone TEE_SERVICE_VOICE_REC漏洞
二进制漏洞分析-5.华为安全监控漏洞(SMC MNTN OOB 访问)
此通报包含有关以下漏洞的信息:
CVE-2021-40036 漏洞-2021-40036 DecryptData 中的 OOB 访问
CVE-2021-40010 漏洞 SendTaGmmBuf 中的堆缓冲区溢出
CVE-2021-40027 漏洞 还原中的 OOB 访问
CVE-2021-40032 漏洞-2021-40032 比较中的信息泄漏
CVE-2021-40014 漏洞 还原中的信息泄漏
HWPSIRT-2021-56065 CheckModelHash 中的 Null 指针取消引用
华为TEE_SERVICE_VOICE_REC TA符合GlobalPlatform TEE Internal Core API。因此,它从位于正常环境中的客户端应用程序接收命令。这些命令由函数处理。此 trustlet 实现了 19 个命令。TA_InvokeCommandEntryPoint
restore
¶函数中存在信息泄漏:restore
int RestoreTemplate(int ival0_a, int ibuf1_addr, int ibuf1_size) {
// [...]
uid_file_content = TEE_Malloc(0x80000, 0);
// [...]
for (int i = 0; i < 0xA; i++) {
// [...]
restore(
g_voiceUserDb->templates[i].voiceType,
ibuf1_addr,
ibuf1_size,
uid_file_content,
uid_path_filesize,
SaveTemplateWithNoDecrypt);
// [...]
}
// [...]
}int restore(unsigned int voiceType,
void *ibuf1_addr,
uint32_t ibuf1_size,
void *middle_buffer,
uint32_t middle_buffer_len,
void *save_callback)
{
// [...]
if ( ... ) {
SLog(
"%s %s: parms NULL !!!%d %p %d %p %d %p\n",
"[Error]",
"restore",
voiceType,
ibuf1_addr,
ibuf1_size,
middle_buffer,
middle_buffer_len,
save_callback);
return -1;
}
// [...]
}
中的日志字符串将泄漏 3 个指针:restore
ibuf1_addr
、输入参数TEE_Param
middle_buffer
,堆分配的缓冲区
save_callback
,指向函数的指针SaveTemplateWithNoDecrypt
我们通过概念验证触发了此 bug,并获得了以下输出:
[TEE_SERVICE_VOICE_REC-1] [Error] restore: parms NULL !!!9 0x70003000 524288 0x69fe010 524288 0x3c69a8c
restore
¶函数中有一个 OOB 访问:restore
int restore(unsigned int voiceType,
void *ibuf1_addr,
uint32_t ibuf1_size,
void *middle_buffer,
uint32_t middle_buffer_len,
void *save_callback)
{
// [...]
buf_off = *(uint32_t *)ibuf1_addr;
buf_ptr = ibuf1_addr + buf_off + 8;
buf_len = *(uint32_t *)(ibuf1_addr + buf_off + 4);
// [...]
}
在 中,从(其内容完全由用户控制的缓冲区)中读取。然后,此偏移值用于从 读取长度。由于我们控制 ,我们可以指向内存中的任何位置,从而触发 OOB 读取。restore
buf_off
ibuf1_addr
ibuf1_addr
buf_off
ibuf1_addr + buf_off + 4
我们通过概念验证触发了此 bug,并获得了以下崩溃:
[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: 0xb1417145, fault_code: 0x92000005
[HM]
[HM] Dump task states for tcb
[HM] ----------
[HM] name=[TEE_SERVICE_VOI] 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=8347 prefer-ca=8347
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <restore+0x94/0x248>
[HM] <RestoreTemplate>+0x2f0/0x494
[HM] <RestoreTemplate>+0x2f0/0x494
[HM] <Restore>+0x74/0x98
[HM] <TA_InvokeCommandEntryPoint>+0x98/0xe4
[HM] <tee_task_entry>+0x398/0xcd4
[HM] Dump task states END
[HM]
[HM] [TRACE][1212]pid=72 exit_status=130
CheckModelHash
¶函数中有一个空指针取消引用:CheckModelHash
int CompareVoiceTa(uint32_t label, uint8_t *inbuf, uint32_t inbuf_size, uint8_t *outbuf) {
// [...]
CheckModelHash(g_sendGmmBuf, g_sendGmmBufTotalLen, HashCheck);
// [...]
}int CheckModelHash(uint8_t * model_buf, uint32_t model_len, void *callback) {
// [...]
version = *(uint32_t *)(model_buf + 8);
// [...]
}
CompareVoiceTa
将使用全局变量和 As 参数进行调用。这些变量应该由函数设置,但从不检查这些值是否为非零,如果未调用该命令,则会导致 NULL 指针取消引用。CheckModelHash
g_sendGmmBuf
g_sendGmmBufTotalLen
SendTaGmmBuf
CheckModelHash
SendGmmBuf
我们通过概念验证触发了此 bug,并获得了以下崩溃:
[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: 0x8, fault_code: 0x92000006
[HM]
[HM] Dump task states for tcb
[HM] ----------
[HM] name=[TEE_SERVICE_VOI] 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=8398 prefer-ca=8398
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <CheckModelHash+0x24/0xe0>
[HM] <CompareVoiceTa>+0x184/0x3a0
[HM] <CompareVoiceTa>+0x184/0x3a0
[HM] <Auth>+0x1cc/0x24c
[HM] <TA_InvokeCommandEntryPoint>+0x98/0xe4
[HM] <tee_task_entry>+0x398/0xcd4
[HM] Dump task states END
[HM]
[HM] [TRACE][1212]pid=74 exit_status=130
compare
¶函数中存在信息泄漏:compare
int Auth(unsigned int paramTypes, TEE_Param params[4]) {
// [...]
plaintext = TEE_Malloc(0x100010, 0);
// [...]
CompareVoiceTa(
*(uint32_t *)plaintext,
plaintext + 4,
*(uint32_t *)(plaintext + 0x80004),
obuf1_addr);
// [...]
}int CompareVoiceTa(uint32_t label, uint8_t *inbuf, uint32_t inbuf_size, uint8_t *outbuf) {
// [...]
templ_data = g_voiceUserDb->templates[i].data;
// [...]
float0 = 0.0;
float1 = 0.0;
// [...]
compare(label, inbuf, inbuf_size, templ_data, templ_length, &float0, &float1);
// [...]
}
int compare(
uint32_t label,
uint8_t *inbuf,
uint32_t inbuf_size,
void *templ_data,
int templ_length,
float *float0_p,
float *float1_p)
{
// [...]
if ( ... ) {
SLog(
"%s %s: parms NULL !!! %p %d %p %d %p %p \n",
"[Error]",
"compare",
inbuf,
inbuf_size,
templ_data,
templ_length,
float0_p,
float1_p);
return -1;
}
// [...]
}
中的日志字符串将泄漏 4 个指针:restore
inbuf
,堆分配的缓冲区
templ_data
,另一个堆分配的缓冲区
float0_p
,指向堆栈变量的指针
float1_p
,另一个指向堆栈变量的指针
我们通过概念验证触发了此 bug,并获得了以下输出:
[TEE_SERVICE_VOICE_REC-1] [Error] compare: parms NULL !!! 0x6bed014 0 0x6950010 360000 0x694f9dc 0x694f9e0
DecryptData
¶函数中有一个 OOB 访问:DecryptData
int DecryptDataTa(int paramTypes, TEE_Param *params) {
// [...]
if (!params[1].memref.buffer
|| params[1].memref.size - 1 >= 0x32000
|| !params[2].memref.buffer
|| params[2].memref.size != 0x16
|| !params[3].memref.buffer) {
SLog("%s: VO_TEE_DECRYPT_DATA_CMD_ID: Bad expected parameter types.\n", "[Error]");
// [...]
}
// [...]
decrypt_data.value_a = params->value.a;
decrypt_data.src = params[1];
decrypt_data.dest = params[3];
decrypt_data.IV.memref.buffer = params[2].memref.buffer;
decrypt_data.IV.memref.size = 0x10;
// [...]
DecryptData(&decrypt_data);
// [...]
}int DecryptData(decrypt_data_t *decrypt_data) {
// [...]
size = decrypt_data->src.memref.size;
if (size > 0x32000) {
SLog("%s: bad parameter.\n", "[Error]");
// [...]
}
// [...]
dest = TEE_Malloc(size, 0);
TA_AesDecryptPKCS5(
g_aesKey,
0x10,
decrypt_data->IV.memref.buffer,
decrypt_data->IV.memref.size,
decrypt_data->src.memref.buffer,
size,
dest,
&size);
// [...]
if (size <= 4) {
SLog("%s: decrypt data len is too short.\n", "[Error]");
// [...]
}
// [...]
memmove_s(decrypt_data->dest.memref.buffer, size - 4, dest + 4, size - 4) )
// [...]
}
在 中,数据解密后,会将其复制到输出参数中。但是,用作调用的源和目标大小的大小是输出数据大小(上限为 0x32000)。从不检查输出参数的实际大小,并且可能小于输出数据大小,从而导致 OOB 写入访问。DecryptData
TEE_Param
memmove_s
TEE_Param
我们通过概念验证触发了此 bug,并获得了以下崩溃:
[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: 0x70038000, fault_code: 0x92000047
[HM]
[HM] Dump task states for tcb
[HM] ----------
[HM] name=[TEE_SERVICE_VOI] 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=10397 prefer-ca=10397
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] sp > fpDump task states END
[HM]
[HM] [TRACE][1212]pid=88 exit_status=130
SendTaGmmBuf
¶函数中存在堆缓冲区溢出:SendTaGmmBuf
int SendGmmBuf(uint8_t paramTypes, TEE_Param *params) {
// [...]
ibuf1_addr = params[1].memref.buffer;
ibuf1_size = params[1].memref.size;
if (params->value.a <= 0x680000 && ibuf1_addr && ibuf1_size <= 0x80000) {
params->memref.size = SendTaGmmBuf(params->value.a, ibuf1_addr, ibuf1_size);
return 0;
}
// [...]
}int SendTaGmmBuf(uint32_t totalLen, int buf, unsigned int bufLen) {
// [...]
if (!buf || bufLen > 0x680000) {
SLog("%s: buf is NULL or bufLen is too large\n", "[Error]");
return 0x7A000003;
}
if (!g_sendGmmBufCount) {
g_sendGmmBuf = TEE_Malloc(totalLen, g_sendGmmBufCount);
// [...]
g_sendGmmBufTotalLen = totalLen;
}
memcpy_s(g_sendGmmBuf + (g_sendGmmBufCount << 0x13), bufLen, buf, bufLen);
// [...]
++g_sendGmmBufCount;
// [...]
}
CA 使用该命令将模型发送到 TA。模型以块的形式发送。当收到第一个块时,TA 将分配一个缓冲区来存储完整的模型。模型的总大小以第一个的值给出。数据块位于第二个 .SendGmmBuf
TEE_Param
TEE_Param
不进行任何验证以确保块大小的总和不大于模型的总大小。因此,可以为模型大小指定一个小值(例如 8),并仅用一个块(例如大小为 8)溢出分配的缓冲区(例如大小为 > 8)。
我们通过概念验证触发了此 bug,并获得了以下崩溃:
[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: 0x10e8000, fault_code: 0x92000047
[HM]
[HM] Dump task states for tcb
[HM] ----------
[HM] name=[TEE_SERVICE_VOI] 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=10293 prefer-ca=10293
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] sp > fpDump task states END
[HM]
[HM] [ERROR][2171]vmem_as_ondemand_prepare failed
[HM] [ERROR][2496]process 2200000022 (tid: 34) data abort:
[HM] [ERROR][2498]Bad memory access on address: 0x8, fault_code: 0x92000046
[HM]
[HM] Dump task states for tcb
[HM] ----------
[HM] name=[TEE_SERVICE_VOI] tid=34 is-idle=0 is-curr=0
[HM] state=BLOCKED@MEMFAULT sched.pol=0 prio=49 queued=1
[HM] aff[0]=ff
[HM] flags=1000 smc-switch=0 ca=0 prefer-ca=0
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <?>+0x0/0x0
[HM] invalid fp. backtrace abort
[HM] Dump task states END
[HM]
在本节中,我们将演示如何利用影响 TEE_SERVICE_VOICE_REC Trustlet 的漏洞来控制执行流。为此,我们将使用函数中的信息泄漏和函数中的堆缓冲区溢出。compare
SendTaGmmBuf
我们开发漏洞利用的设备是运行固件更新的P40 Pro。ELS-LGRP4-OVS_11.0.0.223
trustlet 二进制 MD5 校验和如下:
HWELS:/ # md5sum /vendor/bin/859703f3-3cc5-4e88-b263-08f9ce82e3d0.sec
2e0202c5c2a3f28c55491e1bbb44a875 /vendor/bin/859703f3-3cc5-4e88-b263-08f9ce82e3d0.sec
华为TEE OS iTrustee实现了白名单机制,只允许特定的客户端应用(原生二进制文件或APK)与可信应用通信。
在我们的例子中,TEE_SERVICE_VOICE_REC TA 只能由原生二进制文件调用:
/vendor/bin/hw/[email protected]
(1000 元)
身份验证机制分 3 个部分实现:
实现 TEE 客户端 API 的守护进程检查哪个原生二进制文件/APK 正在与它通信,并将该信息发送到内核驱动程序;teecd
内核驱动程序确保它正在与 通信,并将其接收到的信息转发给 TEE OS;teecd
TEE OS 验证客户端应用程序是否在 TA 的白名单中。
由于我们不想费心在这些二进制文件之一中注入代码,因此我们选择通过修补内核驱动程序来规避身份验证,以添加模拟任何原生二进制文件/APK 的功能。
要触发函数中的信息泄露,我们需要克服几个障碍:compare
g_voiceUserDb
必须包含至少一个“模板”
命令的第三个输入参数必须包含 AES 加密的数据TEE_Param
Auth
compare
仅当模型哈希值与 2 个预期的 SHA256 哈希值之一匹配时,才会调用模型哈希值
为了克服第一个障碍,我们做了以下几点:
我们发送一个命令。此函数将调用 ,该函数将设置为 0(而不是其默认值:0xffffffff)SetActiveGroup
ResetVoiceDB(0)
g_voiceUserDb->user_id
我们发送一个命令,该命令也需要AES加密的数据。此函数将向 添加一个“模板”。Enroll
g_voiceUserDb
为了克服第二个障碍,我们滥用了 AES 密钥由命令初始化的事实。如果我们不调用这个函数,(位于 BSS 中)将全部为零。这使我们能够使用已知密钥加密我们的数据。g_decryptKey
GetEccPublicKey
g_decryptKey
为了克服第三个障碍,我们反转了与 trustlet () 通信的客户端应用程序,并发现可以在磁盘 中找到与 SHA256 哈希值对应的模型之一。voiceid_alg_ree.so
/odm/etc/audio/voiceid/imedia/msbc_vpu/china/plda_combine_model.dat
触发易受攻击的代码路径后,我们只需解析输出,查找类似于 .logcat
[TEE_SERVICE_VOICE_REC-1] [Error] compare: parms NULL !!! 0x6bed014 0 0x6950010 360000 0x694f9dc 0x694f9e0
有许多方法可以利用堆缓冲区溢出。在本演示中,我们选择执行在释放对象时触发的经典取消链接堆漏洞(在命令中)。此技术允许在内存中的任意位置写入受控的 dword 值。g_sendGmmBuf
Release
通过定位保存在堆栈上的链路寄存器,我们可以劫持 Trustlet 的执行流。我们计算了 的局部变量和 的保存链路寄存器之间的堆栈偏移量。然后,我们在结果地址上写入任意值,并观察到控制流被重定向。float0
CompareVoiceTa
TEE_Free
ReleaseTa
在漏洞利用中,我们使用 0x70000000,因为我们只能使用取消链接技术写入表示可写地址的值,但应该可以使用类似于我们在TEE_SERVICE_MULTIDRM漏洞中使用的技术来构造不受限制的任意写入。
以下是漏洞利用代码的输出,显示了泄露的堆栈地址:
adb wait-for-device shell su root sh -c "/data/local/tmp/tee_service_voice_rec"
stack_addr = 6b969dc
以及当控制流重定向时立即发生的崩溃:
[HM] ESR_EL1: 8200000f, ELR_EL1: 70000000, FAR is not valid
[HM] TEE_SERVICE_VOI vm fault prefetch abort: 70000000
[HM] fault: 8200000f tcb cref 2200000028
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <?>+0x0/0x0
[HM] [ERROR][2496]process 2200000028 (tid: 40) data abort:
[HM] [ERROR][2498]Bad memory access on address: 0x70000000, fault_code: 0x8200000f
[HM]
[HM] Dump task states for tcb
[HM] ----------
[HM] name=[TEE_SERVICE_VOI] 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=10335 prefer-ca=10335
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <?>+0x0/0x0
[HM] Dump task states END
[HM]
[HM] [TRACE][1212]pid=67 exit_status=130
我们验证了这些漏洞是否影响了以下设备:
麒麟990:P40 专业版 (ELS)
请注意,其他型号可能已受到影响。
名字 | 严厉 | CVE漏洞 | 补丁 |
---|---|---|---|
OOB 访问DecryptData | 危急 | CVE-2021-40036 漏洞-2021-40036 | 2022 年 1 月 |
堆缓冲区溢出SendTaGmmBuf | 危急 | CVE-2021-40010 漏洞 | 2022 年 1 月 |
OOB 访问restore | 高 | CVE-2021-40027 漏洞 | 2022 年 1 月 |
信息泄露compare | 中等 | CVE-2021-40032 漏洞-2021-40032 | 2022 年 1 月 |
信息泄露restore | 中等 | CVE-2021-40014 漏洞 | 2022 年 1 月 |
空指针取消引用CheckModelHash | 低 | 不适用 | 固定 |
2021年11月09日 - 向华为PSIRT发送漏洞报告。
2021年11月22日 - 华为PSIRT确认该漏洞报告。
2022年1月1日 - 华为PSIRT表示,这些问题已在2022年1月的更新中修复。
从 2022 年 11 月 30 日至 2023 年 7 月 19 日 - 我们定期交换有关公告发布的信息。
2023年6月13日 - 我们通知华为PSIRT,部分漏洞未修复。
2023年6月20日 - 华为PSIRT回复,将在2023年7月更新中修复。
二进制漏洞(更新中)
其它课程
windows网络安全防火墙与虚拟网卡(更新完成)
windows文件过滤(更新完成)
USB过滤(更新完成)
游戏安全(更新中)
ios逆向
windbg
恶意软件开发(更新中)
还有很多免费教程(限学员)
更多详细内容添加作者微信