rev-protocol
协议与接口逆向参考手册
SKILL.mdskills/premium/library/rev-protocol/SKILL.md
GitHub协议与接口逆向参考手册
Scope
目标与操作范围以用户当前请求为准;核对方法前置条件、工具和附件,按实际执行层级记录结果。
一、Wireshark 过滤器速查
显示过滤器
# 基础过滤
ip.addr == 192.168.1.1 # IP 地址
tcp.port == 8080 # TCP 端口
udp.port == 5353 # UDP 端口
http.request.method == "POST" # HTTP POST
dns.qry.name contains "example" # DNS 查询
# 协议过滤
websocket # WebSocket 流量
tls.handshake # TLS 握手
mqtt # MQTT 协议
modbus # Modbus 工业协议
protobuf # Protobuf
# 组合过滤
http && ip.dst == 10.0.0.1 && tcp.port == 443
tcp.flags.syn == 1 && tcp.flags.ack == 0 # SYN 包
frame.len > 1000 # 大于 1000 字节的包
捕获过滤器(BPF)
host 192.168.1.1 # 指定主机
port 80 # 指定端口
src net 10.0.0.0/8 # 源网段
tcp port 443 and host 10.0.0.1 # 组合
not arp and not icmp # 排除协议
实用操作
# 跟踪 TCP/HTTP 流:右键 → Follow → TCP Stream
# 导出 HTTP 对象:File → Export Objects → HTTP
# 会话统计:Statistics → Conversations
# 协议分层:Statistics → Protocol Hierarchy
二、mitmproxy 脚本编程
# addon_example.py
from mitmproxy import http, ctx
class LogAddon:
def request(self, flow: http.HTTPFlow):
ctx.log.info(f"[REQ] {flow.request.method} {flow.request.url}")
# 修改请求头
flow.request.headers["X-Custom"] = "injected"
def response(self, flow: http.HTTPFlow):
ctx.log.info(f"[RSP] {flow.response.status_code} {flow.request.url}")
# 修改响应体
if "application/json" in flow.response.headers.get("content-type", ""):
ctx.log.info(f" Body: {flow.response.text[:200]}")
def websocket_message(self, flow: http.HTTPFlow):
msg = flow.websocket.messages[-1]
direction = "->" if msg.from_client else "<-"
ctx.log.info(f"[WS] {direction} {msg.content[:100]}")
addons = [LogAddon()]
# 运行: mitmproxy -s addon_example.py -p 8080
三、TCP/UDP 协议分析
tcpdump 常用命令
tcpdump -i eth0 -nn port 8080 # 抓指定端口
tcpdump -i eth0 -nn -w capture.pcap tcp port 443 # 保存到文件
tcpdump -r capture.pcap -nn # 读取文件
tcpdump -i eth0 -nn 'tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x47455420' # 匹配 GET 请求
协议结构识别模式
常见协议层次:
┌──────────┬──────────┬──────────────────────┐
│ Header │ Length │ Payload │
│ (固定) │ (变长) │ (Length 指定) │
└──────────┴──────────┴──────────────────────┘
长度字段模式:
1. 固定长度: 每个包都是 N 字节
2. TLV (Type-Length-Value): [1B type][2B length][NB value]
3. 分隔符: 以 \r\n 或 \0 分隔
4. 长度前缀: [4B 长度][数据]
Python struct 解析
import struct
# 大端序解析
header = data[:4]
msg_type, msg_len = struct.unpack('>HH', header)
payload = data[4:4+msg_len]
# 小端序
val = struct.unpack('<I', data[:4])[0]
# 常见格式: >I (大端32位), <H (小端16位), >Q (大端64位)
# 网络字节序通常为大端(Big Endian)
字节序检测技巧
# 方法:发送已知值,观察字节排列
# 值 0x12345678
# 大端: 12 34 56 78
# 小端: 78 56 34 12
# 通常:网络协议用大端,x86 本地程序用小端
四、Scapy 协议重放
from scapy.all import *
# 基础包构造
pkt = IP(dst="10.0.0.1") / TCP(dport=8080, flags="S") # SYN
resp = sr1(pkt) # 发送并等待一个响应
# 自定义协议层
class MyProto(Packet):
name = "MyProto"
fields_desc = [
ShortField("msg_type", 0),
ShortField("msg_len", 0),
StrLenField("payload", "", length_from=lambda p: p.msg_len),
]
pkt = IP(dst="10.0.0.1") / TCP(dport=9000) / MyProto(msg_type=1, payload=b"hello")
send(pkt)
# 从 pcap 读取并重放
pkts = rdpackets("capture.pcap")
for pkt in pkts:
if pkt.haslayer(TCP) and pkt[TCP].dport == 8080:
send(pkt[IP]) # 重放
# Fuzzing
pkt = IP(dst="10.0.0.1") / TCP(dport=8080) / MyProto(payload=Fuzz())
send(pkt, count=100)
五、Protobuf 逆向
识别
特征: 字节流以 0x0a (field 1, wire type 2) 开头
varint 编码: 最高位为 1 表示后续字节属于同一值
field tag = (field_number << 3) | wire_type
wire_type: 0=varint, 1=64bit, 2=length-delimited, 5=32bit
解码
# protoc 原始解码(无需 .proto 文件)
echo "0a0368656c6c6f" | xxd -r -p | protoc --decode_raw
# blackboxprotobuf(Python,无需 .proto)
pip install blackboxprotobuf
python3 -c "
import blackboxprotobuf
data = bytes.fromhex('0a0368656c6c6f')
message, typedef = blackboxprotobuf.decode_message(data)
print(message)
print(typedef)
"
# protobuf-inspector
pip install protobuf-inspector
cat data.bin | protobuf_inspector
Proto 文件还原
# 用 blackboxprotobuf 生成 typedef 后手动还原为 .proto
import blackboxprotobuf
data = open('sample.bin', 'rb').read()
message, typedef = blackboxprotobuf.decode_message(data)
# typedef 是字段定义字典,转换为 .proto 格式
gRPC 反射
# 使用 grpcurl 调用反射 API
grpcurl -plaintext localhost:50051 list # 列出服务
grpcurl -plaintext localhost:50051 describe pkg.ServiceName # 描述服务
grpcurl -plaintext -d '{"key":"value"}' localhost:50051 pkg.ServiceName/Method
六、序列化格式识别
| 格式 | 魔数/特征 | 识别方法 |
|---|---|---|
| Protobuf | 0x0a 开头,varint 编码 |
protoc --decode_raw |
| MessagePack | 0xc0-0xdf 前缀 |
msgpack -b data.bin |
| Thrift | 0x80 或 0x00 开头 |
TBinaryProtocol: 4B version + method |
| Avro | Obj\x01 魔数 |
avro-tools |
| CBOR | major type 前 3 bit | cbor2 Python 库 |
| JSON | { 或 [ 开头 |
直接可读 |
MessagePack 解码
import msgpack
with open('data.bin', 'rb') as f:
obj = msgpack.unpack(f)
print(obj)
自定义二进制格式
import struct
def parse_custom(data):
offset = 0
results = []
while offset < len(data):
# 假设: [2B type][2B length][NB payload]
if len(data) - offset < 4:
break
msg_type, msg_len = struct.unpack('>HH', data[offset:offset+4])
offset += 4
payload = data[offset:offset+msg_len]
offset += msg_len
results.append({'type': msg_type, 'payload': payload})
return results
七、RPC 协议逆向
gRPC
# gRPC over HTTP/2
# 识别: HTTP/2 帧,Content-Type: application/grpc
# 帧格式: [1B compressed][4B length][NB message]
# 反射
grpcurl -plaintext localhost:50051 list
grpcurl -plaintext localhost:50051 describe package.Service
# 无需反射时:抓包分析 protobuf 编码的请求/响应
JSON-RPC 2.0
// 请求
{"jsonrpc":"2.0","method":"getUser","params":{"id":1},"id":1}
// 响应
{"jsonrpc":"2.0","result":{"name":"test"},"id":1}
// 特征: 固定 jsonrpc 字段,method/params/id 结构
IPC 机制
# Named Pipes (Windows)
# 特征: \\.\pipe\pipename
# 工具: Pipe Monitor, Process Monitor
# Unix Domain Sockets (Linux)
# 特征: /tmp/.X11-unix/X0 等 socket 文件
strace -e trace=connect -f target_program
# D-Bus (Linux)
dbus-monitor --session # 监控会话总线
dbus-monitor --system # 监控系统总线
busctl list # 列出总线连接
八、蓝牙与 IoT
BLE 分析
# nRF Connect (手机 App) - 扫描 BLE 设备
# Wireshark - 导入 BLE 抓包
# 关键: GATT Service UUID → Characteristic UUID → Read/Write/Notify
# BLE 特征码
# Service: 0x1800 (Generic Access), 0x1801 (Generic Attribute)
# 常见: 0x180A (Device Information), 0x180F (Battery)
MQTT
# 订阅所有主题
mosquitto_sub -h broker.example.com -t '#' -v
# 发布消息
mosquitto_h -h broker.example.com -t 'test/topic' -m 'hello'
# 抓包分析: Wireshark MQTT dissector
# 关键: CONNECT/SUBSCRIBE/PUBLISH 包, topic, payload
Modbus
# 功能码: 01=读线圈, 02=读离散输入, 03=读保持寄存器, 04=读输入寄存器
# 05=写单个线圈, 06=写单个寄存器
# Wireshark 过滤
modbus && ip.addr == 192.168.1.100
# Python Modbus 客户端
from pymodbus.client import ModbusTcpClient
client = ModbusTcpClient('192.168.1.100', port=502)
result = client.read_holding_registers(0, 10, slave=1)
print(result.registers)
九、协议文档化
字段表模板
## 消息格式: LoginRequest (type=0x01)
| 偏移 | 长度 | 类型 | 字段名 | 说明 |
|------|------|------|--------|------|
| 0x00 | 2 | uint16 BE | msg_type | 消息类型 = 0x01 |
| 0x02 | 2 | uint16 BE | msg_len | 后续数据长度 |
| 0x04 | 4 | uint32 BE | user_id | 用户 ID |
| 0x08 | N | string | username | 用户名(UTF-8) |
| 0x08+N | 16 | bytes | token | 认证令牌 |
Wireshark Lua Dissector 模板
-- myproto.lua
local myproto = Proto("myproto", "My Custom Protocol")
myproto.fields.msg_type = ProtoField.uint16("myproto.type", "Message Type", base.HEX)
myproto.fields.msg_len = ProtoField.uint16("myproto.len", "Length", base.DEC)
myproto.fields.payload = ProtoField.bytes("myproto.payload", "Payload")
function myproto.dissector(buf, pinfo, tree)
pinfo.cols.protocol = "MYPROTO"
local subtree = tree:add(myproto, buf())
subtree:add(myproto.fields.msg_type, buf(0,2))
subtree:add(myproto.fields.msg_len, buf(2,2))
local len = buf(2,2):uint()
subtree:add(myproto.fields.payload, buf(4, len))
end
-- 注册到 TCP 端口
local tcp_table = DissectorTable.get("tcp.port")
tcp_table:add(9000, myproto)
-- 加载: wireshark -X lua_script:myproto.lua
使用前核对
目标与操作范围以用户当前请求为准;核对方法前置条件、工具和附件,按实际执行层级记录结果。正文不表示示例代码已经在本机执行或验证。