Wireshark 自定义协议解析概述
Wireshark 支持通过 Lua 脚本扩展协议解析功能,可以解析自定义的私有协议。这对于调试、分析和理解网络通信非常有用。
为什么需要自定义协议解析
- 私有协议:公司或项目内部使用的自定义协议
- 协议分析:需要可视化协议字段和结构
- 调试工具:快速定位协议问题
- 学习研究:深入理解协议实现
Wireshark Lua 插件架构
graph TB
A[网络数据包] --> B[Wireshark捕获]
B --> C[协议识别]
C --> D{Lua插件?}
D -->|是| E[Lua解析器]
D -->|否| F[内置解析器]
E --> G[协议树显示]
F --> G
style E fill:#ccffcc
style G fill:#ccccff
Lua 脚本基础结构
基本框架
Wireshark Lua 插件有固定的代码格式:
1 | do |
组件说明
graph TB
A[Lua插件结构] --> B[协议定义
Proto]
A --> C[字段定义
ProtoField]
A --> D[解析函数
dissector]
A --> E[端口绑定
DissectorTable]
B --> B1[协议名称
显示名称]
C --> C1[字段类型
字段名称
显示格式]
D --> D1[解析逻辑
协议树构建]
E --> E1[TCP/UDP端口
协议识别]
style A fill:#ffcccc
style B fill:#ccffcc
style C fill:#ccccff
style D fill:#ffffcc
style E fill:#ffccff
详细组件说明
1. 协议定义(Proto)
语法
1 | local PROTO = Proto('protocol_name', 'Protocol Display Name') |
参数说明
- protocol_name:协议内部名称(唯一标识符)
- Protocol Display Name:在 Wireshark 中显示的名称
示例
1 | -- 定义 Redis 协议 |
2. 字段定义(ProtoField)
字段类型
Wireshark 支持多种字段类型:
| 类型 | 说明 | 示例 |
|---|---|---|
uint8 |
8位无符号整数 | ProtoField.uint8("field", "Field Name", base.DEC) |
uint16 |
16位无符号整数 | ProtoField.uint16("field", "Field Name", base.HEX) |
uint32 |
32位无符号整数 | ProtoField.uint32("field", "Field Name", base.DEC) |
uint64 |
64位无符号整数 | ProtoField.uint64("field", "Field Name", base.DEC) |
int8 |
8位有符号整数 | ProtoField.int8("field", "Field Name", base.DEC) |
int16 |
16位有符号整数 | ProtoField.int16("field", "Field Name", base.DEC) |
int32 |
32位有符号整数 | ProtoField.int32("field", "Field Name", "base.DEC") |
string |
字符串 | ProtoField.string("field", "Field Name") |
bytes |
字节数组 | ProtoField.bytes("field", "Field Name") |
ipv4 |
IPv4 地址 | ProtoField.ipv4("field", "Field Name") |
ipv6 |
IPv6 地址 | ProtoField.ipv6("field", "Field Name") |
显示格式(base)
| 格式 | 说明 | 示例值 |
|---|---|---|
base.DEC |
十进制 | 123 |
base.HEX |
十六进制 | 0x7B |
base.OCT |
八进制 | 0173 |
base.BIN |
二进制 | 01111011 |
base.NONE |
无格式 | 原始数据 |
字段定义示例
1 | -- 定义各种类型的字段 |
3. 解析函数(Dissector)
函数签名
1 | function proto_dissector(buf, pinfo, root) |
参数说明
buf(Tvb - Testy Virtual Buffer)
数据缓冲区,提供数据访问方法:
1 | -- 获取数据长度 |
pinfo(Pinfo - Packet Information)
数据包信息,包含协议、端口等信息:
1 | -- 设置协议名称 |
root(TreeItem)
协议树根节点,用于构建协议树:
1 | -- 创建协议树 |
解析函数示例
1 | local function proto_dissector(buf, pinfo, root) |
4. 端口绑定
TCP 端口绑定
1 | -- 获取 TCP 端口解析器表 |
UDP 端口绑定
1 | -- 获取 UDP 端口解析器表 |
动态端口识别
1 | -- 根据协议特征识别(不依赖端口) |
完整示例
示例 1: 简单的自定义协议
假设有一个简单的自定义协议格式:
1 | +--------+--------+--------+ |
完整实现
1 | do |
示例 2: Redis 协议解析器
Redis 使用 RESP(REdis Serialization Protocol)协议。
协议格式
1 | *<number of arguments>\r\n |
实现代码
1 | do |
示例 3: 二进制协议解析器
假设有一个二进制协议:
1 | +--------+--------+--------+--------+ |
实现代码
1 | do |
示例 4: 解析 Protobuf 数据
Protocol Buffers(Protobuf)是二进制、非自描述格式,在 Wireshark 中解析有两种常见方式:使用内置 Protobuf 解析器(需提供 .proto)和 Lua 手写 Wire Format 解析(无需 .proto,适合调试或逆向)。
Protobuf 在 Wireshark 中的两种方式
graph LR
A[Protobuf 数据] --> B{有 .proto?}
B -->|是| C[内置解析器]
B -->|否/需轻量解析| D[Lua Wire Format]
C --> E[Edit -> Preferences -> Protobuf]
C --> F[pinfo.private pb_msg_type]
D --> G[Varint / Length-delimited]
style C fill:#ccffcc
style D fill:#ccccff
方式一:使用 Wireshark 内置 Protobuf 解析器
Wireshark 自 2.6 起支持 Protobuf,需配置 Protobuf 搜索路径(存放 .proto 的目录),解析器根据消息类型从这些路径加载定义。
1. 配置 .proto 搜索路径
- 打开 Edit -> Preferences -> Protocols -> ProtoBuf -> Protobuf search paths
- 添加包含
.proto的目录(可多个),若有import依赖,需同时加入依赖所在目录 - 勾选 Load all files 可在启动时预加载
2. 在 Lua 中调用内置解析器
通过 Dissector.get("protobuf") 获取解析器,并用 pinfo.private["pb_msg_type"] 指定根消息类型(格式:"message,包名.消息类型"):
1 | local protobuf_dissector = Dissector.get("protobuf") |
3. 完整示例:UDP/TCP 上承载 Protobuf(带 4 字节长度头)
下面脚本提供「纯 Protobuf UDP」「纯 Protobuf TCP」以及「指定消息类型的 AddressBook」解析器,并支持 Decode As:
1 | do |
使用步骤:将脚本放到 Wireshark 个人配置目录的 plugins 下,配置好 Protobuf 搜索路径后,对 UDP/TCP 包使用 Decode As 选择对应协议即可。
4. 可选:Protobuf UDP 消息类型表
若协议是「UDP + 单包单条 Protobuf 消息」,可在 Edit -> Preferences -> Protocols -> ProtoBuf -> Protobuf UDP Message Types 中配置「UDP 端口 -> 消息类型」映射,无需 Lua 即可按端口自动用指定类型解析。
方式二:Lua 手写 Protobuf Wire Format 解析
在没有 .proto 或只需要轻量解析(看字段号、wire type、长度)时,可以在 Lua 里按 Protobuf 编码规则 解析。
Wire 格式简述
- 每条字段:Tag(Varint) + Payload。
- Tag =
(field_number << 3) | wire_type,其中wire_type常用:- 0:Varint(int32/64, bool, enum)
- 2:Length-delimited(string, bytes, 嵌套 message)
- Varint:每字节 7 位有效位,最高位为延续位;小端序拼接。
- Length-delimited:先一个 Varint 表示长度 L,再 L 字节数据。
Lua 示例:解析 Varint 与 Tag
Wireshark 内嵌 Lua 5.2,可使用标准库 bit32 做位运算:
1 | -- 从 buf 的 offset 起读取一个 Varint,返回 (value, new_offset) |
(若运行环境提供 bit 而非 bit32,可改用 bit.band、bit.lshift、bit.rshift。)
简单 Protobuf Wire 解析器示例
下面脚本不依赖 .proto,只在协议树中展示「字段号、Wire 类型、长度或 Varint 值」,便于调试或逆向:
1 | do |
使用方式:对已知为 Protobuf 的 TCP/UDP 流使用 Decode As 选择「Protobuf Wire (generic)」,即可在包内看到字段号、wire type 和原始值/长度,便于对照 .proto 或逆向。
小结
| 方式 | 适用场景 | 需要 |
|---|---|---|
| 内置 Protobuf 解析器 | 有 .proto、需要完整字段名与类型 | 配置搜索路径,可选 UDP 端口表或 Lua 指定 pb_msg_type |
| Lua Wire Format 解析 | 无 .proto、调试、逆向、只看字段号与 wire 类型 | 仅 Lua 脚本,可配合 Decode As |
实际项目中可先用手写 Wire 解析确认字段布局,再在拿到 .proto 后切换到内置解析器获得完整解析。
高级功能
1. 协议分层
支持协议嵌套,一个协议可以调用另一个协议:
1 | -- 获取底层协议解析器 |
2. 数据包重组
处理分片的数据包:
1 | -- 使用 TvbRange 处理分片 |
3. 协议字段过滤
支持基于字段的过滤:
1 | -- 定义字段时,字段名可用于过滤 |
4. 协议统计
添加协议统计信息:
1 | -- 统计信息 |
5. 自定义列显示
在 Wireshark 的列中显示自定义信息:
1 | function PROTO.dissector(buf, pinfo, root) |
调试技巧
1. 使用 print 调试
1 | function PROTO.dissector(buf, pinfo, root) |
2. 检查数据长度
1 | function PROTO.dissector(buf, pinfo, root) |
3. 验证协议特征
1 | function PROTO.dissector(buf, pinfo, root) |
4. 错误处理
1 | function PROTO.dissector(buf, pinfo, root) |
最佳实践
1. 协议识别
flowchart TD
A[收到数据包] --> B{检查数据长度}
B -->|长度不足| C[返回false]
B -->|长度足够| D{检查魔数/特征}
D -->|不匹配| C
D -->|匹配| E[解析协议]
E --> F{解析成功?}
F -->|是| G[返回true]
F -->|否| C
style C fill:#ffcccc
style G fill:#ccffcc
2. 字段命名规范
1 | -- 好的命名:使用协议前缀 |
3. 错误处理
1 | function PROTO.dissector(buf, pinfo, root) |
4. 性能优化
1 | -- 1. 尽早返回(快速失败) |
5. 代码组织
1 | -- 将相关功能组织在一起 |
常见问题
1. 脚本不生效
问题:Lua 脚本加载后不工作。
解决:
- 检查脚本语法错误(Wireshark -> Help -> About Wireshark -> Plugins)
- 确认端口绑定正确
- 检查协议识别逻辑
2. 解析器冲突
问题:多个解析器同时匹配。
解决:
- 使用更严格的协议识别
- 调整解析器优先级
- 使用启发式解析
3. 数据不完整
问题:数据包被分片,解析不完整。
解决:
- 使用
pinfo.desegment_len处理分片 - 检查数据长度
- 等待完整数据包
4. 性能问题
问题:解析器执行缓慢。
解决:
- 优化解析逻辑
- 减少字符串操作
- 使用缓存
实践案例
Redis 协议解析器
参考实现:wireshark-redis
主要功能
- 解析 RESP 协议:支持数组、字符串、整数等类型
- 命令识别:识别 Redis 命令(GET、SET、PUBLISH 等)
- 参数显示:显示命令参数
- 协议树:构建完整的协议树
使用效果
- 在 Wireshark 中可以直接看到 Redis 命令
- 支持过滤:
redis.command == "GET" - 协议树显示完整的命令和参数
其他协议解析器示例
- MQTT 协议:解析 MQTT 消息
- 自定义游戏协议:解析游戏网络包
- RPC 协议:解析 RPC 调用
- 数据库协议:解析数据库通信
总结
Wireshark Lua 插件开发要点:
核心步骤
- 定义协议:使用
Proto创建协议对象 - 定义字段:使用
ProtoField定义协议字段 - 实现解析函数:解析数据并构建协议树
- 绑定端口:将解析器绑定到特定端口
关键技巧
- 快速失败:尽早检查协议特征,不匹配立即返回
- 错误处理:使用
pcall捕获错误 - 字段命名:使用协议前缀避免冲突
- 性能优化:减少不必要的操作
适用场景
- 私有协议分析
- 协议调试
- 网络问题排查
- 协议学习研究
理解 Wireshark Lua 插件开发有助于:
- 快速分析自定义协议
- 提高网络调试效率
- 深入理解协议实现
- 构建专业的网络分析工具