目录
一 J-Flash读取MCU内部程序
二 程序加密
三 程序解密
通过J-Flash可以读取到MCU内部的程序,步骤如下:
(1)进入J-Flash,选择指定的芯片之后,点击连接Target-Connect;
(2)根据需要选择Read back中的选项,分别是回写Project settings中预设的扇区区间(Selected sectors)、回写芯片全部的扇区(Entire chip)、回写指定范围的扇区(Range)。选择之后J-Flash会回写内存中的数据
(3)可以保存读取到的数据,建议选择HEX格式。这样就可以把回写得到的程序纳为己用,进行重复烧写。
经过上一章,如此简单就可以获得MCU中的程序,那有效地把自己的程序保护起来就显得尤为重要。以所使用的LPC1857为例,关于CRP,其说明书如下:
总结概括为以下几点:
(1)LPC1857有三个CRP权限以及对应的安全码,分别为CRP1(0x12345678)、CRP2(0x87654321)、CRP3(0x43218765)。从这三个安全权限中选择所需的设置值写入到0x1A0002FC或者0x1B0002FC,即可实现ROM1或者ROM2的加密。
(2)无论设置哪一种权限,JTAG都是禁用的。谨慎选择CRP3,我这边选用的是CRP1级别。
(3)加密的时候,需要确认keil的option中,没有定义NO_CRP宏,否则加密失效:
程序加密的方法如下:
(1)在main.c中加入下面的语句,作用在于预开辟出0x1A0002FC处的空间用作CRP功能:
const unsigned crp __attribute__((section(".ARM.__at_0x1A0002FC"))) = 0x12345678;
(2)startup_LPC18xx.s中,关于CRP的部分修改如下:
;CRP address at offset 0x2FC relative to the BOOT Bank address
IF :LNOT::DEF:NO_CRP
AREA |.ARM.__at_0x1A0002FC|, CODE, READONLY
; EXPORT CRP_Key
CRP_Key DCD 0x12345678
; 0xFFFFFFFF => CRP Disabled
; 0x12345678 => CRP Level 1
; 0x87654321 => CRP Level 2
; 0x43218765 => CRP Level 3 (ARE YOU SURE?)
; 0x4E697370 => NO ISP (ARE YOU SURE?)
ENDIF
AREA |.text|, CODE, READONLY
这样,即可以对程序进行CRP1级别加密,烧写MCU,重启之后代码保护生效,再通过JTAG烧写程序,或者通过J-Flash回写内存均不能成功。
程序加密之后,需要通过擦除或者修改0x1A0002FC处的值,来实现解密。主要分为下面两种方式。
(1)加密之后,程序不能通过JTAG口进行烧写,但是可以进入ISP模式,通过Flash Magic软件进行下载或者擦除操作(外部硬件进入ISP模式只对CRP1和CRP2有效;而IAP指令进入ISP模式对CRP1、CRP2、CRP3均有效);
(2)可以通过程序执行IAP指令擦除扇区0整块扇区,来清除0x1A0002FC处的加密值,重新上电之后,就可以恢复未加密的状态(加密或者解密程序下载到MCU之后,都必须重新上电才能生效);
我这边采用的第二种方法,思路是通过串口接收特定的通讯指令(例如XX XX 12 34),当串口数据接收函数收到该指令时,则执行扇区清除:
void exBufRcv(unsigned char *p_rcv_buf)
{
int i = 0;
int j = 0;
//修改IAP与APP切换的标志位
if((p_rcv_buf[2] == APP_CONFIG_SET_VALUE) || (p_rcv_buf[2] == APP_CONFIG_CLEAR_VALUE))
{
iap_init(BANK0);
Iap_Write_Config_Value(p_rcv_buf[2]);
__set_FAULTMASK(1);
NVIC_SystemReset();
}
//收到解锁命令码之后,执行扇区清除操作
if((p_rcv_buf[2] == 0x12) && (p_rcv_buf[3] == 0x34))
{
iap_init(BANK0);
iap_erase_flash(0x1A0002FC, 4);
}
}
/******************************************************************************************************
** 函数名称:iap_erase_flash()
** 函数功能:flash 擦除操作
** 入口参数:addrDst 要擦除flash的起始地址
** length 要擦除flash的长度
** 出口参数:IAP操作状态码
** IAP返回值(paramout缓冲区)
*******************************************************************************************************/
uint32_t iap_erase_flash(uint32_t addrDst, uint32_t length)
{
uint32_t state;
uint32_t baseAddr;
if (length == 0)
{
return 101;
}
if (g_bank_num)
{
baseAddr = 0x1B000000;
} else
{
baseAddr = 0x1A000000;
}
if ((addrDst < baseAddr) || ((addrDst + length) > (0x7FFFF + baseAddr)))
{
return 100;
}
// 计算起始扇区号
if (addrDst < (0x10000 + baseAddr))
{
gSecStart = (addrDst - baseAddr) / (8 << 10);
if ((addrDst + length) < (0x10000 + baseAddr))
{
gSecEnd = (addrDst - baseAddr + length) / (8 << 10);
} else
{
gSecEnd = 8 + (addrDst - baseAddr - 0x10000 + length) / (64 << 10);
}
} else
{
gSecStart = 8 + (addrDst - baseAddr - 0x10000) / (64 << 10);
gSecEnd = 8 + (addrDst - baseAddr - 0x10000 + length) / (64 << 10);
}
// debugPrint("bank %d, len %d,sector start %d, end %d\r\n", g_bank_num, length, gSecStart, gSecEnd);
CLOSE_CORE_INT();
state = pre_sector(gSecStart, gSecEnd, g_bank_num);
if (state == CMD_SUCCESS)
{
state = erase_sector(gSecStart, gSecEnd, g_bank_num);
if (state == CMD_SUCCESS)
{
state = blank_check_sector(gSecStart, gSecEnd, g_bank_num);
}
}
OPEN_CORE_INT();
return state;
}
如果程序中IAP部分程序和APP部分程序是分开的话(【嵌入式】基于串口的IAP在线升级详解与实战1----IAP功能设计),需要对IAP程序和APP程序分开处理,在IAP程序中,初始化设置0x1A0002FC加密。在APP中,串口收到特定码解密。
总的来说,给程序加密,尽管可能会带来程序调试或者debug的不便,但是对于保护自己的劳动成果是非常重要的。