二进制漏洞分析-11.华为TrustZone TEE_SERVICE_MULTIDRM漏洞(上)

admin 2023年11月26日13:42:30评论19 views字数 10806阅读36分1秒阅读模式

二进制漏洞分析-11.华为TrustZone TEE_SERVICE_MULTIDRM漏洞(上)

二进制漏洞分析-10.华为TrustZone TEE_SERVICE_VOICE_REC漏洞

此通报包含有关以下漏洞的信息:

  • CVE-2021-46881 漏洞 MDrm_TA_CMD_OEMCrypto_LoadKeys中的堆缓冲区溢出
  • CVE-2021-40034 漏洞 MDrm_TA_CMD_OEMCrypto_LoadEntitledContentKeys中的堆缓冲区溢出
  • CVE-2021-46882 漏洞-2021-46882 MDrm_TA_CMD_OEMCrypto_RefreshKeys中的堆缓冲区溢出
  • CVE-2021-46883 漏洞CVE-2021-46883 MDrm_TA_OEMCryptoUsageTable_LoadUsageTableHeader中的堆缓冲区溢出
  • CVE-2021-46884 漏洞 MDrm_TA_CMD_OEMCrypto_CopyBuffer中的 OOB 写入访问权限
  • CVE-2021-46885 漏洞CVE-2021-46885 MDrm_TA_CMD_Provision_GetRequest中的 OOB 读取访问权限
  • CVE-2021-46886 漏洞 MDrm_TA_CMD_OEMCrypto_RewrapDeviceRSAKey30中的 OOB 读取访问权限
  • CVE-2021-46814 漏洞 MDrm_TA_CMD_OEMCrypto_DecryptCENC中的 OOB 读取访问权限

华为TEE_SERVICE_MULTIDRM TA符合GlobalPlatform TEE Internal Core API。因此,它从位于正常环境中的客户端应用程序接收命令。这些命令由函数处理。TA_InvokeCommandEntryPoint

堆缓冲区溢出MDrm_TA_CMD_OEMCrypto_LoadEntitledContentKeys

第一个堆缓冲区溢出位于以下函数中:MDrm_TA_CMD_OEMCrypto_LoadEntitledContentKeys


int MDrm_TA_CMD_OEMCrypto_LoadEntitledContentKeys(int paramTypes, TEE_Param params[4]) {
    // [...]
    cur_ptr = params[0].memref.buffer;
    size_left = params[0].memref.size;
    uint32_t *alloc;
    // [...]

ret = MDrm_Utils_ReadU32(cur_ptr, size_left, &user1);
if (ret) goto EXIT;
cur_ptr += 4; size_left -= 4;

ret = MDrm_Utils_ReadU32(cur_ptr, size_left, &user2);
if (ret) goto EXIT;
cur_ptr += 4; size_left -= 4;

ret = MDrm_Memory_Calloc(8 * sizeof(uint32_t) * user2, &alloc);
if (ret) goto EXIT;

for (int i = 0; i != user2; ++i) {
for (int j = 0; j != 8; ++j) {
ret = MDrm_Utils_ReadU32(cur_ptr, size_left, &alloc[i*8+j]);
if (ret) goto EXIT;
cur_ptr += 4; size_left -= 4;
}
}

ret = MDrm_TA_CryptoKeyLadder_LoadEntitledContentKeys(
user1, &params[1], user2, alloc);
EXIT:
MDrm_free(alloc);
return ret;
}

调用 的第一个参数上存在整数溢出。是来自 中的缓冲区的用户控制值。例如,通过给出值,分配的大小将为 。MDrm_Memory_Callocuser2params[0]user20x800000010x80000001 * 0x20 = 0x20

user2然后用作循环计数器。循环的每次迭代都会从缓冲区读取 8 个整数值,并将其读取到堆分配的缓冲区中。由于分配的缓冲区小于预期,这允许攻击者使用受控数据溢出缓冲区。此外,还可以通过限制缓冲区的大小来控制溢出大小,当到达缓冲区末尾时,将返回错误代码。params[0]params[0]MDrm_Utils_ReadU32

我们通过概念验证触发了此 bug,并获得了以下崩溃:


[HM] [ERROR][2171]vmem_as_ondemand_prepare failed
[HM] [ERROR][2496]process 1f00000028 (tid: 40) data abort:
[HM] [ERROR][2498]Bad memory access on address: 0x1032000, fault_code: 0x92000047
[HM]
[HM] Dump task states for tcb
[HM] ----------
[HM]     name=[TEE_SERVICE_MUL] 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=7015 prefer-ca=7015
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <MDrm_TA_CMD_OEMCrypto_LoadEntitledContentKeys+0x19c/0x27c>
[HM] <MDrm_TA_CMD_OEMCrypto_LoadEntitledContentKeys>+0x174/0x27c
[HM] <tee_task_entry>+0x398/0xcd4
[HM] Dump task states END
[HM]
[HM] [ERROR][2171]vmem_as_ondemand_prepare failed
[HM] [ERROR][2496]process 1f00000022 (tid: 34) data abort:
[HM] [ERROR][2498]Bad memory access on address: 0x41414149, fault_code: 0x92000046
[HM]
[HM] Dump task states for tcb
[HM] ----------
[HM]     name=[TEE_SERVICE_MUL] 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]

可以利用此堆缓冲区溢出来获取任意读取和写入功能,我们将在本通报的“利用利用”部分中演示。

堆缓冲区溢出MDrm_TA_CMD_OEMCrypto_LoadKeys

可以在函数中找到类似的堆缓冲区溢出:MDrm_TA_CMD_OEMCrypto_LoadKeys


int MDrm_TA_CMD_OEMCrypto_LoadKeys(int paramTypes, TEE_Param params[4]) {
    // [...]
    cur_ptr = params[0].memref.buffer;
    size_left = params[0].memref.size;
    uint32_t *alloc;
    // [...]

// 5 calls to MDrm_Utils_ReadU32
// [...]

ret = MDrm_Utils_ReadU32(cur_ptr, size_left, &read_val);
if (ret) goto EXIT;
cur_ptr += 4; size_left -= 4;

ret = MDrm_Memory_Calloc(10 * sizeof(uint32_t) * read_val, alloc);
if (ret) goto EXIT;

for (i = 0; i != read_val; ++i) {
for (int j = 0; j != 10; ++j) {
ret = MDrm_Utils_ReadU32(cur_ptr, size_left, &alloc[i*10+j]);
if (ret) goto EXIT;
cur_ptr += 4; size_left -= 4;
}
}

// 5 calls to MDrm_Utils_ReadU32
// [...]
ret = MDrm_TA_CryptoKeyLadder_LoadKeys(...);
EXIT:
MDrm_free(alloc);
return ret;
}

根本原因与上一个 bug 完全相同。例如,通过赋予值 0x6666667,分配的大小将为 。read_val0x6666667 * 0x28 = 0x18

我们通过概念验证触发了此 bug,并获得了以下崩溃:


[HM] [ERROR][2171]vmem_as_ondemand_prepare failed
[HM] [ERROR][2496]process 1f00000028 (tid: 40) data abort:
[HM] [ERROR][2498]Bad memory access on address: 0x1281000, fault_code: 0x92000047
[HM]
[HM] Dump task states for tcb
[HM] ----------
[HM]     name=[TEE_SERVICE_MUL] 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=7265 prefer-ca=7265
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <MDrm_TA_CMD_OEMCrypto_LoadKeys+0x2d8/0x484>
[HM] <MDrm_TA_CMD_OEMCrypto_LoadKeys>+0x2b4/0x484
[HM] <tee_task_entry>+0x398/0xcd4
[HM] Dump task states END
[HM]
[HM] [ERROR][2171]vmem_as_ondemand_prepare failed
[HM] [ERROR][2496]process 1f00000022 (tid: 34) data abort:
[HM] [ERROR][2498]Bad memory access on address: 0x41414149, fault_code: 0x92000046
[HM]
[HM] Dump task states for tcb
[HM] ----------
[HM]     name=[TEE_SERVICE_MUL] 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]

堆缓冲区溢出MDrm_TA_CMD_OEMCrypto_RefreshKeys

可以在函数中找到类似的堆缓冲区溢出:MDrm_TA_CMD_OEMCrypto_RefreshKeys


int MDrm_TA_CMD_OEMCrypto_RefreshKeys(int paramTypes, TEE_Param params[4]) {
    // [...]
    cur_ptr = params[0].memref.buffer;
    size_left = params[0].memref.size;
    uint32_t *alloc;
    // [...]

// 1 call to MDrm_Utils_ReadU32
// [...]

ret = MDrm_Utils_ReadU32(cur_ptr, size_left, &read_val);
if (ret) goto EXIT;
cur_ptr += 4; size_left -= 4;

ret = MDrm_Memory_Calloc(6 * sizeof(uint32_t) * read_val, alloc);
if (ret) goto EXIT;

for (i = 0; i != read_val; ++i) {
for (int j = 0; j != 6; ++j) {
ret = MDrm_Utils_ReadU32(cur_ptr, size_left, &alloc[i*6+j]);
if (ret) goto EXIT;
cur_ptr += 4; size_left -= 4;
}
}

ret = MDrm_TA_CryptoKeyLadder_RefreshKeys(...);
EXIT:
MDrm_free(alloc);
return ret;
}

根本原因与上一个 bug 完全相同。例如,通过赋予值 0x6666667,分配的大小将为 。read_val0x80000001 * 0x18 = 0x18

我们通过概念验证触发了此 bug,并获得了以下崩溃:


[HM] [ERROR][2171]vmem_as_ondemand_prepare failed
[HM] [ERROR][2496]process 1f00000028 (tid: 40) data abort:
[HM] [ERROR][2498]Bad memory access on address: 0xc96000, fault_code: 0x92000047
[HM]
[HM] Dump task states for tcb
[HM] ----------
[HM]     name=[TEE_SERVICE_MUL] 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=7182 prefer-ca=7182
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <MDrm_TA_CMD_OEMCrypto_RefreshKeys+0x1b4/0x23c>
[HM] <MDrm_TA_CMD_OEMCrypto_RefreshKeys>+0x18c/0x23c
[HM] <tee_task_entry>+0x398/0xcd4
[HM] Dump task states END
[HM]
[HM] [ERROR][2171]vmem_as_ondemand_prepare failed
[HM] [ERROR][2496]process 1f00000022 (tid: 34) data abort:
[HM] [ERROR][2498]Bad memory access on address: 0x41414149, fault_code: 0x92000046
[HM]
[HM] Dump task states for tcb
[HM] ----------
[HM]     name=[TEE_SERVICE_MUL] 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]

堆缓冲区溢出MDrm_TA_OEMCryptoUsageTable_LoadUsageTableHeader

可以在函数中找到类似的堆缓冲区溢出:MDrm_TA_OEMCryptoUsageTable_LoadUsageTableHeader


int MDrm_TA_OEMCryptoUsageTable_LoadUsageTableHeader(TEE_Param *param1, uint32_t *a2) {
    // [...]
    // HMAC/SHA256 verification and AES/CBC/PKCS5 decryption of param1

cur_ptr = plaintext.memref.buffer;
size_left = plaintext.memref.size;
uint64_t *alloc;

// [...]
// 1 call to read_u64_update_ptrs

ret = MDrm_Utils_ReadU32(cur_ptr, size_left, &read_val);
if (ret) goto EXIT;
cur_ptr += 4; size_left -= 4;

ret = MDrm_Memory_Calloc(sizeof(uint64_t) * read_val, alloc);
if (ret) goto EXIT;

for (i = 0; !ret && i != read_val; ++i)
ret = read_u64_update_ptrs(&cur_ptr, &size_left, &alloc[i]);

// [...]
// loading of the usage table header
EXIT:
return ret;
}

根本原因与上一个 bug 完全相同。例如,通过赋予值 0x20000001,分配的大小将为 。read_val0x20000001 * 8 = 8

不幸的是,我们无法通过概念验证来触发此错误,因为输入数据首先经过身份验证和解密,并且我们没有找到一种独立的方法来泄漏这些操作中使用的密钥(但我们可以使用另一个错误来做到这一点)。

OOB 写入访问权限MDrm_TA_CMD_OEMCrypto_CopyBuffer

函数中有一个 OOB 写入访问权限,可以通过函数中开始的代码路径访问:MDrm_TA_Decryption_CopyBufferMDrm_TA_CMD_OEMCrypto_CopyBuffer


int MDrm_TA_CMD_OEMCrypto_CopyBuffer(int paramTypes, TEE_Param params[4]) {
    // [...]
    cur_ptr = params[0].memref.buffer;
    size_left = params[0].memref.size;
    memset_s(&cpybuf, 0x10, 0, 0x10);
    // [...]

ret = MDrm_Utils_ReadU32(cur_ptr, size_left, &session_id);
// [...]

ret = MDrm_Utils_ReadU32(cur_ptr, size_left, &cpybuf.type);
// [...]

ret = MDrm_TA_ION_MemoryMap(
params[1].memref.buffer, params[1].memref.size, 1, 1, &ionbuf);
// [...]

switch (cpybuf.type) {
// [...]
case 1:
cpybuf.buf = params[2];
ret = MDrm_Utils_ReadU32(cur_ptr, size_left, &cpybuf.offset);
// [...]
break;
// [...]
}

ret = MDrm_Utils_ReadU32(cur_ptr, size_left, &val_read);
// [...]

MDrm_TA_Decryption_CopyBuffer(session_id, &ionbuf, &cpybuf, val_read);
// [...]

if (ionbuf.memref.buffer)
ret = MDrm_TA_ION_MemoryUnMap(
ionbuf.memref.buffer, ionbuf.memref.size, 1, params[1].memref.buffer);

return ret;
}

MDrm_TA_CMD_OEMCrypto_CopyBuffer从缓冲区中读取两个整数值和 。然后,它将 ION 缓冲区映射到 .如果为 1,则从缓冲区读取另一个整数值 。最后,它读取另一个值并调用 .session_idcpybuf.typeparams[0]params[1]ionbufcpybuf.typecpybuf.offsetparams[0]MDrm_TA_Decryption_CopyBuffer


int MDrm_TA_Decryption_CopyBuffer(int session_id, TEE_Param *ionbuf, copy_buf_t *cpybuf, int val_read) {
    // [...]
    TEE_Param outbuf;
    // [...]

is_type_2 = 0;
ret = check_copybuf(ionbuf, cpybuf, &outbuf, &is_type_2);

if (!ret && !is_type_2) {
if (MDrm_TA_OEMCryptoEngine_FindSession(session_id, &session)) {
memcpy_s(outbuf.memref.buffer, ionbuf->memref.size,
ionbuf->memref.buffer, ionbuf->memref.size);
} else {
// [...]
}
}

// [...]
return ret;
}

MDrm_TA_Decryption_CopyBuffer调用。如果检查成功且不是 2,则调用 。如果会话 ID 无效,则它将从 into 复制,大小为 。是输入参数之一,所以让我们看看如何设置 .check_copybufcpybuf.typeMDrm_TA_OEMCryptoEngine_FindSessionionbuf->memref.bufferoutbuf.memref.bufferionbuf->memref.sizeionbufcheck_copybufoutbuf


int check_copybuf(TEE_Param *ionbuf, copy_buf_t *cpybuf, TEE_Param *outbuf, uint8_t *is_type_2) {
    // [...]
    TEE_Param *out_ionbuf;
    // [...]

if (cpybuf.type == 1) {
cpybuf_buf = cpybuf->buf.memref.buffer;
cpybuf_size = cpybuf->buf.memref.size;

if (ionbuf->memref.size + cpybuf->offset <= cpybuf_size) {
outbuf->memref.size = ionbuf->memref.size;

is_secure_mem = 0;
ret = MDrm_TA_ION_IsSecureMemory(cpybuf_buf, cpybuf_size, &is_secure_mem);
if (!is_secure_mem)
return 0x77771001;

ret = MDrm_TA_ION_MemoryMap(cpybuf_buf, cpybuf_size, 0, 1, &out_ionbuf);
outbuf->memref.buffer = out_ionbuf->memref.buffer + cpybuf->offset;
return ret;
}
return 0x77770007;
}
// [...]
return ret;
}

check_copybuf,如果类型为 1,则检查 + <= 。它还确保从安全内存中分配。然后,它调用将其映射到 .最后,设置为 + 和 。ionbuf->memref.sizecpybuf->offsetcpybuf->buf.memref.sizecpybufMDrm_TA_ION_MemoryMapout_ionbufoutbuf->memref.bufferout_ionbuf->memref.buffercpybuf->offsetoutbuf->memref.sizeionbuf->memref.size

问题是加法时可能出现的整数溢出。例如,如果大小为 0x1000,则通过指定偏移量 ,总和将为 。因此,可以指定一个“负”偏移量,该偏移量将通过检查,从而位于 之前。ionbuf->memref.size + cpybuf->offset-0x1000 = 0xfffff0000x1000 + 0xfffff000 = 0outbuf->memref.bufferout_ionbuf->memref.buffer

回到 ,将 copy from 复制到我们的 out-of-bounds 值中。MDrm_TA_Decryption_CopyBuffermemcpy_sionbuf->memref.bufferoutbuf.memref.buffer

虽然我们为此错误编写了一些概念证明,但它并没有触发崩溃。我们认为这是在内存之前/内存中映射的,并且由于我们只能在范围内指定偏移量,因此 OOB 访问发生在映射的内存上。ionbufcpybufoutbuf[ -(ionbuf->memref.size) ; cpybuf_size - ionbuf->memref.size ]

原文始发于微信公众号(安全狗的自我修养):二进制漏洞分析-11.华为TrustZone TEE_SERVICE_MULTIDRM漏洞(上)

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年11月26日13:42:30
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   二进制漏洞分析-11.华为TrustZone TEE_SERVICE_MULTIDRM漏洞(上)https://cn-sec.com/archives/2240991.html

发表评论

匿名网友 填写信息