黑盒通信协议逆向实战:从物理层波形到单片机插桩解析

黑盒通信协议逆向实战:从物理层波形到单片机插桩解析 1. 整体思路拆解黑盒逆向不是玄学是一套方法论我做了这么多年嵌入式开发接到过不少“只有一块板子没有原理图、没有协议文档、没有固件源码”的项目。说白了就是纯黑盒逆向。以前带新人的时候我经常跟他们讲一句话单片机通信协议逆向本质上是“物理层确定信号模型逻辑层枚举帧结构应用层猜业务语义”的三步走过程。这个思路不是拍脑袋想出来的而是被无数次项目验证过的。当年我接到第一个黑盒逆向项目时甲方只给了一块PCB板子一个UART转串口头外加一句“搞不定就换人”。当时毫无头绪拿着示波器满板子乱戳三天没进展。后来我停下来认真想了一夜把问题拆成了三层物理层我到底看到了什么信号逻辑层这些信号按什么规则组织应用层这些数据代表什么含义想明白之后第二天就找到了突破口。这套方法论放在任何通信协议逆向项目里都成立。你用逻辑分析仪抓UART波形用光耦隔离差分探头去啃CAN总线、RS485这类差分信号甚至去碰SPI、I2C、单总线物理层永远是第一个关卡——它决定了你后面用软件怎么解。物理层抓错后面全错。这就是为什么我把物理层放在第一位的原因。这篇文章的核心适用人群是三种嵌入式工程师日常调试没有文档的定制协议需要自己摸出通信规约。单片机安全/兼容开发人员比如做第三方硬件兼容、耗材芯片替换、私有协议网关。想入坑固件逆向的爱好者对逻辑分析仪、示波器这类硬核工具感兴趣但不知道从哪下手。我下面写的所有内容都是基于一个真实场景一台老式的工控仪表通过两线制串行总线对外通信线缆上有一个光耦隔离板卡主控端是一个STM32F103单片机。整台设备没有任何文档我需要把它的通信协议完整扒出来。这个场景非常经典——既有物理层的盲人摸象又有光耦这种隔离器件的反相陷阱最后还要靠单片机插桩做深度解析。一篇文章把所有核心套路都覆盖掉。2. 物理层盲猜先从波形里读出一切能读到的信息2.1 先动手别急着猜协议拿到一块黑盒板子第一步绝对不是去百度搜索“XXX协议”也不是去翻芯片手册如果芯片被打磨了型号翻也没用。第一步永远是上示波器满板子找信号把所有带电的引脚都扫一遍。我习惯先把板子正常上电让设备处于正常运行状态然后拿着示波器探头地线夹在板子的公共地上探头挨个去触碰芯片引脚的过孔、排针、测试点、跳线焊盘观察有没有周期性波形。这一步看起来笨实际上非常有效。只要是有通信功能的系统总会有引脚在吐数据。你不需要立刻知道波形是什么协议只需要先把“有哪些引脚存在周期性信号”这个清单列出来。当年我扫那块仪表板时整个板子上面大概三十多个可能带电的网络节点花了半小时最终锁定了三个有明显波形的引脚一个引脚是方波脉冲频率约500Hz占空比恒定约50%一个引脚是高电平为主、偶尔有低电平毛刺一个引脚是稀疏的串行脉冲串间隔较规律。根据经验第一个像PWM第二个像片选或中断信号第三个likely是串行数据。这三个候选对象里我优先盯上了第三路——因为真正通信数据往往是间歇性、突发性的而不是持续均匀跳变。注意这一步的坑在于地线夹子。很多人用示波器探头时地线夹悬空或者夹的位置离测量点太远导致波形上叠加了一大堆振铃噪声。我一般会把地线夹剪短用弹簧地线针贴着被测点旁边的地。信号弱的话可以先把探头打到10x档位降低负载效应对电路的影响。2.2 从波形特征反推协议类型锁定信号源之后就要从波形细节反推协议了。这是整个物理层阶段最核心的一步也是经验成分最高的一步。我抓到的第三路串行脉冲波形先看单帧。把示波器时基调小比如从200ms/div缩到200us/div能看到一次脉冲串的整体轮廓。再看单个bit。把时基继续缩到20us/div甚至2us/div观察单个脉冲的宽度。这里有一个最常用的经验法则单bit宽度直接对应波特率。比如一个bit宽度是8.68us波特率就是1/8.68us ≈ 115200bps如果是104us波特率就是9600bps。不同波特率的bit宽度差异巨大光凭这一点就能过滤掉大部分候选协议。我当时抓到的这个信号单bit宽度大约104us波特率在9600bps附近。加上波形是“空闲时高电平起始拉低”的典型UART特征基本可以确认这是一条UART串口线。还有一个判断UART的经典特征看低电平脉冲是不是等于1个bit宽度。UART协议里起始位固定是1个bit的低电平后面跟着8个或9个数据位可选的校验位最后是停止位高电平。所以一帧数据进来最先看到的必然是拉低约1个bit宽度的起始位。如果抓到的波形空闲状态是低电平发送时跳高那就是反逻辑常见于带反相器的电路——后面讲光耦时我会展开说。串行脉冲之外那个500Hz恒定方波我也顺手查了一下。每个脉冲宽度约1ms周期2ms占空比50%这种波形在通信协议里通常不是数据而是仅用于同步的时钟或唤醒信号。很多私有协议会把时钟和数据分开传我当时记下了这个特征后面果然在帧结构里用到了它。我整理了一个常见的物理层协议速判表方便新手参考波形特征大概率协议关键判据空闲高、起始低、1bit低脉冲UART/RS232起始位宽度 1bit差分信号、两线绞合RS485/CAN差分幅度、共模电平时钟数据两路并行SPI/I2C时钟线恒定、数据线伴随变化单总线、长低电平同步头单总线/DALI同步头宽度远大于普通bit高频载波调制无线/载波通信波形带高频抖动包络这个表不是万能金标准但能在你毫无头绪时提供一个切入方向。2.3 逻辑分析仪进场多头抓取、记录全貌示波器只能看你手动触发的几帧波形要看完整的通信过程把几百几千帧数据一次抓到必须上逻辑分析仪。我常用的方案是几十块钱的USB逻辑分析仪配一个开源上位机8通道版本足够用。采样率选1MHz甚至500kHz就行——目标是9600bps级别的串口1MHz采样意味着每bit采100多个点波形恢复精度完全够。这里有一个关键参数经验采样率至少是波特率的4倍以上否则边沿识别会出错。你算一下9600bps时一个bit宽度104us1MHz采样率下一bit采104个点完全没问题。但如果是115200bps一bit只有8.68us1MHz采样率下只有8个点勉强可用再快的协议比如1Mbps就需要至少4MHz以上的采样率。抓数据时我习惯把逻辑分析仪的通道1接串行数据通道2接前面看到的500Hz方波。两个信号同步采集后面分析帧结构时就能看出时钟信号与数据之间的对齐关系。第一次抓回来看到的就是一串hex7E 7E 7E 7E 01 03 00 1A 00 00 02 5A 7E这样的东西。这里面有些信息已经能读出来了7E连续出现大概率是帧头或同步填充符01 03这种短字节可能是地址、功能码后面跟着的可变长数据区是载荷倒数第二个字节看起来不像ASCII字符更像校验码。到这一步物理层的“盲猜”阶段基本结束。我们已经确定了这是一个9600bps的UART串口通信空闲为高电平帧结构有固定帧头目前还缺一块关键拼图——光耦反相的问题。但很多人不知道的是物理层如果不完整处理后面逻辑层解析全是错位。3. 光耦上有个“隐形杀手”反相信号怎么处理3.1 光耦隔离为什么会对波形做反相很多嵌入式工程师做协议逆向时抓到波形就直接开始解UART从来没想过一个问题板子上如果存在光耦隔离器件你看到的波形很可能是反相的。光耦的工作原理很简单输入侧发光二极管通电发光输出侧光敏三极管受光导通输入和输出之间只有光耦合没有电气连接从而实现信号隔离。这个设计很妙但它带来一个现实问题光耦输出端的电平逻辑往往和输入端相反。原因在电路接法上。常见的光耦输入侧是发光二极管串联限流电阻接在信号和地之间输出侧光敏管接集电极电阻到VCC发射极接地。无信号时发光管不亮光敏管截止输出端被上拉电阻拉到高电平有信号输入时发光管点亮光敏管导通输出端被拉到低电平。你看信号来了输出反而变低了——这就是反相。还有一个更容易被忽略的坑光耦的传输延迟和上升沿/下降沿不对称。光耦是一种慢器件PC817这种常见型号上升时间通常要几个微秒下降时间和输入侧电流强相关。如果你在9600波特率下做UART一个bit只有104us几个微秒的边沿偏移对解码影响不算致命但如果波特率上去了比如38400或115200光耦引入的边沿偏移就会让采样点偏移导致解码偶尔出错。当年我就被这个坑坑过一次。那块仪表板上的光耦隔离板卡输入侧和输出侧的反相关系没有体现在任何文档里我直接用逻辑分析仪去抓光耦后面的信号解出来的数据全是乱码。后来我用示波器同时测光耦输入侧和输出侧波形一对才发现输入是高电平时输出是低电平输入是低电平时输出是高电平——信号在整个链路中被反了一次。3.2 实际项目中光耦反相的三种处理方式知道光耦反相总得有对策。我总结三种常用处理方式按优先级从高到低排第一种直接在物理层解决——抓光耦输入端。既然光耦输出端是反相的那就绕开它直接抓光耦输入侧的原始信号。这个思路最直接。用逻辑分析仪把通道接到光耦的输入电阻之前测到的信号就是设备MCU真正发出的原始波形不需要做任何软件反相处理。但这里有一个前提你要能找得到光耦输入端。光耦隔离板卡通常是一个独立的模块有时候输进去的信号被胶封死在模块内部外部无法探测。这时候就只能在输出端抓反相信号并做软件反转。第二种逻辑分析仪或示波器软件反转。大部分逻辑分析仪软件都支持通道反相功能可以直接对采集到的信号做“0变1、1变0”处理。这种方式适合事后分析但前提是你已经确认了反相关系并且反相只发生了一次。注意信号链路上可能不只一个光耦。有些设计会串两级光耦反两次等于没反这时候你再做一次软件反转反而把正信号搞反了。务必用示波器对照输入输出确认实际反相次数。第三种在解码层处理——软件解调时识别反相逻辑。UART协议最底层其实就是电平变化空闲高、起始低是正逻辑空闲低、起始高反而是反逻辑。你可以在解码参数里把“极性”选项设为反相这样逻辑分析仪在采样时就会自动反向还原。实测下来大部分现成协议分析插件都支持极性反转你只需要找准设置位置。我当时那个项目的处理方式是光耦之前的原始串口信号用通道1抓光耦之后反相信号用通道2同时抓两个通道互为印证。这样既确认了反相关系又保留了原始信号后续单片机插桩阶段也用上了这套双通道数据。3.3 光耦延时的实测数据与经验值关于光耦延迟很多人不重视但在某些场景下必须认真对待。你可以用示波器同时接光耦输入和输出触发沿测量输入到输出的延迟。实测PC817这类低速光耦信号从输入端到输出端的传输延迟大概是几微秒到十几微秒具体数值取决于输入电流和输出侧上拉电阻。我就实测过一组数据输入端输入一个1kHz方波输出端相比输入端延迟了约4us。在9600bps下一个bit是104us4us的延迟只有4%完全不影响。但如果用同样的光耦去做115200bps一个bit只有8.68us4us的延迟几乎占了一半bit采样点如果恰好落在边沿附近就会出现偶发性的解码错误。所以做高速串口隔离时不能拿PC817硬上要选高速光耦。这个经验我在很多项目里都用到了写下来给各位避个坑。4. 单片机插桩用代码把协议“钉”在内存里4.1 为什么需要单片机插桩物理层抓完波形逻辑层解出hex流这个时候会有一个尴尬的局面你确实拿到了一串串的数据但这些数据到底是什么意思帧格式怎么划分哪个字节是地址、哪个是功能码、哪个是校验光靠逻辑分析仪的协议分析插件还不够——你需要交互式地操控通信对象观察它对不同输入的响应。这就到了单片机插桩的用武之地。单片机插桩的核心思路是用一块单片机通常就是STM32、STM8、ATmega这类主流MCU模拟通信链路的一端通过GPIO/定时器/串口外设精确控制通信时序在接收对方数据的同时把每个bit的边沿变化记录下来还原到PC端做分析。这样不仅能被动解析协议还能主动发送试探帧观测对方的响应从而推断协议语义。我当时面临的问题特典型用逻辑分析仪抓数据和用串口助手直接解析数据都是七零八落的。因为光耦板卡后面到底走了什么逻辑未知帧数据的每个字节怎么组织未知校验码的算法未知。纯静态分析效率太低。4.2 插桩方案的选择定时器捕获是最稳的路说到插桩实现第一种顺手方案是直接用单片机的串口外设接收。把波特率配成9600把光耦输出信号接在RX引脚上打开串口中断数据就能直接进FIFO。这个方案最省事但有一个致命缺陷你只能拿到“被串口外设解释过”的数据拿不到原始bit序列。如果对方的协议不是标准UART呢如果波特率有微小偏移呢如果帧结构里带有非标准电平呢串口外设全部无解。第二种方案是用定时器输入捕获模块这也是我更推荐的插桩方式。STM32的定时器输入捕获可以在信号的上升沿和下降沿分别记录定时器计数器的当前值从而推算出每个电平持续了多长时间。这些沿间隔本质上就是“bit宽度序列”有了这个序列你可以100%精确恢复原始波形不受波特率偏差影响不受协议类型限制。我第一次做插桩时就是这么写的把GPIO配成定时器通道的输入引脚PC6映射到TIM3_CH1定时器时钟72MHz预分频72得到1MHz的计数频率也就是每计数一次就是1us。开输入捕获中断每次沿变化都进中断把CNT值存入环形缓冲区同时记录方向上升沿还是下降沿。这样抓回来的就是一组时间戳数组[0, 104, 208, 320, 424...]这种相邻两个时间戳之差就是一个电平的持续宽度。有了这个数组后续解析就特别灵活。你可以写个脚本把这些时间戳还原成波形也可以直接解码成UART数据帧。即使后面发现不是UART而是PWM占空比调制的私有协议这份沿时间数据依然够用——这就是插桩相对串口外设最大的优势。4.3 光耦反相在插桩阶段怎么处理写到代码层面光耦反相还有个有意思的表现上升沿和下降沿互换位置了。正逻辑UART的一帧是空闲高起始位拉低然后数据位依次变化停止位拉高。如果光耦反相你从输出端看的是空闲低起始位拉高数据位全部反转停止位拉低。用定时器输入捕获抓沿时反相不反相并不影响沿时间戳的提取——你照样能拿到每个电平的持续时间只是需要根据“先高后底还是先低后高”判断当前是起始位还是空闲。但如果你直接用单片机的中断引脚去检测“起始位”就必须要知道反相关系。正逻辑的起始位是下降沿反逻辑的起始位是上升沿。检测不对第一帧就对不齐。我在插桩代码里专门加了一个宏开关#define SIGNAL_POLARITY_INVERTED 1。为1时起始沿检测定义为上升沿为0时定义为下降沿。切换一次编译下载就能适配不同逻辑的板子平时调试很方便。如果项目里光耦板卡不确定串口助手先收到0x00再收到0xFF这种极端值多半是反相了直接切开关。4.4 主动探测插桩的进阶玩法被动抓包只是插桩的第一步。真正拉开效率差距的是主动探测。同样一块单片机配置好定时器输入捕获的同时再用一个GPIO复用成UART发送功能编程按我猜的帧格式主动往总线上发数据比如发7E 01 03 00 1A 00 00 02 5A 7E这种试探帧然后看对方的响应——这就是主动探测。这套玩法的逻辑很简单黑盒系统虽然不给你文档但它会“回答”你的问题。你发一条查询指令它如果回一条固定格式的数据说明你至少踩对了地址和帧头如果你发一个错误校验码对方直接不应答说明校验算法猜错了。我当时照着已有的hex数据拼了几个查询帧比如向设备请求当前状态、请求运行参数、请求历史记录逐一发送并记录响应。经过几十轮试探我总结出了这台仪表的协议基本盘帧头是固定7E长度字节放在第二字节功能码放第三字节后面是数据区最后两个字节是CRC16低字节和高字节。功能码等于01时是查询状态03时是读取参数10是写参数——一个大致的Modbus风格私有协议框架就这样被摸透了。4.5 插桩代码的完整示例下面贴一段我常用的STM32定时器输入捕获插桩初始化代码用的是标准外设库写的实测在STM32F103C8T6上稳定运行。原理就是把TIM3_CH1配置成输入捕获上升沿和下降沿都捕获进中断后把沿时间戳和方向存入缓冲区。#include stm32f10x.h #include stdio.h #define RING_BUF_SIZE 2048 volatile uint16_t edge_time[RING_BUF_SIZE]; volatile uint8_t edge_dir[RING_BUF_SIZE]; volatile uint16_t edge_cnt 0; void TIM3_CH1_Capture_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_ICInitTypeDef TIM_ICInitStructure; NVIC_InitTypeDef NVIC_InitStructure; // 开启时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // PA6 复用为 TIM3_CH1 GPIO_InitStructure.GPIO_Pin GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, GPIO_InitStructure); // 定时器时基72MHz / 72 1MHz即计数一次 1us TIM_TimeBaseStructure.TIM_Period 0xFFFF; TIM_TimeBaseStructure.TIM_Prescaler 72 - 1; TIM_TimeBaseStructure.TIM_ClockDivision 0; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM3, TIM_TimeBaseStructure); // 输入捕获配置直接映射到 IC1同时捕获上升沿和下降沿 TIM_ICInitStructure.TIM_Channel TIM_Channel_1; TIM_ICInitStructure.TIM_ICPolarity TIM_ICPolarity_Rising; // 初始上升沿 TIM_ICInitStructure.TIM_ICSelection TIM_ICSelection_DirectTI; TIM_ICInitStructure.TIM_ICPrescaler TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICFilter 0x0; TIM_ICInit(TIM3, TIM_ICInitStructure); // 使能捕获中断 TIM_ITConfig(TIM3, TIM_IT_CC1, ENABLE); // 中断分组 NVIC_InitStructure.NVIC_IRQChannel TIM3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority 0; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStructure); TIM_Cmd(TIM3, ENABLE); } void TIM3_IRQHandler(void) { if (TIM_GetITStatus(TIM3, TIM_IT_CC1) ! RESET) { TIM_ClearITPendingBit(TIM3, TIM_IT_CC1); // 记录当前边沿时间戳 if (edge_cnt RING_BUF_SIZE) { edge_time[edge_cnt] TIM_GetCapture1(TIM3); edge_dir[edge_cnt] GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6) ? 1 : 0; edge_cnt; } // 切换捕获极性上升沿 - 下降沿或下降沿 - 上升沿 if (TIM_GetCapture1(TIM3) ! 0) { if (TIM3-CCER TIM_CCER_CC1P) { TIM3-CCER ~TIM_CCER_CC1P; } else { TIM3-CCER | TIM_CCER_CC1P; } } } } // 主函数示例等待采集完后通过串口把时间戳打印出去 int main(void) { // 串口初始化略… Delay_Init(); TIM3_CH1_Capture_Init(); while (1) { if (edge_cnt 100) { uint16_t i; for (i 0; i edge_cnt; i) { printf(%d,%d\r\n, edge_time[i], edge_dir[i]); } edge_cnt 0; Delay_Ms(500); } } }这段代码的注意点有几个。一是定时器溢出问题当两个边沿之间的时间超过65535us时CNT会溢出回绕时间戳就要做溢出补偿。我当时的解决方案是开启TIM3更新中断在更新中断里维护一个32位溢出计数器组合出完整的32位时间戳。二是中断里频繁切换捕获极性一定要先清中断标志再切换否则可能漏掉第一次边沿。三是如果信号频率过高、中断过于频繁缓冲区可能溢出最简单的处理是增大缓冲区或者用DMA配合捕获比较事件但这会让代码复杂度上一个台阶。4.6 插桩数据的后续分析流程把时间戳数组拿到PC端之后我的处理流程一般是这样的第一步把时间戳数组还原成波形图。用Python的matplotlib画一个阶梯波形横轴是时间纵轴是电平。这个波形图看起来和示波器抓的一模一样但分辨率更高每个边沿位置都是精确到微秒的数值。第二步按UART协议解码。写一个简单的解码函数把时间戳数组扫描一遍找到起始位正逻辑下是第一个下降沿紧跟着约104us的低电平然后按波特率逐bit采样。判断每个bit是0还是1拼成字节输出hex字符串。这个过程很简单不依赖串口外设所以无论对方波形有微小畸变只要bit宽度能对上都能正确解出。第三步批量分析帧结构。把解码得到的hex流按连续性切分成帧寻找固定值段如帧头、长度不固定段如数据区、明显校验段如帧尾几字节通过对比不同帧之间相同位置的差异逐步锁定字段含义。這个流程我用过很多次稳定可靠。当初在仪表项目里我抓完波形到解出完整协议用了大约两个晚上。第一天晚上完成了物理层探测和光耦反相确认第二天下午写完了插桩采集和解码脚本晚上把帧结构分析完毕第三天一早就把整个私有协议规约整理成了一份文档。5. 从协议逆向到产品化的经验总结5.1 几个想起来就肉疼的坑上面整个流程走下来最值得拿出来分享的其实不是那套方法论本身而是我在实操过程中犯过的错、踩过的坑。每个坑背后都是一段时间的损失我回想了一下大概能总结出下面几条每一条我都亲手栽过。第一个大坑是逻辑分析仪采样率不够导致误判。早期我贪便宜用一个标称24MHz采样率的逻辑分析仪去抓一个当时以为很快的信号。实际一测那个信号是2MHz的SPI时钟24MHz采样率意味着每周期采12个点勉强能看但边沿位置误差很大解析出的数据偶尔多位。后来换高速分析仪再测数据完全不一样。千万别低估采样率的问题逻辑分析仪采样率不足时解码出来的错误不是偶发而是系统性的很难排查。第二个大坑是没有确认光耦反相直接拿串口助手收数据。一路数据全是乱码还以为是波特率不对换了十几种波特率也没用折腾了一下午。后来示波器一测才发现光耦输出端反相整个数据位都被反转了。串口助手可不会帮你反转电平它只管按电平高低解0和1。所以我建议新手在抓任何一个孤立模块的数据之前先用示波器同时看输入输出端确认信号极性。第三个大坑是忘记考虑共地问题。逻辑分析仪和被测板子必须共地否则波形上全是工频噪声信号完全看不清。尤其是在测试一些工业老设备时板子本身通过开关电源供电如果你再拿USB供电的逻辑分析仪去测两边的地电位可能相差很大轻则波形乱飞重则烧毁逻辑分析仪输入通道。我现在的习惯是测之前先拿万用表量一下逻辑分析仪的USB地与被测板子地之间的电压超过0.5V就要想办法处理。5.2 工具链的最终推荐清单项目全部做完之后我把我常用的工具链整理了一下给有需要的人一个参考工具/环节推荐方案理由物理层观察双通道数字示波器100MHz带宽能同时看输入输出对比光耦两侧波形多通道采集USB逻辑分析仪8通道24MHz采样率便宜、够用、上位机界面友好波形还原与协议解码Python matplotlib 自写解码脚本灵活支持任意私有协议主动探测STM32F103最小系统板便宜、资料多、定时器输入捕获好用文档整理Markdown 协议表格方便后续查阅和交接这套工具链总成本大概在几百元以内性价比很高。那些动辄上万的商用协议分析仪功能固然强大但很多时候它解析不了私有非标协议反而没有自写脚本灵活。我的观点一直是工具够用即可核心价值在分析思路。5.3 这套方法论还能往哪延伸最后说说这套方法的延展性。物理层盲猜光耦极性确认单片机插桩这套组合不只是UART能用。SPI协议的黑盒逆向思路一模一样先找时钟线通常是有固定频率的方波再在时钟边沿处看数据线的电平变化组合出比特流。CAN总线的话更简单示波器能看到差分波形逻辑分析仪的CAN解码插件直接就能解出ID、数据、CRC单片插桩则用来做总线数据的重放和注入测试。I2C逻辑分析仪的协议分析插件也能解但难点在确认地址和寄存器映射关系此时单片机插桩主动读写寄存器特别有用。如果你再往深处走还可以在做完通信协议逆向后继续对固件本身做逆向分析用反汇编工具找到处理接收数据的函数。比如拿到固件bin文件后用Ghidra载入定位到串口中断处理函数的交叉引用一路追踪数据缓冲区的处理逻辑很多协议的关键算法校验和、加密、混淆都能在汇编层面找到答案。这条路更有挑战性但回报也更大。我个人在实际操作中的体会是黑盒逆向最难的阶段不是后期写脚本、猜校验算法而是最初的物理层和信号完整性问题。物理层的波形看不准后面全白搭光耦极性搞反数据全乱插桩代码写得不稳采集的数据也就没法用。每一步都是下一层的前提。最后再分享一个小技巧做物理层探测时不要只用一个固定触发电平我从一开始就习惯把触发电平设在信号幅度的中点附近并且开启边沿触发holdoff功能这样波形稳定且不会因毛刺误触发。多花几分钟设置示波器后面能省好几小时的无效分析时间。