5.5:通用代码加密库
集成代码加密库到项目中并正确使用,对开发经验有较高的要求,此外,请熟读本文档,并按照流程进行操作,本文档将尽可能将细节阐述清晰,以便减少出现问题的可能性。
当前库为全功能库,并且大部分加密算法,校验算法等均为内置算法,如果对项目有更高的要求,或者需要对库进行裁剪以降低内存需求(最低可降低至1K 以下)、降低Flash占用(最低可裁剪到16K左右)、或者基于更高定制要求,可联系我们授权获取商业应用加密库的源码。
5.5.1:概述
Power Writer 内置了标准的Matrix 签名算法, 以及可配合 ICWKEY 来做芯片的数字证书分发, 在一些基础应用中,可实现防止二进制代码的防拷贝的要求,但随着软件保护级别要求的提高,用户有更深一层的软件保护的需求,为此,创芯工坊将自身产品使用的加密库部分功能以 库文件(Library) 的形式进行开放,以便满足更高阶的代码加密防护需求,当前加密库包含以下模块:
MCU通用软件保护库 (advance_software_protect_library) 可提供
- 对象监控(监控对象是否被非法访问)
- 固件校验(支持分区段校验,用户可自由设置校验区域)
- 函数加密(部分固件加密,函数级加密,避免核心代码泄露)
- 反调试服务框架
- 固件签名(防止固件被拷贝到其他芯片)
- 固件压缩(压缩部分二进制代码)
- 虚拟机(ARM 指令集转化为ARM虚拟机指令)
- 其他安全服务
固件压缩、虚拟机等技术当前属于保密技术,暂不进行公开,当前已开放的技术,可满足绝大部门应用场景,如果对加密有客制化的需求,可与我们取得联系,邮箱:cs@icworkshop.com 。
5.5.2:准备工作
5.5.2.1:基本要求
- 支持的操作系统:windows 、Linux、MacOS。
- 支持的编译器(开发环境):
- ARMCC(Clang):典型的IDE 为 MDK,必须开启C99 模式。
- GCC:典型的IDE 为 Eclipse CDT 开发环境,比如 STM32 Cube IDE、或者基于 VSCode + GCC + GDB 组合环境。
- IAR:IAR Embedded Workbench IDE。
- 开启全功能时的最小Flash 大小:> 4.8K Byte(堆大小)。
- 开启全功能时的最小Flash 大小 :> 46K Byte。
- 支持的CPU 体系:ARM Cortex 系列 CPU。
- 下载站将根据需求反馈,提供最新的库文件、依赖源码、示例源码等。
- ARM 内核以外的其他指令集(如RISC-V) 、支持列表之外的其他编译器当前无计划进行移植,如有此需求,请反馈给我们。
5.5.2.2:下载地址
点击如下连接进入到 Power Writer 下载中心,PowerWriter 下载中心,如下图所示,点击 【立刻下载】将加密库以及相关文件下载到本次磁盘,如下所示:
5.5.2.3:文件结构
文件结构随着库的更新可能会有变化,不会再额外进行说明。
├── Docs.url /* 在线文档 */
├── ico.png
├── project /* 加密库 library 以及相关 Demo */
│ ├── lib /* 加密库 library */
│ │ ├── advance_software_protect_base.h /* 基础头文件定义 */
│ │ ├── advance_software_protect_lib.h /* 导出函数声明 */
│ │ ├── advance_software_protect_porting.c /* 用户移植接口 */
│ │ ├── advance_software_protect_samples.c /* 示例教程 */
│ │ ├── advance_software_protect_user.h /* 用户移植以及自定义参数 */
│ │ ├── gcc /* GCC 库文件 */
│ │ ├── iar /* IAR 库文件 */
│ │ └── mdk /* ARMCC 库文件 */
│ │ ├── advance_software_protect_m0.lib
│ │ ├── advance_software_protect_m0plus.lib
│ │ ├── advance_software_protect_m0plus_mpu.lib
│ │ ├── advance_software_protect_m1.lib
│ │ ├── advance_software_protect_m23.lib
│ │ ├── advance_software_protect_m23_tz.lib
│ │ ├── advance_software_protect_m3.lib
│ │ ├── advance_software_protect_m33.lib
│ │ ├── advance_software_protect_m33_dsp_fp.lib
│ │ ├── advance_software_protect_m33_dsp_fp_tz.lib
│ │ ├── advance_software_protect_m33_tz.lib
│ │ ├── advance_software_protect_m35plus.lib
│ │ ├── advance_software_protect_m35plus_dsp_fp.lib
│ │ ├── advance_software_protect_m35plus_dsp_fp_tz.lib
│ │ ├── advance_software_protect_m35plus_tz.lib
│ │ ├── advance_software_protect_m4.lib
│ │ ├── advance_software_protect_m4_fp.lib
│ │ ├── advance_software_protect_m55.lib
│ │ ├── advance_software_protect_m7.lib
│ │ ├── advance_software_protect_m7_dp.lib
│ │ ├── advance_software_protect_m7_sp.lib
│ │ ├── advance_software_protect_m85.lib
│ │ ├── advance_software_protect_sc000.lib
│ │ ├── advance_software_protect_sc300.lib
│ │ ├── advance_software_protect_v81mml_dsp_dp_mve_fp.lib
│ │ ├── advance_software_protect_v8m_bl.lib
│ │ ├── advance_software_protect_v8m_mml.lib
│ │ ├── advance_software_protect_v8m_mml_dp.lib
│ │ ├── advance_software_protect_v8m_mml_dsp.lib
│ │ ├── advance_software_protect_v8m_mml_dsp_dp.lib
│ │ ├── advance_software_protect_v8m_mml_dsp_sp.lib
│ │ ├── advance_software_protect_v8m_mml_sp.lib
│ │ └── devices_list
│ └── mdk_demo /* MDK demo */
│ └── stm32f103rfc6
│ ├── Core
│ ├── Drivers
│ ├── EWARM
│ ├── MDK-ARM
│ └── stm32f103rfc6.ioc
├── sample /* PowerWriter 系列产品,示例工程 */
│ ├── STM32F103rf_ecdsa.pkg /* 加密库使用 ecdsa 签名算法示例工程 */
│ ├── STM32F103rf_matrix.pkg /* 加密库使用 matrix 签名算法示例工程 */
│ ├── ecdsa /* ICWKEY 工程,以及导出公钥信息 */
│ │ ├── SafeLic_53ECCC98.uprj /* ICWKEY 示例工程 */
│ │ ├── password.txt /* ICWKEY 示例工程密码信息 */
│ │ ├── cortex_chipid_binding.c /* ICWKEY 示例工程,导出源码信息 */
│ │ └── cortex_chipid_binding.h /* ICWKEY 示例工程,导出头文件信息 */
│ ├── matrix /* 使用Matrix 导出示例工程 */
│ │ ├── DebugConfig
│ │ │ └── STM32F10xSample_STM32F103RG_1.0.0.dbgconf
│ │ ├── Listings
│ │ ├── Objects
│ │ ├── UidSample.uvguix.CSHSOFT
│ │ ├── UidSample.uvoptx
│ │ ├── UidSample.uvprojx
│ │ ├── cortex_chipid_binding.c
│ │ ├── cortex_chipid_binding.h
│ │ ├── main.c
│ │ └── startup_stm32f10x_hd.s
│ └── password.txt /* PowerWriter 示例工程密码说明 */
└── tool
├── linux
│ └── advance_software_protect /* 加密库Linux 命令行 */
├── macos
│ └── advance_software_protect /* 加密库 Macos 命令行 */
└── windows
└── advance_software_protect.exe /* 加密库 Windows 命令行 */
在实际项目使用中,请不要修改 advance_software_protect_base.h 和 advance_software_protect_lib.h 两个源文件中的任何定义,这两个文件均为加密库基础定义内容。
5.5.2.4:测试环境
- 目标测试板使用 STM32F103RFT6 核心板作为测试板,如使用其他系统板,参考环境搭建,进行配置。
- 目标板采用 HSI 时钟源,系统最高时钟 64Mhz。
- 使用 PC13 LED 作为系统指示器。
- 使用 USART 1(PA9 、PA10)作为日志打印接口。
- 开启 SWD 调试(PA13、PA14)。
- BOOT0 下拉电阻到 GND,从Main Flash 启动。
- 调试工具,串口工具,编程工具均为 Power Writer,ECDSA 签名算法使用 ICWKEY。
当前加密库可以在所有的 ARM Cortex -M 芯片上进行使用,但是推介在 Flash >128K Byte、SRAM > 48K 、并且有足够的剩余存储空间的项目上进行使用。
5.5.3:项目配置
5.5.3.1:MDK 项目配置
5.5.3.1.1:准备测试项目
按照Demo 的要求,准备LED 指示灯,当前使用PC13作为指示器,用于验证,开启SWD 调试接口,并配置 USART1 ,配置系统时钟为 64Mhz ,准备完成后,生成Demo 代码,如下图所示。
5.5.3.1.2:调整堆栈大小
加密库会使用动态内存分配来分配内部资源,在进行签名运算时,由于调用层数较深,可能也需要适当的进行栈大小的调整,当前项目的堆栈配置文件为 startup_stm32f103xg.s,如下所示:
Stack_Size EQU 0x400 ;当前的栈空间大小,如果不够,可适当增加
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
; <h> Heap Configuration
; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
Heap_Size EQU 0x2000 ;当前项目的堆大小,这里设置为 0x2000
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
PRESERVE8
THUMB
加密库使用 ECDSA 签名时,最低需要 5K 以上的 动态内存,在额外加上加密库本身,至少预留6K 以上,需要在实际使用项目堆配置的基础上,再增加6K 以上的堆分配,如果使用操作系统,则需要配置库的内存管理接口回调,详见后续章节。
5.5.3.1.3:调整工程资源分配
加密库将使用部分的 SRAM 作为代码执行缓冲区,所以,项目在原来的基础上,需要进行额外的调整,将Flash实际用量调小,将SRAM 进行调整,如下所示:
调整资源的目的是将部分SRAM(当前为 4K)作为加密库缓冲区使用,同时对应的FLASH 容量需要减小对应的容量,用于存放加密数据,调整后的整体代码空间不变,内存减少。
#调整前 Flash / SRAM 配置信息
IROM1:0x08000000 0xC0000 SRAM:0x20000000 0x18000
#调整后 Flash / SRAM 配置信息
IROM1:0x08000000 0xC0000 SRAM:0x20000000 0x17000
IROM2:0x20017000 0X1000
调整前:
调整后:
接下来我们回到工程设置中,切换到 Linker 设置,取消勾选 ‘Use Memory Layout from Target Dialog’ 选项,使用自定义 Scatter File 文件,点击编辑按钮,进入编辑模式,如下所示:
; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************
LR_IROM1 0x08000000 0x000BF000 { ; load region size_region
ER_IROM1 0x08000000 0x000BF000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
.ANY (+XO)
}
RW_IRAM1 0x20000000 0x00017000 { ; RW data
.ANY (+RW +ZI)
}
}
LR_IROM2 0x20017000 0x00001000 {
ER_IROM2 0x20017000 0x00001000 { ; load address = execution address
.ANY (+RO)
.ANY (.advance_software_protect_encryption_section) ; 在 LR_ROM2 后面追加当前定义。
}
}
5.5.3.1.4:加入lib 和 source
在压缩包中lib 目录,找到对应的基础源码,相关库文件,添加到工程中,如下图所示:
添加完成后,工程结构如下图所示:
请根据芯片的内核版本,添加对应的库文件,当前实例工程为 STM32F103RFT6,内核为 ARM Cortex M3 内核。
5.5.3.1.5:加入命令行
MDK 编译的固件为原始的固件,如不经过命令行进行处理,会导致相关功能没有开启,并且在实际产品中,可能无法运行,因为,我们需要将命令行工具,添加到工程配置中,如下所示:
RUN #1,我们将 axf 文件输出bin 文件,使用命令为:
fromelf --bin -o "$L@L.bin" "#L"
RUN#2,我们将第一步输出的 bin 文件,生成最终用于生产的固件,使用的命令为:
../../../../tool/windows/advance_software_protect.exe
-s 0x08000000 #-s 表示Flash 固件的起始地址为 0x08000000
-k 0xfb90ffcb9b62c2e6 #-k 表示加密的密码,长度为8 字节,必须和源码进行对应,每一个项目必须更改,避免和别人的项目采用一样的密码
-i ./stm32f103rfc6/stm32f103rfc6.bin/ER_IROM1 #-i 导入主Flash 固件
-r ./stm32f103rfc6/stm32f103rfc6.bin/ER_IROM2 #-r 导入SRAM 固件
每一个项目的 -k,也就是加密密码,必须随机生成 8字节,避免采用公用的密码,以提高安全性。
更多关于命令行的用法,请参考 命令行 章节。
5.5.3.1.6:其他设置
为了避免出现编译问题,建议开启C99 模式,如下图所示:
当前调试器采用PowerWriter 自带的调试器,使用方法,请参考:3.1.7:调试器使用教程 | PowerWriter文档中心,在实际项目进行开发当中,由于不需要MDK 进行下载和校验,所以,需要将调试器的编程,校验功能进行关闭,如下图所示:
加入加密库之后,依然可以进行项目的调试,但是下载采用PowerWriter 进行下载和校验,MDK 不进行下载和校验,以便验证最终的效果。
当加入加密库之后,可能会存在代码运行之后,关闭调试器,并开启读保护,将导致调试功能断线,因为,我们还需要做两个工程配置,一个用于调试,一个用于发布,两者的区别在于,发布版本多了一个 RELEASE 宏定义,如下图所示:
5.5.3.2:GCC 项目配置
5.5.3.2.1:准备测试项目
按照Demo 的要求,准备LED 指示灯,当前使用PC13作为指示器,用于验证,开启SWD 调试接口,并配置 USART1 ,配置系统时钟为 64Mhz ,准备完成后,生成Demo 代码,如下图所示。
5.5.3.2.2:调整堆栈大小
加密库会使用动态内存分配来分配内部资源,在进行签名运算时,由于调用层数较深,可能也需要适当的进行栈大小的调整,当前项目的堆栈配置文件为 STM32F103RFTX_FLASH.ld,如下所示:
_Min_Heap_Size = 0x4000 ; /* required amount of heap */
_Min_Stack_Size = 0x1000 ; /* required amount of stack */
加密库使用 ECDSA 签名时,最低需要 5K 以上的 动态内存,在额外加上加密库本身,至少预留6K 以上,需要在实际使用项目堆配置的基础上,再增加6K 以上的堆分配,如果使用操作系统,则需要配置库的内存管理接口回调,详见后续章节。
5.5.3.2.3:调整工程资源分配
加密库将使用部分的 SRAM 作为代码执行缓冲区,所以,项目在原来的基础上,需要进行额外的调整,将Flash实际用量调小,将SRAM 进行调整,如下所示:
调整资源的目的是将部分SRAM(当前为 4K)作为加密库缓冲区使用,同时对应的FLASH 容量需要减小对应的容量,用于存放加密数据,调整后的整体代码空间不变,内存减少。
#调整前 Flash / SRAM 配置信息
/* Memories definition */
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 96K
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 768K
}
#调整后 Flash / SRAM 配置信息
/* Memories definition */
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 92K /* SRAM from 96K to 92K, 4K for cryptographic code execution */
ENCRYPT_CODE (xr) : ORIGIN = 0x20017000 LENGTH = 4K /* 4K for cryptographic code execution */
VECTOR (rx) : ORIGIN = 0x08000000 LENGTH = 0x700 /* interrupt vector table */
SN (rx) : ORIGIN = 0x08000700 LENGTH = 4 /* For storing serial numbers */
LIC (rx) : ORIGIN = 0x08000704 LENGTH = 0xFC /* For storing PowerWriter signatures */
FLASH (rx) : ORIGIN = 0x8000800, LENGTH = 762K /* 768K (0xC0000) */
}
/* Sections */
SECTIONS
{
/* ENCRYPT CODE */
.advance_software_protect_encryption_section :
{
. = ALIGN(4);
KEEP(*(.advance_software_protect_encryption_section))
} > ENCRYPT_CODE
/* SN */
.advance_software_protect_sn :
{
. = ALIGN(4);
KEEP(*(.advance_software_protect_sn))
} > SN
/*
ECDSA Signature
*/
.asp_signature_license :
{
. = ALIGN(4);
KEEP(*(.asp_signature_license))
} > LIC
/* The startup code into "FLASH" Rom type memory */
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector)) /* Startup code */
. = ALIGN(4);
} >VECTOR
/*****/
//....more
}
5.5.3.2.4:加入lib 和 source
在压缩包中lib 目录,找到对应的基础源码,相关库文件,添加到工程中,如下图所示:
添加完成后,工程结构如下图所示:
请根据芯片的内核版本,添加对应的库文件,当前实例工程为 STM32F103RFT6,内核为 ARM Cortex M3 内核。
5.5.3.2.5:加入命令行
编译的固件为原始的固件,如不经过命令行进行处理,会导致相关功能没有开启,并且在实际产品中,可能无法运行,因为,我们需要将命令行工具,添加到工程配置中,如下所示:
在post-build steps,我们将第一步输出的 hex 文件,生成最终用于生产的固件,使用的命令为:
../advance_software_protect.exe -i ./stm32f103rfc6.hex -r 0x20017000
-s 0x08000000 #-s 表示Flash 固件的起始地址为 0x08000000
-k 0xfb90ffcb9b62c2e6 #-k 表示加密的密码,长度为8 字节,必须和源码进行对应,每一个项目必须更改,避免和别人的项目采用一样的密码
-i ./stm32f103rfc6.hex #-i 导入主Flash 固件
-r 0x20017000 #-r 可以指定从 hex 文件中抽取 ENCRYPT_CODE 作为加密代码区间
每一个项目的 -k,也就是加密密码,必须随机生成 8字节,避免采用公用的密码,以提高安全性。
更多关于命令行的用法,请参考 命令行 章节。
5.5.3.2.6:其他设置
为了让编译的输出的固件可以被命令行处理,我们需要输出hex 文件格式,设置如下
当前调试器采用PowerWriter 自带的调试器,使用方法,请参考:3.1.7:调试器使用教程 | PowerWriter文档中心,在实际项目进行开发当中,由于不需要IDE 进行下载和校验,所以,需要将调试器的编程,校验功能进行关闭,如下图所示:
加入加密库之后,依然可以进行项目的调试,但是下载采用PowerWriter 进行下载和校验,IDE不进行下载和校验,以便验证最终的效果。
当加入加密库之后,可能会存在代码运行之后,关闭调试器,并开启读保护,将导致调试功能断线,因为,我们还需要做两个工程配置,一个用于调试,一个用于发布,两者的区别在于,发布版本多了一个 RELEASE 宏定义,如下图所示:
此外,由于我们在资源分配时定义了新的节点,编译可能会产生警告,我们在GCC Linker 中去除特定的警告,如下图所示:
完成所有的设置之后,我们编译工程,将看到如下输出信息,如下所示:
5.5.3.3:IAR 项目配置
5.5.3.3.1:准备测试项目
按照Demo 的要求,准备LED 指示灯,当前使用PC13作为指示器,用于验证,开启SWD 调试接口,并配置 USART1 ,配置系统时钟为 64Mhz ,准备完成后,生成Demo 代码,如下图所示。
5.5.3.3.2:调整堆栈大小
加密库会使用动态内存分配来分配内部资源,在进行签名运算时,由于调用层数较深,可能也需要适当的进行栈大小的调整,同时调整 Flash 和 SRAM 均减小4K,用来存储加密代码,顺便将堆栈大小进行调整,以防止加密库运行时,堆栈溢出,如下所示:
加密库使用 ECDSA 签名时,最低需要 5K 以上的 动态内存,在额外加上加密库本身,至少预留6K 以上,需要在实际使用项目堆配置的基础上,再增加6K 以上的堆分配,如果使用操作系统,则需要配置库的内存管理接口回调,详见后续章节。
5.5.3.3.3:调整工程资源分配
加密库将使用部分的 SRAM 作为代码执行缓冲区,所以,项目在原来的基础上,需要进行额外的调整,将Flash实际用量调小,将SRAM 进行调整,如下所示:
调整资源的目的是将部分SRAM(当前为 4K)作为加密库缓冲区使用,同时对应的FLASH 容量需要减小对应的容量,用于存放加密数据,调整后的整体代码空间不变,内存减少。
/*###ICF### Section handled by ICF editor, don't touch! ****/
/*-Editor annotation file-*/
/* IcfEditorFile="$TOOLKIT_DIR$\config\ide\IcfEditor\cortex_v1_0.xml" */
/*-Specials-*/
define symbol __ICFEDIT_intvec_start__ = 0x08000000;
/*-Memory Regions-*/
define symbol __ICFEDIT_region_ROM_start__ = 0x08000000;
define symbol __ICFEDIT_region_ROM_end__ = 0x080BF000;
define symbol __ICFEDIT_region_RAM_start__ = 0x20000000;
define symbol __ICFEDIT_region_RAM_end__ = 0x20017000;
/* 新增 ENCRYPT_CODE 区段定义 */
define symbol __ICFEDIT_region_RAMCODE_start__ = 0x20017000;
define symbol __ICFEDIT_region_RAMCODE_end__ = 0x20018000;
/*-Sizes-*/
define symbol __ICFEDIT_size_cstack__ = 0x1000;
define symbol __ICFEDIT_size_heap__ = 0x4000;
/**** End of ICF editor section. ###ICF###*/
define memory mem with size = 4G;
define region ROM_region = mem:[from __ICFEDIT_region_ROM_start__ to __ICFEDIT_region_ROM_end__];
define region RAM_region = mem:[from __ICFEDIT_region_RAM_start__ to __ICFEDIT_region_RAM_end__];
/* 新增 ENCRYPT_CODE 区段 */
define region RAMCODE_region = mem:[from __ICFEDIT_region_RAMCODE_start__ to __ICFEDIT_region_RAMCODE_end__];
define block CSTACK with alignment = 8, size = __ICFEDIT_size_cstack__ { };
define block HEAP with alignment = 8, size = __ICFEDIT_size_heap__ { };
initialize by copy { readwrite };
do not initialize { section .noinit };
place at address mem:__ICFEDIT_intvec_start__ { readonly section .intvec };
place in ROM_region { readonly };
place in RAM_region { readwrite,block CSTACK, block HEAP };
/* 设置加密代码节点为 advance_software_protect_encryption_section !!!!!*/
place in ENCRYPT_CODE_region { readonly section .advance_software_protect_encryption_section};
5.5.3.3.4:加入lib 和 source
在压缩包中lib 目录,找到对应的基础源码,相关库文件,添加到工程中,如下图所示:
请根据芯片的内核版本,添加对应的库文件,当前实例工程为 STM32F103RFT6,内核为 ARM Cortex M3 内核。
5.5.3.3.5:加入命令行
编译的固件为原始的固件,如不经过命令行进行处理,会导致相关功能没有开启,并且在实际产品中,可能无法运行,因为,我们需要将命令行工具,添加到工程配置中,如下所示:
在post-build steps,我们将第一步输出的 hex 文件,生成最终用于生产的固件,使用的命令为:
"./../../../../tool/windows/advance_software_protect.exe" -k 0xfb90ffcb9b62c2e6 -i ./stm32f103rfc6-Release/Exe/stm32f103rfc6.hex -r 0x20017004 && echo > "$BUILD_FILES_DIR$/.postbuild"
-s #-s 表示Flash 固件的起始地址为 0x08000000
-k #-k 表示加密的密码,长度为8 字节,必须和源码进行对应,每一个项目必须更改,避免和别人的项目采用一样的密码
-i #-i 导入主Flash 固件
-r #-r 可以指定从 hex 文件中抽取 ENCRYPT_CODE 作为加密代码区间
每一个项目的 -k,也就是加密密码,必须随机生成 8字节,避免采用公用的密码,以提高安全性。
更多关于命令行的用法,请参考 命令行 章节。
-r 参数在 IAR 编译中,虽然我们设置加密代码节点为 某个指定的地址,比如0x20001700,但是编译器输出的实际地址可能并不是 0x20001700,比如变成了0x20001704,可以通过PowerWriter 加载固件,查看分段地址,是多少,同时代码中的 加载地址,以及命令行参数,也应该同步进行修改,按照编译器最终的输出文件为准。
5.5.3.3.6:其他设置
为了让编译的输出的固件可以被命令行处理,我们需要输出hex 文件格式,设置如下
当前调试器采用PowerWriter 自带的调试器,使用方法,请参考:3.1.7:调试器使用教程 | PowerWriter文档中心,在实际项目进行开发当中,由于不需要IDE 进行下载和校验,在调试时,使用Debug without download 模式,如下图所示:
加入加密库之后,依然可以进行项目的调试,但是下载采用PowerWriter 进行下载和校验,IDE不进行下载和校验,以便验证最终的效果。
当加入加密库之后,可能会存在代码运行之后,关闭调试器,并开启读保护,将导致调试功能断线,因为,我们还需要做两个工程配置,一个用于调试,一个用于发布,两者的区别在于,发布版本多了一个 RELEASE 宏定义,如下图所示:
完成所有的设置之后,我们编译工程,将看到如下输出信息,如下所示:
5.5.4:配置以及使用教程
通过 5.5.3 节的内容,现在我们完成了主流 IDE 开发环境的配置,本章节,将详细介绍,加密库的相关功能,以及使用方法。
5.5.4.1:系统配置
在加密库进行执行前,我们需要完成加密库的初始化,以及加入 sample code,如下图所示:
/*
* IMPORTANT:
* Do not encrypt the parent function () that contains the
* advance_software_protect_encryption_code_loader!
*/
void advance_software_protect_init()
{
/* initial */
advance_software_protect_set_mem_calloc_free_callback(calloc_mem_func, free_mem_func);
advance_software_protect_set_logger_callback(advance_software_protect_log_output);
/* show informations */
advance_software_protect_rights();
#ifdef ADVANCE_SOFTWARE_PROTECT_CODE_ENCRYPT_ENABLE
/* encrypt code loader */
advance_software_protect_encryption_code_loader(ADV_ENCRYPT_CODE_SEGMENT_ADDR, ADV_DATA_ENCRYPT_KEY, advance_software_protect_encrypt_code_sample);
#endif
}
int main(void)
{
/* 初始化加密库 */
advance_software_protect_init();
while (1){
}
/* USER CODE END 3 */
}
5.5.4.1.1:内存接口配置
加密库内部部分功能模块,需要动态申请内存,库内部默认配置的 内存接口为,calloc 和 free,但是在使用RTOS 的项目中使用时,可能会存在内存管理问题,此外也不利于调试分析,数据的观察,因此,提供了内存管理分配接口,函数原型为如下所示:
bool advance_software_protect_set_mem_calloc_free_callback(calloc_func_cb calloc_func, free_func_cb free_func);
配置参考如下所示:
/* memory alloc & free */
void *calloc_mem_func(size_t count, size_t size)
{
return calloc(count, size);
}
void free_mem_func(void *blk_obj)
{
free(blk_obj);
}
void advance_software_protect_init()
{
/* initial */
advance_software_protect_set_mem_calloc_free_callback(calloc_mem_func, free_mem_func);
}
如果不使用RTOS,此接口为非必须,当使用操作系统时,请注意需要 对申请的内存执行 memset 为 0 ,详见 操作系统任务堆分配。请注意,如果注册内存接口,内存分配和内存释放必须成对出现。
5.5.4.1.2:日志接口配置
为了便于开发人员分析和观察数据,加密库提供了标准的日志功能模块,在加密库运行时,内部也会输出一部分日志信息,以便于项目的调试,函数原型为:
bool advance_software_protect_set_logger_callback(const advance_software_logger_callback cb);
配置参考如下所示:
/* message callback */
#ifdef STM32F103xG
extern UART_HandleTypeDef huart1;
#endif
static void advance_software_protect_log_output(const char *msg)
{
#ifdef STM32F103xG
HAL_UART_Transmit(&huart1, (uint8_t *)msg, strlen(msg), strlen(msg));
#endif
}
void advance_software_protect_init()
{
/* initial */
advance_software_protect_set_logger_callback(advance_software_protect_log_output);
//advance_software_protect_set_logger_callback(0); //如果参数为0,内部将自动释放资源,并关闭功能
}
输出日志的标准调用方式,同 printf 函数类似,区别在于,当前的日志模块,一个参数为日志类型,见 E_ADV_SOFWARE_PROTECT_LOG_TYPE 定义,如下所示:
/* Log type*/
typedef enum E_ADV_SOFWARE_PROTECT_LOG_TYPE
{
ADV_BLANK,
ADV_INFO,
ADV_DEBUG,
ADV_WARN,
ADV_EXCEPTION,
ADV_ERROR,
ADV_MAX
} E_ADV_SOFWARE_PROTECT_LOG_TYPE;
advance_software_protect_logger(type, format, ...)
//使用方法请参考 advance_software_protect_samples.c 中范例
5.5.4.1.3:加密函数loader
在进行加密库的任何功能调用,或者是用户自定义的加密函数调用之前,必须进行加密函数的初始化,否则将产生异常,函数原型声明如下:
void advance_software_protect_encryption_code_loader(uint32_t load_address, uint64_t key, CodeLoaderCallback callback);
load_address:表示加载地址,必须和 .advance_software_protect_encryption_section 节点地址完全一致,否则将产生异常,详见 调整工程资源分配。
key:数据加密的密码,必须和 命令行 传参一致,否则将会导致加载失败,详见 加入命令行。
callback:加载回调函数,用于检测加载信息。
加密函数的典型初始化方式如下所示:
/* message callback */
/* segment address */
#ifdef ADVANCE_SOFTWARE_PROTECT_CODE_ENCRYPT_ENABLE
#define ADV_ENCRYPT_CODE_SEGMENT_ADDR (0x20017000) /* refer to advance_software_protect_encryption_section */
#endif
/* key for data encrypt */
#if (defined ADVANCE_SOFTWARE_PROTECT_FLASH_VERIFY_ENABLE) || (defined ADVANCE_SOFTWARE_PROTECT_CODE_ENCRYPT_ENABLE)
#define ADV_DATA_ENCRYPT_KEY (0xfb90ffcb9b62c2e6) /* 64 bit*/
#endif
/* for ram code loader */
void advance_software_protect_encrypt_code_sample(E_ENCRYPT_CODE_EVENT event)
{
switch (event)
{
/* Development mode running */
case EncryptCodeUnprocessed:
advance_software_protect_logger(ADV_EXCEPTION, " (未处理 !) 请使用命令行将固件进行处理后,并使用PowerWriter 编程后再试 ...\r\n");
while (1)
{
} /* suspended */
/* Loading RAM code succeeded */
case EncryptCodeOk:
advance_software_protect_logger(ADV_DEBUG, " (成功) 加载成功 ...\r\n");
break;
/* Failed to load RAM code */
case EncryptCodeError:
advance_software_protect_logger(ADV_ERROR, " (失败 !!!!) 加载失败 ...\r\n");
/* reset system */
advance_software_protect_system_reset();
break;
}
}
void advance_software_protect_init()
{
/* initial */
advance_software_protect_encryption_code_loader(ADV_ENCRYPT_CODE_SEGMENT_ADDR, ADV_DATA_ENCRYPT_KEY, advance_software_protect_encrypt_code_sample);
}
加密代码初始化为最重要的功能,必须在所有加密函数前调用前,进行初始化,否则将导致异常,此外,加载地址 和 数据加密的密码需要和命令行的密码一致,加入加密库之后,需要用PowerWriter 进行编程后,再进行调试,详见 调试与验证 。
5.5.4.2:对象监控
对象监视器可以监控任意一个对象有没有被异常修改,比如通过调试器动态修改内存,或者通过代码补丁,修改默认初始化的值,或者是 Library 内部代码,防止被Application 程序修改内存,有意或者无意修改到其他程序所属的内存空间,目的是绕过 Library 的某些限制,或者是,注册权限等,Object Watcher 提供监控 单一对象 (如结构体变量,通用内置类型)等数据的监控,如果数据被外部程序修改,做周期性检查的时候将会产生错误,然后根据对应的错误,处理错误即可。
5.5.4.2.1:单对象监控
定义格式:
//测试用结构体,里面的类型可以是任意类型,并且无需对齐
typedef struct S_Object
{
uint32_t m_sample1;
uint32_t m_sample2;
uint32_t m_sample3;
uint8_t * m_ptr8;
}S_Object;
/* 声明一个对象类型监控包装类型,导出的类型将为 S_Object_Watcher */
S_ADV_SOFTWARE_PROTECT_OBJECTWATCH_Watcher(S_Object)
/* 定义受托管的对象 */
S_Object_Watcher m_Object;
API:
//检查对象是否正常
bool advance_software_protect_objectwatch_check(object,type); //其中object 为对象的指针,type 为托管对象类型
bool advance_software_protect_objectwatch_update(object,type); //其中object 为对象的指针,type 为托管对象类型
演示代码:
/*
* 单个对象监控演示
*/
void advance_software_protect_single_object_watcher()
{
#ifdef ADVANCE_SOFTWARE_PROTECT_OBJECTWATCH_ENABLE
advance_software_protect_logger(ADV_DEBUG," ---------------Single Object Watcher Simple Start ----------\r\n");
/* 第一次调用检查函数,会执行初始化 */
bool m_object_check_result = advance_software_protect_objectwatch_check(&m_Object,S_Object_Watcher);
/* 返回一定是成功的 */
if(m_object_check_result){
advance_software_protect_logger(ADV_DEBUG," m_Object check passed at first time\r\n");
}else{
advance_software_protect_logger(ADV_ERROR," m_Object check failed at first time, Never execute at first time!\r\n");
}
/*
* 模拟Object 被修改:如被调试器修改,被其他程序修改,内存补丁,或者是异常等原因,但是外部并不明确 此 Object 有校验功能,
* 故而并没有更新 Object 的校验信息,这将导致周期性的 Check 触发异常.
*/
m_Object.m_Object.m_sample1 = 1;
m_Object.m_Object.m_sample2 = 2;
m_Object.m_Object.m_sample3 = 3;
m_Object.m_Object.m_ptr8 = (uint8_t *)0x12345678;
m_object_check_result = advance_software_protect_objectwatch_check(&m_Object,S_Object_Watcher);
/* 结果将会触发异常 */
if(m_object_check_result){
advance_software_protect_logger(ADV_DEBUG," m_Object has been modified but there is no update validation, which is not performed here\r\n");
}else{
advance_software_protect_logger(ADV_ERROR," m_Object will fail if it is modified without updating the checksum value!\r\n");
}
/* 更新对象的校验值,再检查, 将会通过 ,正常项目开发时,托管对象后,更新完 对象中的成员后,再顺便更新对象校验信息 ,在周期的检查中将不会触发错误 */
advance_software_protect_objectwatch_update(&m_Object,S_Object_Watcher); /* update checksum */
m_object_check_result = advance_software_protect_objectwatch_check(&m_Object,S_Object_Watcher);
/* 将会通过 */
if(m_object_check_result){
advance_software_protect_logger(ADV_DEBUG," m_Object had modified & updated, will be passed !\r\n");
}else{
advance_software_protect_logger(ADV_ERROR," m_Object had modified & updated, never failed !\r\n");
}
advance_software_protect_logger(ADV_DEBUG," ---------------Single Object Watcher Simple End----------\r\n");
#endif
}
Object Watcher 只校验对象实体数据本身,不会将结构体中的指针指向的对象包含在校验中,如果需要监控指针的指针,请使用动态对象监控模式。
本项目示例工程采用标准crc32 校验算法,实际项目可更换更换一套自己的校验方法. 校验方法接口在 advance_software_protect_port.c (advance_software_protect_crc32) 中。
5.5.4.2.2:动态对象监控
单个对象监控可以提供一个简单的监控目标,针对复杂度不高的系统,简单易用,针对复杂的系统,为了降低 内存监控 功能的使用难度,以及尽可能减少对现有代码的修改,此时便提供了 对象监控托管功能,包含常见的功能: 添加对象到托管池,从托管池中删除对象,更新托管池中的某个对象,更新托管池中的 某个对象,更新托管池中的所有对象 等功能。
API 列表:
/* Object Watcher Exception type */
typedef enum E_ObjectWatchException
{
ObjectParamError = 0, /* 参数错误 */
ObjectMemoryError, /* 内存错误 */
ObjectAlreadyExits, /* 当前对象已在监控列表中 */
ObjectModified, /* 对象被异常访问 */
} E_ObjectWatchException;
/* callback for object watcher */
typedef void (*ObjectWatcherCallback)(E_ObjectWatchException exception,void * object);
//设置监控回调
void advance_software_protect_objectwatch_list_set_callback(ObjectWatcherCallback eventCallback);
//查找某个对象是否在托管中,参数为对象的指针
bool advance_software_protect_objectwatch_list_find(void * object_ptr);
//托管一个对象,参数为:对象指针、对象的大小、当从托管中移除时是否同时删除对象实体(仅适用动态分配的对象)
bool advance_software_protect_objectwatch_list_add(void * object_ptr, size_t object_size,bool del_object_when_remove);
//获取托管对象的数量
size_t advance_software_protect_objectwatch_list_size(void);
//托管队列中删除移动某个对象,参数为对象指针
bool advance_software_protect_objectwatch_list_remove(void * object_ptr);
//清空所有托管的对象
void advance_software_protect_objectwatch_list_reset(void);
//检查某一个对象是否被修改,参数为对象指针。
bool advance_software_protect_objectwatch_list_check(void * object_ptr);
//检查托管对象中的所有对象,返回值为:检查通过的数量,(注意:如果注册了事件回调接口,将会通过事件接口打印失败的详情)
size_t advance_software_protect_objectwatch_list_check_all(void);
//更新某一个对象的校验值,参数为对象指针
bool advance_software_protect_objectwatch_list_update(void * object_ptr);
//更新所有对象的校验值
void advance_software_protect_objectwatch_list_update_all(void);
Object Watcher List 演示代码:
/* 定义用于动态托管的对象 */
S_Object m_object;
S_A m_aType;
S_B m_bType;
S_Class m_class;
/* 用于动态托管的事件回调接口 */
void advance_software_protect_dynamic_muti_object_watcher_callback(E_ObjectWatchException excep, void* param)
{
switch(excep)
{
/* 参数错误为一般性错误,提示参数有问题 */
case ObjectParamError:
advance_software_protect_logger(ADV_WARN, " Error param ...\r\n");
break;
/* 提示某一个对象被修改了, 请及时处理异常,防止软件被破解,param 指向对象首地址 */
case ObjectModified:
advance_software_protect_logger(ADV_ERROR, " [0x%08X] Object was modified!\r\n", (uint32_t)param);
break;
/* 对象已经存在于托管列表中,重复添加时提示 */
case ObjectAlreadyExits:
advance_software_protect_logger(ADV_WARN, " [0x%08X] Object already exist!\r\n", (uint32_t)param);
break;
/* 内存不足,提示 Heap 内存 分配太小 */
case ObjectMemoryError:
advance_software_protect_logger(ADV_ERROR, " Memory space is used up!\r\n");
break;
}
}
void advance_software_protect_dynamic_muti_object_watcher()
{
advance_software_protect_logger(ADV_DEBUG, " ---------- Dynamic Muti-Object Watcher Simple Start ----------\r\n");
/* 设置事件回调接口:可选,设置回调接口可以拿到更详细的信息 */
advance_software_protect_logger(ADV_DEBUG, " Set ObjectWatcher Event callback...\r\n");
advance_software_protect_objectwatch_list_set_callback(advance_software_protect_dynamic_muti_object_watcher_callback);
/* 动态添加对象到托管列表中 */
advance_software_protect_objectwatch_list_add(&m_object, sizeof(m_object), false);
advance_software_protect_objectwatch_list_add(&m_aType, sizeof(m_aType), false);
advance_software_protect_objectwatch_list_add(&m_bType, sizeof(m_bType), false);
advance_software_protect_objectwatch_list_add(&m_class, sizeof(m_class), false);
//重复添加将会产生异常 (ObjectAlreadyExits)
advance_software_protect_objectwatch_list_add(&m_class, sizeof(m_class), false);
//参数错误异常 (ObjectParamError)
advance_software_protect_objectwatch_list_add(0, sizeof(m_class), false);
//动态添加多个对象并自动托管内存释放
for(size_t i = 0; i < 200; i++)
{
S_Class* m_new_obj = (S_Class*)malloc(sizeof(S_Class));
if(m_new_obj)
{
//当移除托管时,同时也删除对象实体,最后一个参数设置为true
advance_software_protect_objectwatch_list_add(m_new_obj, sizeof(S_Class), true);
}
else
{
//内存不足
//...
break;
}
}
/* 查找一个对象是否在托管中 */
bool m_find = advance_software_protect_objectwatch_list_find(&m_aType);
if(m_find)
{
advance_software_protect_logger(ADV_INFO, " m_aType exist..\r\n");
}
else
{
advance_software_protect_logger(ADV_INFO, " m_aType not exist..\r\n");
}
/* 统计托管对象的数量 */
advance_software_protect_logger(ADV_DEBUG, " Total %d objects in watching..\r\n", advance_software_protect_objectwatch_list_size());
/* 校验单个对象 */
bool m_check = advance_software_protect_objectwatch_list_check(&m_class);
if(m_check)
{
advance_software_protect_logger(ADV_DEBUG, " m_class check passed ..\r\n");
}
else
{
//failed...
//...
}
/* 校验所有对象 */
m_class._data_.m_a.m_data.m_sample1 ^= 0xAB; //模拟外部修改了内存
size_t m_passed_count = advance_software_protect_objectwatch_list_check_all(); //失败的对象将会通过事件回调接口打印详情
size_t m_total_count = advance_software_protect_objectwatch_list_size();
//判断通过的和总数是否相等.来决定失败还是成功,
if(m_passed_count == m_total_count)
{
advance_software_protect_logger(ADV_DEBUG, " All objects check passed ..\r\n");
}
else
{
advance_software_protect_logger(ADV_DEBUG, " Passed: %d, Failed : %d ..\r\n", m_passed_count, m_total_count);
}
/* 更新单个对象校验值, 正常代码 */
if(advance_software_protect_objectwatch_list_update(&m_class))
{
advance_software_protect_logger(ADV_DEBUG, " m_class update ok ..\r\n");
}
else
{
advance_software_protect_logger(ADV_DEBUG, " m_class update failed ..\r\n");
}
advance_software_protect_logger(); //再次全部检查时,将会通过
/* 删除单个对象 */
bool m_del = advance_software_protect_objectwatch_list_remove(&m_bType);
if(m_del)
{
advance_software_protect_logger(ADV_INFO, " m_bType removed, left %d object in watching..\r\n", advance_software_protect_objectwatch_list_size());
}
else
{
advance_software_protect_logger(ADV_INFO, " m_bType not exist!..\r\n");
}
/* 更新所有对象 */
advance_software_protect_objectwatch_list_update_all();
advance_software_protect_logger(ADV_INFO, " All objects updated...\r\n");
/* 删除所有对象 */
advance_software_protect_objectwatch_list_reset();
advance_software_protect_logger(ADV_INFO, " All objects removed, left %d object in watching..\r\n", advance_software_protect_objectwatch_list_size());
advance_software_protect_logger(ADV_DEBUG, " ---------------Dynamic Muti-Object Watcher Simple End----------\r\n");
}
5.5.4.2.3:补充说明
Object Watcher 的目的是监控内存是否被异常修改,使用流程
使用单一对象包装宏监控一个对象、或者使用托管方法动态添加监控对象。
setter 方法:项目代码在更新对象内部属性时,同时更新对象的校验值,使用update 方法。
getter 方法:项目代码在取用对象内部属性时,调用check 方法来检查数据是否被异常修改、从而判断有无异常访问、如外部使用了调试器直接设置了断点, 强行停止运行、修改关键内存数据、应用层修改到了协议层的数据、Object Watcher 可以用于此目的。
关于对象中的指针如何处理:如果想监控对象实体内部的指针指向的对象,请讲指针指向的对象再次托管到 监控列表中即可,多个分支采用一样的操作流程,如下所示
struct A
{
int a;
};
struct B
{
int b;
struct A * sa;
}
struct A m_a;
struct B m_b =
{
.b = 10,
.sa = &m_a;
};
//上面的m_b 对象托管时,如何托管 m_b.sa?
//示例:
advance_software_protect_objectwatch_list_add(&m_b, sizeof(m_b), false);
advance_software_protect_objectwatch_list_add(m_b.sa, sizeof(m_b.sa), false); //指针再次托管,即可监控对象m_a, 也可以直接托管m_a
5.5.4.3:固件签名
为了防止芯片固件被直接拷贝,将固件与特定芯片进行绑定或者认证是必须的,PowerWriter 提供了Matrix 固件签名功能,使用方法详见:3.4 Matrix示例 | PowerWriter文档中心,以及 ECDSA 数字签名功能,使用方法详见 3.3 ECDSA示例 | PowerWriter文档中心,在当前加密库中,这两种签名方法均已包含,相当于PowerWriter 的标准模式,加密库在原有的基础上,将会对签名验证代码、UID 读取代码进行函数级加密、配合固件校验功能、对象监控功能,可在相当高的程度上,防止固件被反编译、动态分析、动态调试,本节内容,需要开发者先熟悉 Matrix 和 ECDSA 签名的传统用法,如不熟悉以上两种签名的使用流程,建议先阅读文档教程,然后再做加密库的集成。
在开始之前,我们先配置 Matrix 和 ECDSA 的公共定义,在 lib/advance_software_protect_user.h
用户头文件中,我们可以找到如下定义
/* 混淆用的随机数,在实际项目中必须进行调整,以用于混淆关键信息 */
#define ASP_RAND_FACTOR (0x45623513)
/* 芯片UID 地址 */
#define UID_CHIP_ADDR (0x1FFFF7E8) /* 参考导出头文件 cortex_chipid_binding.h 中 (UID_CHIP_ADDR_MIX ^ UID_CHIP_ADDR) */
#define UID_CHIP_SIZE (12) /* 参考 cortex_chipid_binding.h 中的 UID_CHIP_SIZE */
#define UID_KEYADDR_PLACEHOLDER_EN (1) /* 是否开启签名信息预定义,开启后可在对应地址预分配空间,建议开启 */
#define UID_KEYADDR_INNER_VAL (0x08002000) /* 签名存储地址 */
#ifdef ADVANCE_SOFTWARE_PROTECT_COMPILER_MDK
#define UID_KEYADDR_INNER ".ARM.__at_0x08002000" /* 签名存储地址在MDK 中的定义形式 */
#elif defined ADVANCE_SOFTWARE_PROTECT_COMPILER_GCC
#define UID_KEYADDR_INNER ".asp_signature_license" /* 签名存储地址在 GCC中的 segment,请不要修改此定义,具体参考GCC 的配置 */
#endif
以上功能定义,需要根据PowerWriter 或者 ICWKEY 的项目配置进行调整,注意请留意注释内容 ,Matrix 和 ECDSA 的教程源码中有详细介绍。
实现CID 读取API,并将其函数声明为加密函数:
/* for signature */
#ifdef ADVANCE_SOFTWARE_PROTECT_SIGNATURE_ENABLE
/*
* @brief Gets the chip ID of the current chip.
* @pram id_data : A buffer that stores ids
* size : id length
* @retval void
*/
ASP_ENCRYPT_F_DECL(void advance_software_protect_get_cid(uint32_t *id_addr, uint8_t size,uint32_t *id_data))
void advance_software_protect_get_cid(uint32_t *id_addr, uint8_t size, uint32_t *id_data)
{
// If the ID is discontinuous, you need to handle it yourself...
// offset 0x00,0x04,0x14 ( Such as STM32L0, STM32L1, STM32L4 etc)
#if ((defined STM32L0) || (defined STM32L1) || (defined STM32L4))
id_data[0] = *(id_addr + (0x00 / 4));
id_data[1] = *(id_addr + (0x04 / 4));
id_data[2] = *(id_addr + (0x14 / 4));
#else
// The ID is continuous...
for (int i = 0; i < (size / 4); i++){
*id_data++ = *id_addr++;
}
#endif
}
#endif
添加签名验证代码:
#ifdef ADVANCE_SOFTWARE_PROTECT_SIGNATURE_ENABLE
void advance_software_protect_signature()
{
advance_software_protect_logger(ADV_DEBUG, " ---------------Product Signature start----------\r\n");
if (advance_software_protect_verify_authorization(&m_signature_config))
{
advance_software_protect_logger(ADV_DEBUG, " Chip signature verify passed...\r\n");
}
else
{
advance_software_protect_logger(ADV_ERROR, " Chip signature verify failed!!!!!\r\n");
}
advance_software_protect_logger(ADV_DEBUG, " ---------------Product Signature end----------\r\n");
}
#endif
ASP_ENCRYPT_F_DECL(void advance_software_protect_sample())
void advance_software_protect_sample()
{
/* chip signature */
#ifdef ADVANCE_SOFTWARE_PROTECT_SIGNATURE_ENABLE
advance_software_protect_signature();
#endif
}
签名验证的关键函数,建议声明为加密函数。
5.5.4.3.1:使用 Matrix 签名
启用 Matrix 签名配置:
/* Signature */
#define ADVANCE_SOFTWARE_PROTECT_SIGNATURE_MATRIX /* using matrix as signature */
//#define ADVANCE_SOFTWARE_PROTECT_SIGNATURE_ECDSA /* using ecdsa as signature */
调整Matrix 签名配置:
#define UID_KEY_LENGTH (12) /* refer to UID_KEY_LENGTH in cortex_chipid_binding.h */
#define UID_DATAENDIAN littleEndian /* refer to UID_DATAENDIAN in cortex_chipid_binding.h */
#define UID_USERID_KEY1 (0x53A886F5) /* refer to UserID 1 in cortex_chipid_binding.h */
#define UID_USERID_KEY2 (0x5D71DDB3) /* refer to UserID 2 in cortex_chipid_binding.h */
#define UID_USERID_KEY3 (0x32677FE8) /* refer to UserID 3 in cortex_chipid_binding.h */
ASP_ENCRYPT_F_DECL(static void ChipUIDAlgo(char pUserID[], char pChipID[], char pKey[]))
//The following code may warn in KEIL(MDK), ignore it
static void ChipUIDAlgo(char pUserID[], char pChipID[], char pKey[])
{
pKey[0] = pUserID[8] | pChipID[5] - pUserID[10] + pChipID[2] ;
pKey[1] = pChipID[1] & pChipID[0] ^ pUserID[4] * pChipID[11] ;
pKey[2] = pChipID[3] ^ pChipID[10] | pChipID[6] - pUserID[11] ;
pKey[3] = pUserID[9] & pChipID[4] + pUserID[6] * pUserID[0] ;
pKey[4] = pChipID[7] + pUserID[1] * pChipID[9] - pUserID[7] ;
pKey[5] = pUserID[2] & pUserID[3] ^ pUserID[5] | pChipID[8] ;
pKey[6] = pChipID[6] * pChipID[4] & pUserID[6] + pChipID[11] ;
pKey[7] = pChipID[1] ^ pUserID[0] - pUserID[5] | pUserID[7] ;
pKey[8] = pChipID[8] - pUserID[10] | pChipID[2] * pUserID[2] ;
pKey[9] = pChipID[10] & pUserID[11] + pChipID[5] ^ pUserID[4] ;
pKey[10] = pUserID[9] & pUserID[3] ^ pChipID[7] - pChipID[0] ;
pKey[11] = pChipID[3] + pUserID[8] | pUserID[1] * pChipID[9] ;
}
请注意,签名计算代码,声明为加密函数。
5.5.4.3.2:使用 ECDSA 签名
配置 ECDSA 公钥:
/* using ecdsa to signature */
// Public key
const static uint8_t PUBLIC_KEY[49] = {
0x04, 0x58, 0x82, 0x74, 0x51, 0x40, 0x7E, 0xEF, 0x99, 0x94, 0x7A, 0x66, 0xA6, 0x80, 0x07, 0x1B,
0xE4, 0xED, 0x5C, 0xD4, 0x68, 0xDB, 0xBC, 0xDB, 0x12, 0x7B, 0x5B, 0xE0, 0xA7, 0x85, 0xFA, 0xD1,
0x33, 0x61, 0x91, 0xB8, 0x37, 0x6B, 0x24, 0xC3, 0x82, 0xB5, 0x48, 0x10, 0x94, 0x32, 0x39, 0xB5,
0x46};
5.5.4.3.2:签名示例工程
为了降低开发者的疑虑,我们准备了示例 ICWKEY 和 PowerWriter 的Demo 工程项目,关于Matrix 和 ECDSA 的配置均在以下路径中 /sample/
文件夹下,文件结构如下所示:
.
├── STM32F103rf_ecdsa.pkg /* STM32F103 ecdsa powerwriter 工程 */
├── STM32F103rf_matrix.pkg /* STM32F103 ecdsa powerwriter matirx工程 */
├── ecdsa
│ ├── SafeLic_53ECCC98.uprj /* ICWKEY ECDSA 工程项目 */
│ ├── cortex_chipid_binding.c /* ICWKEY ECDSA 工程源码 */
│ └── cortex_chipid_binding.h /* ICWKEY ECDSA 工程源码 */
├── matrix /* PowerWriter Matrix 工程源码 */
│ ├── DebugConfig
│ ├── Listings
│ ├── Objects
│ ├── UidSample.uvguix.CSHSOFT
│ ├── UidSample.uvoptx
│ ├── UidSample.uvprojx
│ ├── cortex_chipid_binding.c
│ ├── cortex_chipid_binding.h
│ ├── main.c
│ └── startup_stm32f10x_hd.s
└── password.txt
5.5.4.4:固件校验
固件校验功能为加密库最核心的功能,通过固件校验模块,可检测到当前固件是否被篡改,并对修改做出响应,从而避免核心功能被破解,保护用户固件,固件校验算法,只会校验当前工程编译的固件的范围,不会校验固件范围之外的数据,并且,针对当前固件内的动态数据范围,可灵活设置跳过地址和大小,配置跳过校验的地址和参考如下配置:
定义动态数据:
/* 在Flash 中定义 SN 动态数据 ,当前定义为0x08001000 */
#ifdef ADVANCE_SOFTWARE_PROTECT_SN_ENABLE
#ifdef ADVANCE_SOFTWARE_PROTECT_COMPILER_GCC
#define ADVANCE_SOFTWARE_PROTECT_SN_ADDR ".advance_software_protect_sn" /*defined in ld link Script */
#elif defined(ADVANCE_SOFTWARE_PROTECT_COMPILER_IAR)
#define ADVANCE_SOFTWARE_PROTECT_SN_ADDR 0x08001000
#else
#define ADVANCE_SOFTWARE_PROTECT_SN_ADDR ".ARM.__at_0x08001000"
#endif
#ifdef ADVANCE_SOFTWARE_PROTECT_COMPILER_IAR
#pragma diag_suppress = Pa082
const uint32_t m_serialnumber @ADVANCE_SOFTWARE_PROTECT_SN_ADDR = {0}; /* Fixed address for easy maintenance */
#else
const uint32_t m_serialnumber __attribute__((section(ADVANCE_SOFTWARE_PROTECT_SN_ADDR))) = {0}; /* Fixed address for easy maintenance */
#endif
#endif
跳过校验区域配置方法
S_ADV_SOFTWARE_PROTECT_FLASH_VERIFY_START
/* Please add the dynamic storage area you want to skip, refer to the sequence
* number definition below and add the definition after it.
*/
/***************************** START *********************************/
/* 跳过 SN 校验 */
#ifdef ADVANCE_SOFTWARE_PROTECT_SN_ENABLE
{
.m_skip_addr = (void *)&m_serialnumber,
.m_skip_size = sizeof(m_serialnumber)
},
#endif
/* 如果需要跳过其他校验区域,请参考SN 的跳过方法,在下面进行定义即可 */
/* ... */
/****************************** END **********************************/
S_ADV_SOFTWARE_PROTECT_FLASH_VERIFY_END S_ADV_SOFTWARE_PROTECT_FLASH_VERIFY_APP
调用方法:
/* for flash verify */
#ifdef ADVANCE_SOFTWARE_PROTECT_FLASH_VERIFY_ENABLE
void advance_software_protect_flash_verify_sample()
{
advance_software_protect_logger(ADV_DEBUG, " ---------------Flash Verify start----------\r\n");
E_ADV_SOFTWARE_PROTECT_FLASH_VERIFY_RESULT result = advance_software_protect_flash_verify(ADV_DATA_ENCRYPT_KEY);
switch (result)
{
case ConfigError: /* configure error ! */
advance_software_protect_logger(ADV_DEBUG, " 配置错误 !\r\n");
break;
case UnprocessedAfterComplied: /* Run in Debug mode */
advance_software_protect_logger(ADV_DEBUG, " (当前为调试模式) 固件没有处理,功能未生效...\r\n");
break;
case FlashVerifyFailed: /* Verify Failed */
advance_software_protect_logger(ADV_DEBUG, " 校验失败 !!!\r\n");
break;
case FlashVerifyPassed: /* Verify Passed */
advance_software_protect_logger(ADV_DEBUG, " 校验成功...\r\n");
break;
}
advance_software_protect_logger(ADV_DEBUG, " ---------------Flash Verify end----------\r\n");
}
#endif
ASP_ENCRYPT_F_DECL(void advance_software_protect_sample())
void advance_software_protect_sample()
{
/* flash verify */
#ifdef ADVANCE_SOFTWARE_PROTECT_FLASH_VERIFY_ENABLE
advance_software_protect_flash_verify_sample();
#endif
}
5.5.4.5:函数加密
函数加密为加密库最核心的功能之一,通过函数加密,将会将部分函数在芯片中,进行加密存储,然后通过,加密函数loader 进行加载,再进行调用,可确保核心代码不会被反编译,从而保护固件的安全,声明加密函数的方法如下:
// 再函数定义处的前面,增加如下声明,即可声明为加密函数。
ASP_ENCRYPT_F_DECL(函数原型)
- 加密库的核心代码均已声明为加密函数,当前 advance_software_protect_porting.c 中的所有函数,均为加密函数。
- 用户可通过此声明,将函数定义为加密函数。
advance_software_protect_signature、advance_software_protect_flash_verify、advance_software_protect_anti_debugger 等核心函数调用的父函数,推介设置为加密函数。
加密函数在调用前,必须通过 Loader 进行加载,否则将导致指令总线硬件异常,详见 加密函数 loader。
此外,加密函数的存储空间根据工程资源分配的 .advance_software_protect_encryption_section
段进行存储,如果加密函数过多的导致编译出错时,适当调整资源配置。
5.5.4.6:反调试服务
为了辅助项目开发对项目进行辅助加密,加密库同时提供了标准的流程,用于帮助开发者,完成固件的基础反调试功能,相对于传统的开发模式,反调试服务,在对项目开发辅助时更加便捷。
配置方法:
/* for anti debugger*/
#ifdef ADVANCE_SOFTWARE_PROTECT_ANTI_DEBUGGER_ENABLE
/*
* @brief Disabling the debugger
* @pram none
* @retval none
*/
ASP_ENCRYPT_F_DECL(void advance_software_protect_debugger_disable())
void advance_software_protect_debugger_disable()
{
#ifdef RELEASE
/* dsiable debugger */
__HAL_AFIO_REMAP_SWJ_DISABLE();
#endif
}
/*
* @brief Check whether read protection is enabled
* @pram none
* @retval true is enable rdp
*/
ASP_ENCRYPT_F_DECL(bool advance_software_protect_rdp_check())
bool advance_software_protect_rdp_check()
{
#ifdef RELEASE
/*check RDP */
FLASH_OBProgramInitTypeDef m_cur_ob;
HAL_FLASHEx_OBGetConfig(&m_cur_ob);
return (m_cur_ob.RDPLevel != OB_RDP_LEVEL_0);
#else
return true; /* default return true */
#endif
}
/*
* @brief Disabling Read Protection
* @pram none
* @retval none
*/
ASP_ENCRYPT_F_DECL(void advance_software_protect_rdp_enable())
void advance_software_protect_rdp_enable()
{
#ifdef RELEASE
bool rdp_result = {0};
/* get current ob */
FLASH_OBProgramInitTypeDef m_cur_ob;
HAL_FLASHEx_OBGetConfig(&m_cur_ob);
/* unlock ob */
if (HAL_FLASH_Unlock() == HAL_OK && HAL_FLASH_OB_Unlock() == HAL_OK)
{
/* Set RDP to Level 1 or Level 2*/
m_cur_ob.RDPLevel = OB_RDP_LEVEL_1;
/* update ob */
rdp_result = (HAL_FLASHEx_OBProgram(&m_cur_ob) == HAL_OK);
/* lock flash */
HAL_FLASH_OB_Lock();
HAL_FLASH_Lock();
/*
To make the option byte take effect, note that by resetting the target chip,
the target chip reloads the option byte to make it take effect
*/
if (rdp_result)
{
HAL_FLASH_OB_Launch();
}
}
#endif
}
#endif
为了加强代码的安全性,核心函数必须再次 使用 ASP_ENCRYPT_F_DECL 声明,进行加密,此外,当前接口必须根据实际芯片进行实现。
在开发调试阶段,使用无 RELEASE 定义的宏进行调试,在最后发布阶段,在切换到Release 模式进行发布编译,详见 其他设置。
调用方法:
bool advance_software_protect_anti_debugger_sample(E_AntiDebuggerEvent event)
{
switch (event)
{
/* Development mode running */
case Development:
advance_software_protect_logger(ADV_DEBUG, " (Running in development mode) Anti Debugger ...\r\n");
return true;
/* The RDP mode is disabled */
case RDPDisabled:
advance_software_protect_logger(ADV_DEBUG, " (Read protection is not enabled) Anti Debugger ...\r\n");
return true; /* Return true if you want to enable RDP */
/* The RDP mode is enabled */
case RDPEnabled:
advance_software_protect_logger(ADV_DEBUG, " (Read protection enabled) Anti Debugger ...\r\n");
return true; /* Return true if you want to reset */
/* The system is resetting */
case SystemReset:
advance_software_protect_logger(ADV_DEBUG, " (Request system reset) Anti Debugger ...\r\n");
return true; /* Return true if you want to reset */
/* Disable Debugger */
case DebuggerDisable:
advance_software_protect_logger(ADV_DEBUG, " (Disabling the debugger) Anti Debugger ...\r\n");
return true; /* Return true if you want to disable the debugger */
}
return true;
}
ASP_ENCRYPT_F_DECL(void advance_software_protect_sample())
void advance_software_protect_sample()
{
#ifdef ADVANCE_SOFTWARE_PROTECT_ANTI_DEBUGGER_ENABLE
/* anti debugger */
advance_software_protect_anti_debugger(advance_software_protect_anti_debugger_sample);
#endif
}
调用 advance_software_protect_anti_debugger 的父函数,再次使用 ASP_ENCRYPT_F_DECL 进行加密函数声明,可以进一步增强安全性。
5.5.4.7:数据加密
加密库内置了私有数据加密算法,在实际项目中,请确保源码配置 和 命令行参数 -k
保持一致,源码中的配置位置为如下所示:
/* key for data encrypt */
#if (defined ADVANCE_SOFTWARE_PROTECT_FLASH_VERIFY_ENABLE) || (defined ADVANCE_SOFTWARE_PROTECT_CODE_ENCRYPT_ENABLE)
#define ADV_DATA_ENCRYPT_KEY (0xfb90ffcb9b62c2e6) /* 64 bit*/
#endif
每一个项目都应该设置不同的密码,并且和命令行参数 -k
保持一致。
5.5.4.8:运行状态监控
如果加密库相关核心功能,被逆向破解,去除,通过获取加密库的运行状态接口,读取系统状态,根据状态,执行相对应的动作,进一步增加固件的安全性,返回状态参考如下:
/* system executed status */
typedef struct advance_software_protect_status
{
/* Signature executed */
bool m_signature_executed;
/* Flash verify executed */
bool m_flash_verify_executed;
/* Anti debugger */
bool m_anti_debugger_executed;
} advance_software_protect_status;
当前demo,在main 函数中进行 加密库运行状态检查,根据状态,打印加密库的执行状态信息,如下所示
int main(void)
{
while (1)
{
/* USER CODE END WHILE */
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
HAL_Delay(500);
/* 获取加密库的反馈状态 */
advance_software_protect_status status;
if(!advance_software_protect_system_status_get(&status)){
advance_software_protect_logger(ADV_ERROR," 检测到核心代码,被动态修改 !!! \r\n");
}
if(!status.m_signature_executed){
advance_software_protect_logger(ADV_ERROR,"签名服务被非法去除,或者篡改 !!! \r\n");
}
if(!status.m_flash_verify_executed){
advance_software_protect_logger(ADV_ERROR,"固件校验功能被非法修改 !!! \r\n");
}
if(!status.m_signature_executed){
advance_software_protect_logger(ADV_ERROR,"反调试服务框架被非法去除 !!! \r\n");
}
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
系统状态监控服务,属于加密库最底层内核,且本身即为加密函数,无法通过静态反编译的方法进行去除,由于数据本身进行多次签名,理论上也无法通过动态内存补丁的方式,直接修改系统状态。
系统状态的检查,可以在固件中多处进行埋点,当检测到异常时,再进行对应的响应,这是整个加密库最后一道防线,用于检测加密库本身被非法移除或者修改。
5.5.4.9:其他
除之前提供的核心功能之外,此外提供了一些辅助功能,方便进行项目开发,如下所示:
/* 读取加密库的版本 */
extern const char *advance_software_protect_version(void);
/* dump 数据,用于分析 */
extern void advance_software_protect_dump_buf(const char *title, unsigned char *buf, size_t len);
/* 随机函数 */
extern int advance_software_protect_random(unsigned char *buf, size_t len);
/* 设置最后一次错误码,可利用此接口清楚加密库错误码信息 */
extern void advance_software_protect_set_last_error(E_ADV_SOFTWARE_PROTECT_ERROR_CODE err);
/* 当遇到失败时,可以通过此接口读取错误码信息,方便进行错误分析 */
extern E_ADV_SOFTWARE_PROTECT_ERROR_CODE advance_software_protect_get_last_error(void);
/* 加密库自带的 hash 函数 */
extern uint32_t crc32_inner(const void *data, int nBytes);
extern uint32_t crc32_continue_inner(uint32_t prev_crc, const void *data, int nBytes);
5.5.4.2:调试与发布
5.5.4.2.1:工程调试方法
当所有的配置完成后,我们对工程进行编译,将会看到IDE 的编译输出,如下所示:
Rebuild started: Project: stm32f103rfc6
*** Using Compiler 'V5.06 update 7 (build 960)', folder: 'C:\Keil_v5\ARM\ARMCLANG5\Bin'
Rebuild target 'stm32f103rfc6_release'
assembling startup_stm32f103xg.s...
compiling stm32f1xx_hal_gpio_ex.c...
compiling stm32f1xx_hal.c...
compiling stm32f1xx_hal_gpio.c...
compiling main.c...
compiling stm32f1xx_hal_crc.c...
compiling stm32f1xx_hal_rcc_ex.c...
compiling stm32f1xx_it.c...
compiling stm32f1xx_hal_cortex.c...
compiling stm32f1xx_hal_msp.c...
compiling stm32f1xx_hal_dma.c...
compiling stm32f1xx_hal_rcc.c...
compiling system_stm32f1xx.c...
compiling stm32f1xx_hal_tim_ex.c...
compiling stm32f1xx_hal_tim.c...
compiling stm32f1xx_hal_pwr.c...
compiling advance_software_protect_porting.c...
compiling advance_software_protect_samples.c...
compiling stm32f1xx_hal_exti.c...
compiling stm32f1xx_hal_flash_ex.c...
compiling stm32f1xx_hal_uart.c...
compiling stm32f1xx_hal_flash.c...
linking...
Program Size: Code=42962 RO-data=5574 RW-data=180 ZI-data=9956
FromELF: creating hex file...
After Build - User command #1: fromelf --bin -o "E:\MainProjects\2021\powerwriter_advance_software_protect\powerwriter_advance_software_protect\library\project\mdk_demo\stm32f103rfc6\MDK-ARM\stm32f103rfc6\stm32f103rfc6.bin" "E:\MainProjects\2021\powerwriter_advance_software_protect\powerwriter_advance_software_protect\library\project\mdk_demo\stm32f103rfc6\MDK-ARM\stm32f103rfc6\stm32f103rfc6.axf"
After Build - User command #2: ../../../../tool/windows/advance_software_protect.exe -s 0x08000000 -k 0xfb90ffcb9b62c2e6 -i ./stm32f103rfc6/stm32f103rfc6.bin/ER_IROM1 -r ./stm32f103rfc6/stm32f103rfc6.bin/ER_IROM2
-----------------------------------------------------------------------------------------------------------------------
Open source advance software protect library
This project provides a set of universal MCU software encryption algorithm.The goal is simple, easy to use,easy
to integrate, and does not require any additional hardware,The following are the test cases of the project, please
select the appropriate use method, some functions need to be used together with this tool, if you have any questions,
please contact us,customer service email:cs@icworkshop.com.
team : ICWorkshop HPT Dep Team
author : cshsoft(csh@icworkshop.com)
website: www.icworkshop.com
tools : www.powerwriter.cn(.com)
-----------------------------------------------------------------------------------------------------------------------
[advance software protect][debug]: Environment:E:\MainProjects\2021\powerwriter_advance_software_protect\powerwriter_advance_software_protect\library\project\mdk_demo\stm32f103rfc6\MDK-ARM
[advance software protect][debug]: Prepare encrypted firmware...
[advance software protect][info ]: Start address: 0x08000000
[advance software protect][info ]: End address: 0x0800b498
[advance software protect][info ]: Ram code size: 0x000009b4
[advance software protect][debug]: search flash verify ...
[advance software protect][debug]: flash verify enabled ...
[advance software protect][info ]: flash verify skip verify total [3]...
[advance software protect][info ]: flash verify skip item : Address:0x0800A078 Size:0x24
[advance software protect][info ]: flash verify skip item : Address:0x08002000 Size:0x8d
[advance software protect][info ]: flash verify skip item : Address:0x08001000 Size:0x4
[advance software protect][debug]: anti debugger enabled ...
[advance software protect][debug]: search dynamic function encryption (ram code) ...
[advance software protect][debug]: dynamic function encryption enabled ...
[advance software protect][info ]: Ram code address: 0x00000000(dynamic in user code)
[advance software protect][debug]: search flash done ...
[advance software protect][debug]: ram code updating ...
[advance software protect][debug]: ram code update ok ...
[advance software protect][debug]: Process anti debugger check ok ...
[advance software protect][debug]: Process flash verify start ...
[advance software protect][info ]: hash block item : Start:0x08000000 End:0x8001000 Size:0x1000 hash:0xa5a3e42e
[advance software protect][info ]: hash block item : Start:0x08001004 End:0x8002000 Size:0xffc hash:0x56320614
[advance software protect][info ]: hash block item : Start:0x0800208d End:0x800a078 Size:0x7feb hash:0x68c5f96b
[advance software protect][info ]: hash block item : Start:0x0800a09c End:0x800be4c Size:0x1db0 hash:0x72d02bb6
[advance software protect][debug]: Process flash verify ok ...
[advance software protect][debug]: Save to file : ./stm32f103rfc6/stm32f103rfc6.bin/ER_IROM1_ER_IROM2_encrypted.bin ...
[advance software protect][debug]: All done ...
".\stm32f103rfc6\stm32f103rfc6.axf" - 0 Error(s), 0 Warning(s).
Build Time Elapsed: 00:00:06
请注意,IDE 编译是否报错,典型的错误,比如空间不足,可适当调整资源配置,或者提示文件已存在,请删除编译输出,重新编译项目。并留意最终的输出文件路径。
使用PowerWriter 进行固件的烧录:
烧录完成后,参考 其他设置 配置方法,使用MDK 进行调试,并开启串口助手,我们将从串口输出中看到如下信息,如下图所示:
---------------------------------------------------------------------------------
Advance software protect lib
This project provides a set of universal MCU software encryption algorithm.
The goal is simple, easy to use, easy to integrate, and does not require any
additional hardwareThe following are the test cases of the project, please
select the appropriate use method, some functions need to be used together
with repack tool, if you have any questions, please contact us
PowerWriter team
Version:V1.0.0.0 Jan 9 2025 14:37:54
Author: csh@icworkshop.com(cshsoft)
Site: www.powerwriter.com
Docs: https://docs.powerwriter.com/docs/next/powerwriter_for_arm/application_note/advance_software_protect
---------------------------------------------------------------------------------
[D]: (Successful) Dynamic function encryption ...
[I]:---------------Product Serial Number Info Start----------
[I]: Product Serial number is : 0x123456AE , In Flash Address 0x08001000.
[I]:---------------Product Serial Number Info End----------
[D]: ---------------Single Object Watcher Simple Start ----------
[D]: m_Object check passed at first time
[E]: m_Object will fail if it is modified without updating the checksum value!
[D]: m_Object had modified & updated, will be passed !
[D]: ---------------Single Object Watcher Simple End----------
[D]: ---------- Dynamic Muti-Object Watcher Simple Start ----------
[D]: Set ObjectWatcher Event callback...
[W]: [0x2000014C] Object already exist!
[W]: Error param ...
[I]: Alloc 100 dynamic object done ..
[I]: m_aType exist..
[D]: Total 126 objects in watching..
[D]: m_class check passed ..
[E]: [0x2000014C] Object was modified!
[D]: Passed: 125, Total : 126 ..
[D]: m_class update ok ..
[I]: m_bType removed, left 125 object in watching..
[I]: All objects updated...
[I]: All objects removed, left 0 object in watching..
[D]: ---------------Dynamic Muti-Object Watcher Simple End----------
[D]: ---------------Product Signature start----------
[D]: Chip signature verify passed...
[D]: ---------------Product Signature end----------
[D]: ---------------Flash Verify start----------
[D]: Flash Verify passed...
[D]: ---------------Flash Verify end----------
[D]: (Read protection enabled) Anti Debugger ...
[D]: (Disabling the debugger) Anti Debugger ...
5.5.4.2.2:发布
当工程所有的验证均完成之后,将工程切换到发布模式(含 RELEASE 宏定义,用于开启反调试的部分功能),如下所示:
对项目重新编译,参考调试的方法,重新添加RELEASE 的固件到PowerWriter 工程,进行最终的发布验证,进行生产。
5.5.5:命令行
本加密库支持主流的 编译器环境,同时也支持,不同的系统开发平台,在加密库压缩包的 tool 目录中,可获取对应平台的二进制文件,如下图所示:
当在控制台执行运行命令时,可看到如下的帮助信息,如下所示:
-----------------------------------------------------------------------------------------------------------------------
Open source advance software protect library
This project provides a set of universal MCU software encryption algorithm.The goal is simple, easy to use,easy
to integrate, and does not require any additional hardware,The following are the test cases of the project, please
select the appropriate use method, some functions need to be used together with this tool, if you have any questions,
please contact us,customer service email:cs@icworkshop.com.
team : ICWorkshop HPT Dep Team
author : cshsoft(csh@icworkshop.com)
website: www.icworkshop.com
tools : www.powerwriter.cn(.com)
-----------------------------------------------------------------------------------------------------------------------
usage:
advance_software_protect {options} -i origin_firmware_path [-o encode_firmware_path]
options:
[-s 0xxxxxxxxx] Specifies the start address of the firmware. If the Flash verification function
is enabled for the firmware, the parameters in the firmware will be updated. If
the Flash verification function is not enabled or the parameter is manually spe-
cified, the default value is 0x08000000.
[-l [0,1,2]] Specify the log output level(The default is Level 2):
Level 0: only error information is displayed
Level 1: only debug information is displayed
Level 2: displays all information, including processing infomation
-i origin_firmware_path Specifies the compiled origin firmware path,Currently, only bin files are
supported, *.afx or intel hex format firmware may be supported in the future.
[-r ramcode_path] Specify the ram code path to encrypt,The default supported executables are
binary files, ER_IROM2 is probably the default name for MDK compilation
[-o encrypted_firmware_path] Specifies the Release firmware path,The bin file is currently output directly,
If the output path is not specified, origin_firmware_path (*_encrypted.bin) is
saved by default.
-k 0xxxxxxxxxxxxxxxxx Specifies the password for encrypting data,64bit.
-g [true|false] Generate encryption matrix.true allows repetition, false does not
example:
advance_software_protect -s 0x08000000 -i ./stm32f103rfc6.bin
advance_software_protect -l 0 -i ./stm32f103rfc6.bin
advance_software_protect -i ./stm32f103rfc6.bin -o ./stm32f103rfc6_encrypted.bin
notes:
Curly braces{} indicate a group of options, and curly braces[] indicate optional options.If you do not enter this parameter, default parameters will be used instead, and all other parameters are mandatory.
5.5.5.1:参数详解
用法: advance_software_protect {options} -i origin_firmware_path [-o encode_firmware_path]
选项:
[-s 0xxxxxxxxx] 指定固件的起始地址。如果固件的闪存验证功能
将更新固件中的参数。如果
闪存验证功能未启用或该参数为手动指定,则默认值为 0x08000000。
默认值为 0x08000000。
[-l [0,1,2]] 指定日志输出级别(默认为 2 级):
0 级:只显示错误信息
1 级:只显示调试信息
第 2 级:显示所有信息,包括处理信息
-i origin_firmware_path 指定编译后的原始固件路径,目前只支持 bin 文件,hex 文件格式。
[-r ramcode_path] 指定要加密的 ram 代码路径。
二进制文件,ER_IROM2 可能是用于 MDK 编译的默认名称。
[-o encrypted_firmware_path] 指定 Release 固件路径,当前直接输出 bin 文件、
如果未指定输出路径,则默认保存 origin_firmware_path (*_encrypted.bin)。
默认保存。
-k 0xxxxxxxxxxxxxxxxx 指定用于加密数据的密码(64 位)。
-g [true|false] 生成加密交换矩阵。true 允许重复,false 不允许重复
大括号{}表示一组选项,大括号[]表示可选选项。如果不输入该参数,将使用默认参数,所有其他参数均为必选参数。
5.5.5.2:Windows 演示
5.5.5.3:Linux 演示
5.5.5.4:MacOS 演示
5.5.5.5:与IDE 进行集成
请参考 项目配置章节 与 IDE 进行集成。
5.5.6:使用操作系统
当使用RTOS 时,如使用Free RTOS 时,适当调整操作系统的堆大小,下面是 Cube MX 中 Free RTOS 的配置参考
5.5.6.1:任务栈分配
当创建加密库校验任务时,请适当调整 stack 大小,避免加密库执行深度过深,导致栈溢出,如下所示:
/* USER CODE BEGIN PV */
osThreadId_t advance_software_protect_TaskHandle;
const osThreadAttr_t ASPLTask_attributes = {
.name = "advance_software_protect_task",
/* 需要将加密库的stack 设置得大一些,太小,可能会导致堆栈溢出! */
.stack_size = 1024 * 4,
.priority = (osPriority_t) osPriorityNormal1,
};
/* USER CODE BEGIN 0 */
void advance_software_protect_Task(void *argument)
{
/* USER CODE BEGIN 5 */
/* init */
advance_software_protect_init();
/* Infinite loop */
for(;;)
{
advance_software_protect_logger(ADV_INFO,"-------------OS Task in-------------\r\n");
/* sample */
advance_software_protect_sample();
/* system status */
advance_software_protect_status status;
if(!advance_software_protect_system_status_get(&status)){
advance_software_protect_logger(ADV_ERROR,"Abnormal access and modification of firmware detected !!! \r\n");
}
if(!status.m_signature_executed){
advance_software_protect_logger(ADV_ERROR,"Illegal removal of signature services !!! \r\n");
}
if(!status.m_flash_verify_executed){
advance_software_protect_logger(ADV_ERROR,"Illegal removal of flash verify services !!! \r\n");
}
if(!status.m_signature_executed){
advance_software_protect_logger(ADV_ERROR,"Illegal removal of anti debugger services !!! \r\n");
}
advance_software_protect_logger(ADV_INFO,"-------------OS Task out-------------\r\n");
osDelay(100);
}
/* USER CODE END 5 */
}
5.5.6.2:任务堆分配
在操作系统中使用时,我们可通过 注册堆分配接口,使用操作系统的堆来管理内存,参考如下:
/* 注册堆内存管理接口原型 */
bool advance_software_protect_set_mem_calloc_free_callback(calloc_func_cb, free_func_cb);
/* memory alloc & free */
void *calloc_mem_func(size_t count, size_t size)
{
void * obj = pvPortMalloc(count * size); /* 使用操作系统的堆内存分配 */
if(obj){
memset(obj,0,count * size); /* 当使用pvPortMalloc API 时,一定要清 0 */
}
return obj;
}
void free_mem_func(void *blk_obj)
{
vPortFree(blk_obj); /* 释放对象接口 */
}
/*
* IMPORTANT:
* Do not encrypt the parent function () that contains the
* advance_software_protect_encryption_code_loader!
*/
void advance_software_protect_init()
{
/* initial */
advance_software_protect_set_mem_calloc_free_callback(calloc_mem_func, free_mem_func);
}
以 Free RTOS 为例,也可以用 osMemoryPoolNew 、osMemoryPoolAlloc、osMemoryPoolFree 堆内存管理接口来做加密库堆内存的管理。
当使用 pvPortMalloc 时,一定要将分配的内存 清 0。
5.5.6.3:混合使用
当和操作系统混合使用,将加密库放在操作系统之外运行时,按照之前的思路,使用自带的堆,则调整工程默认的堆栈大小,见 startup_stm32f103xg.s 中默认堆栈的大小调整,也可以在生成项目时,在配置界面进行预分配,如下所示:
5.5.7:更多
如果在使用使用本加密库时,遇到问题或者有建议和意见,请反馈给我们!
反馈邮箱:cs@icworkshop.com
在线表单反馈:Power Writer 反馈
Alan(cshsoft ) @ICWorkshop 版权所有