​二进制漏洞分析-20.TrustZone Task_Phone_Novelchd漏洞(下)

​二进制漏洞分析-20.TrustZone Task_Phone_Novelchd漏洞(下)

原创 haidragon 安全狗的自我修养 2023-12-10 11:00

二进制漏洞分析-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漏洞

缺少长度和偏移检查NOVEL_CHDRM_Copyordecrypt¶

该函数从用户提供的输入缓冲区中提取偏移量和长度,对其进行字节交换,并使用它们执行各种操作。特别是,许多对这些用户控制值的参数的调用和从这些用户控制的值派生的参数会导致 ION 缓冲区 OOB 写入、ION 缓冲区 OOB 读取,甚至堆栈缓冲区溢出。NOVEL_CHDRM_CopyordecryptTEE_ParamNOVEL_CHDRMw_MemcpyDRM_SVP_AES

例如,下面是基于堆栈的缓冲区溢出:

int NOVEL_CHDRM_Copyordecrypt(TEE_Param params[4]) {
    /* [...] */
    int userprovided_license[4];

    /* [...] */
    ibuf3_addr = (uint32_t *)params[3].memref.buffer;
    ibuf3_int0 = bswap32(ibuf3_addr[0]); /* user-controlled */
    ibuf3_int1 = bswap32(ibuf3_addr[1]); /* user-controlled */
    if (ibuf3_int1)
        /* Stack-based buffer overflow */
        NOVEL_CHDRMw_Memcpy(userprovided_license, ibuf3_addr + 2, ibuf3_int1);
    /* [...] */
}

以下是导致 ION 缓冲区 OOB 写入/OOB 读取的调用之一:NOVEL_CHDRMw_Memcpy

int NOVEL_CHDRM_Copyordecrypt(TEE_Param params[4]) {
    /* [...] */
    NOVEL_CHDRM_Mmap(params[0].memref.buffer, 0, params[0].memref.size, &ionmap0_addr, 1, 1);
    NOVEL_CHDRM_Mmap(params[2].memref.buffer, 0, params[0].memref.size, &ionmap2_addr, ibuf3_int0 == 0, 1);

    /* [...] */
    ibuf3_curr_ptr = ibuf3_addr + 7;
    before_datalen = 0;
    before_offset = 0;
    while (1) {
        offset = bswap32(ibuf3_curr_ptr[0]); /* user-controlled */
        datalen = bswap32(ibuf3_curr_ptr[1]); /* user-controlled */

        /* [...] */
        if (before_datalen + before_offset != offset)
            /* ION buffer OOB write/OOB read */
            NOVEL_CHDRMw_Memcpy(
                ionmap2_addr + before_datalen + before_offset,
                ionmap0_addr + before_datalen + before_offset,
                offset - (before_datalen + before_offset));

        /* [...] */
        before_datalen = datalen;
        before_offset = offset;
    }
    /* [...] */
}

以下是导致 ION 缓冲区 OOB 写入/OOB 读取的调用之一:DRM_SVP_AES

int NOVEL_CHDRM_Copyordecrypt(TEE_Param params[4]) {
    /* [...] */
    ibuf3_curr_ptr = ibuf3_addr + 7;
    tmp_enc_datalen = 0;
    global_offset = 0;
    while (1) {
        offset = bswap32(ibuf3_curr_ptr[0]); /* user-controlled */
        datalen = bswap32(ibuf3_curr_ptr[1]); /* user-controlled */

        if (tmp_enc_datalen) {
            /* [...] */
        } else {
            enc_datalen = datalen;
        }

        if ((flag & 2) != 0) {
            /* [...] */
        } else if (enc_datalen <= 0xF) {
            /* [...] */
        } else {
            tmp_enc_datalen = enc_datalen & 0xF;
            if ((enc_datalen & 0xF) != 0) {
                /* [...] */
            } else {
                /* ION buffer OOB write/OOB read */
                DRM_SVP_AES(aes_op, aes_obj, flag,
                            ionmap0_addr + offset + global_offset,
                            ionmap2_addr + offset + global_offset,
                            enc_datalen);
                /* [...] */
            }
        }
    }
    /* [...] */
}

我们确定了 7 个易受攻击的调用:NOVEL_CHDRMw_MemcpyNOVEL_CHDRM_Copyordecrypt

地址 访客 冲击
0x8204 NOVEL_CHDRM_Copyordecrypt+108 基于堆栈的缓冲区溢出(如果大小为 > 0x10)
0x8584 NOVEL_CHDRM_Copyordecrypt+488 ION 缓冲液 OOB 写入/OOB 读取
0x85c0 NOVEL_CHDRM_Copyordecrypt+4C4 ION 缓冲液 OOB 读取
0x8634 NOVEL_CHDRM_Copyordecrypt+538 ION 缓冲液 OOB 写入
0x864c NOVEL_CHDRM_Copyordecrypt+550 ION 缓冲液 OOB 写入
0x8724 NOVEL_CHDRM_Copyordecrypt+628 ION 缓冲液 OOB 读取
0x8850 NOVEL_CHDRM_Copyordecrypt+754 ION 缓冲液 OOB 读取

我们还确定了 7 个易受攻击的调用:DRM_SVP_AESNOVEL_CHDRM_Copyordecrypt

地址 访客 冲击
0x86a4 NOVEL_CHDRM_Copyordecrypt+5A8 ION 缓冲液 OOB 写入/OOB 读取
0x8758 NOVEL_CHDRM_Copyordecrypt+65摄氏度 ION 缓冲液 OOB 写入/OOB 读取
0x87e8 NOVEL_CHDRM_Copyordecrypt+6ec ION 缓冲液 OOB 写入/OOB 读取

缺少长度签入NOVEL_CHDRM_SetDRMCertData¶

NOVEL_CHDRM_SetDRMCertData
从输入缓冲区中提取两个证书。在不检查其大小的情况下,该函数会将它们复制到两个堆栈分配的缓冲区中,分别为 和 。这可能导致基于堆栈的缓冲区溢出。server_Cert_pserver_CACert_pTEE_Paramserver_Cert_cpy_lenserver_CACert_cpy

int NOVEL_CHDRM_SetDRMCertData(int *ibuf0_addr, unsigned int ibuf0_size) {
    char server_CACert_cpy[2048];
    char server_Cert_cpy[2048];
    /* [...] */
    ibuf0_size_cpy = ibuf0_size;
    unpack_tlv_data(0x18, ibuf0_addr, &ibuf0_size_cpy, &server_Cert_p,
        &server_Cert_len);
    unpack_tlv_data(0x19, ibuf0_addr, &ibuf0_size_cpy, &server_CACert_p,
        &server_CACert_len);
    /* [...] */
    if (server_CACert_p)
        NOVEL_CHDRMw_Memcpy(server_CACert_cpy, server_CACert_p, server_CACert_len);
    /* [...] */
    if (server_Cert_p)
        NOVEL_CHDRMw_Memcpy(server_Cert_cpy, server_Cert_p, server_Cert_len);
    /* [...] */
}

缺少长度签入NOVEL_CHDRM_SetRegisterResData¶

包含相同的漏洞模式两次:NOVEL_CHDRM_SetRegisterResData
– 它从输入缓冲区中的 TLV 数据中解析用户控制大小的缓冲区TEE_Param

  • 它调用此缓冲区作为输入,将固定大小的缓冲区作为输出DRM_AES_Encrypt_cbc
uint8_t Root_CA_Cert[0x800]; /* in the BSS */

int NOVEL_CHDRM_SetRegisterResData(uint8_t *ibuf0_addr, uint32_t ibuf0_size) {
    char aes_outbuf[0x800];
    /* [...] */
    unpack_tlv_data(0x21, ibuf0_addr, &ibuf0_size, &aes_inbuf, &aes_inbuf_size);
    /* [...] */
    /* 1st Stack-based buffer overflow */
    DRM_AES_Encrypt_cbc(aes_inbuf, aes_inbuf_size, aes_outbuf, aes_key, aes_iv, 1);

    /* [...] */
    unpack_tlv_data(0x42, ibuf0_addr, &ibuf0_size, &root_ca_inbuf, &root_ca_inbuf_len);
    /* [...] */
    /* 2nd Stack-based buffer overflow */
    DRM_AES_Encrypt_cbc(root_ca_inbuf, root_ca_inbuf_len, Root_CA_Cert, aes_key, aes_iv, 1);
    /* [...] */
}

此模式可能导致缓冲区溢出:
– 对堆栈分配的缓冲区进行溢出的第一次调用DRM_AES_Encrypt_cbc

  • 第二次调用溢出位于 BSS 中的缓冲区DRM_AES_Encrypt_cbc

呼叫时长度检查缺失/错误NOVEL_CHDRMw_MemCompare¶

该函数不检查它传递到的用户控制的长度,允许在 BSS 缓冲区之后逐字节泄漏数据。NOVEL_CHDRM_SetRegisterResDataNOVEL_CHDRMw_MemComparesg_salt

int NOVEL_CHDRM_SetRegisterResData(int *ibuf0_addr, unsigned int ibuf0_size) {
    /* [...] */
    unpack_tlv_data(0x34, ibuf0_addr, &ibuf0_size, &salt_des, &salt_des_size);
    if (NOVEL_CHDRMw_MemCompare(salt_des, &sg_salt, salt_des_size)) {
        /* [...] */
        return 0xFFFF0000;
    }
    /* [...] */
}

该函数检查传递给 的用户控制的长度,但仅在调用之后,从而导致 BSS 缓冲区的过度读取。NOVEL_CHDRM_SetDrmTimeNOVEL_CHDRMw_MemCompareOTPChipIDHex

int NOVEL_CHDRM_SetDrmTime(uint8_t *ibuf0_addr, uint32_t ibuf0_size) {
    /* [...] */
    if (!unpack_tlv_data(0x36, ibuf0_addr, &ibuf0_size, &chipid_p, &chipid_size)
          && (NOVEL_CHDRMw_MemCompare(chipid_p, &OTPChipIDHex, chipid_size) || chipid_size != 0x20) ) {
        /* [...] */
    }
    /* [...] */
}

我们在调用时发现了 11 个缺失/错误的长度检查(它们可能并非都可被利用):NOVEL_CHDRMw_MemCompare

地址 访客 冲击
0x2ef8 NOVEL_CHDRM_GetLicenseReqData+5d0 缓冲区过度读取
0x3554 NOVEL_CHDRM_DelLicenseReqData+228 缓冲区过度读取
0x40a0 NOVEL_CHDRM_SetDRMDataLicenseResData+A4C 缓冲区过度读取
0x50d8 NOVEL_CHDRM_SetDRMDataLicenseResData+1A84 缓冲区过度读取
0x7464 NOVEL_CHDRM_SetRegisterResData+D4 缓冲区过度读取
0x8c40 NOVEL_CHDRM_SetDrmTime+E4 缓冲区过度读取
0x35c80 DRM_DelLicenseForCEKID+38 缓冲区过度读取
0x35d38 DRM_FindLicenseForCEKID+38 缓冲区过度读取
0x35fd4 DRM_FindLicenseForCEKIDExt+38 缓冲区过度读取
0x36170 DRM_FindLocalLicenseByCEKID+24 缓冲区过度读取
0x36388 DRM_FindMemLicenseByCEKID+24 缓冲区过度读取

调用后缺少长度检查unpack_tlv_data¶

该函数包含易受攻击模式的实例之一:NOVEL_CHDRM_SetDrmTime
– 使用以下命令从输入缓冲区中解压缩值TEE_Paramunpack_tlv_data

  • 该值的使用方式就好像它的大小为 >= 4(在此示例中)
int NOVEL_CHDRM_SetDrmTime(uint8_t *ibuf0_addr, uint32_t ibuf0_size) {
    /* [...] */
    unpack_tlv_data(1, ibuf0_addr, &ibuf0_size_, &data_p, &length_p);
    /* [...] */
    FUN(
        "%s %s: drmtime[%x],[%x],[%x]\n\n",
        "[Warning]",
        "NOVEL_CHDRM_SetDrmTime",
        data_p[0],
        data_p[1]),
        data_p[2]));
    DRM_Set_CurTime(*(uint32_t *)data_p);
    /* [...] */
}

由于从不检查读取值的实际长度(例如,当预期有四个字节时读取单个字节),这可能会导致堆缓冲区过度读取(因为解压缩的值是从堆中分配的)。

在调用 后,我们发现了 6 个缺失的长度检查,所有这些检查都可能导致堆缓冲区过度读取:unpack_tlv_data

地址 访客 冲击
0x2bb8 NOVEL_CHDRM_GetLicenseReqData+290 堆分配的缓冲区过度读取
0x3418 NOVEL_CHDRM_DelLicenseReqData+欧共体 堆分配的缓冲区过度读取
0x39dc NOVEL_CHDRM_SetDRMDataLicenseResData+388 堆分配的缓冲区过度读取
0x74ac NOVEL_CHDRM_SetRegisterResData+11c 堆分配的缓冲区过度读取
0x79a8 NOVEL_CHDRM_SetRegisterResData+618 堆分配的缓冲区过度读取
0x8b98 NOVEL_CHDRM_SetDrmTime+3c 堆分配的缓冲区过度读取

堆栈/堆/BSS 指针泄漏DRM_AES_Encrypt_xxx¶

受信任的应用程序中使用了多个日志字符串,这些字符串泄漏了有关地址空间的信息,例如堆栈指针、堆指针和 bss 指针。

下面是函数中此漏洞的一个实例的示例。DRM_AES_Encrypt_cbc

TEE_Result DRM_AES_Encrypt_cbc(
        char *buffer_input,
        int buffer_len,
        char *buffer_output,
        const void *key,
        int iv,
        int mode)
{
    /* [...] */
    tee_print(0,
        "%s %d:buffer_input = %p, buffer_len = %d,buffer_output = %p,key = %p ",
        "[error]",
        0x195,
        buffer_input,
        buffer_len,
        buffer_output,
        key);
    /* [...] */
}

此函数泄漏作为参数传递的指针,从而生成可从 读取的日志。有关可能泄漏的指针类型的示例,我们可以查看对 in 的调用。tee_printlogcatDRM_AES_Encrypt_cbcNOVEL_CHDRM_SetRegisterResData

uint8_t Root_CA_Cert[0x800]; /* in the BSS */

int NOVEL_CHDRM_SetRegisterResData(uint8_t *ibuf0_addr, uint32_t ibuf0_size) {
    /* [...] */
    uint8_t aes_key[8];
    /* [...] */
    unpack_tlv_data(0x42, ibuf0_addr, &ibuf0_size, &root_ca_inbuf, &root_ca_inbuf_len);
    /* [...] */
    DRM_AES_Encrypt_cbc(root_ca_inbuf, root_ca_inbuf_len, Root_CA_Cert, aes_key, aes_iv, 1);
    /* [...] */
}

NOVEL_CHDRM_SetRegisterResData
将以下参数传递给:DRM_AES_Encrypt_cbc
– buffer_input => root_ca_inbuf
,指向由unpack_tlv_data;

  • buffer_output => Root_CA_Cert
    ,指向 BSS 缓冲区的指针;

  • key => aes_key
    ,指向堆栈分配的缓冲区的指针。

我们发现了 3 个泄漏指针的日志字符串(它们可能并非都可访问):

地址 访客 冲击
0x33218 DRM_AES_Encrypt_ecb+54 堆栈指针泄漏
0x3333C DRM_AES_Encrypt_cbc+54 堆栈/堆/BSS 指针泄漏
0x33454 DRM_AES_Encrypt_ctr+4C 指针泄漏

整数下溢输入unpack_tlv_data¶

unpack_tlv_data
通过遍历输入缓冲区包含的 TLV 对象,从输入缓冲区中提取值。

对于每个 TLV 对象,它检索长度和指向嵌入其中的数据的指针。然后,它会检查与对象关联的标记:
– 如果它与参数匹配,则迭代将停止,并在提取的数据复制到其中之前分配堆缓冲区;tag

  • 否则,它将继续输入缓冲区内的下一个 TLV 对象,直到剩余大小等于或小于 4 个字节。
int unpack_tlv_data(
        uint32_t tag,
        uint8_t *inbuf,
        uint32_t *insize_p,
        void **data_p_p,
        uint32_t *length_p)
{
    /* [...] */
    // Remaining size in the input buffer
    uint32_t remaining_size = *insize_p;
    curr_offset = 0;
    while (1) {
        // TLV object length
        data_length = bswap32(*(uint32_t *)&inbuf[curr_offset + 1]);
        // TLV object data offset
        data_offset = curr_offset + 5;
        // Returns if the tag was found
        if (inbuf[curr_offset] == tag)
            break;
        // Computes the new data offset
        curr_offset = data_length + data_offset;
        /*
         * Computes how many bytes remain in the input buffer
         * The integer underflow occurs here.
         */
        remaining_size += -5 - data_length;
        // Unsigned comparison unable to detect the underflow
        if (remaining_size <= 4)
            return 0x7FFF0001;
    }
    if (data_length) {
        data_p = NOVEL_CHDRMw_Malloc(data_length);
        *data_p_p = data_p;
        NOVEL_CHDRMw_Memcpy(data_p, &inbuf[data_offset], data_length);
    }
    *length_p = data_length;
    return 0;
}

但是,对剩余大小的比较是无符号的,并且没有考虑整数下溢。当数据长度和标头的大小从 中 的剩余大小中减去时,可能会变为负数。remaining_size += -5 – data_lengthremaining_size

例如,在循环开始时,如果:
– inbuf
尺寸为 0x100;

  • remaining_size
    是0x10;

  • curr_offset
    是0xf0;

  • 从输入缓冲区中提取的是 。data_length0x1000

更新这些值后,我们将得到:
– curr_offset
= 0xf0 + 5 + 0x1000 = 0x10f5;

  • remaining_size
    = 0x10 – 5 – 0x1000 = 0xfffff00b。

我们可以看到,由于 上的比较是无符号的,因此它将通过检查,因为 .然后,循环将继续迭代,并开始提取之前输入之外的值。例如,长度将在 address 读取,这超出了我们的 0x100 字节输入缓冲区的范围。remaining_size0xfffff00b > 4inbuf + curr_offset + 1 = inbuf + 0x10f6

整数下溢输入find_tlv_data¶

find_tlv_data
执行与 相同的操作,只是它不分配缓冲区来将 TLV 对象复制到其中。相反,它返回对象的偏移量和长度。因此,它容易受到与上一节中解释的漏洞类型相同的漏洞的影响。unpack_tlv_data

int find_tlv_data(
        uint32_t tag,
        uint8_t *inbuf,
        uint32_t *inbuf_size_p,
        uint32_t *data_offset_p,
        uint32_t *data_length_p)
{
    /* [...] */
    // Remaining size in the input buffer
    uint32_t remaining_size = *inbuf_size_p;
    curr_offset = 0;
    do {
        // TLV object length
        data_length = bswap32(*(uint32_t *)(inbuf + curr_offset + 1));
        // Check the TLV object type
        if (*(uint8_t *)(inbuf + curr_offset) == tag) {
            // Return the TLV object offset and length
            *data_offset_p = curr_offset;
            *data_length_p = data_length + 5;
            return 0;
        }
        /*
         * Computes how many bytes remain in the input buffer
         * The integer underflow occurs here.
         */
        curr_offset += data_length + 5;
        remaining_size += -5 - data_length;
    } while (remaining_size > 4); // Unsigned comparison unable to detect the underflow
    return 0x7FFF0001;
}

OOB 访问getvaluewithtypeandindex¶

在该函数中,可以有多个 OOB 访问:getvaluewithtypeandindex
– 在 ,由于(16 位值)由用户控制,因此访问最多可以超出 0xffff 个字节[1]key_length

  • 在 ,由于(8 位值)由用户控制,因此访问最多可以超出 0xff 个字节[2]rule_offset

  • at 和 ,因为(8 位值)和(8 位值)是用户控制的,所以访问最多可以越界 0xffff 字节[3][4]rule_offsetrule_count

  • 在 ,由于(8 位值)由用户控制,因此访问最多可以超出 0xff 个字节[5]some_offset

int getvaluewithtypeandindex(
        int type,
        int key_type,
        int inbuf,
        int insize,
        void *data_buf,
        unsigned int *length_p)
{
    /* [...] */
    offset = 0;
    value_offset = 0;
    value_length = 0;

    while (1) {
        // Outer TLV length
        length = bswap16(*(uint16_t *)(inbuf + offset + 2));

        // Check the outer TLV type
        if (*(uint8_t *)(inbuf + offset) == type) {
            value_offset = offset + 4;

            if (type == 1) {
                value_length = *(uint8_t *)(inbuf + value_offset + 8) + 9;
                goto FOUND_IT;

            } else if (type == 3) {
                key_length = bswap16(*(uint16_t *)(inbuf + value_offset + 1));
                /* [1]: The access below can be OOB by a maximum of 0xffff bytes */
                if (*(uint8_t *)(inbuf + value_offset + key_length + 3) == key_type) {
                    value_offset += 3;
                    value_length = key_length;
                    goto FOUND_IT;
                }

            } else if (type == 4) {
                rule_offset = *(uint8_t *)(inbuf + value_offset + 1);
                /* [2]: The access below can be OOB by a maximum of 0xff bytes */
                rule_count = *(uint8_t *)(inbuf + value_offset + rule_offset + 2);
                for (int i = 0; i /*signed*/< rule_count: i++) {
                    rule = inbuf + value_offset + rule_offset;
                    /* [3]: The access below can be OOB by a maximum of 0xffff bytes */
                    rule_type = *(uint8_t *)(rule + 3);
                    /* [4]: The access below can be OOB by a maximum of 0xffff bytes */
                    rule_length = *(uint8_t *)(rule + 4);
                    if (key_type == rule_type) {
                        value_offset = rule_offset + 5;
                        value_length = rule_length;
                        goto FOUND_IT;
                    }
                    rule_offset += 2 + rule_length;
                }

            } else if (type == 0xff) {
                if (key_type) {
                    some_offset = *(uint8_t *)(inbuf + value_offset + 1);
                    /* [5]: The access below can be OOB by a maximum of 0xff bytes */
                    value_length = *(uint8_t *)(inbuf + value_offset + some_offset + 3);
                    value_offset += 4 + some_offset;
                } else {
                    value_length = offset;
                    value_offset = 0;
                }
                goto FOUND_IT;
            }
        }

        // Increment the current offset
        offset += 4 + length;
        if (offset /*signed*/>= insize) {
            /* [...] */
            return -1;
        }
    }

FOUND_IT:
    // Copy the value into the argument buffer
    if (value_offset + value_length /*signed*/<= insize) {
        NOVEL_CHDRMw_Memcpy(data_buf, inbuf + value_offset, value_length);
        *length_p = value_length;
        return 0;
    } else {
        /* [...] */
        return 0xFFFFFFFD;
    }
}

未经检查的 Malloc 返回值¶

在 中,不检查调用的返回值(这是包装器)。因此,如果分配失败(例如,如果系统内存不足),则在使用此指针时将导致空指针取消引用。unpack_tlv_dataNOVEL_CHDRMw_MallocTEE_Malloc

int unpack_tlv_data(
        uint32_t tag,
        uint8_t *inbuf,
        uint32_t *insize_p,
        void **data_p_p,
        uint32_t *length_p)
{
    /* [...] */
    // Remaining size in the input buffer
    uint32_t remaining_size = *insize_p;
    curr_offset = 0;
    while (1) {
        // TLV object length
        data_length = bswap32(*(uint32_t *)&inbuf[curr_offset + 1]);
        // TLV object data offset
        data_offset = curr_offset + 5;
        // Returns if the tag was found
        if (inbuf[curr_offset] == tag)
            break;
        // Computes the new data offset
        curr_offset = data_length + data_offset;
        /*
         * Computes how many bytes remain in the input buffer
         * The integer underflow occurs here.
         */
        remaining_size += -5 - data_length;
        // Unsigned comparison unable to detect the underflow
        if (remaining_size <= 4)
            return 0x7FFF0001;
    }
    if (data_length) {
        data_p = NOVEL_CHDRMw_Malloc(data_length);
        *data_p_p = data_p;
        NOVEL_CHDRMw_Memcpy(data_p, &inbuf[data_offset], data_length);
    }
    *length_p = data_length;
    return 0;
}

此外,还可以通过将负长度传递给 (在这种情况下,长度由用户控制)来强制分配失败。 然后,将作为指向 null 指针的指针,并在下次使用时崩溃。调用(这是包装器)也会失败,因为长度为负数,但由于其返回值也未选中,因此该函数将成功返回。NOVEL_CHDRMw_Mallocdata_p_pNOVEL_CHDRMw_Memcpymemcpy_s

我们发现了 10 个未检查返回值的实例:NOVEL_CHDRMw_Malloc

地址 访客 冲击
0x1858 NOVEL_CHDRM_GetSecurityReqData+2直流 空指针取消引用
0x6e54 NOVEL_CHDRM_GetRegisterReqData+160 空指针取消引用
0x1c7c NOVEL_CHDRM_GetSignature+178 空指针取消引用
0x35ae4 DRM_NewLicense+C 空指针取消引用
0x371c8 Secure_Store_DataDecrypt+60 空指针取消引用
0x374f8 Secure_Store_DataEncrypt+cc 空指针取消引用
0x37d30 Secure_Store_EncryptWrite+9C 空指针取消引用
0x3849c Secure_Store_PlainWrite+9C 空指针取消引用
0x397c8 unpack_tlv_data+64 空指针取消引用
0x3999c DRM_Hexstringtobyte+1c 空指针取消引用

受影响的设备¶

我们验证了这些漏洞是否影响了以下设备:
– 麒麟990:P40 专业版 (ELS)

请注意,其他型号可能已受到影响。

补丁¶

名字 严厉 CVE漏洞 补丁
缺少长度签入GetOCSPResponse 危急 CVE-2021-46813 漏洞 2022 年 6 月
缺少长度和偏移检查NOVEL_CHDRM_Copyordecrypt 危急 CVE-2021-46813 漏洞 2022 年 6 月
缺少长度签入NOVEL_CHDRM_SetDRMCertData 危急 CVE-2021-46813 漏洞 2022 年 6 月
缺少长度签入DRM_Secure_Store_Read CVE-2021-40062 漏洞 2022 年 3 月
缺少长度签入getvaluewithtypeandindex CVE-2021-40056 漏洞 2022 年 3 月
缺少长度签入和Secure_Store_EncryptWriteSecure_Store_PlainWrite CVE-2021-40057 漏洞 2022 年 3 月
缺少长度签入NOVEL_CHDRM_SetRegisterResData CVE-2021-40058 漏洞 2022 年 3 月
呼叫时长度检查缺失/错误NOVEL_CHDRMw_MemCompare CVE-2021-40060 漏洞 2022 年 3 月
整数下溢输入find_tlv_data CVE-2021-46813 漏洞 2022 年 6 月
OOB 访问getvaluewithtypeandindex 地中海 CVE-2022-39003 漏洞 2022 年 9 月
未经检查的 Malloc 返回值 不适用 固定
缺少长度签入pack_tlv_data 不适用 固定
调用后缺少长度检查unpack_tlv_data 不适用 固定
堆栈/堆/BSS 指针泄漏DRM_AES_Encrypt_xxx 不适用 固定
整数下溢输入unpack_tlv_data 不适用 固定

时间线¶

  • 2021年12月02日 – 向华为PSIRT发送漏洞报告。

  • 2022年1月17日 – 华为PSIRT确认该漏洞报告。

  • 2022年9月1日 – 华为PSIRT表示,这些问题已在2022年3月、2022年6月和2022年9月的更新中修复。

  • 从 2022 年 11 月 
    30 日至 2023 年 
    7 月 19 日 – 我们定期交换有关公告发布的信息。

  • 二进制漏洞(更新中)

  • 其它课程

  • windows网络安全防火墙与虚拟网卡(更新完成)

  • windows文件过滤(更新完成)

  • USB过滤(更新完成)

  • 游戏安全(更新中)

  • ios逆向

  • windbg

  • 恶意软件开发(更新中)

  • 还有很多免费教程(限学员)

  • 更多详细内容添加作者微信