垂直应用

信息安全(十)

SBSFU初体验





大家好,欢迎大家观看 STM32 信息安全(十):SBSFU初体验。


上一期我们了解了SBSFU的原理,为什么要实现安全启动和安全固件升级。要实现这个服务,拆分下来需要打通哪些关键环节。这一期我们在STM32G0的nucleo板上,运行一个现成的、ST实现的SBSFU软件包,以建立最初关于SBSFU的感性认识。




体验 SBSFU:基于STM32G071-nucleo板



大家现在下载的SBSFU软件包可能已经是V2.3.0了,我做胶片的时候还是2.2.0。不过没关系,2.3.0比2.2.0的升级主要在于增加了对H7B3 DK板 和G031 Nucleo板的支持。对于我们今天运行在G071-nucleo板子上的程序,没有变化。


这个初体验的环节,稍后在hands on的时候,我给大家留时间自己运行、体验。现在请先听我讲。在SBSFU软件包中,先定位到Nucleo G071的目录,我们将要体验的是用户代码双image方案。进入2_Images的文件夹,会看到有四个子目录。我们依次打开SECore_Bin,SBSFU,UsurApp三个目录下的工程文件。注意,一定要按顺序编译。


三个项目顺序编译完成后,大家去查看SBSFU项目下对应路径,和UserApp项目下对应路径是否生成了胶片里的三个文件。这三个文件,我们后面需要通过串口工具,喂给G071。现阶段,我们不讲为什么,请大家就按照胶片里的步骤,操作一遍。




准备工作


继续我们的准备工作。把G071 nucleo板和电脑连接,注意板子上的跳线设置上成STLINK供电。由于STLINK的虚拟串口功能,这个时候上位机会识别出一个新的串口,那么我们使用串口调试工具,我用的是Tera Term,打开该串口,并把参数做如图配置。因为SBSFU安全包的参考实现里,我们使用YMODEM协议做串口上的文件传输,因此你使用的串口调试工具需要支持该协议。


接着我们使用STM32CubeProgrammer来做板子的初始化,主要是配置芯片的选项字节:

nBOOT_SEL,和BOOT_Lock控制启动入口。我希望大家手里的板子,现在的option byte都不是处于RDP1和bootlock都使能的状态。否则你使用CubeProgrammer都连不上。之前讲了,那种状态一般是产品开发测试完成后,出厂前才会做的设置。


大概率,你们不会去动BOOT_Lock,ok,那就继续保持boot_lock功能不使能;然后nBoot_SEL的设置,基本直接在选项字节里改就可以了,因为它不大受到其他设置的制约。


WRP写保护,使能和撤销,操作也比较方便,因为也不大受其他选项字节设置的限制。如果之前设置了写保护,只要编辑对应选项字节即可。G0的写保护,是通过起始page和结束page来指定的。要撤销写保护,只需起始地址设定的比结束地址大,即可。这两项在GUI界面里修改完成后,请大家apply一下,让修改的设置生效。


PCROP如果之前已经使能了,要撤销,只能通过RDP降级。如果你现在本来就是RDP0,需要先升到RDP1;然后改变此时PCROP选项字节中起始地址和结束地址的值,让前者大于后者,并且置位PCROP_RDP, 最后把RDP从1改到0。再apply一下,让这几把操作生效。


操作成功后,芯片会自动做全片擦除,撤销之前设置过的PCROP、WRP等保护。芯片的初始状态就设置成功了。


本来想:nBOOT_SEL = 0;BOOT0引脚低电平,一定从Flash启动;BOOT0引脚高电平,(nBoot1=1),从系统Flash启动。但是SBSFU的代码运行起来,会代码把nBOOT_SEL=1,理由是这样完全可以通过选项自己的nBOOT_SEL=1, nBOOT0=1来保证从用户flash启动;如果走第一行的选择,BOOT0引脚的电平,客户会自己改,就不能保证从用户flash启动了。




下载SBSFU固件



芯片初始状态准备好了,待烧录的文件也编译、链接就绪,接下来要把第二个项目SBSFU生成的 project.bin下载到 G0-Nucleo板子里。


这里仅下载,不调试,通常有三种方式。由于刚才使用STM32CubeProgrammer检查和设置了选项字节,那就接着使用它来下载bin文件,并同时勾选上“Run after programming”如图一。 根据个人喜好,还有其他的方式,比如直接把bin文件拖拽到Nucleo板对应的盘符中,也很方便。




启动SBSFU固件的运行



在串口打印窗口,我们看到一条警告信息,意思是说,代码运行起来,设置了读保护级别1,也许需要重新插拔USB线。然后就没有然后了,没有任何后续打印。


这是怎么回事呢?我们烧下去的SBSFU代码,默认是使能了全部存储与执行模块的保护功能,除了Boot_Lock。在读保护方面,设置成RDP1;写保护,PCROP也都通过选项字节做了配置。前面讲过,程序是通过flash接口寄存器配置选项字节,寄存器写完后,配置并不会马上生效;需要通过软件复位或者其他方式产生一个复位序列,新设置才能生效。而一旦复位,RDP1生效了,若G0检测到有来自调试端口的访问信号,会认为这是一种入侵行为,从而把自己的复位信号一直拉住,就是说芯片一直处于复位状态。


由于板子上的G0芯片和STLINK都是由USB电缆供电,串口连接已经建立,因此不建议去重新插拔USB线缆。只要给G0芯片重新上电即可。此时JTAG端口不会再用可能的读写信号。重新插拔一下JP2跳线组里最右边的STLINK供电开关即可。




首次运行SBSFU



在一个空芯片上,下载了SBSFU.bin,然后首次成功运行。我们看一下串口打印出来的log信息。一来,先是检查芯片当前的安全配置,如果没有配置就给你配上。有哪些安全配置呢?就包括了之前讲的所有静态配置和动态配置。包括RDP,WRP、PCROP、MPU、WDG、TAMPER等等。使用STM32CubeProgrammer再次连接后,你可以直观看到哪些静态保护被设置上了,以及保护的范围。


下一个打印信息,打印出来slot0和slot1的地址,swap区域是新固件解密并且和老版本固件交换位置时,需要借助的一块空间。其实它的背后,是在检查SBSFU运行需要的各个存储区间的大小是否合适。存储区间的地址和大小是在三个项目共享的几个linker文件里由用户定义的。现在先不展开来说。


这些检查都通过,正式进入SBSFU的程序,就是打印的copyright 2017 STMicroelectronics这一段抬头。

紧接着有一段warning的打印信息。先说一下,ST的SBSFU参考实现里,使用状态机来管理整个流程,所以会看到一些打印信息是以“SBOOT STATE”开头,表示当前在状态机里的位置。这条warning信息的字面意思是说:使用出厂默认值初始化安全引擎。这是实现上的一个细节了,就是每次复位运行,会检查存储在RAM的一段叫做BootInfo的数据结构,里面存储的是上次复位的标志,以及出错次数。为了保证这个信息的狭义完整性,RAM里同时存储了它的CRC校验值。因为我们是第一次运行,所以RAM里并没有之前有效的记录,RAM的初始值也不符合CRC的校验,因此会使用出厂默认值来把这一段ram中的数据结构先做一个初始化。然后系统重启。


后面几条打印和之前的一样,因为重新走了一遍流程。当再次来到状态机里的这个步骤时,CRC检查通过。

然后进入下一个状态机节点,检查复位标志。可以看到,刚才是一次软件复位。

接着再下一个状态机节点:检查是否有新固件下载。这个是我们参考实现的一个方式,通过检测复位时板子上的用户按钮是否按下,来判断是否此时用户要下载新的固件。如果有需求,就使用loader程序从串口去接收新版本固件,放到slot1,代码仓库区。没有需求,就运行slot0中已有的用户程序。当然要先检查用户程序的状态,发现slot0和slot1里,都还是空空如也,因为我们是从一片空白芯片开始运行的安全启动代码呀。于是进入下载用户固件的状态机节点,等待上位机来自串口的输入。



e_ret_status = SE_INFO_BootInfoAreaFactoryReset();       

 if (e_ret_status == SE_SUCCESS)       

 {          *peSE_Status = SE_BOOT_INFO_ERR_FACTORY_RESET;        }

@brief  Reset the BootInfo shared Area to factory value.

@note This can happen if the MCU is rebooting the 1st time after this code has been flashed,  or if for some reason the BootInfoArea and its backup copy are corrupted




首次下载用户固件



如果你使用的是Tera term串口上位机,请按照右边图示的指引,把之前编译第三个工程项目生成的二进制文件之一,UserApp.sfb,通过串口下载下来。


有人可能会问,平时下载都是使用bin或者hex文件,这个.sfb尾缀的是什么文件?大家还记得在讲安全启动的原理时,如何对固件签名和验签吗?原始的用户文件要经过开发者签名,再和用户文件的密文,一起给出去。就是图里绿色框里的两部分一起。这就是我们现在使用的这个.sfb文件。 IDE除了编译、连接还能对固件进行签名和加密?当然这不是IDE本身做的,是IDE调用ST写的脚本,去完成的。这个后面来讲。




用户应用运行



接着刚才的打印信息来看,sfb文件传输完成后,系统再次重启。


好,第三次从头走这些过程了,我们直接跳到检查用户固件状态这一步。现在是检测到在slot1,“代码仓库区” ,有新下载的用户固件了,下一步,对它解密并安装到“代码执行区”。由于现在slot0还是空空如也,安装非常方便,直接解密后拷贝到slot0即可,无需swap操作。安装完毕后,运行用户应用程序之前,核实用户代码的签名。验签通过,把执行权交给用户程序,即PC直接跳转过去。


就是下面看到的“copyright 2017 STMicroelectronics“这一段抬头,写着:用户代码A。然后是它的业务逻辑本身。从打印出的菜单来看,它本身也集成了loader,支持对新版本用户固件的下载;除此之外,还支持对SBSFU代码保护机制的测试,和从用户代码调用SE引擎提供的加解密服务的测试。SE引擎是SBSFU中执行解密、验签等密码学操作的一段代码,由于它要操作对应的密钥,因此属于对安全高度敏感的操作,是SBSFU代码中被特别保护的一部分。


为了体验从用户代码下载新版本固件,我们要先在上位机上生成新版本固件。




生成新版本用户应用代码



打开第三个工程项目UserApp中的main.c文件,如由上图所示,第50行代码,把UserAppID,从A改到B。这个字符变量,就是刚才看到用户应用运行起来后,打印出来的标记,为了方便识别。光这个还不够,这个字符串只是为了打印出来,方便大家看到,嗯,确实是新代码跑起来了。由于我们的SBSFU安全包还实现了版本管理功能,防止回滚。即不允许从高版本切换到低版本,这也是安全升级通常会采用的一个保护措施。否则攻击者可以通过把当前设备里的固件降低到老版本,从而削弱其健壮性,实施攻击。这个版本信息,由于在安装的时候就需要检查,所以是作为meta data(元数据)存放在用户代码的签名的。


因此,我们要在第三个工程项目UserApp中,修改post build命令行,最后一个字符1,改成2。然后再重新编译,生成新的UserApp.sfb。




从用户程序(版本A)下载新用户程序(版本B)



回到刚才版本A的用户程序运行界面,根据菜单提示,输入1;然后使用串口下载新生成的版本B的用户程序,按照用户代码双image的方案,接收到的新版本用户代码密文,存放在slot0,“代码仓库区”。下载完毕,软件执行系统重启。




重启后运行新版本用户程序(版本B)



我们现在是第几次启动了?第四次了,对不对?前面几步状态机的状态节点处理都一样,到了安装新固件这一步,截图里黄色箭头指的位置。打印信息是:image preparation done, swapping the firmware images…。表示要开始做slot0里面版本A的用户程序,和slot1里面,解密好的版本B的用户程序 的交!换!


交换完毕后,检查版本B用户程序的合法性,验签通过,就把执行权交给用户程序去执行。PC跳转过去,用户程序版本B开始运行,从打印的抬头可以看到,版本B的用户程序跑起来了。




同时下载sbsfu和初始用户代码



回忆一下,刚才我们先把SBSFU.bin下到板子里,运行起来的时候,芯片里连第一个版本的应用程序都没有,是通过SBSFU里面集成的loader程序,从串口下载的第一版用户程序。实际应用中,设备出厂的时候,肯定第一个版本的业务代码是和SBSFU安全启动代码都烧好了的。这个下载,在产线上完成。如果是可信的产线,那么只要提供一个包含了SBSFU image和初始版本用户代码image的一个bin就可以。如果是不可信的产线,可以使用SFI服务,先对这个大bin做加密,生成对应.sfi文件。


现在我们先不考虑SFI的情况,假设产线可靠,或者就是我们自己在实验室里调试,不想像刚才一样,烧一次bin,再烧一次sfu。而是一次把两部分代码,都通过调试端口下进去。我们重新来做一次,这一次走捷径。


首先,要恢复芯片的选项字节;刚才运行的SBSFU,一来就把所有保护都设置上了,你现在使用STM32CubeProgrammer连接,会看到用户闪存区无法读取,这正是RDP1生效了,但是选项字节我们还是可以操作。把它恢复成初始状态。并且由于RDP的降级,芯片的用户flash也会被全片擦除。


然后假设是第三个版本的用户程序了,我们按照刚才的方法,分别修改第三个工程项目里,用户代码ID改到字符C,然后版本是3。重新编译,在第三个工程项目对应目录下,重新生成了两个文件。这次我们会使用SBSFU_UserApp.bin。从文件的名字就可以看出,它是包含了SBSFU image和初始版本用户代码image。

请注意,由于芯片已经全擦除,即使我还是使用版本2的时候,编译用户程序得到的的SBSFU_UserApp.bin,也是可以,并不会受“防止回滚”保护机制的阻拦。




下载SBSFU_UserApp.bin



和首次下载SBSFU.bin文件类似,使用以下任意一种方式下载。




启动SBSFU_UserApp.bin的运行




同样的,RDP1生效,又同时检测到调试端口连接的,芯片保存复位,可以通过插拔G0的供电跳线开关解决。




下载SBSFU_UserApp.bin后,启动




我们看一下运行起来的打印信息,前面都一样,到了“检查用户固件状态”的状态机节点,由于slot0,“代码运行区”已经被刚才的大bin文件,写上了版本C的用户程序,因此打印信息是:在active slot,就是slot0,有有效的固件,且版本是3。然后就是检查该版本程序的有效性,验签通过就执行,和之前都一样的。




从SBSFU下载新用户程序(版本D)



安全启动代码,开始运行后,在检查用户代码状态之前,就是去看slot0和slot1里有没有东西之前,提供了一个让用户主动要求升级的机会。如果我们按着用户按键,同时复位G0 nucleo板,SBSFU代码会在“检查是否有新固件需要下载”的节点,判断出用户的主动升级请求。如右图中黄色箭头所指,它就弹出等待下载的提示。由于现在slot0已经有版本3的用户代码了,在“防止版本回滚”的保护机制下,我们要给一个版本4的的用户代码才行。后面的操作都跟雷同,不在此累述。


那么,对G071-Nucleo板子上SBSFU安全包的初次体验,就暂时告一段落。




对安全启动和安全固件升级,有了初次使用感受之后,下一期我们来分析一下,之前讲的安全启动、安全升级的原理,是如何在G0芯片上实现的。敬请大家关注,谢谢观看。


微信扫一扫