Tampering with MCU specific code data to fix the bug
Source | Embedded Base
Overview
Create a demo project
Also set the vector offset at the beginning of the app code (calling one line of code):
NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x6000);
The logic of the app project is: first execute the LED flashing process at three different speeds in sequence (20ms, 200ms, 500ms, switching on and off), and finally enter a loop state to switch the LED state flashing once per second. The code is as follows:
void init_led(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_ResetBits(GPIOB, GPIO_Pin_10);
GPIO_SetBits(GPIOB, GPIO_Pin_10);
}
void led_blings_1(void)
{
uint32_t i;
for (i = 0; i < 10; i++)
{
GPIO_SetBits(GPIOB, GPIO_Pin_10);
delay_ms(20);
GPIO_ResetBits(GPIOB, GPIO_Pin_10);
delay_ms(20);
}
}
void led_blings_2(void)
{
uint32_t i;
for (i = 0; i < 10; i++)
{
GPIO_SetBits(GPIOB, GPIO_Pin_10);
delay_ms(200);
GPIO_ResetBits(GPIOB, GPIO_Pin_10);
delay_ms(200);
}
}
void led_blings_3(void)
{
uint32_t i;
for (i = 0; i < 10; i++)
{
GPIO_SetBits(GPIOB, GPIO_Pin_10);
delay_ms(500);
GPIO_ResetBits(GPIOB, GPIO_Pin_10);
delay_ms(500);
}
}
int main()
{
NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x6000);
SysTick_Init(72);
init_led();
led_blings_1();
led_blings_2();
led_blings_3();
while (1)
{
GPIO_SetBits(GPIOB, GPIO_Pin_10);
delay_ms(1000);
GPIO_ResetBits(GPIOB, GPIO_Pin_10);
delay_ms(1000);
}
}
In order to analyze the assembly and view the bin file data, we need to add two commands in Keil to generate .dis disassembly and .bin code files respectively. (The specific directory situation is similar to the one in the previous example)
fromelf --text -a -c --output=all.dis Obj\Template.axf
fromelf --bin --output=test.bin Obj\Template.axf
First, burn the app code into the MCU. Note that in the burning settings, select "Erase Sectors" to only erase the areas that need to be burned.
2. Bootloader project
#define FLASH_PAGE_SIZE 2048
#define ERROR_PROCESS_CODE_ADDR 0x8000800
void error_process(void) __attribute__((section(".ARM.__at_0x8000800")));
void init_led(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_ResetBits(GPIOB, GPIO_Pin_10);
GPIO_SetBits(GPIOB, GPIO_Pin_10);
}
uint32_t pageBuf[FLASH_PAGE_SIZE / 4];
void error_process(void)
{
}
void eraseErrorProcessCode(void)
{
FLASH_Unlock();
FLASH_ClearFlag(FLASH_FLAG_BSY | FLASH_FLAG_EOP |
FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);
FLASH_ErasePage(ERROR_PROCESS_CODE_ADDR);
FLASH_Lock();
}
void(*boot_jump2App)();
void boot_loadApp(uint32_t addr)
{
uint8_t i;
if (((*(vu32*)addr) & 0x2FFE0000) == 0x20000000)
{
boot_jump2App = (void(*)())*(vu32*)(addr + 4);
__set_MSP(*(vu32*)addr);
for (i = 0; i < 8; i++)
{
NVIC->ICER[i] = 0xFFFFFFFF;
NVIC->ICPR[i] = 0xFFFFFFFF;
}
boot_jump2App();
while (1);
}
}
int main()
{
uint32_t flag;
SysTick_Init(72);
flag = *((uint32_t *)ERROR_PROCESS_CODE_ADDR);
if ((flag != 0xFFFFFFFF) && (flag != 0))
{
init_led();
GPIO_ResetBits(GPIOB, GPIO_Pin_10);
delay_ms(1000);
delay_ms(1000);
error_process();
eraseErrorProcessCode();
}
boot_loadApp(0x8006000);
while (1);
}
Modify app specific parameters
$t
i.led_blings_1
led_blings_1
0x08006558: b510 .. PUSH {r4,lr}
0x0800655a: 2400 .$ MOVS r4,#0
0x0800655c: e010 .. B 0x8006580 ; led_blings_1 + 40
0x0800655e: f44f6180 O..a MOV r1,#0x400
0x08006562: 4809 .H LDR r0,[pc,#36] ; [0x8006588] = 0x40010c00
0x08006564: f7fffea2 .... BL GPIO_SetBits ; 0x80062ac
0x08006568: 2014 . MOVS r0,#0x14
0x0800656a: f7ffffaf .... BL delay_ms ; 0x80064cc
0x0800656e: f44f6180 O..a MOV r1,#0x400
0x08006572: 4805 .H LDR r0,[pc,#20] ; [0x8006588] = 0x40010c00
0x08006574: f7fffe98 .... BL GPIO_ResetBits ; 0x80062a8
0x08006578: 2014 . MOVS r0,#0x14
0x0800657a: f7ffffa7 .... BL delay_ms ; 0x80064cc
0x0800657e: 1c64 d. ADDS r4,r4,#1
0x08006580: 2c0a ., CMP r4,#0xa
0x08006582: d3ec .. BCC 0x800655e ; led_blings_1 + 6
0x08006584: bd10 .. POP {r4,pc}
$d
0x08006586: 0000 .. DCW 0
0x08006588: 40010c00 ...@ DCD 1073810432
Since the LED is alternately on and off every 20ms, if we think there is a problem with this parameter and want to change it to 100ms, from the assembly point of view, we need to change two lines of code:
0x08006568: 2014 . MOVS r0,#0x14
0x08006578: 2014 . MOVS r0,#0x14
改为
0x08006568: 2064 2 MOVS r0,#0x64
0x08006578: 2064 2 MOVS r0,#0x64
The error_process function in the bootloader project is implemented as follows:
void error_process(void)
{
#define MODIFY_FUNC_ADDR_START 0x08006558
uint32_t alignPageAddr = MODIFY_FUNC_ADDR_START / FLASH_PAGE_SIZE * FLASH_PAGE_SIZE;
uint32_t cnt, i;
// 1. copy old code
memcpy(pageBuf, (void *)alignPageAddr, FLASH_PAGE_SIZE);
// 2. change code.
//由于Flash操作2KB页的特性,0x08006558不满2kb,因此偏移为0x558,0x558/4=342
pageBuf[90 + 256] = (pageBuf[90 + 256] & 0xFFFF0000) | 0x2064;
pageBuf[94 + 256] = (pageBuf[94 + 256] & 0xFFFF0000) | 0x2064;
// 3. erase old code, copy new code.
FLASH_Unlock();
FLASH_ClearFlag(FLASH_FLAG_BSY | FLASH_FLAG_EOP |
FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);
FLASH_ErasePage(alignPageAddr);
cnt = FLASH_PAGE_SIZE / 4;
for (i = 0; i < cnt; i++)
{
FLASH_ProgramWord(alignPageAddr + i * 4, pageBuf[i]);
}
FLASH_Lock();
}
Due to the 2KB page erase feature of Flash, the Flash page data of the code area to be modified is first copied to the buffer, then the data in the buffer is modified, then the relevant Flash page is erased, and finally the modified data in the buffer is written back to Flash. The disassembly of the error_process function is as follows:
$t
.ARM.__at_0x8000800
error_process
0x08000800: b570 p. PUSH {r4-r6,lr}
0x08000802: 4d1a .M LDR r5,[pc,#104] ; [0x800086c] = 0x8006000
0x08000804: 142a *. ASRS r2,r5,#16
0x08000806: 4629 )F MOV r1,r5
0x08000808: 4819 .H LDR r0,[pc,#100] ; [0x8000870] = 0x20000008
0x0800080a: f7fffcbd .... BL __aeabi_memcpy ; 0x8000188
0x0800080e: 4818 .H LDR r0,[pc,#96] ; [0x8000870] = 0x20000008
0x08000810: f8d00568 ..h. LDR r0,[r0,#0x568]
0x08000814: f36f000f o... BFC r0,#0,#16
0x08000818: f2420164 B.d. MOV r1,#0x2064
0x0800081c: 4408 .D ADD r0,r0,r1
0x0800081e: 4914 .I LDR r1,[pc,#80] ; [0x8000870] = 0x20000008
0x08000820: f8c10568 ..h. STR r0,[r1,#0x568]
0x08000824: 4608 .F MOV r0,r1
0x08000826: f8d00578 ..x. LDR r0,[r0,#0x578]
0x0800082a: f36f000f o... BFC r0,#0,#16
0x0800082e: f2420164 B.d. MOV r1,#0x2064
0x08000832: 4408 .D ADD r0,r0,r1
0x08000834: 490e .I LDR r1,[pc,#56] ; [0x8000870] = 0x20000008
0x08000836: f8c10578 ..x. STR r0,[r1,#0x578]
0x0800083a: f7fffd53 ..S. BL FLASH_Unlock ; 0x80002e4
0x0800083e: 2035 5 MOVS r0,#0x35
0x08000840: f7fffcca .... BL FLASH_ClearFlag ; 0x80001d8
0x08000844: 4628 (F MOV r0,r5
0x08000846: f7fffccd .... BL FLASH_ErasePage ; 0x80001e4
0x0800084a: 14ae .. ASRS r6,r5,#18
0x0800084c: 2400 .$ MOVS r4,#0
0x0800084e: e007 .. B 0x8000860 ; error_process + 96
0x08000850: 4a07 .J LDR r2,[pc,#28] ; [0x8000870] = 0x20000008
0x08000852: f8521024 R.$. LDR r1,[r2,r4,LSL #2]
0x08000856: eb050084 .... ADD r0,r5,r4,LSL #2
0x0800085a: f7fffd0d .... BL FLASH_ProgramWord ; 0x8000278
0x0800085e: 1c64 d. ADDS r4,r4,#1
0x08000860: 42b4 .B CMP r4,r6
0x08000862: d3f5 .. BCC 0x8000850 ; error_process + 80
0x08000864: f7fffcfe .... BL FLASH_Lock ; 0x8000264
0x08000868: bd70 p. POP {r4-r6,pc}
$d
0x0800086a: 0000 .. DCW 0
0x0800086c: 08006000 .`.. DCD 134242304
0x08000870: 20000008 ... DCD 536870920
Then these 124 bytes are the function data that will eventually be transferred to 0x8000800. After the transfer is completed, the MCU is soft reset, and the bootloader tampers with the Flash data of the app to achieve the purpose of changing the program function.
Why do we need to tamper with the app data when the bootloader is running? In theory, the app can run immediately after receiving the updated data of the error_process function when it is running, but because it involves modifying the app's own code, some related functions involved in the Flash modification may be temporarily damaged, causing the code to crash.
Skip some functions of the app
即将以下汇编语句
0x0800655a: 2400 .$ MOVS r4,#0
修改为
0x0800655a: e013 .$ B 0x08006584
At the entry of the "led_blings_1" function, the instruction is modified to jump directly to the exit of the function. As for the assembled machine code and usage, there is relevant information at the end of the article for reference.
Because the byte offset of the modification is 0x55a, which is the high 2 bytes of the element with index 342 of pageBuf, the following modification needs to be made in the error_process function:
pageBuf[342] = (pageBuf[342] & 0x0000FFFF) | 0xe0130000;
2. Skip the function call
The main function is compiled as follows:
$t
i.main
main
0x080065f8: f44f41c0 O..A MOV r1,#0x6000
0x080065fc: f04f6000 O..` MOV r0,#0x8000000
0x08006600: f7fffe5c ..\. BL NVIC_SetVectorTable ; 0x80062bc
0x08006604: 2048 H MOVS r0,#0x48
0x08006606: f7ffff01 .... BL SysTick_Init ; 0x800640c
0x0800660a: f7ffff85 .... BL init_led ; 0x8006518
0x0800660e: f7ffffa3 .... BL led_blings_1 ; 0x8006558
0x08006612: f7ffffbb .... BL led_blings_2 ; 0x800658c
0x08006616: f7ffffd3 .... BL led_blings_3 ; 0x80065c0
0x0800661a: e011 .. B 0x8006640 ; main + 72
0x0800661c: f44f6180 O..a MOV r1,#0x400
0x08006620: 4808 .H LDR r0,[pc,#32] ; [0x8006644] = 0x40010c00
0x08006622: f7fffe43 ..C. BL GPIO_SetBits ; 0x80062ac
0x08006626: f44f707a O.zp MOV r0,#0x3e8
0x0800662a: f7ffff4f ..O. BL delay_ms ; 0x80064cc
0x0800662e: f44f6180 O..a MOV r1,#0x400
0x08006632: 4804 .H LDR r0,[pc,#16] ; [0x8006644] = 0x40010c00
0x08006634: f7fffe38 ..8. BL GPIO_ResetBits ; 0x80062a8
0x08006638: f44f707a O.zp MOV r0,#0x3e8
0x0800663c: f7ffff46 ..F. BL delay_ms ; 0x80064cc
0x08006640: e7ec .. B 0x800661c ; main + 36
$d
0x08006642: 0000 .. DCW 0
0x08006644: 40010c00 ...@ DCD 1073810432
The following is the calling statement
0x0800660e: f7ffffa3 .... BL led_blings_1 ; 0x8006558
Simply change this statement to a null statement nop (0xbf00) to skip the call. Since the command occupies 4 bytes and nop is a two-byte command, it is replaced with two nop commands.
0x0800660e: bf00bf00 .... NOP
Because the byte offset of the modification is 0x60e, which is the high 2 bytes of the element with subscript 387 and the low 2 bytes of the element with subscript 388 of pageBuf, the following modification needs to be made in the error_process function:
pageBuf[387] = (pageBuf[387] & 0x0000FFFF) | 0xbf000000;
pageBuf[388] = (pageBuf[388] & 0xFFFF0000) | 0x0000bf00;
Autumn
The recruitment has already begun. If you are not well prepared,
Autumn
It's hard to find a good job.
Here is a big employment gift package for everyone. You can prepare for the spring recruitment and find a good job!