MCP协议数据包结构:帧头、负载与校验和

好,咱们今天来聊聊MCP协议的数据包结构。说实话,搞协议逆向这么多年,我见过太多人一上来就盯着二进制流猛看,结果越看越晕。其实啊,只要把数据包拆成三块——帧头、负载、校验和,事情就清晰多了。

我个人习惯把MCP数据包比作一个快递包裹。帧头就是快递单,告诉你从哪来、到哪去、包裹多大;负载就是里面的货物;校验和呢,就是封箱胶带上的防拆标签,确保货物没被调包。

MCP帧头格式

帧头是数据包的“身份证”。我当年逆向第一个MCP设备时,就是靠帧头里的魔数(Magic Number)确认了协议版本。来看一下标准帧头的结构:

偏移量 字段名 长度(字节) 说明
0 Magic 2 固定值 0x4D43("MC")
2 Version 1 协议版本号,当前为0x01
3 Flags 1 标志位:0x01=请求,0x02=响应,0x04=错误
4 Payload Length 2 负载长度(小端序)
6 Sequence 2 序列号,用于去重和重传
8 Checksum 2 帧头校验和(CRC-16)

嗯,这里要注意:帧头总共10个字节。我见过有人把Checksum算到负载里,结果怎么都对不上。记住,帧头校验和只覆盖前8个字节。

关键点:Magic字段是协议识别的第一道关卡。我在项目中遇到过某厂商魔改了Magic值,导致标准解析器全部失效。所以做逆向时,第一步就是抓包看Magic。

MCP负载格式

负载部分就灵活多了。它由TLV(Type-Length-Value)结构组成。说白了,就是每个数据块都带标签、长度和内容。为什么用TLV?因为扩展性好,你想想看,加一个新功能只需要定义一个新的Type值就行。

负载的通用格式如下:

+--------+--------+--------+--------+--------+--------+
|  Type  | Length |        Value (可变长度)          |
| (1字节)| (2字节)|                                   |
+--------+--------+--------+--------+--------+--------+

常见的Type值:

  • 0x01:设备信息查询
  • 0x02:传感器数据上报
  • 0x03:控制指令下发
  • 0x04:心跳包
  • 0xFF:自定义扩展

我曾经逆向过一个智能电表,它的负载里嵌套了三层TLV。一开始我以为是协议设计有问题,后来才发现是为了兼容不同厂商的扩展功能。所以啊,解析负载时一定要递归处理。

实战技巧:写解析器时,先按TLV拆开,再根据Type分发处理。别想着一次性解析完,容易漏字段。

MCP校验和计算

校验和这块,我踩过不少坑。MCP用的是CRC-16/IBM算法,多项式是0x8005。但有些设备实现时用了不同的初始值,导致校验总是不通过。

标准计算步骤:

  1. 初始化CRC寄存器为0x0000
  2. 对每个字节进行异或和移位操作
  3. 最终结果取反(有些实现不取反)

来看一个C语言实现:

uint16_t mcp_crc16(const uint8_t *data, size_t len) {
    uint16_t crc = 0x0000;
    for (size_t i = 0; i < len; i++) {
        crc ^= data[i];
        for (int j = 0; j < 8; j++) {
            if (crc & 0x0001) {
                crc = (crc >> 1) ^ 0x8005;
            } else {
                crc >>= 1;
            }
        }
    }
    return crc;
}

避坑指南:我曾经因为校验和初始值没对上,排查了整整两天。后来发现某厂商的设备用了0xFFFF作为初始值。所以做模拟测试时,一定要先验证校验和算法是否和官方一致。

完整数据包示例

咱们来看一个实际抓包的数据:

帧头: 4D 43 01 02 00 14 00 01 12 34
负载: 01 00 05 48 65 6C 6C 6F
校验和: A5 B6

解析一下:

  • Magic: 0x4D43("MC")
  • Version: 0x01
  • Flags: 0x02(响应包)
  • Payload Length: 0x0014(20字节)
  • Sequence: 0x0001
  • 帧头校验和: 0x1234
  • 负载: Type=0x01, Length=0x0005, Value="Hello"
  • 负载校验和: 0xA5B6

嗯,这里要注意:负载长度是20字节,但实际负载只有7字节(1+2+5)。剩下的13字节是填充数据,用于对齐。我刚开始做逆向时,被这种填充搞晕过好几次。

核心总结:MCP数据包结构其实不复杂,但细节决定成败。帧头校验和只覆盖前8字节,负载用TLV组织,校验和算法要确认初始值。把这些搞清楚了,逆向和模拟测试就成功了一半。

MCP数据包结构图 帧头(10字节) Magic(2) | Version(1) | Flags(1) | Payload Length(2) | Sequence(2) | Checksum(2) 负载(可变长度) TLV结构:Type(1) | Length(2) | Value(可变) 可包含多个TLV块,支持嵌套 校验和(2字节) CRC-16/IBM,覆盖整个数据包(帧头+负载) 注:帧头校验和只覆盖前8字节,负载校验和覆盖整个数据包

个人经验:做模拟测试时,我习惯先写一个校验和验证工具。把抓到的包丢进去,能快速确认协议实现是否正确。省得后面调试时到处找bug。

专注资料整理