尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

黑盒协议逆向实战:从物理层盲猜到单片机插桩解析

黑盒协议逆向实战:从物理层盲猜到单片机插桩解析 接手一台没有原理图、没有固件源码、连通信线定义都要靠万用表猜的老设备还要从抓到的波形里把它和上位机之间的协议“翻译”成人能看懂的报文——这活儿听着像杂技其实在嵌入式开发里比想象中常见得多。不管是给老产线设备做上位机替代还是分析第三方模组的私有协议本质都是同一件事黑盒逆向。这篇文章要聊的就是我从物理层盲猜开始一路到光耦反相踩坑最后靠单片机插桩把协议彻底吃透的完整过程。标题里的三个词——物理层、光耦、单片机插桩恰好对应了协议逆向的三个递进阶段先解决“波形到底是不是UART”的问题再解决“电平逻辑为什么看着总不对”的问题最后解决“数据帧结构和校验到底怎么定”的问题。整个过程不依赖什么昂贵设备一支逻辑分析仪、一颗几块钱的STC单片机就能干完。适合那些手里有通信协议要逆向、但不想一上来就上USRP级别设备的嵌入式工程师和硬件爱好者参考。1. 动手前的“摸底”先搞清楚黑盒里到底是什么类型的通信很多人在协议逆向上一上来就抓波形、抄帧结果抓了半天发现连通信制式都判断错了——把CAN总线的差分信号当UART解析或者把I2C的时钟线当数据线去数波特率这种错误我见过太多次。所以拿到黑盒设备的第一步不是上逻辑分析仪而是老老实实用万用表和肉眼做三件事。第一件事是查供电和地。通信接口不管是什么制式总有参考地先拿万用表蜂鸣档测接口各引脚与设备金属外壳或电源负极的导通关系凡是通的基本就是GND。剩下引脚再测对地电压有持续3.3V或5V输出的一般是电源引脚电压在0V到3.3V之间跳变、并且跳变频率肉眼可见的多半是信号线。这一步能帮你把接口的引脚定义从“全未知”缩小到“一个电源、一个地、两根信号”的水平。第二件事是判断信号是单端还是差分。单端信号测量时以GND为参考差分信号则需要比较两根线之间的电压差。这里有个土办法把万用表打到直流电压档红黑表笔分别搭在两根信号线上如果测出稳定的2.5V或1.65V总线空闲偏置电压大概率是差分对——RS485的A/B空闲时就是2.5V左右CAN的CANH/CANL空闲时在2.5V附近。如果两根线对GND分别测都是0V或都是3.3V基本就是单端逻辑电平。第三件事才是上逻辑分析仪看波形但这里有个非常关键的习惯先记录空闲电平。协议在没有数据帧传输时总线会停留在某个固定电平上这个电平决定了后续所有解析的方向。比如UART空闲是高电平数据位是低电平有效起始位而某些光耦隔离输出的电路空闲电平可能是低电平——这直接埋下了后面要讲的“反相”大坑。我建议这一步把采样率调到至少4倍于预估波特率先抓一段屏住呼吸都能等来的“含着空闲期的波形”而不是一上来就触发。这三件事做完你手里已经有一份初步结论通信制式大概率是什么、有几根信号线、空闲电平是什么、大概的波特率量级。这比直接拿逻辑分析仪上去一顿乱抓要稳妥得多因为逻辑分析仪的触发条件设错了抓一百次也触发不了。2. 物理层盲猜用波形特征反推协议类型而不是瞎试波特率物理层“盲猜”这个词听起来很不严谨但真正做逆向的人知道这不是瞎猜而是基于波形形状和电气特征做排除法。我一般按“看形状定大类看边沿定时钟看帧间隔定协议”的顺序来。2.1 从波形形状先分个大类UART、SPI、I2C、还是CAN拿逻辑分析仪抓一段波形后先别急着看数据先看波形的“长相”UART/RS232/RS485的特征是一整根线上空闲期很长然后突然出现一段从起始位开始的串行脉冲数据位等宽帧与帧之间有明显间隔。整个波形看起来像“一条平静的线上偶尔扔几块石子”。SPI的特征是至少三根线同步活动其中一根时钟线SCLK始终在规律地翻转不管有没有数据时钟都在跑或者只在传输时跑但边沿非常规则。数据线的变化总对齐时钟边沿。I2C的特征是两根线SCL和SDA都是开漏结构空闲都被上拉到高电平传输时SCL时钟脉冲规则但SDA数据线在时钟低电平期间变化且没有片选线靠地址来区分设备。CAN的特征是两根差分线上空闲时两条线都在2.5V附近波形看起来是上下翻的差分对而且帧起始是“显性位”把差分电压拉偏。这些特征你多抓几个波形就有了手感比背协议规范有用得多。我自己常用的一个办法是把抓到的波形截图放大如果数据位宽度非常均匀、且每个字节之间有固定的停止位间隔那大概率是UART家族如果时钟线窄脉冲非常密集且均匀那大概率是SPI或I2C。2.2 波特率盲扫从52.1k调到115200不如直接算最小脉冲宽度新手最爱犯的错是把逻辑分析仪上的波特率列表挨个试一遍试到波形对上了就算猜中。这办法效率太低而且对没有起始位概念的协议比如SPI完全无效。正确做法是量最小脉冲宽度然后按“位时间”反推波特率。具体操作在逻辑分析仪里打开测量工具找到波形里最短的那个低电平或高电平脉冲记录它的时间宽度。比如看到最小的脉冲宽度是8.68μs那波特率就是1/8.68μs ≈ 115200bps。如果最小脉宽是104μs左右那波特率就是9600bps。这个办法的准确率非常高因为UART的起始位就是1个位宽数据位里的“0/1翻转”总会暴露最小位宽。但要注意一个问题因为协议里数据字长通常是8位最小脉宽不一定等于位宽也可能是几个位连在一起的宽度。所以更稳妥的办法是先量出所有不同宽度的脉冲取它们的最大公约数。比如量到8.68μs、17.36μs、26.04μs最大公约数就是8.68μs对应115200bps。这一步可以用逻辑分析仪自带的数据光标配合计算器我习惯直接拉一段50ms的波形把所有边沿间隔导出来用Excel算公约数又快又准。2.3 帧间隔是重要的协议指纹确定波特率之后还要看帧与帧之间的间隔。UART协议比如Modbus RTU要求帧内字节之间间隔小于3.5个字符时间帧间间隔要大于3.5个字符时间而很多私有协议根本不管这个帧间隔就是固定延时。把这个间隔量出来能帮你判断协议是“字节流式”还是“帧式”也决定了你后面用单片机插桩解析时应该在什么时机做帧截断。物理层盲猜到这里基本收官你已经知道是什么协议族、大概波特率、帧特征。但这里我必须要说一句物理层猜归猜最终还是要用“已知内容”来验证——比如很多设备的串口上电会发一串开机日志把这串日志按猜到的波特率解出来看到ASCII字符比如“System Start”就说明波特率没猜错。如果解出来全是乱码赶紧回头查空闲电平和数据格式8N1还是7E1。3. 光耦反相为什么明明抓到了波形解析出来却全是反的在分析某些带隔离的工业设备时我遇到过一种非常折磨人的情况逻辑分析仪抓到的波形非常漂亮波特率也对数据格式也设对了但解析出来的数据跟预期完全对不上——不是乱码而是反码。具体表现是抓到的“1”在逻辑分析仪里看着是高电平但协议解析器读出来的却是“0”。3.1 光耦隔离输出的电平翻转原理问题出在光耦上。工业设备为了抗干扰和隔离经常在通信接口上加光耦隔离比如PC817、TLP521这些。它的工作原理是输入侧是发光二极管输出侧是光敏三极管中间用光传递信号实现电气隔离。但要注意光耦的输出级常见两种接法接法输入高电平时输出引脚电平逻辑关系集电极接上拉发射极接地同相低电平反相集电极接VCC发射极作为输出射极跟随高电平同相工业设备上最常见的是第一种输入侧高电平点亮发光二极管光敏三极管导通把输出引脚拉到低电平。也就是说输入的逻辑“1”经过光耦输出变成了逻辑“0”。这在电路设计时是常态因为很多MCU的UART接收引脚本来就是低电平有效起始位是低光耦反一下正好不用额外加反相器。但问题在于我们做逆向时逻辑分析仪探头夹的是光耦的输出侧不是MCU的引脚。这时候如果还按照默认的“高电平为1低电平为0”去解析就会得到完全反码的数据——这和你录了一段磁带的倒带播放差不多每个字节的位顺序反了整个协议就没法看了。3.2 怎么确认是不是光耦反相而不是协议本身反了最直接的办法看空闲电平。前面我说过记录空闲电平是摸底第一步。如果总线空闲时是高电平而光耦输出侧空闲是低电平那基本可以断定经过了反相处理。具体操作是用万用表量光耦输出引脚在无通信时的静态电平是0V还是VCC。如果是0V就说明空闲低也就是反相输出。另外一个验证技巧是用已知内容做反转测试。比如你确信某一段抓到的波形对应的是ASCII字符“A”十六进制0x41二进制0100_0001而你的解析结果是1011_11100xBE那就说明每个位都反了。把逻辑分析仪软件里的“总线极性”设置为“低电平为1”或者直接在解析前对原始数据做一次按位取反再解析基本就正常了。3.3 光耦反相叠加的问题波特率正确但数据错位的排查顺序这里要补充一个容易混淆的场景有时候不是单纯的反相而是反相叠加了位序问题。UART数据位是低位先发送如果你在解析时又设置了MSB first那即使电平极性正确解析出来的字节也是倒序的。遇到“波形正常但数据不对”的情况我的排查顺序是先查极性/空闲电平反相没反相再查数据位顺序LSB first还是MSB first最后查校验位无校验、奇校验、偶校验这个顺序很重要。有一次我为了一个光耦隔离的RS485设备折腾了一晚上发现问题是TTL转RS485模块上带有自动收发切换电路导致数据的前几个位被吃掉了一半——那是另一个坑跟光耦反相无关但症状极其相似。所以排查时一定不要认死理发现问题对不上就退一步看看整个信号链路上还有没有其他处理环节。4. 数据帧结构分析从一堆字节里抠出“地址、功能码、数据、校验”物理层搞定了、电平极性没问题了接下来就是最耗耐心的一步把抓到的字节流拆解成有意义的帧。这一步对工具依赖不强但很考验对常见协议模式的敏感度。4.1 从Modbus这类已知协议入手做参照不少工业设备的私有协议都是基于Modbus RTU改的地址功能码数据CRC16。所以抓到一批报文后我习惯先按Modbus的思路套一套把所有报文按“帧间隔超过3.5字符时间”来切分然后看每帧的第一个字节是否在一个小范围内比如0x01、0x02、0x03或0xF7、0xF8如果是那大概率是地址字段。第二个字节如果集中在几个固定值0x03、0x04、0x06、0x10这些那基本就是功能码了。举个例子抓到的报文长这样01 03 00 10 00 02 C5 CD 01 03 04 00 00 00 00 FA 33 02 03 00 10 00 02 C5 CA第一字节01、02是设备地址第二字节03是读保持寄存器第三、四字节是寄存器起始地址第五、六字节是寄存器数量最后两字节是CRC16低位在前。这类结构在Modbus设备里一抓一大把就算设备本身不是标准Modbus厂商也喜欢沿用这个套路做私有协议因为开发省事。4.2 没有参照时靠统计和“固定值/变化值”来暴力拆帧如果设备压根不按Modbus来那就得换一种思路把抓到的几百帧数据导进Excel里按字节位置做对齐然后观察每个位置的数值规律。这里有一个非常好用的分类法固定值字段所有帧里某个位置的值都不变那它有可能是协议版本、帧起始标记或保留字节。小范围变化字段值集中在少数几个枚举值0x00、0x01、0x02可能是命令字或状态字。大范围跳变字段每个帧都不一样且没有单调性大概率是数据字段、序列号或校验字段。有规律递变字段值跟着每个帧单调递增或循环是包序号。这个方法看似笨但真的好用。我曾用这个办法逆向过一个贴片机厂商的私有协议从三百多个帧里抠出了控制命令结构第0字节是帧头固定0xAA第1字节是命令字第2到5字节是目标坐标第6、7字节是速度最后1字节是累加和校验。整个过程没看一行厂商代码纯粹靠Excel肉眼。4.3 校验算法识别CRC16还是累加和校验字段是逆向里最考验经验的一环。常见的有三种累加和、异或校验、CRC16/CRC32。识别方法很简单把帧里校验之外的所有字节做无符号累加看结果低8位是否等于校验字节如果是那就是累加和。把所有字节做异或看结果是否等于校验字节如果是就是异或校验。如果前两种都不对大概率是CRC16这时候需要借助工具比如RevEng软件、CRC计算器枚举常见的CRC参数多项式、初值、输入输出反转通常能试出来。我遇到过一种很坑的情况厂商用的是CRC16_MODBUS但把高低字节顺序交换了。如果你按标准MODBUS参数算大部分帧能对上偶尔几帧对不上那就要怀疑是“低字节在前还是高字节在前”的问题。用RevEng的暴力枚举功能把多项式、初值、反射、结果异或、字节序五个参数全组合跑一遍几秒钟就能出结果。5. 单片机插桩用一颗几块钱的MCU把协议“按住问话”波形看明白了、帧格式推测出来了但还没法确认——因为黑盒设备不会主动告诉你“你的理解对不对”。这时候就需要主动出击让设备“开口说话”验证你的判断。最省钱也最灵活的办法就是单片机插桩。5.1 插桩的本质替代上位机在半路“窃听”或者“主动审讯”通信插桩分两种场景。第一种是监听模式单片机并联在设备的通信线上只收不发把抓到的数据带时间戳地存下来用于分析。第二种是模拟模式单片机断开上位机直接按你推测的协议内容模拟上位机向设备发请求观察设备有没有正确响应。两种场景我建议都做先监听后模拟这样才算完整验证。之所以用单片机而不是直接用电脑串口调试助手原因有三一是单片机的GPIO中断能做到微秒级时间戳这对分析帧间隔和字节间隔非常关键二是可以直接对接3.3V/5V TTL电平省去USB转串口模块的中间环节三是可以在监听的同时做一些触发逻辑比如检测到特定帧头才存储后续数据节省存储空间。5.2 STC8单片机监听串口的参考实现以STC8系列为例随便一块几块钱的开发板就行监听UART总线只需要用到两个外部中断引脚加上定时器。核心思路是把两个引脚分别配置为下降沿中断和上升沿中断每次中断记录当前定时器值然后通过中断间隔还原出每个位的电平宽度。但这方案有个麻烦如果波特率是115200每个位只有8.68μs中断响应本身就要几微秒处理不过来。所以更稳妥的办法是直接用单片机自带的UART外设做监听用另一路串口把数据打印到电脑上。代码框架大概这样// STC8G1K08 双串口监听示例思路 void UART1_Isr() interrupt 4 using 1 { if (RI) { RI 0; rx1_buf[rx1_cnt] SBUF; // 目标设备发来的数据 if (rx1_cnt BUF_SIZE) rx1_cnt 0; } } void UART2_Isr() interrupt 8 using 2 { if (RI) { RI 0; SBUF SBUF; // 原样转发到调试串口 } }实际使用时要加上时间戳和帧间隔判断。我建议用定时器0做一个1ms时基每次UART1收到字节时记下当前时间戳这样后续分析时就能看出哪些字节属于同一帧。监听模式下单片机与设备的接线很简单设备的TXD接单片机UART1的RXDGND共地。如果担心影响设备通信可以用高阻输入的运放或直接挂在线上——多数TTL电平设备驱动能力足够不会因为多一个高阻输入就拉垮信号。5.3 主动模拟主机把推测的协议变成实际请求监听只能被动收数据想确认帧结构对不对还得模拟主机主动发请求。拿前面Modbus例子里的报文来说你推测“01 03 00 10 00 02 C5 CD”是读设备1的保持寄存器地址0x10、数量2那就让单片机把这帧发出去然后看设备回什么。如果回了一帧符合你预期的数据比如后面的地址变成了01、功能码还是03、字节计数为04说明你拆的帧结构猜对了。如果设备毫无反应或者回错误码那就得回头调整推测。这里有个很容易被忽略的点很多设备的通信波特率是可以通过拨码或参数配置的主动模拟之前一定要确认设备当前的实际波特率。别问我怎么知道的我曾经对着一个波特率已经被人改成19200的旧设备用9600发请求发了一下午设备毫无响应差点怀疑人生。5.4 插桩电路的注意事项光耦反相要在这里一并处理如果被测设备的通信线上有光耦隔离那插桩单片机的接收引脚要注意电平极性。由于光耦输出是反相的建议在单片机UART引脚前加一级三极管反相器或者干脆把单片机收到数据后的每个字节做一次按位取反处理。前一种方案硬件上多一个三极管后一种方案软件上多一行代码。我一般选择软件取反因为这不会占用额外硬件而且只在调试阶段需要等确认协议后正式设备可以直接用带光耦的串口模块。另外插桩期间不要忽略供电问题。很多工业设备的通信接口虽然只有三两根线但本身可能不对外供电插桩单片机需要独立供电且必须和设备共地。如果不共地单片机看到的全是浮空电平波形一塌糊涂。这点听起来像废话但在现场最容易翻车。6. 现场排查记录几个让我印象深刻的“疑难杂症”和对应的解决思路最后这部分写给看完上面流程、正准备上手实操的人。逆向协议这件事顺利的话一两天就能出结果不顺利的话bug藏在你想不到的角落。分享几个我亲自踩过、也帮别人排查过的坑希望能帮你少走几步弯路。第一个坑逻辑分析仪的采样率不够导致的“假波形”。有一次抓一个1MHz的SPI总线用的逻辑分析仪采样率只有2MHz结果抓出来的数据线波形有大量毛刺看着像协议本身有干扰。后来换了16MHz采样率的设备重抓波形立刻干净了。做物理层盲猜时采样率至少是信号频率的8倍以上否则你看到的“毛刺”可能只是采样混叠。第二个坑串口工具自动添加的换行符污染了协议数据。USB转TTL模块加上串口调试助手默认会在收到0x0A换行后做显示换行如果你把串口助手的接收内容直接复制到Hex编辑器里分析这些换行符会混进数据里导致帧切分错乱。正确做法是把串口调试助手的接收模式设为“Hex模式”且关闭自动换行或者直接在单片机端把数据以hex字符串发送避免终端干扰。第三个坑万用表和示波器探头引入的阻抗影响。在测量一些高阻输出的光耦电路时示波器探头的输入电容10pF左右就可能让边沿变缓看起来像是波特率不对或者线缆过长。遇到这种情况尽量用10倍衰减档的探头并且把探头接地弹簧尽量短接减少地线环。信号还原度比阻抗匹配都重要。第四个坑线序接反导致的“设备完全没响应”。RS485的A/B反接CANH和CANL反接这是老生常谈但关键是有时候你的设备用的不是标准线序——比如某国产设备把A线做成了绿色、B线做成了绿白你按国际标准的棕/棕白去接反了却不知道。接好后量一下A/B对地电压确认正负再开始测试。第五个坑也是我觉得最值得强调的——不要一开始就怀疑协议复杂先怀疑自己的工具链路。大多数“反向对了也不出数据”的案例最后查出来是线没接牢、电平不匹配、收发方向接反这类低级问题。把工具链路验透再碰协议本身的内容会省掉大量浪费时间。写在最后一点关于“逆向思维”的体会设备通信协议逆向说到底不是拼设备高级而是拼观察力。我见过有人用几千块的开发板加几百块的协议分析仪折腾两天没结果也见过老师傅靠一把万用表和一颗LED灯珠就判断出串口波特率。工具只是放大了你的判断力前提是你得先学会像电路一样思考——从物理层电平开始逐层往上剥而不是一上来就指望抓到完整报文。光耦反相这个坑之所以值得单独拿出来说是因为它太容易让人陷入自我怀疑数据解出来不对你以为是波特率错了改了波特率还是不对又去怀疑帧格式最后绕一大圈才发现只是静寂电平翻了个面。这也是我坚持在每次抓波形时先把空闲电平记录下来、凡是经过光耦的都默认它可能反相的原因。习惯养成了后面能少走很多弯路。如果你手里正好有一台旧设备要做协议对接建议按这个顺序来先摸底再抓波形先定电平再定波特率先拆帧再验校验最后再用单片机插桩主动验证。过程中有任何一步想不通回来翻翻这篇文章尤其是关于光耦反相和帧结构的那两节。等技术跑通的那一刻你会发现原来设备背后的“黑匣子”也没有那么神秘。
返回列表