executing-plans
executing-plans
Use when executing an implementation plan in the current session as the implementer yourself — your human partner chose inline execution, or no subagent tool is available
re-proto-rev协议状态机重建:Scapy 解析、消息结构。 触发词:协议逆向、状态机、scapy、自定义协议
Independent GitHub snapshot · Automated classification · Original documentation governs
所有工具先验证再使用。本技能只处理数据与解析脚本,不运行样本,可免沙箱([[re-analyze/platform-tips]] 最高原则);抓流量环节在 [[re-netcap]] 内进行。
apt install python3 python3-pip / dnf install python3 python3-pip / pacman -S python python-pip(多数自带)brew install python(或 Xcode CLT 自带)python3 --versionpip install scapy(Python 3,推荐)apt install python3-scapy)python3 -c "import scapy; print(scapy.__version__)"rdpcap 即可,纯 Python 解析(无需 libpcap;libpcap 仅实时抓包 sniff() 时需要,强制纯 Python 解析用 conf.use_pcap = False)python3 -c "import tempfile,os; from scapy.all import rdpcap, wrpcap, IP, TCP, Raw; f=os.path.join(tempfile.gettempdir(),'t.pcap'); wrpcap(f,[IP()/TCP()/Raw(b'test')]); print(len(rdpcap(f)))" —— 输出 1 即正常tshark -r out.pcap -q -z conv,tcp 会话统计、-z io,stat,1 时间序列——步骤 1 聚类用tshark --versionbinwalk blob.bin 确认格式按顺序执行,每步记下结果。前提:流量必须是明文(密文先走 [[re-crypto-decrypt]])。
分组统计与聚类(长度/方向/时序):
tshark -r out.pcap -T fields -e tcp.len -e ip.src -e ip.dst | awk '{print $1}' | sort -n | uniq -c # 长度分布
tshark -r out.pcap -q -z conv,tcp # 会话列表:谁和谁、包数、字节数
tshark -r out.pcap -q -z io,stat,1 # 每秒流量:找规律性心跳
TCP 流重建 → 应用层 framing(强制前置,不可跳过):
bytes(p[TCP].payload) 逐包当消息会系统性出错:无间隔的连续 send() 常被合并成一个 chunk,大数据或慢链路则被切开;"一次 write 恰好一段"只是时序巧合(src,sport,dst,dport) 四元组归一化后分 A→B / B→A)分别重组:依 TCP SEQ 排序、去重(重传/重叠段)、补齐乱序空洞,并记录 capture gap(丢包造成的字节缺失位置)——跨 gap 的候选消息丢弃,不要拿错误字节硬拼TCPSession 会话化 + tcp_reassemble() 钩子(可处理重复/重叠);不写代码可先用 tshark -r out.pcap -q -z follow,tcp,raw,<流号> 取单向字节流人工看定位固定头(magic / 长度字段):
# 对重组后的单向字节流扫描候选 magic 前缀(不是逐包取前 8 字节)
from collections import Counter
stream = b"" # 步骤 2 按 (四元组, 方向) 重组出的单向字节流
cnt = Counter()
for off in range(max(0, len(stream) - 8)):
cnt[stream[off:off + 8]] += 1
for k, v in cnt.most_common(10):
print(v, k.hex())
0xAA55、\xDE\xAD\xBE\xEF、ASCII 品牌名);同一流中第 2-4 字节变化但前缀不变 → 变长消息,变化处是长度字段m、其后第 2-4 字节是长度字段 L,则下一个 magic 应落在 m + 头部长度 + L 处;只有多数出现位置都满足这一递推的候选才是真 magic(借此排除数据里碰巧出现的字节对)tshark -r out.pcap -q -z follow,tcp,raw,<流号>),确认 magic 与长度字段字节序(见坑 1)字段推断(类型 / 长度 / CRC):
crcmod(pip install crcmod)试 CRC16/CRC32 常见多项式,或与载荷异或检查是否为简单 XOR 校验Scapy 写解析器(自定义协议类):
from scapy.all import Packet, ByteField, ShortField, XByteField, StrFixedLenField
class C2(Packet):
name = "C2"
fields_desc = [
XShortField("magic", 0xAA55),
ByteField("type", 0),
ShortField("len", 0),
StrFixedLenField("payload", b"", 64),
]
# 测试解析(hex 与 fields_desc 对齐:aa55 | 01 | 000a | 54657374696e672e2e2e,len=0x000a=10 = "Testing..." 的 10 字节)
pkt = C2(bytes.fromhex("aa5501000a" + "54657374696e672e2e2e" + "00" * 54)) # aa55 | 01 | 000a | "Testing..."(10B)+ 54B 零填充,凑满 64B payload
pkt.show()
show())即验证字段布局正确C2(load))——循环从当前偏移解一条消息、按解析出的长度前进,直到剩余字节不足以构成完整消息为止。parser 必须能处理 0 条 / 1 条 / N 条消息对应一个 segment 三种情形;遇到 gap 或解析失败即停在该位置记录,不要跳过错误字节继续对齐(否则错位会静默传播)。Scapy 侧可直接用 TCPSession 让 C2 类在重组流上实例化follow,tcp,raw 里确认字节一致状态机推演(握手 / 心跳 / 结束):
长度字段字节序错误 → 解析全错:现象——解析器按小端读长度,长度值巨大/异常,后续字段全部错位;原因——协议长度字段是大端(网络序),按主机序小端读了;对策——步骤 4 对长度字段大小端都试,用"长度值是否合理(≤ 该 frame 在重组流中的实际跨度)"裁定,解析器里显式 !H/!I 网络序
CRC/校验先识别再解析:现象——把包尾校验和当业务数据解析,字段错位且消息对不上;原因——未知协议里校验和与数据无法靠名字区分;对策——包尾 2-4 字节对载荷敏感变化即先假设为校验和,用 crcmod/异或验证识别,解析器里跳过或单独字段
心跳消息混淆:现象——把周期心跳当业务消息,状态机出现"多发一条"的假分支,指令序列错乱;原因——心跳与业务消息同结构(同 magic 同类型前缀),只有时序规律;对策——步骤 1 的 io,stat 找固定周期小包,标记为心跳单独处理,状态机里作为独立状态转移
方向混淆(C2 交互模式):现象——把服务端→客户端的方向看反,命令消息被当响应、下发数据被当回包;原因——C2 中客户端主动轮询(请求里带命令)或服务端主动下发(无请求也有流量)混在一起;对策——按 IP:端口归属先明确 A/B 角色,分方向做统计(步骤 1),结合时序确认谁是主动方,状态图里标注每个转移的方向
Scapy 内置层绑定劫持自定义载荷:现象——自定义协议载荷被 Scapy 自动解成 DNS 等其他内置层,字段全乱(如 UDP 53 载荷解出来总是默认值);原因——bind_layers() 的默认绑定(UDP/53→DNS 等)在 guess_payload_class() 里先命中;对策——split_layers() 解除默认绑定,自定义协议类 override guess_payload_class() 显式分发,或解析脚本每包用自定义类强制构造
短包/固定长度字段静默降级 Raw:现象——解析器对某些包无报错输出 Raw / 字段错位,包一多错得隐蔽;原因——包短于字段最小长度时 Scapy 静默把整包当 Raw(无 Wireshark 式 malformed 标记),StrFixedLenField 与包缓存交互还会构建不一致;对策——解析前显式检查载荷长度 ≥ 字段和,短包单独记录不硬解;每个自定义类做 round-trip 验证(bytes(pkt) 再解回,比对上机字节)后再批量跑
重注入后对端报 FCS 错 / 帧长不对:现象——把抓到的帧重注入后,对端 Wireshark 报 "ETHERNET FRAME CHECK SEQUENCE INCORRECT" 或帧长与预期不符;原因——要分开看两个长度:以太网 FCS 是 4 字节,而典型以太网头部是 14 字节(6 目的 MAC + 6 源 MAC + 2 类型)——"短 14 字节"指向的是头部/链路层封装问题,不是 FCS;抓包库的帧抽象(Padding 处理、FCS 字段是否解析、是否带 trailer)会因版本与配置而异,不能笼统归结为"不保留尾部";对策——先量出实际少了多少字节再定位:缺 4 字节查 FCS 是否被剥离,缺 14 字节查是不是丢/重复了以太网头或注入到了错误的层;重注入优先用原始字节而非 round-trip 后的包,或在本机环回(lo)用 L3 注入避开以太网层
只读序列化函数,布局单边确认:现象——按 serialize 推的字段顺序在真实流量里对不上;原因——打包/解包可能不对称(条件字段、版本分支);对策——序列化/反序列化函数对读互证(serialize vs parse),两个方向对上了才是真布局;协议库通常同时含打包/解包函数
漏掉协议行为锚点:现象——特殊命令(心跳/时间校正)走独立组装路径,按通用帧结构解析全乱;原因——协议库内硬编码的特判字符串/命令是行为锚点;对策——grep 原生库与配置里的命令字符串(heartbeat/correcttime 类),先定位特殊路径再按通用结构解析
Java 客户端忽略 JNI 契约:现象——原生库逆向找不到入口;原因——Java 层通过 JNI 调原生,静态注册的方法会以 Java_包名_类名_方法名 留在动态导出表(strip 不删 .dynsym)可作接口文档;但 RegisterNatives 动态注册的库没有 Java_* 导出;对策——nm -D 列 JNI 导出,有则函数名直接给契约(encode/parse/setKey 分工)从导出函数开始逆向;无则从 JNI_OnLoad/RegisterNatives 调用点、JNI 函数表 xref、字符串与运行期 hook([[re-frida]])定位入口
IRC/文本协议分片在单词中间:现象——从抓包提取的协议消息里出现"半个词"(如 "giant s"、"undead eye"),按消息边界解析材料/字段错位;原因——IRC 服务器(InspIRCd)按 ~400 字节硬切长消息(不尊重单词边界),客户端收到后拼接全部分片再按分隔符重分割;对策——按客户端逻辑处理:拼接相邻消息(Join(msgs, ""))→ 再按分隔符(, 等)分割,而不是按消息边界独立解析;TSHARK follow 的 ascii 模式有 ~451 字节显示截断,长数据必须用 raw(hex)模式提取
消息累积触发机制:现象——协议客户端对长消息分片处理,只在特定条件下触发完整处理;原因——客户端按"结束标记"累积消息(如以 ./? 结尾才处理整段),中间分片只是缓存;对策——反编译客户端消息处理器找累积条件(包含自己名字 + 结束字符等),重放时按该格式构造,否则消息被静默丢弃
伪服务端重放验证协议:现象——协议逻辑复杂、静态逆向慢;原因——客户端程序(bot/C2 信标)逻辑在交互中触发;对策——伪造服务端(假 IRC/HTTP + DNS hook 到本地)按 pcap 真实序列重放,观察客户端行为(发送消息/调用链),快速验证协议理解
Web 验证码/现代 JS 协议:从 Verify 接口反向推:现象——直接逆向验证码加密算法无从下手,或还原后校验仍失败;原因——验证码真正提交的数据全部封装在单一字段(如 captchaBody),算法只是最后一步;对策——流水线:抓 Verify 请求 → 定位核心字段 → JS 插桩找 SDK 加密前明文 plain → 逐个分析 plain 字段来源(接口返回免算 / 缺口识别:下载图片+OpenCV 找缺口算拖动距离 / 浏览器环境采集按 SDK 格式生成 / 轨迹坐标)→ 明文完全一致后加密反而是最简单一步(沿 SDK 调用链执行与浏览器一致的加密流程);核心原则是输入完全一致而非算法等价
协议化时同步算额外签名层:现象——核心字段(captchaBody)还原正确 Verify 仍被拒;原因——Verify 之外还有页面 SDK 自动生成的请求保护签名(协议化时漏算);对策——从页面 SDK 完整调用链确认所有自动生成参数一并纳入;同类场景先列全"请求包含的所有非手工字段"再实现
私有协议无解析器:现象——Wireshark 只显示 Data;原因——私有协议无 dissector;对策——写 Lua dissector(<100 行)或 Python 离线分析
数据无规律 → 先验加密:现象——每帧都不同;原因——压缩/加密层;对策——熵分析(>7.5 → 加密/压缩/高随机性候选,结合帧长度规律与熵分布佐证,高熵不是判定),加密层找 nonce/IV 字段;长度字段解不出时按「长度可能 little/big-endian、含/不含自身」列方程组解语义
重放被拒:现象——数据正确但服务端不响应;原因——协议带递增 seq/nonce;对策——搞清楚 seq 计算方式(前一帧 hash 或递增计数器),复现时正确推进
闭环验证:现象——协议还原是否成立无判据;原因——静态分析无法自证还原正确;对策——用还原的帧结构写 client 发一帧,服务端响应一致才算成立 (来源:reverse-skill field-journal,MIT)