【信息安全】英飞凌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 = 1
且DBGIFLCK = 1
时,调试接口会被锁定,用户提供密码即可访问, - 当
OCDSDIS = 1
且DBGIFLCK = 0
,即使输入正确密码,也无法解锁调试接口。此时需要在 TC3XX 应用程序中提供密码,并执行 “disable protection” 命令序列来解锁调试接口(默认密码全为0)。 - 当
OCDSDIS = 0
且DBGIFLCK = 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区域:
在程序中定义下面结构体值:
#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则也可用于解锁调试口。
void flash_ucb_dbg(void)
{uint32 i;uint32 buffer[0x80]; //512 Byteuint32 addr = 0xAF402400; //UCB_DBG_ORIGdflash_erase(addr, 1); //erase DF0_UCBwhile ((DMU_HF_STATUS.U & 0xFF) != 0) ; //Wait Flash is not busyfor (i = 0; i < 0x80; i += 1)buffer[i] = 0x0; //clear bufferbuffer[0x00] = 0x00000003; //Set PROCONDBGbuffer[0x40] = 0x00000001; //Set 256-bit Password 0buffer[0x41] = 0x00000002; //Set 256-bit Password 1buffer[0x42] = 0x00000003; //Set 256-bit Password 2buffer[0x43] = 0x00000004; //Set 256-bit Password 3buffer[0x44] = 0x00000005; //Set 256-bit Password 4buffer[0x45] = 0x00000006; //Set 256-bit Password 5buffer[0x46] = 0x00000007; //Set 256-bit Password 6buffer[0x47] = 0x00000008; //Set 256-bit Password 7buffer[0x7C] = 0x43211234; //Set Confirmation codefor (i = 0; i < (0x80/8); i += 1) //32 Byte page per burst{dflash_program(addr + i * 32, &buffer[i * 8]); //program DF0_UCBwhile ((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; //PW7while ((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 enabledCBS_OEC.U = 0x00010000;//Wait debug interface is unlockwhile ((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.
欢迎关注 『十六宿舍』,大家喜欢的话,给个👍,更多关于嵌入式相关技术的内容持续更新中。