前言
由于项目太忙公众号已经停更一段时间,近期会重新开始更新并活跃。本篇文章取自实验室内部学习材料,记录了一次TBOX从硬件分析到软件逆向的完整过程,希望可以给大家起到一定的启示作用。
硬件分析
整板
关键芯片
主控STM32F105VCT6
主频72MHz,64KB SRAM,256KB FLASH
CAN收发器TJA1043
LTE模块 基带:MDM9607
EEPROM
24AA161 16KB I2C接口
接口分析
SWD及UART接口分析
使用STM32CubeMX可以方便的查询STM32系列芯片的参数及引脚定义
使用万用表通断档可测出芯片引脚有如下引出,但调试接口和芯片中间有几个空电阻焊盘隔离,后续使用焊锡短接即可。
CAN接口分析
TJA1043的引脚定义如图:
使用万用表通断档打两片TJA1043的CANH,CANL和T-BOX白色接口的针脚则可确定两路CAN的引脚
电源输入接口分析
GND一般为PCB上面积最大的网络,非常容易定位,屏蔽罩、板框、螺丝孔多为GND网络。在PCB背面找到了一个保险丝,使用万用表通断档打保险丝和白色接口的针脚则可确定VIN引脚,T-BOX供电电压一般为12V,但如果完全从理论上进行逆向计算,则需要根据DCDC芯片的数据手册确定。
固件分析
固件提取
根据之前确定的SWD引脚位置,连接PCB与J-link,J-TAG与SWD连接的方法如下:
之后使用J-Flash读出固件,读取的起始地址和大小根据STM32F105的数据手册为0x8000000和0x4000
静态分析
使用IDA Pro打开读出的bin文件,Processor type
选择ARM Little-endian [ARM]
,Processor options
中选择Edit ARM architecture options
,STM32F105属于ARM Cortex-M3内核,属于ARMv7-M,但是没有FPU,所以VFP instructions
选择 None
根据数据手册,设置ram及rom(flash)的起始地址及大小分别为0x20000000 0x1000和0x8000000 0x4000
加载后如下图所示:
绝大多少的Cortex-M芯片将MSP栈顶指针和中断向量表存放在flash的首部,可对照startup_stm32f105xc.s(不同编译器的汇编格式不同,这里选用了gcc-arm的)中的内容在IDA中还原中断向量表
.word _estack
.word Reset_Handler
.word NMI_Handler
.word HardFault_Handler
.word MemManage_Handler
.word BusFault_Handler
.word UsageFault_Handler
.word 0
.word 0
.word 0
.word 0
.word SVC_Handler
.word DebugMon_Handler
.word 0
.word PendSV_Handler
.word SysTick_Handler
.word WWDG_IRQHandler
.word PVD_IRQHandler
.word TAMPER_IRQHandler
.word RTC_IRQHandler
.word FLASH_IRQHandler
.word RCC_IRQHandler
.word EXTI0_IRQHandler
.word EXTI1_IRQHandler
.word EXTI2_IRQHandler
.word EXTI3_IRQHandler
.word EXTI4_IRQHandler
.word DMA1_Channel1_IRQHandler
.word DMA1_Channel2_IRQHandler
.word DMA1_Channel3_IRQHandler
.word DMA1_Channel4_IRQHandler
.word DMA1_Channel5_IRQHandler
.word DMA1_Channel6_IRQHandler
.word DMA1_Channel7_IRQHandler
.word ADC1_2_IRQHandler
.word CAN1_TX_IRQHandler
.word CAN1_RX0_IRQHandler
.word CAN1_RX1_IRQHandler
.word CAN1_SCE_IRQHandler
.word EXTI9_5_IRQHandler
.word TIM1_BRK_IRQHandler
.word TIM1_UP_IRQHandler
.word TIM1_TRG_COM_IRQHandler
.word TIM1_CC_IRQHandler
.word TIM2_IRQHandler
.word TIM3_IRQHandler
.word TIM4_IRQHandler
.word I2C1_EV_IRQHandler
.word I2C1_ER_IRQHandler
.word I2C2_EV_IRQHandler
.word I2C2_ER_IRQHandler
.word SPI1_IRQHandler
.word SPI2_IRQHandler
.word USART1_IRQHandler
.word USART2_IRQHandler
.word USART3_IRQHandler
.word EXTI15_10_IRQHandler
.word RTC_Alarm_IRQHandler
.word OTG_FS_WKUP_IRQHandler
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word TIM5_IRQHandler
.word SPI3_IRQHandler
.word UART4_IRQHandler
.word UART5_IRQHandler
.word TIM6_IRQHandler
.word TIM7_IRQHandler
.word DMA2_Channel1_IRQHandler
.word DMA2_Channel2_IRQHandler
.word DMA2_Channel3_IRQHandler
.word DMA2_Channel4_IRQHandler
.word DMA2_Channel5_IRQHandler
.word 0
.word 0
.word CAN2_TX_IRQHandler
.word CAN2_RX0_IRQHandler
.word CAN2_RX1_IRQHandler
.word CAN2_SCE_IRQHandler
.word OTG_FS_IRQHandler
编写了以下idc python脚本用于自动还原中断向量表
import idc
address = 0x8000000
f = open(r"interrupt_list.txt", "r")
lines = f.readlines()
# 跳过MSP指针
idc.create_dword(address)
address += 4
ISR_list=[]
# 还原中断向量表并创建函数
for line in lines[1:]:
idc.create_dword(address)
fun_addr = idc.ida_bytes.get_dword(address) - 1
fun_name = line.split()[1]
idc.set_cmt(address, fun_name, 0)
# 跳过占位的0
if fun_name == "0":
address += 4
continue
# 去重
if fun_addr in ISR_list:
idc.set_name(fun_addr, "Default_handler")
else:
print(f"{fun_name}:{hex(fun_addr)}")
idc.ida_auto.auto_make_code(fun_addr)
idc.add_func(fun_addr)
idc.set_name(fun_addr, fun_name)
ISR_list.append(fun_addr)
address += 4
针对Cortex-M内核,由于没有MMU,因而除了RAM和ROM外,还需要针对各外设创建专门的段,在IDA 7.5后,IDA支持根据svd文件自动创建外设段(但是BUG比较多,有的外设创建不出来)。
由于ResetHandle是芯片复位后执行的中断,则必定会指向main函数,但注意中断向量表中储存的地址实际上是函数的第二个字节,需要向上移动一个字节才能按c反编译然后按p创建函数。
可以看到第一个函数中主要是芯片时钟(RCC)的配置
int SystemInit()
{
int v0; // r0
int v1; // r0
int v2; // r0
int v3; // r0
int v4; // r0
int result; // r0
v0 = RCC_CR;
RCC_CR = v0 | 1;
v1 = RCC_CFGR;
RCC_CFGR = v1 & 0xF0FF0000;
v2 = RCC_CR;
RCC_CR = v2 & 0xFEF6FFFF;
RCC_CR = v2 & 0xFEF2FFFF;
v3 = RCC_CFGR;
RCC_CFGR = v3 & 0xFF80FFFF;
v4 = RCC_CR;
RCC_CR = v4 & 0xEBFFFFFF;
RCC_CIR = 0xFF0000;
RCC_CFGR2 = 0;
result = (_DWORD)&RCC_CR << 15;
unk_E000ED08 = (_DWORD)&RCC_CR << 15;
return result;
}
跟进第二个函数,看到一大堆函数调用,然后是while 1死循环,这是嵌入式开发中典型的前后台结构,因此可以确定这就是main函数:
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // r0
int v4; // r0
int v5; // r1
int v6; // r2
int v7; // r3
HAL_Init();
SystemClock_Config();
USART1_Init();
v3 = AFIO_MAPR;
AFIO_MAPR = v3 & 0xF8FFFFFF | 0x2000000;
sub_80146C8(1);
sub_8002F58();
CAN_Config();
TIM_Init();
sub_801A228();
v4 = RTC_Init();
Modem_Init(v4, v5, v6, v7);
sub_8002118(&hw_vehicle_type);
printf("Initialize hw_vehicle_type is %d\r\n", hw_vehicle_type);
printf("Initialize the tbox...\r\n");
printf("Initialize sensor...\r\n");
Sensor_Init();
while ( 1 )
{
sub_8014D6E();
sub_8018D30();
}
}
根据操作的外设地址可以确定前面几个函数负责初始化的外设,于是修改函数名以提高可读性。在嵌入式固件中,由于MCU的存储空间有限,在保证基本的内存对齐的情况下函数会在flash中紧密排布,而观察IDA上方的内存布局发现有大量未识别的函数,但IDA目前还不支持主动搜索函数,因此需要结合Ghidra中主动搜索的函数以及经验进行手动还原。这里写了另一个IDA脚本来半自动创建函数
import idc
start_ea=idc.get_screen_ea()
while idc.ida_funcs.get_func(start_ea) is None:
idc.ida_auto.auto_make_code(start_ea)
#一般函数开头指令均为压栈,如果不是压栈则需要人工判断
if "PUSH" in idc.GetDisasm(start_ea):
idc.add_func(start_ea)
start_ea=idc.find_func_end(start_ea)
else:
break
idc.jumpto(start_ea)
在标记出大部分函数后,则可使用BinDiff对将TBOX固件的IDA数据库和其它同型号STM32程序的IDA数据库进行比对,以还原其中有关外设的库函数,为分析具体的应用逻辑做准备。
对于STM32平台,常见的外设库有: STM32 Standard Peripheral Libraries STM32 标准外设库:创建于2007年,在早期的设备上更常见 STM32Cube MCU Package STM32 HAL库:创建于2014年,如今最常使用的库 两种库在推出以来尽力了许多次迭代,其中许多函数和结构体都有过大幅修改,因此为了尽快比配到对应的外设库,需要先确定TBOX固件的大致开发时间。有关时间的信息经常出现在固件包含的字符串或设备PCB的丝印上,然而很不幸的是我在这两处都没有找到有效信息。不过对于STM32系列芯片,其芯片上的丝印其实除了型号还包含有生产批号:
图中748表示该芯片生产于x7年第48周,首先排除07年,自然这个TBOX大概开发于17年左右,经过一番比对,最终确认该固件使用的外设库库为:STM32CubeF1 Firmware Package V1.4.0 不同于PC程序,MCU固件抽象程度低,不同芯片的内存布局也不相同,因此缺乏有效的自动化函数指纹分析工具。这里主要还是借助BinDiff和人工比对出了大部分HAL库函数:
在还原出外设库后则可以分析出部分应用逻辑代码,例如:
if ( hcan1.pRxMsg->StdId == 0x7A0
&& hcan1.pRxMsg->Data[0] == 2
&& hcan1.pRxMsg->Data[1] == 0x10
&& hcan1.pRxMsg->Data[2] == 1 )
{
byte_20000259 = 1;
memset(hcan1.pTxMsg->Data, 8, 0xAA);
hcan1.pTxMsg->Data[0] = '\x06';
hcan1.pTxMsg->Data[1] = 0x50;
hcan1.pTxMsg->Data[2] = 1;
hcan1.pTxMsg->Data[3] = 0;
hcan1.pTxMsg->Data[4] = 0x32;
hcan1.pTxMsg->Data[5] = 0;
hcan1.pTxMsg->Data[6] = 0xC8;
hcan1.pTxMsg->Data[7] = 0xAA;
hcan1.pTxMsg->DLC = 8;
hcan1.pTxMsg->StdId = 1960;
HAL_CAN_Transmit_IT(&hcan1);
result = HAL_CAN_Receive_IT(&hcan1, 0);
}
else if ( hcan1.pRxMsg->StdId == 0x7A0
&& hcan1.pRxMsg->Data[0] == 3
&& hcan1.pRxMsg->Data[1] == 0x22
&& hcan1.pRxMsg->Data[2] == 0xF1
&& hcan1.pRxMsg->Data[3] == 0x93 )
{
memset(hcan1.pTxMsg->Data, 8, 0xAA);
hcan1.pTxMsg->Data[0] = 0x10;
hcan1.pTxMsg->Data[1] = 0x13;
hcan1.pTxMsg->Data[2] = 0x62;
hcan1.pTxMsg->Data[3] = 0xF1;
hcan1.pTxMsg->Data[4] = 0x93;
hcan1.pTxMsg->Data[5] = get_hw_vehicle_type();
hcan1.pTxMsg->Data[6] = 0x20;
hcan1.pTxMsg->Data[7] = 0x20;
hcan1.pTxMsg->DLC = 8;
hcan1.pTxMsg->StdId = 0x7A8;
byte_20000258 = 1;
HAL_CAN_Transmit_IT(&hcan1);
result = HAL_CAN_Receive_IT(&hcan1, 0);
}
此处为TBOX固件中对CAN总线数据的处理,可以看到TBOX对一些UDS诊断命令的响应,如: 02 10 01
跳转到默认会话 03 22 F1 93
读取硬件版本
ISO 14229-1-2013 340页
int sub_800F34C()
{
__int64 v0; // r0
unsigned __int64 v1; // kr00_8
bool v2; // zf
int v4; // [sp+0h] [bp-110h] BYREF
unsigned __int16 v5; // [sp+4h] [bp-10Ch]
unsigned __int16 v6; // [sp+6h] [bp-10Ah]
unsigned __int16 v7[132]; // [sp+8h] [bp-108h] BYREF
if ( !sub_800363C(usart2_rx_fifo) )
{
LODWORD(v0) = (unsigned __int8)byte_2000414D;
if ( byte_2000414D )
{
v1 = sub_8003B44() - qword_20004150;
LODWORD(v0) = HIDWORD(v1);
if ( v1 > 0x1388 )
{
v0 = sub_8003B44();
qword_20004150 = v0;
}
}
return v0;
}
fifo_get((int)usart2_rx_fifo, (int)&v4, 8u);
if ( v6 )
fifo_get((int)usart2_rx_fifo, (int)v7, v6);
Log(2u, "Rx:cmd=%d", v5);
qword_20004150 = sub_8003B44();
LODWORD(v0) = v5;
HIDWORD(v0) = v5 - 325;
if ( v5 == 325 )
{
LODWORD(v0) = sub_8010106();
return v0;
}
if ( v5 > 0x145u )
{
if ( v5 == 504 )
{
LODWORD(v0) = sub_8010AF0(v7, 179);
return v0;
}
if ( SHIDWORD(v0) <= 179 )
{
if ( v5 == 351 )
{
LODWORD(v0) = sub_8011150();
return v0;
}
if ( SHIDWORD(v0) > 26 )
{
if ( v5 == 361 )
{
LODWORD(v0) = sub_8010EF4(v7);
return v0;
}
if ( SHIDWORD(v0) <= 36 )
{
switch ( v5 )
{
case 0x162u:
LODWORD(v0) = sub_800F97C(v7);
return v0;
case 0x163u:
LODWORD(v0) = sub_8011058(v7);
return v0;
case 0x165u:
LODWORD(v0) = sub_801024C();
return v0;
}
v2 = HIDWORD(v0) == 34;
if ( v5 == 359 )
{
LODWORD(v0) = sub_8010F60(v7);
return v0;
}
goto LABEL_30;
}
switch ( v5 )
{
case 0x191u:
LODWORD(v0) = sub_80111F8();
return v0;
case 0x193u:
LODWORD(v0) = sub_8011290();
return v0;
case 0x195u:
LODWORD(v0) = sub_800FEA4();
return v0;
}
v2 = HIDWORD(v0) == 177;
if ( v5 == 502 )
{
LODWORD(v0) = sub_8010AB8(v7);
return v0;
}
LABEL_44:
if ( v2 )
{
LODWORD(v0) = sub_80102DC();
return v0;
}
goto LABEL_20;
}
if ( v5 == 339 )
{
LODWORD(v0) = sub_800FA00();
return v0;
}
if ( SHIDWORD(v0) > 14 )
{
switch ( v5 )
{
case 0x155u:
LODWORD(v0) = sub_800F9BC();
return v0;
case 0x157u:
LODWORD(v0) = sub_800FBA4(v7);
return v0;
case 0x15Bu:
LODWORD(v0) = sub_801A86C();
return v0;
}
v2 = HIDWORD(v0) == 24;
if ( v5 == 349 )
{
LODWORD(v0) = sub_8002404();
return v0;
}
}
else
{
if ( v5 == 333 )
{
LODWORD(v0) = sub_800FB48();
return v0;
}
if ( SHIDWORD(v0) <= 8 )
{
if ( v5 == 327 )
{
LODWORD(v0) = sub_80100EC();
return v0;
}
v2 = HIDWORD(v0) == 6;
goto LABEL_71;
}
if ( v5 == 335 )
{
LODWORD(v0) = sub_800FB08();
return v0;
}
v2 = HIDWORD(v0) == 12;
if ( v5 == 337 )
{
LODWORD(v0) = sub_8010154();
return v0;
}
}
LABEL_20:
if ( v2 )
LODWORD(v0) = sub_80108C4(v7, HIDWORD(v0));
return v0;
}
LODWORD(v0) = v5 - 806;
if ( v5 == 806 )
{
LODWORD(v0) = sub_8010D40(v7);
return v0;
}
if ( SHIDWORD(v0) <= 481 )
{
if ( v5 == 605 )
{
LODWORD(v0) = sub_801102C(v7);
return v0;
}
if ( SHIDWORD(v0) > 280 )
{
HIDWORD(v0) = v5 - 702;
switch ( v5 )
{
case 0x2BEu:
LODWORD(v0) = sub_8010B4C(v7);
return v0;
case 0x2C0u:
LODWORD(v0) = sub_8010B72(v7);
return v0;
case 0x322u:
LODWORD(v0) = sub_8010D18(v7);
return v0;
}
v2 = HIDWORD(v0) == 102;
if ( v5 == 804 )
{
LODWORD(v0) = sub_8010CE8(v7);
return v0;
}
}
else
{
switch ( v5 )
{
case 0x1FAu:
LODWORD(v0) = sub_8010B28(v7);
return v0;
case 0x1FCu:
LODWORD(v0) = sub_80115DC(v0);
return v0;
case 0x1FEu:
LODWORD(v0) = sub_8010BB2(v7);
return v0;
}
v2 = HIDWORD(v0) == 278;
if ( v5 == 603 )
{
LODWORD(v0) = sub_801100C(v7);
return v0;
}
}
goto LABEL_57;
}
if ( v5 == 908 )
{
LODWORD(v0) = sub_8011654(v7);
return v0;
}
if ( (int)v0 > 102 )
{
if ( v5 == 1002 )
{
LODWORD(v0) = sub_8010A78(v7);
return v0;
}
if ( v5 == 1004 )
return v0;
LODWORD(v0) = v5 - 1101;
if ( v5 == 1101 )
{
LODWORD(v0) = sub_800FBC4();
return v0;
}
v2 = (_DWORD)v0 == 16;
if ( v5 == 1117 )
{
LODWORD(v0) = sub_801073C((unsigned __int8 *)v7);
return v0;
}
}
else
{
switch ( v5 )
{
case 0x328u:
LODWORD(v0) = sub_8010CC0(v7);
return v0;
case 0x386u:
LODWORD(v0) = sub_8010BD8(v7);
return v0;
case 0x388u:
LODWORD(v0) = sub_80115F4(v7);
return v0;
}
v2 = (_DWORD)v0 == 100;
if ( v5 == 906 )
{
LODWORD(v0) = sub_8010C4C(v7);
return v0;
}
}
LABEL_71:
if ( v2 )
{
LODWORD(v0) = sub_800FBFA(v7);
return v0;
}
goto LABEL_20;
}
if ( v5 == 209 )
{
LODWORD(v0) = sub_8010D68(v7);
return v0;
}
if ( v5 <= 0xD1u )
{
if ( v5 == 23 )
{
LODWORD(v0) = sub_8010180();
return v0;
}
if ( v5 <= 0x17u )
{
if ( v5 == 13 )
{
LODWORD(v0) = sub_80108AE();
return v0;
}
if ( v5 <= 0xDu )
{
if ( v5 == 6 )
{
LODWORD(v0) = sub_80102D0(v7, HIDWORD(v0));
return v0;
}
if ( v5 > 6u )
{
if ( v5 == 9 )
{
LODWORD(v0) = sub_8010284();
return v0;
}
v2 = v5 == 12;
if ( v5 == 12 )
return v0;
}
else
{
if ( v5 == 2 )
{
LODWORD(v0) = sub_8011330(v7, HIDWORD(v0));
return v0;
}
v2 = v5 == 3;
}
goto LABEL_20;
}
switch ( v5 )
{
case 0xFu:
LODWORD(v0) = sub_80107C0((int *)v7);
return v0;
case 0x12u:
return v0;
case 0x14u:
LODWORD(v0) = sub_8010994((int *)v7);
return v0;
}
v2 = v5 == 22;
LABEL_30:
if ( v2 )
{
LODWORD(v0) = sub_8011530(v7, HIDWORD(v0));
return v0;
}
goto LABEL_20;
}
if ( v5 == 33 )
{
LODWORD(v0) = sub_800FA4C(v7, HIDWORD(v0));
return v0;
}
if ( v5 <= 0x21u )
{
if ( v5 == 26 || v5 == 28 )
return v0;
if ( v5 == 29 )
{
LODWORD(v0) = sub_80110D8(v7, HIDWORD(v0));
return v0;
}
v2 = v5 == 32;
if ( v5 == 32 )
return v0;
goto LABEL_20;
}
switch ( v5 )
{
case 'e':
LODWORD(v0) = sub_8011460();
return v0;
case 'g':
LODWORD(v0) = sub_801142C();
return v0;
case 'i':
LODWORD(v0) = sub_8010884(v7, HIDWORD(v0));
return v0;
}
v2 = v5 == 107;
goto LABEL_44;
}
HIDWORD(v0) = v5 - 305;
if ( v5 == 305 )
{
LODWORD(v0) = sub_80111C8();
return v0;
}
if ( v5 <= 0x131u )
{
if ( v5 == 221 )
{
LODWORD(v0) = sub_8003506(221, SHIDWORD(v0), 305, 0x2018);
return v0;
}
if ( v5 <= 0xDDu )
{
LODWORD(v0) = v5 - 212;
LABEL_52:
switch ( (int)v0 )
{
case 1:
LODWORD(v0) = nullsub_20();
break;
case 3:
LODWORD(v0) = sub_8010DA4(v7[0]);
break;
case 7:
LODWORD(v0) = sub_80113F4();
break;
default:
return v0;
}
return v0;
}
if ( v5 == 226 )
return v0;
if ( v5 == 231 )
{
LODWORD(v0) = sub_801071C(v7, HIDWORD(v0));
return v0;
}
LODWORD(v0) = v5 - 301;
if ( v5 == 301 )
{
LODWORD(v0) = sub_80111DE();
return v0;
}
v2 = (_DWORD)v0 == 2;
LABEL_57:
if ( v2 )
{
LODWORD(v0) = sub_8011276();
return v0;
}
goto LABEL_20;
}
LODWORD(v0) = v5 - 307;
switch ( v5 )
{
case 0x133u:
LODWORD(v0) = sub_8011260();
break;
case 0x134u:
case 0x136u:
case 0x138u:
case 0x13Au:
case 0x13Cu:
case 0x13Eu:
case 0x140u:
case 0x142u:
return v0;
case 0x135u:
LODWORD(v0) = sub_8010EE4();
break;
case 0x137u:
LODWORD(v0) = sub_8010E88();
break;
case 0x139u:
LODWORD(v0) = sub_801020C();
break;
case 0x13Bu:
LODWORD(v0) = sub_80112FC();
break;
case 0x13Du:
LODWORD(v0) = sub_80112C8();
break;
case 0x13Fu:
LODWORD(v0) = sub_8010120();
break;
case 0x141u:
LODWORD(v0) = sub_8010980();
break;
case 0x143u:
LODWORD(v0) = sub_800FBE6();
break;
default:
goto LABEL_52;
}
return v0;
}
此处代码为对LTE模块传入指令的一些处理
结尾
到此,我们已经获取了TBOX的固件,并对其进行了一些还原,逆向了其部分功能的逻辑。接下来便是漏洞挖掘的过程,在本篇文章不过多阐述这部分内容。