【信息安全】英飞凌TC3xx安全调试口功能实现(调试口保护)

本文介绍了英飞凌TC3xx系列芯片的调试保护功能实现,重点分析了TriCore核调试加密、HSM核调试禁用及Flash读写保护三种安全机制。文章详细阐述了通过正确配置UCB(用户配置块)实现渐进式安全保护的原理,包括密码验证、状态确认等关键环节,并详细提供了三种不同实现方法:Memtool工具操作、程序结构体定义和程序代码实现。同时,针对产品开发不同阶段的需求,给出了调试接口加密与解密的详细步骤及注意事项。这些安全功能既满足了开发调试需求,又能有效防止攻击者通过调试接口窃取或篡改关键数据,为嵌入式系统提供了重要的安全保障。

目录

功能背景说明

英飞凌TC3xx安全调试功能介绍

调试口加密以及调试方法

通过Memtool工具进行加密配置

在程序中用结构体定义UCB区域内容

在代码中直接用Flash命令来写UCB

加密情况下调试

HSM调试禁用

Flash读写保护

读保护

写保护

补充说明


功能背景说明

对于嵌入式开发工程师来说,MCU的调试接口对我们来说是工作中不可或缺的工具。通过将调试器连接到板卡的接口,我们不仅可以监视MCU的运行状态,还可以在程序运行过程中查看或修改寄存器、变量以及内存某个地址下的的数值,设置断点并观察代码逻辑的不同表现,甚至可以将内存中的中间数据直接拷贝出来以供我们分析。工程师通过IDE、调试器等开发工具的配合,并通过上边所述的各种方法,可以方便地排查各种棘手的问题。

但是由于调试接口功能强大,这个给开发带来巨大便利的接口,也给产品埋下了信息安全的隐患。攻击者可以通过物理接触车辆电子控制单元(ECU),窃取数据、获取并篡改软件,甚至控制车辆的核心功能。

针对这个问题,很多高附加值或安全敏感的产品,会选择在生产过程的最后一步,选择直接不焊调试口,产品出厂后,调试接口已被封死,简单粗暴地解决调试接口带来的风险。但是,产品的售后、维护往往不是一帆风顺的。产品在客户现场,也许会出现各种各样奇怪的问题。此时,由于调试接口被封掉,留给工程师的调试排查手段就显得捉襟见肘了,产品在现场出现问题后,难以定位更难以解决。这就给我们提出一个关于信息安全的功能开发需求,即只让开发者合法地调试芯片,而不会被攻击者利用。


英飞凌TC3xx安全调试功能介绍

我们下面从三个比较常用的安全调试功能来进行介绍,分别是:

  • TriCore核通过正确密码来连接调试。
  • HSM核禁用调试。
  • PFlash/DFlash读/写保护

TC3xx芯片采用的是渐进式的安全保护机制:

上图前边的两个控制门是由TriCore来控制,分别通过OCDS或者DMU模块,而后边的两个则是由HSM核控制,通过这两个门,HSM可以锁定对Host CPU的调试访问权限,也可以关闭HSM自身的调试支持。

UCB(User Configuration Block)是 DFlash 的一部分,用于存储设备启动配置和保护信息。总共有48个UCB块,每个UCB大小为512字节。某些 UCB 由一对 ORIG 和 COPY 组成,擦除一个时,另一个必须保持有效。

可以通过上图看到UCB18和UCB19分别对应的由TriCore核和HSM核(对应的UCB26和UCB27为UCB_DBG_COPY和UCB_HSM_COPY,图太长就不截了)。在设备启动之初总会评估UCB的内容,寄存器PROCONxxx(PROCONDBG对应UCB18,PROCONHSM对应UCB19)的值每次启动都会根据UCB的值完成配置。下图为UCB18的内容Layout。

其中PW0~PW7为保存 32 字节的密码。

CONFIRMATION为固定 4 字节,确认UCB有效性。

  • 0x57B5327F:UCB状态为CONFIRMED,UCB的读写会被禁止。
  • 0x43211234:UCB状态为UNLOCKED,UCB可读写。
  • 0x00000000:UCB状态为ERASED,芯片将不会再启动。
  • 其余值:UCB状态为ERRORED,芯片将不会再启动。

PROCONDBG中的Bit 0(OCDSDIS)和Bit 1(DBGIFLCK)与安全调试口有关。

  • OCDSDIS = 1DBGIFLCK = 1 时,调试接口会被锁定,用户提供密码即可访问,
  • OCDSDIS = 1DBGIFLCK = 0,即使输入正确密码,也无法解锁调试接口。此时需要在 TC3XX 应用程序中提供密码,并执行 "disable protection" 命令序列来解锁调试接口(默认密码全为0)。
  • OCDSDIS = 0DBGIFLCK = 0,则无此加密调试口功能,但是若UCB_DBG处于确认状态,则那么调试接口将不会被锁定,但UCB的读写访问将受到限制。用户必须在应用程序代码中执行禁用保护命令序列,同时使用正确的调试接口密码,以暂时禁用UCB访问保护。

下图是UCB19内容Layout。

PROCONHSM保护配置寄存器有两个与调试保护相关的位:

  • Bit 0:如果被设置为'1',则禁用了HSM调试功能。
  • Bit 1:如果被设置为'1',则锁定了HSM调试功能。

最后,我们补充一个英飞凌提供的安全调试口的另一个功能,PFLASH读写保护(DFlash保护与PFlash大致相同,这里就不赘述了)。

其中在PROCONPF寄存器里的RPRO会在启动保护之后置位。由于PFLASH读保护会同时锁定调试接口,所以必须确保DBGIFLCK=1,才能通过调试器解锁调试接口。

UCB在UNLOCK状态时,UCB可以重复擦写,当CONFIRMED状态时(相当于写保护),只有输入正确的密码,用" Disable Protection"命令序列对 UCB 临时解密,才能再操作UCB。而此时在Memtool界面下,一旦UCB处在CONFIRMED状态,则就不能再改变UCB的状态了。在量产的程序中,必须改为CONFIRMED状态,因为在UNLOCK状态下,UCB的Password部分是可以读出来的(在调试器连接不上情况下,也还是可以通过 ASCBootloader 读出来)。

在使能了调试保护后,当UCB_DBG的CONFIRMATION改为CONFIRMED后,只要知道调试密码,依然还是可以调试,但是HSM在使能了调试保护后,当UCB_HSM的CONFIRMATION改为 CONFIRMED后,那么HSM的代码就永远不能调试了,除非在HSM的代码中加入一段后门代码,例如检测到一个信号或者命令后,把UCB中的调试保护解除。UCB_HSM被CONFIRMED 后,只有 HSM代码才能操作这块UCB。


调试口加密以及调试方法

设置调试加密的有三种方法,分别是在Memtool界面上设置,在程序中用数据定义UCB区域内容(这样在程序刷写的过程中就会将对应UCB区域内容写入),在代码中直接用Flash命令操作UCB。


通过Memtool工具进行加密配置

下图为通过Memtool(或者UDE中集成的PLS UDE Memtool)来进行加密操作。

在实际应用中,应该把"Set UCB to confirmed state" 勾选上,把 CONFIRMED 状态改成CONFIRMED(在调试和测试时可以先不勾选)。


在程序中用结构体定义UCB区域内容

在工厂生产时,UCB部分内容不可能再使用调试器去设置,这时往往需要把UCB内容和程序一起烧录进去,然后重新上电后 UCB 的内容就有效了。可以用结构体的方式定义 UCB 内容,来实现这种方式。在HighTec的link文件中,定义一段UCB区域:

在程序中定义下面结构体值:

cpp 复制代码
#pragma section ".rodata.SEC_UCB_DEBUG" a    
const Ifx_Dbg_Config dbg_orig =
{
    0x00000003, /* procondbg, DBGIFLOK=1 */
    {
        0x00000000, /**< \brief 0x004: Reserved */
        0x00000000, /**< \brief 0x008: Reserved */
        0x00000000, /**< \brief 0x00C: Reserved */
        0x00000000, 0x00000000, 0x00000000, 0x00000000, /**< \brief 0x010: Reserved (0x010 - 0x01F) */
        0x00000000, 0x00000000, 0x00000000, 0x00000000, /**< \brief 0x020: Reserved (0x020 - 0x02F) */
        0x00000000, 0x00000000, 0x00000000, 0x00000000, /**< \brief 0x030: Reserved (0x030 - 0x03F) */
        0x00000000, 0x00000000, 0x00000000, 0x00000000, /**< \brief 0x040: Reserved (0x040 - 0x04F) */
        0x00000000, 0x00000000, 0x00000000, 0x00000000, /**< \brief 0x050: Reserved (0x050 - 0x05F) */
        0x00000000, 0x00000000, 0x00000000, 0x00000000, /**< \brief 0x060: Reserved (0x060 - 0x06F) */
        0x00000000, 0x00000000, 0x00000000, 0x00000000, /**< \brief 0x070: Reserved (0x070 - 0x07F) */
        0x00000000, 0x00000000, 0x00000000, 0x00000000, /**< \brief 0x080: Reserved (0x080 - 0x08F) */
        0x00000000, 0x00000000, 0x00000000, 0x00000000, /**< \brief 0x090: Reserved (0x090 - 0x09F) */
        0x00000000, 0x00000000, 0x00000000, 0x00000000, /**< \brief 0x0A0: Reserved (0x0A0 - 0x0AF) */
        0x00000000, 0x00000000, 0x00000000, 0x00000000, /**< \brief 0x0B0: Reserved (0x0B0 - 0x0BF) */
        0x00000000, 0x00000000, 0x00000000, 0x00000000, /**< \brief 0x0C0: Reserved (0x0C0 - 0x0CF) */
        0x00000000, 0x00000000, 0x00000000, 0x00000000, /**< \brief 0x0D0: Reserved (0x0D0 - 0x0DF) */
        0x00000000, 0x00000000, 0x00000000, 0x00000000, /**< \brief 0x0E0: Reserved (0x0E0 - 0x0EF) */
        0x00000000, 0x00000000, 0x00000000, 0x00000000, /**< \brief 0x0F0: Reserved (0x0F0 - 0x0FF) */
    },
    0x00000001, 0x00000002, 0x00000003, 0x00000004, /**< \brief 0x100: PW0-PW3 */
    0x00000005, 0x00000006, 0x00000007, 0x00000008, /**< \brief 0x110: PE4-PW7 */
    {
        0x00000000, 0x00000000, 0x00000000, 0x00000000, /**< \brief 0x120: Reserved (0x120 - 0x02F) */
        0x00000000, 0x00000000, 0x00000000, 0x00000000, /**< \brief 0x130: Reserved (0x130 - 0x03F) */
        0x00000000, 0x00000000, 0x00000000, 0x00000000, /**< \brief 0x140: Reserved (0x140 - 0x04F) */
        0x00000000, 0x00000000, 0x00000000, 0x00000000, /**< \brief 0x150: Reserved (0x150 - 0x05F) */
        0x00000000, 0x00000000, 0x00000000, 0x00000000, /**< \brief 0x160: Reserved (0x160 - 0x06F) */
        0x00000000, 0x00000000, 0x00000000, 0x00000000, /**< \brief 0x170: Reserved (0x170 - 0x07F) */
        0x00000000, 0x00000000, 0x00000000, 0x00000000, /**< \brief 0x180: Reserved (0x180 - 0x08F) */
        0x00000000, 0x00000000, 0x00000000, 0x00000000, /**< \brief 0x190: Reserved (0x190 - 0x09F) */
        0x00000000, 0x00000000, 0x00000000, 0x00000000, /**< \brief 0x1A0: Reserved (0x1A0 - 0x0AF) */
        0x00000000, 0x00000000, 0x00000000, 0x00000000, /**< \brief 0x1B0: Reserved (0x1B0 - 0x0BF) */
        0x00000000, 0x00000000, 0x00000000, 0x00000000, /**< \brief 0x1C0: Reserved (0x1C0 - 0x0CF) */
        0x00000000, 0x00000000, 0x00000000, 0x00000000, /**< \brief 0x1D0: Reserved (0x1D0 - 0x0DF) */
        0x00000000, 0x00000000, 0x00000000, 0x00000000, /**< \brief 0x1E0: Reserved (0x1E0 - 0x0EF) */
    },
    0x43211234, /**< \brief 0x1F0: .confirmation: 32-bit CODE, (always same)*/
    {
        0x00000000, /* 0x004, Reserved */
        0x00000000, /* 0x008, Reserved */
        0x00000000, /* 0x00C, Reserved */
    }
};
#pragma section

在代码中直接用Flash命令来写UCB

下面为相关的代码,dflash_program与dflash_erase为Flash命令的封装,flash_ucb_dbg与ucb_disable_protection为加密和失能加密的代码实现。ucb_resume_protection为恢复加密功能实现,ocds_clear_InterfaceLocked则也可用于解锁调试口。

cpp 复制代码
void flash_ucb_dbg(void)
{
    uint32 i;
    uint32 buffer[0x80]; //512 Byte
    uint32 addr = 0xAF402400; //UCB_DBG_ORIG

    dflash_erase(addr, 1); //erase DF0_UCB
    while ((DMU_HF_STATUS.U & 0xFF) != 0) ; //Wait Flash is not busy

    for (i = 0; i < 0x80; i += 1)
    buffer[i] = 0x0; //clear buffer

    buffer[0x00] = 0x00000003; //Set PROCONDBG
    buffer[0x40] = 0x00000001; //Set 256-bit Password 0
    buffer[0x41] = 0x00000002; //Set 256-bit Password 1
    buffer[0x42] = 0x00000003; //Set 256-bit Password 2
    buffer[0x43] = 0x00000004; //Set 256-bit Password 3
    buffer[0x44] = 0x00000005; //Set 256-bit Password 4
    buffer[0x45] = 0x00000006; //Set 256-bit Password 5
    buffer[0x46] = 0x00000007; //Set 256-bit Password 6
    buffer[0x47] = 0x00000008; //Set 256-bit Password 7
    buffer[0x7C] = 0x43211234; //Set Confirmation code

    for (i = 0; i < (0x80/8); i += 1) //32 Byte page per burst
    {
        dflash_program(addr + i * 32, &buffer[i * 8]); //program DF0_UCB
        while ((DMU_HF_STATUS.U & 0xFF) != 0) ; //Wait Flash is not busy
    }
 }

uint32 dflash_erase(uint32 addr, uint32 cnt)
{
    if (!cnt)
    {
        return (1); //Stop operation if no sector
    }

    //Erase sector
    *(volatile uint32 *)(0xAF00AA50) = (uint32) addr;
    *(volatile uint32 *)(0xAF00AA58) = (uint32) cnt;
    *(volatile uint32 *)(0xAF00AAA8) = (uint32) 0x80;
    *(volatile uint32 *)(0xAF00AAA8) = (uint32) 0x50;
    __dsync();
    return (0);
 }

uint32 dflash_program(uint32 addr, uint32* pmem)
{
    volatile uint32 i;
    volatile uint32 low32bit, high32bit;

    //Enter page mode
    *(volatile uint32 *)(0xAF005554) = 0x5D;
    __dsync();

    if ( ((DMU_HF_STATUS.U & 0x00100000) == 0) || ((DMU_HF_ERRSR.U & 0x07) != 0) )
    {
        return (1); //Stop operation if error
    }

    i = 0;
    while (i < 0x8) //Load page
    {
        low32bit = pmem[i];
        high32bit = pmem[i + 1];
        *(volatile uint32 *)(0xAF0055F0) = low32bit;
        __dsync();
        *(volatile uint32 *)(0xAF0055F4) = high32bit;
        __dsync();
        i += 2;
    }
    //Write burst
    *(volatile uint32 *)(0xAF00AA50) = (uint32) addr;
    *(volatile uint32 *)(0xAF00AA58) = (uint32) 0x00;
    *(volatile uint32 *)(0xAF00AAA8) = (uint32) 0xA0;
    *(volatile uint32 *)(0xAF00AAA8) = (uint32) 0xA6;
    __dsync();

    return (0);
 }

void ucb_disable_protection(void)
{
    //Reset to read
    *(volatile uint32 *)(0xAF005554) = 0x000000F0;

    //Disable protection
    *(volatile uint32 *)(0xAF00553C) = 0x00000012; //UCB_DBG
    *(volatile uint32 *)(0xAF00553C) = 0x00000001; //PW0
    *(volatile uint32 *)(0xAF00553C) = 0x00000002; //PW1
    *(volatile uint32 *)(0xAF00553C) = 0x00000003; //PW2
    *(volatile uint32 *)(0xAF00553C) = 0x00000004; //PW3
    *(volatile uint32 *)(0xAF00553C) = 0x00000005; //PW4
    *(volatile uint32 *)(0xAF00553C) = 0x00000006; //PW5
    *(volatile uint32 *)(0xAF00553C) = 0x00000007; //PW6
    *(volatile uint32 *)(0xAF00553C) = 0x00000008; //PW7

    while ((DMU_HF_STATUS.U & 0xFF) != 0) ; //Wait Flash is not busy
}

void ucb_resume_protection(void)
{
    //Reset to read
    *(volatile uint32 *)(0xAF005554) = 0x000000F0;

    //Resume protection
    *(volatile uint32 *)(0xAF005554) = 0x000000F5;

    while ((DMU_HF_STATUS.U & 0xFF) != 0) ; //Wait Flash is not busy
}

void ocds_clear_InterfaceLocked(void)
{
    //OCDS enabling pattern (access needs to be done at 32 bit)
    CBS_OEC.U = 0xA1;
    CBS_OEC.U = 0x5E;
    CBS_OEC.U = 0xA1;
    CBS_OEC.U = 0x5E;

    //Clear OSTATE.IF_LCK with write access enabled
    CBS_OEC.U = 0x00010000;

    //Wait debug interface is unlock
    while ((OSTATE.B.IF_LCK) != 0) ;
}

加密情况下调试

如果在调试加密的情况下,正常连接仿真器会连不上。

按照下面的指导,完成密码的输入。

密码输入正确之后,再次Retry,即可正常连接核心。


HSM调试禁用

HSM 使能后,HSM 的SSW会判断下面DMU_SP_PROCONHSM.HSMDBGDIS,如果这个位是1,则会锁住 HSM 的调试接口,反之则使能HSM的调试接口。

设置HSM调试加密实际上就是设置下面这个位,注意这个位会在SSW中自动从UCB_HSM中装载到下面寄存器,前提是UCB_HSM 的CONFIRMATION状态会UNLOCK或者CONFIRMED。

设置HSM调试加密的有同样也有三种方法,分别是在Memtool界面上设置,在程序中用数据定义UCB区域内容,在代码中直接用 Flash 命令操作UCB。我们下面只介绍第一种,后边两种的实现方式与调试口加密基本一致。按照下图所示方法,即可禁用HSM调试。

UCB_HSM没有密码,在 UDE 界面中不支持输入密码调试。如果要解除保护,则可以点击上图所示的"Erase configuration " (仅支持在Unlocked状态下擦除)。

如果在HSM调试加密的情况下,正常连接仿真器会连不上。

擦除保护后,连接HSM恢复正常。


Flash读写保护

读保护

设置Flash读写保护的有同样也有三种方法,下面我们只简要介绍一下使用Memtool实现读写保护。首先我们找到UCB_PFLASH的配置界面。

然后,勾选"Flash read protection", 点击"Write configuration", 输入密码,勾选"Set UCB to confirmed state", 再点击OK。

重新下电,再上电(读保护生效),即可看到,各个Section已经加锁。

点击read

选中0x80000000 -- 0x800000FF,点击Start。

读完后存成另外一个hex。

用notepad++打开这个hex, 发现读到数据都是0 (读取失败)。

读保护的解除首先点击Disable lock,输入密码。

此时发现write configuration从灰色变成可用,然后取消PFlash read protection, 点击write configuration, 取消Set UCB to confirmed state, 点击OK。

重新断电,再上电后再连接,发现Flash lock已经解除。


写保护

点击UCB,选择UCB_PFLASH, 选择写保护区域,点击write configuration, 写入密码, 点击 Set UCB to confirmed state。

断电再上电后连接,发现PFlash sector0已经锁上。

解除写保护,则需要点击UCB,选择UCB_PFLASH。

点击Disable lock, 输入正确的密码,点击OK。

临时解除保护成功后,会发现write configuration 从灰色变为可用。然后解除对PFLASH sector0的保护,write configuration, 把set UCB comfirmed state勾选去掉,点击OK。

下电再重新上电后,写保护解除。

补充说明

在实际应该中,即使MCU不能被调试,但是用外部工具通过Bootstrap loader mode,还是可以把Flash程序读出来,有下面两种方式可以进入Bootstrap loader mode模式。

要进入这个模式,首先PINDIS为0,

然后可以根据HWCFG[1~6]引脚状态来选择对应的启动模式。

当PINDIS为1时,也可以通过内部的internal BMI来选择启动模式。

所以,为了不进入Bootstrap loader mode模式,则需要PINDIS为1,而且HWCFG[3:1]= 111. 那么程序一定会从内部 Flash 启动,则进入不了Bootstrap loader mode,从而运行客户代码,则无法绕过加密读取Flash以及调试MCU。


十六宿舍 原创作品,转载必须标注原文链接。

©2023 Yang Li. All rights reserved.

欢迎关注 『十六宿舍』,大家喜欢的话,给个 👍 ,更多关于嵌入式相关技术的内容持续更新中。