【快乐分享】献丑之STM32F单片机IAP方法

2020-03-08 19:24发布

本帖最后由 gbchang 于 2013-9-23 10:03 编辑

【快乐分享】献丑之STM32F单片机IAP方法

看点:STM32F单片机IAP加密下载,一边接收一边擦写

本贴分享内容提要:
为啥不用芯片内置的ISP,相关术语介绍,本IAP方法流程,APP程序部分要做的事,关于代码保护,上位机配套程序,代码环境,相关文档下载。

响应21小跑堂的号召,用大众话贴出来,也尝尝当楼主的滋味,;P。
提醒,是分享,不是教程,系统学习要看手册的,大家都是这么说的。
这些代码是我个人之力的结果,有漏洞在所难免,还望各位不吝赐教。


我为啥不用芯片内置的ISP。
STM32F单片机在出厂前,固化了ISP自举程序,挺强悍的,至少我觉得,因为人家还自适应波特率呢,才2KByteROM+512ByteRAM。上位机下载软件现成的,也不用自己操心了。
不过呢,需要boot0跳线进入,详见文档 RM0008芯片参考手册第2章 和 AN2606系统内存引导模式。
那我就要在我明亮的壳子上打个白米粒大的洞,然后就像路由器那样,拿牙签捅啊捅的。
还有,就是把HEX文件发布让客户自己升级,感觉不像回事,老板也总觉得别人会抄它东西,呵呵。
那我第一个想法,就是改造原厂的ISP,把boot脚判断去掉,再加点解密算法什么的,构思完毕,一查手册,那个 ISP 代码是只读的。
那好,有机会玩玩 IAP 也不错,自由方便,说不定还有机会像电脑上那些流氓软件那样偷偷升级:)坏银(话说GPRS这玩意儿蛮恐怖的)。


相关术语介绍。详见文档 PM0042闪存编程。
FPEC(FLASH Program/Erase controller 闪存编程/擦除控制器),内嵌的FPEC模块负责对内置闪存的(含主存储器和信息块)写操作,当然还有擦除。
它的寄存器随便读,但改写就需要先把FPEC解锁,而且一旦解锁错误,就会锁死(并且会产生总线错误),一直到下次复位,都无法擦写主闪存和选项字节了。
为了防止程序跑飞什么的误擦或写闪存,在程序正常工作时,可以人为的把它搞锁死,这样就安全了。
“只要CPU不访问闪存,闪存操作不会延缓CPU的执行”,这句很恐怖,程序放在闪存中,不访问怎么取指啊?不取指咋执行闪存操作~,还不如直接说,你CPU先去凉快吧。

选项字节。  0x1FFF F800 – 0x1FFF F80F
闪存模块组织(中容量产品) .gif 选项字节块组织结构.gif
选项字节装载器(OBL)在系统复位时把闪存信息块中的选项字节装载到 FPEC 对应寄存器中,内容包括读写保护状态,软硬看门狗事件,停待机是否产生复位等。
然后下次复位以前,这些信息就不再变了(这些寄存器是只读的),无论你怎么折腾选项字节区域,这种一次生效的设计,可以更好的防止程序错误导致关键设置改变,就像GPIO模式锁定那样,个人感觉很有用。

读保护。
STM32F单片机在读保护状态下:
。只允许从用户代码中对主闪存存储器的读操作(以非调试方式从主闪存存储器启动)。据我观察,信息块的选项字节也不让读。
。通过从内置SRAM或FSMC执行代码访问主闪存存储器的操作,通过DMA1、DMA2、JTAG、SWV(串行线观察器)、SWD(串行线调试)、ETM和边界扫描方式对闪存的访问都将被禁止。
这句要再具体点说“通过从内置SRAM或FSMC启动并执行代码访问。。。都将被禁止”。
总之就是从主内存启动的普遍工作状态,你就不用去想读保护这些事。
据道听途说,有些号称单片机解密的准坏人,只解未设置读保护的片子:L汗一个。
ST为我们多做了一些事,那就是在读保护状态下,主闪存最前4KByte自动被写保护,这就侧面提醒了坏人,在最前面的这些地方有文章可做,比如改写中断向量等(小球病毒?)。
手册上说解除读保护会自动擦除整个芯片,据我观察,擦除时机是在复位装载选项字节区域时,信息块是不擦的。

写保护。
写保护是以4KByte为单位的。(小中容量产品1K/页 * 4页,大和互联型产品2K/页 * 2页)
如果有需要在单片机里存些运行数据,可以保留几处不做保护(因为解除保护需要系统复位后才会生效)

编程。
编程以半字(16位)为单位,时间40us~70us,编程之前要求对应位置内容为0xffff。
这个芯片强调如果对未擦除的半字编程,将拒绝执行(写0除外),所以无法实现逐位编程的应用,如减计数器。

擦除。
擦除可以页为单位,时间20ms~40ms,擦除后全变1。
或者整片擦除,时间也是20ms~40ms,整片擦不影响选项字节区。
擦写循环 10K次,不过人家10K次以后,还是能保存20年呢,可以的啦。参文档 STM32F103规格书

本IAP方法流程。
由于不握手,直接下载(我设备就只引出一根RXD与一根GND线),所以要 一边接收一边擦除一边编程(用'一边'造句?:o)。
所以要先做点计算:
115.2Kbps波特率,接收1页(设1KB),需时 1K*(1b+8b+1b)/115200bps = 88.89ms
擦除1页需时(最多) 40ms
编程 1KB 需时 1KB/(2B/半字) * 70us = 35.84ms
这样留给代码执行的时间还剩 88.89 - 40 - 35.84 = 13ms左右,对STM32-72MHz-1.25DMIPS/MHz 这种速度级别应该够用了。
流程如下(图):
IAP流程示意.gif
罗嗦几句,
代码完整性校验,我只做了简单的累加和,同时判断向量表最开始的 MSP 值和复位向量值的合法性,复位向量应该指向应用程序所在区域,
本方法中主闪存前4KB为IAP程序区,后面剩余容量为APP程序区。为了达到更高的校验强度,会 MD5 的朋友有用武之地了。

代码加密,我只象征性的的做了下循环和异或。著名加密算法如AES、TEA、3DES、RC6、RSA、MD5、SHA1、SHA256,
从别的论坛下载了个加密算法源码包,感谢win2kddk分享。看官可根据实际情况选取,注意别太复杂,否则就要降波特率来换时间了。
我在代码中把96位ID读出来了,并在升级前从串口发给上位机,如果需要,可以此做密钥加解密,那就变成一机一密了,程序运行过程中随时可以核对,我没这样做是因为我的机子没有TXD线,所以只好取了些固定数据做密钥。

因为在擦除或者编程时,从FLASH的取指操作将导致CPU暂停运行,所以本方法中,iap 程序把自己(含中断向量表)共 4KB 统统搬到 RAM 中,
然后跳到 RAM 中继续运行,
警告!!!用这个方法,需要仔细检查,保证程序不会被32位地址跳转指令影响,即BX和BLX这种通过寄存器跳转的指令,
ARMv7-M的跳转指令如下(图): 详见文档 ARMv7-M体系参考手册
跳转指令.gif

我在编译后的代码里翻了半天,就找到几个用 BX 完成 return 的,未见到用 BLX 实现CALL,所以我才用的。不同的编译器,编译结果不同,所以一定要谨慎使用。
在搬向量表时,要把 0x08xxxxxx 的修改为 0x20xxxxxx,xx部分不变。然后中断向量表映射到 RAM 中。
为了代码安全,保证读保护一直在,每次复位,boot(本文的iap部分)程序都检查,反正无论IAP下载还是APP运行,都不需要解除读保护,读保护会附带写保护iap代码这4KB空间。
为了方便统一烧写iap和app程序,在iap中,为非iap方法下载的app程序补填了校验码,剩余空间填0,这个通过闪存最后一个字节是否为0xFFFF判断。
也就是虽然具iap功能,但传统hex烧录方式仍然可用。让烧片子的PLMM先烧iap,再烧app,还要把校验和填进去,那是多么残忍的一种行为。
FLASH在升级成功后直接跳入用户 ROM 区域程序执行就好,反正对app来说,是从复位向量那开始的。


APP程序部分要做的事。
要做两件事:
在需要升级时,通过擦写 FPEC 选项字节,设置读保护,解除写保护,设置升级标志,复位。
在不需要升级时,锁死 FPEC 直到下次复位,要注意那个总线错误。


关于代码保护。
先说泄密的途径,
最常见的是程序员直接带走源码,这个的应对方法。。。洗脑比较好,哈哈。
其次是烧片子的PLMM带走HEX或者BIN文件,这就需要对BIN文件加密,到芯片内后再解密,不过IAP程序还是要明文烧写的,如果反汇编了IAP程序,解密算法还是曝光了。
再往下游,如果提供升级程序代码,那坏人可以对关键地址(如向量表)的规律数据进行猜测,将可能导致加密方法曝光。
下载到了片子后,被坏人读出来可执行代码,这个防范由ST公司做,即设置读保护。手册上专门提到“从RAM启动不能读”,也就是说,从FLASH启动,然后跳入RAM中执行去读是没问题的,那就是说,只要在代码区写上一条跳到RAM中的指令,如果有机会执行到这条指令,也许就可以干坏事了。

针对以上问题,我想到如下应对措施与诸君分享:
关于程序员,要求每天在心里默念“公司是我家”0xFFFF遍。
烧片子的MM,给他配备富帅高男友,人穷志短。
如果提供升级代码,那么,不要按地址顺序存储或传输,这样有“嵌入式编程”背景的解密者就变得相对平庸了。
最后,怎样防范坏人通过往FLASH里写入跳转指令,跳入RAM或者FLASH尾部的小偷程序中,
首先是时刻注意写保护,即使下载过程中,也要先把选项字节设置为写保护,反正生效是在下次重启后的事呢。
其次如果程序未用完整个FLASH区,那空闲部分填非FFFF随机数或0,或者填些合法但不调用的代码。
第三如果有全FFFF的常量数组,有可能时用全0代替,并在代码中取反使用,这也算招数?:dizzy:
最后,在什么地方放一个MD5或者其它什么校验码,并在boot(即本文iap)中判断。


上位机配套程序。
PC端编译环境VC6.0,版本够低,让您见笑了。
PC程序BinEncrypt.exe,2进制文件加密后以 C 数组格式保存为文件 AppCode.c,以源码形式提供给VC,创建下载程序。
同时也输出不加密的数组,主要用于 iapcode.c ,把iap编译后的二进制以源码形式提供给 MDK,与app连接后统一烧写。

具体说下操作流程,在 MDK 编译 app 代码后,调用 BinEncrypt.exe,得到加密过的 app二进制文件,拿到 VC 环境下,与 UpdateV100.c 一起编译,形成UpdateV100.exe,这样的单一文件方便发布。

让调试 app 更方便,
一般的,iap在完成后,就不怎么再改了,所以可以用 BinEncrypt.exe 把 iap 的二进制转成 .c格式数组,把它加到 MDK 的app 工程中,编译连接下载。
为啥这样麻烦,直接源码统一编译连接不行吗?至少我不会搞,因为 app 和 iap 程序都要求定位在各自的指定地址处。
还希望另有高招的朋友赐教。

PC程序UpdateV100.exe,下载程序,可执行文件内已经包含加密过的单片机程序代码(AppCode.c),从串口将加密过的用户代码送到单片机。
UpdateV100.gif
重形像的朋友可以在界面上加些公司LOGO之类的,弄漂亮些。
这个串口类用了多线程序,编译选项里要选择多线程DLL,如下(图):
PC下载程序编译选项.gif
由于枚举串口时读了注册表,所以WIN7操作系统下,需要管理员身份运行,否则会有操作需要提升之类的提示。
里面的串口类被我改得一踏糊涂,想直接拿来用的朋友要小心。


代码环境。
CPU:
STM32F103RB,72MHz

IDE-Version:
μVision V4.03q

Tool Version Numbers:
Toolchain:        RealView MDK-ARM  Version: 4.11
Toolchain Path:    BIN40
C Compiler:         Armcc.Exe       V4.0.0.728
Assembler:          Armasm.Exe       V4.0.0.728
Linker/Locator:     ArmLink.Exe       V4.0.0.728
Librarian:             ArmAr.Exe       V4.0.0.728
Hex Converter:      FromElf.Exe       V4.0.0.728
CPU DLL:               SARMCM3.DLL       V4.11
Dialog DLL:         DARMSTM.DLL       V1.46
Target DLL:             SeggerJL2CM3.dll       V1.10
Dialog DLL:         TARMSTM.DLL       V1.43

J-LINK:
HW                V8.00
dll                V4.11i
FW                jan 28 2010 19:55:25
我用的 SWD

通过几个截图分别说明一下编译设置
编译iap,如下(图):
编译iap.gif
IAP 程序编译说明:
IAP 程序使用IROM1开始的 4K 刚好是在读保护附带写保护的前4页,不会被改写由于IAP过程需要搬代码到RAM中运行,所以IAP 程序编译时,编译选项要在RAM区中留出 4K空间用于存储程序。
Options for Target 'Target1' --> Target --> IROM1 --> 0x08000000,0x1000
Options for Target 'Target1' --> Target --> IRAM1 --> 0x20001000,0x4000
Options for Target 'Target1' --> Target --> Code Generation --> Use MicroLIB --> 节约空间
iap.c的编译选项.gif
输出2进制文件
Options for Target 'Target1' --> User --> Run User Programs After Build/Rebuild -->填入以下内容,并勾选DOS16
C:KeilARMBIN40fromelf.exe  --bin  -o  .OUTiap.bin  .OUT iap .axf
再调用另一个程序,转成C数组文件 iapcode.c,ICE开发环境编译下载应用程序时,包含此数组
Options for Target 'Target1' --> User --> Run User Programs After Build/Rebuild --> 填入以下内容,并勾选DOS16
.BinEncrypt.exe  .OUTIAP.bin
iap.c的编译选项,调用外部程序.gif


编译用于发布的app,如下(图):
编译用于发布的app.gif
app 用户程序编译说明:
应用程序编译选项,前面4KB空间要跳过,因为这里是被保护的IAP程序,与应用程序无关
Options for Target 'Target1' --> Target --> IROM1 --> 0x08001000,0x1f000
Options for Target 'Target1' --> Target --> IRAM1 --> 0x20000000,0x5000
同时,用户程序初始化相量表时,地址应该在 0x08001000,即0x08000000再偏移0x1000
app.c的编译选项.gif
输出2进制文件
Options for Target 'Target1' --> User --> Run User Programs After Build/Rebuild --> 填入,并勾选DOS16
C:KeilARMBIN40fromelf.exe  --bin  -o  .OUTapp.bin  .OUTapp.axf
再调用另一个程序,2进制文件转C数组加密文件 appcode.c,用于发布下载文件
Options for Target 'Target1' --> User --> Run User Programs After Build/Rebuild --> 填入,并勾选DOS16
.BinEncrypt.exe  .OUTapp.bin

app.c的编译选项,调用外部程序.gif

如果需要一次性烧录两个程序,那应该先把IAP程序的BIN数据转成数组文件 IAPiap.c,
并指定绝对地址 __at(0x08000000),附到应用程序开头。
但注意,连接后,因为有两段地址空间,无法通过 fromelf.exe 转成 .bin 文件。.hex 文件 和  .axf 由于包含地址信息,没问题。
把iap和app一起编译连接,如下(图):
把iap和app一起编译连接.gif

编译器会根据指定的 IROM1 地址分配程序地址。当然,这里要保持缺省:
Options for Target 'Target1' --> Linker --> Use Memory Layout from Target Dialog --> 勾选
编译链接选项.gif
仿真器也要设置
jLink/jTarce Cortex-M Target Driver Setup --> Flash Download --> Download Function --> Erase Sectors


片子锁死?不要惊慌。
我开始真是好一阵惊慌,话说设置了读写保护后,MDK集成环境下再也烧录不了了,但我知道片子肯定不会报销(据说AVR可以报销?如果是,为什么要那样做呢?)
然后就找出这个来,一通乱按,成功解锁。如下(图):
片子被锁后用 JFlashARM.gif
Target --> Secure chip 和 Unsecure chip 先加锁再解锁可解除读保护,操作前先Connect。
再重复一遍以上操作,解除写保护。
再重复一遍以上操作,才可擦除选项字节中的data0,data1部分。


相关文档。
貌似我参考的这些都不是最新版本的,呵呵。
RM0008芯片参考手册,文件太大,上传不了,还好大家基本都有新版本的了。
AN2606系统内存引导模式
STM32F103规格书
ARMv7-M体系参考手册
PM0042闪存编程
酷贴!3DES、AES、RC6、TEA、RSA、MD5、SHA1、SHA256加密源码大聚齐
【快乐分享】献丑之STM32F单片机IAP方法--源码 --源码
【快乐分享】献丑之STM32F单片机IAP方法.pdf


友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
该问题目前已经被作者或者管理员关闭, 无法添加新回复
19条回答
sxhhhjicbb
1楼-- · 2020-03-10 14:51
gbchang 发表于 2013-8-6 17:31
在擦除或者编程期间,CPU会暂停运行的,如果从FLASH中取指令。

LZ将程序放到RAM中运行。跳过FLASH erase时间,是一个方法。赞一下。偶采用的是预erase方法。在进行IAP之前进行erase。另外,偶观察的结果显示,编程时间是很快的。CPU暂停主要发生在erase期间。
gbchang
2楼-- · 2020-03-10 19:47
sxhhhjicbb 发表于 2013-8-7 13:26
LZ将程序放到RAM中运行。跳过FLASH erase时间,是一个方法。赞一下。偶采用的是预erase方法。在进行IAP之 ...

主要原因是,擦除一页需要时要 20~40ms,如果这期间CPU暂停,串口会丢数。如果集中擦除,界面要停顿几秒钟,用户体验又不是很好。所以“一边xx一边xx一边xx”才是我分享的重点,即串口传输完成,编程也就完成了。

不考虑擦除,编程一个半字,时间是 70us ,115200bps时,两个字节174us,所以 CPU 有174-70=104us时间来完成串口接收和编程操作。
天罡星lmy
3楼-- · 2020-03-10 23:00
mark
batsong
4楼-- · 2020-03-11 00:42
 精彩回答 2  元偷偷看……
ZM_Caesar
5楼-- · 2020-03-11 03:37
Mark了。!
电子乌托邦
6楼-- · 2020-03-11 09:06
谢谢分享。IAP方法

一周热门 更多>