news 2026/9/8 23:39:22

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
黑盒通信协议逆向实战:从物理层波形到单片机插桩解析

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宽度104us,1MHz采样率下一bit采104个点,完全没问题。但如果是115200bps,一bit只有8.68us,1MHz采样率下只有8个点,勉强可用;再快的协议比如1Mbps,就需要至少4MHz以上的采样率。

抓数据时,我习惯把逻辑分析仪的通道1接串行数据,通道2接前面看到的500Hz方波。两个信号同步采集,后面分析帧结构时就能看出时钟信号与数据之间的对齐关系。

第一次抓回来,看到的就是一串hex:7E 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是104us,4us的延迟只有4%,完全不影响。但如果用同样的光耦去做115200bps,一个bit只有8.68us,4us的延迟几乎占了一半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功能,这样波形稳定且不会因毛刺误触发。多花几分钟设置示波器,后面能省好几小时的无效分析时间。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 23:33:20

STM32F103 AB双分区OTA升级方案详解:从Bootloader到App完整实现

前阵子有个客户现场的设备需要修一个逻辑bug&#xff0c;设备装在十几公里外的农田排灌站里&#xff0c;跑一趟光高速费就够喝一壶&#xff0c;从那时起我意识到&#xff1a; OTA升级 是嵌入式产品绕不开的必修课。于是我用最经典的 STM32F103 做了一套完整的 AB 双分区 …

作者头像 李华
网站建设 2026/9/8 23:31:33

Type II补偿网络调参困局:穿越频率与相位裕量为何联动?

网络分析仪探头刚夹上去&#xff0c;拧了一圈R2&#xff0c;屏幕上那两个数字——穿越频率和相位裕量——像商量好似的&#xff0c;一起开始漂移。这是几乎所有调过环路补偿的人都会撞上的场面。前几篇我把功率级传递函数、Type I积分补偿的基本功都铺垫完了&#xff0c;这一篇…

作者头像 李华
网站建设 2026/9/8 23:31:29

EH220218-194C-2854三档定时芯片:小家电低成本定时方案深度拆解

做小家电定时的这些年&#xff0c;我一直在找一颗真正"够用、便宜、省事"的定时芯片。市面上通用MCU方案性能过剩&#xff0c;要写程序、要烧录、要维护&#xff0c;成本压不下来&#xff1b;纯模拟电路呢&#xff0c;RC定时精度差&#xff0c;档位一做多阻容就跟着堆…

作者头像 李华
网站建设 2026/9/8 23:29:54

Immich 数据库迁移实战指南:从改 schema 到回滚的完整链路

Immich 数据库迁移实战指南&#xff1a;从改 schema 到回滚的完整链路 【免费下载链接】immich High performance self-hosted photo and video management solution. 项目地址: https://gitcode.com/GitHub_Trending/im/immich 在 Immich 项目里&#xff0c;服务端的表…

作者头像 李华