在嵌入式系统开发领域,逆向工程是一项极具挑战性的工作。最近接手了一个PIC单片机项目的逆向任务,客户的产品核心程序由国外开发团队编写,由于历史原因源码已经丢失,现在需要增加新功能。面对这种情况,我们通常有两种选择:完全重新开发或者逆向现有程序。考虑到产品已经稳定运行多年,且重新开发周期长、风险高,最终决定采用逆向工程的方案。
PIC单片机作为Microchip公司的经典产品,在工业控制、消费电子等领域应用广泛。与常见的x86或ARM架构不同,PIC采用哈佛架构,这意味着程序存储器和数据存储器是分开的,这种设计带来了更高的执行效率,但也增加了逆向的复杂度。我虽然有多年的x86、51和STM32逆向经验,但PIC平台还是第一次接触,这既是一个挑战,也是一次宝贵的学习机会。
逆向PIC单片机需要一套专门的工具链,经过多方比较,我最终确定了以下配置方案:
IDA Pro 7.0+:作为行业标准的反汇编工具,IDA Pro对PIC处理器的支持相对完善。特别需要注意的是,要确保安装的版本支持PIC指令集分析。我选择了7.5版本,它对PIC18系列的支持最为稳定。
MPLAB IDE v8.80:这是Microchip官方提供的经典开发环境。虽然现在有更新的MPLAB X版本,但v8.80对老款PIC芯片的支持更好,界面也更简洁。安装时务必选择英文路径,避免中文字符可能导致的兼容性问题。
mcc18编译器 v3.47:这是Microchip官方的C编译器,用于后续的代码验证。安装后需要在MPLAB中配置工具链路径:Project → Select Language Toolsuite → Microchip C18 Toolsuite。
串口调试助手:用于功能验证,我推荐使用Tera Term或SecureCRT,它们支持多种协议和脚本功能。
拿到客户提供的hex文件后,第一步是用IDA Pro加载分析。操作步骤如下:
注意:PIC的哈佛结构使得IDA的自动分析可能不够准确,特别是对于特殊功能寄存器(SFR)的识别。遇到不确定的地址引用时,一定要对照芯片数据手册进行验证。
加载完成后,我发现IDA的F5反编译功能对PIC汇编无效,这意味着我们只能通过纯手工方式分析指令流。这也是PIC逆向比x86更困难的原因之一。
PIC单片机采用精简指令集(RISC),指令数量不多但各有特点。与常见的冯·诺依曼架构不同,PIC的哈佛架构有以下几个关键特性:
assembly复制movff byte_RAM_3EE, POSTINC1
这条指令是PIC中典型的文件寄存器间传送指令:
movff:表示在两个文件寄存器间传送数据byte_RAM_3EE:源地址,位于RAM的0x3EE位置POSTINC1:目的寄存器,使用后会自动递增用C语言类比可以理解为:
c复制char *ptr = 0x3EE;
char *dest = FSR1; // FSR1是PIC的指针寄存器
*dest = *ptr;
FSR1++; // POSTINC1的自动递增效果
assembly复制addwf 0x20, f, ACCESS
这条指令完成加法运算:
addwf:将W寄存器内容与文件寄存器内容相加0x20:文件寄存器地址f:结果存回文件寄存器(若为w则存到W寄存器)ACCESS:表示使用当前bankassembly复制bsf STATUS, 0, ACCESS
这条指令用于位置位:
bsf:位设置指令STATUS:状态寄存器0:要操作的位(这里是第0位,即C标志位)ACCESS:访问模式PIC18没有硬件堆栈,而是通过软件模拟实现函数调用。典型的函数序言代码如下:
assembly复制movff FSR2L, POSTINC1
movff FSR1L, FSR2L
movlw 4
addwf FSR1L, f, ACCESS
这段代码相当于x86中的:
assembly复制push ebp
mov ebp, esp
sub esp, 4
解释:
逆向PIC程序最有效的方法是"对比法",即通过编写测试代码,观察编译后的汇编模式,再应用到目标程序中。具体步骤如下:
c复制int add(int a, int b) {
int c = a + b;
return c;
}
查看反汇编:通过View → Disassembly Listing查看生成的汇编代码
建立模式库:记录常见C结构对应的汇编模式,例如:
应用到目标程序:在目标hex中寻找相似的汇编模式,推断出原始C代码
PIC程序的函数识别有几个关键特征:
示例:识别到一个函数片断
assembly复制movff FSR2L, POSTINC1
movff FSR1L, FSR2L
movlw 8
addwf FSR1L, f, ACCESS
...
return
可以推断出:
PIC有很多特殊功能寄存器(SFR),IDA可能无法正确识别。例如:
assembly复制bsf byte_RAM_7E, 3, ACCESS
IDA可能错误地将0x7E识别为普通RAM,实际上根据数据手册,这是BAUDCON1寄存器的地址。正确的解析应该是:
assembly复制bsf BAUDCON1, 3, ACCESS
处理建议:
通过分析发现,PIC18的程序入口通常不是0x0000,而是位于复位向量指向的地址。具体步骤:
在本案例中,程序入口位于loc_seg001_1F73C,通过对比编译器生成的启动代码确认了这一判断。
主循环通常具有以下特征:
逆向得到的伪代码结构:
c复制void main() {
hardware_init();
peripheral_init();
while(1) {
if (check_event1()) {
handle_event1();
}
if (check_event2()) {
handle_event2();
}
// ...其他事件处理
}
}
PIC的外设操作通常包括:
例如,逆向一个UART发送函数:
assembly复制movlw 0x20
movwf TXREG, ACCESS
对应的C代码:
c复制void uart_send(char data) {
while(!PIR1bits.TXIF); // 等待发送缓冲区空
TXREG = data; // 写入发送寄存器
}
逆向完成后,必须确保生成的C代码能编译出与原hex完全一致的二进制文件:
验证命令示例(Windows):
bash复制fc /b original.hex reversed.hex
hex不一致:
功能异常:
性能问题:
掌握PIC逆向技术后,可以应用于:
整个逆向过程历时约3周,最终成功将206KB的hex文件完整逆向为C代码,编译后的hex与原文件完全一致。这不仅解决了客户的燃眉之急,也为后续功能扩展奠定了坚实基础。