2026-05-05 21:54:35 +08:00
2026-05-05 21:54:35 +08:00
2026-05-05 21:54:35 +08:00
2026-05-05 21:54:35 +08:00
2026-05-05 21:54:35 +08:00
2026-05-05 21:54:35 +08:00
2026-05-05 21:54:35 +08:00
2026-05-05 21:54:35 +08:00
2026-05-05 21:54:35 +08:00
2026-05-05 21:54:35 +08:00
2026-05-05 21:54:35 +08:00

Tianwen-1-parse-netzob.py 教学说明

这个脚本是一个 Netzob 入门教学脚本。它使用天问一号原始二进制帧数据做例子,但故意不使用 Tianwen.ccsds 里已经写好的协议解析器。

它的核心假设是:

我们只拿到一个 .u8 原始二进制文件,不知道 Tianwen-1 的空间帧具体协议,想先用 Netzob 和基础统计方法观察数据结构。

脚本位置:

/home/zjz/CCSDS_study/test/Tianwen-1-parse-netzob.py

默认输入数据:

/home/zjz/CCSDS_study/Tianwen/tianwen1_frames_20200730.u8

这个脚本不做什么

它不做已知协议解析。

具体来说,它不会:

  • import Tianwen.ccsds
  • 调用 ccsds.AOSFrame.parse(...)
  • 直接使用已知的 AOS 帧字段定义
  • 直接解释 spacecraft ID、virtual channel、APID 的真实协议含义

这样做是为了模拟未知协议逆向分析的第一步。

这个脚本主要做什么

它主要演示:

  1. 如何把原始 bytes 包装成 Netzob RawMessage
  2. 如何把一组消息放入 Netzob Symbol
  3. 如何用 Format.splitStatic 自动观察固定字段和动态字段。
  4. 如何用 Format.clusterByKeyField 按候选字段聚类。
  5. 如何用 FieldRaw 手工写一个候选帧结构。
  6. 如何用简单统计辅助 Netzob 分析,比如熵、唯一值数量、最常见字节值。

运行方式

推荐在项目根目录运行:

/home/zjz/CCSDS_study/ccsds/bin/python /home/zjz/CCSDS_study/test/Tianwen-1-parse-netzob.py

查看帮助:

/home/zjz/CCSDS_study/ccsds/bin/python /home/zjz/CCSDS_study/test/Tianwen-1-parse-netzob.py --help

常用参数:

--frame-size 220

如果你已经知道候选帧长,可以直接指定,跳过帧长估计。

--sample-size 32

控制参与统计和 Netzob 分析的帧数。数值越大,结果越稳定,但运行更慢。

--cluster-sample-size 8

只控制 clusterByKeyField 演示使用多少帧。Netzob 聚类比较耗时,所以默认较小。

Step 1读取未知二进制数据

脚本首先读取整个 .u8 文件:

raw = load_raw_bytes(args.input)

此时 raw 只是一个很长的 bytes,没有帧边界,也没有字段含义。

新手可以理解为:

raw = 一大串连续字节

Step 2估计固定帧长

如果不知道每帧多长,脚本会尝试多个候选帧长,例如默认的 180 到 260。

核心思路:

  1. 用候选帧长把字节流切成多行。
  2. 看每一行开头若干字节的统计特征。
  3. 如果帧长猜对了,帧头字段会对齐。
  4. 对齐后的帧头通常比随机载荷更稳定。
  5. 稳定字段的平均熵更低。

因此脚本会打印类似:

Top frame-size candidates by low average header entropy:
  size=220  avg_entropy=...

这里 220 通常会排在最前面。

注意:这不是数学证明,只是未知协议分析中的启发式方法。

Step 3创建 Netzob RawMessage 和 Symbol

脚本把切好的每一帧包装成 Netzob RawMessage

messages = [RawMessage(data=frame) for frame in frames]

然后把这些消息放进一个 Symbol

symbol = Symbol(messages=messages, name=name)

新手可以这样理解:

  • RawMessage:一条原始消息。
  • Symbol:一组格式相似的消息。

刚开始 Netzob 不知道字段边界,所以一个 Symbol 通常只有一个大字段。

Step 4字节位置统计

在使用 Netzob 自动推断前,脚本先做基础统计。

每个字节位置会统计:

  • offset:字节偏移。
  • unique:这个位置出现过多少种不同字节值。
  • entropy:这个位置的熵。
  • most common byte values:最常见的几个字节值。

例如:

offset  unique  entropy   most common byte values
0       2       0.811     0x7d:72, 0x54:24

可以粗略理解为:

  • unique 很小可能是版本、类型、ID、固定标记。
  • entropy 很小:字段比较稳定。
  • unique 很大:可能是计数器、时间戳、载荷或校验。

脚本还会把连续位置合并成候选区间:

bytes 000-003  low-var
bytes 004-004  dynamic
bytes 005-007  low-var

这一步不是 Netzob 必需的,但很适合新手理解数据形态。

Step 5使用 Format.splitStatic

这一段是 Netzob 的核心教学点之一。

脚本调用:

Format.splitStatic(...)

它会比较同一个 Symbol 中所有消息的同一字节位置:

  • 如果所有样本都一样,可能是 static field。
  • 如果样本之间不同,可能是 dynamic field。

脚本演示两种模式。

合并相邻字段

mergeAdjacentStaticFields=True
mergeAdjacentDynamicFields=True

这种模式会把连续的静态字段或动态字段合并。

如果样本中几乎每个字节位置都变化过,那么可能会合并成一个很大的 dynamic field。

不合并相邻字段

mergeAdjacentStaticFields=False
mergeAdjacentDynamicFields=False

这种模式会让每个字节都变成独立字段。

这样做的好处是:

fields[0] 对应 byte 0
fields[1] 对应 byte 1
fields[2] 对应 byte 2

后面做 clusterByKeyField 时就可以直接选择某个 byte offset。

Step 6使用 clusterByKeyField 聚类

未知协议分析中,经常需要找一个“分类字段”。

例如真实协议里可能有:

  • 版本字段
  • 消息类型字段
  • spacecraft ID
  • virtual channel ID
  • 命令类型

脚本先用统计方法找出低变化候选 offset然后选择一个字段调用

Format.clusterByKeyField(bytewise_symbol, bytewise_symbol.fields[key_offset])

这会把 key 字段值相同的消息放到同一组。

输出类似:

clusters created: 2
key=0x7d  messages=6
key=0x54  messages=2

新手可以理解为:

按某个字节值把帧分组

这个过程不能直接证明字段含义,但可以帮助我们发现数据中是否存在不同类别。

Step 7手工建立候选模型

脚本最后演示如何用 Netzob 手工定义一个候选结构:

candidate_header = Field(Raw(nbBytes=6), name="candidate_header_0_5")
candidate_insert_or_secondary = Field(Raw(nbBytes=8), ...)
candidate_payload = Field(Raw(nbBytes=...), ...)
candidate_tail = Field(Raw(nbBytes=4), ...)

这里的字段名都带 candidate_,意思是:

这只是猜测,不是已经确认的协议规范。

然后脚本创建:

Symbol(name="manual_candidate_tianwen_like_frame", fields=[...])

并打印结构树。

这一段的重点是学习 Netzob 如何表达协议结构,而不是确认 Tianwen-1 的真实字段。

主要函数说明

estimate_frame_size(...)

用于估计固定帧长。

它会遍历候选帧长,计算帧头前若干字节的平均熵,并按熵排序。

slice_frames(...)

把连续 bytes 切成固定长度帧。

build_symbol(...)

把 Python bytes 转成 Netzob RawMessage,再放入 Symbol

byte_statistics(...)

统计每个字节偏移的唯一值数量、熵、最常见字节。

demonstrate_split_static(...)

演示 Netzob Format.splitStatic

demonstrate_cluster_by_key_field(...)

演示 Netzob Format.clusterByKeyField

demonstrate_manual_candidate_model(...)

演示如何用 Netzob FieldRaw 写候选协议结构。

和 Tianwen-1-frame-analysis-v2.py 的区别

Tianwen-1-frame-analysis-v2.py 是在已知 Tianwen-1 / CCSDS 解析结构的基础上分析数据。

它会使用:

Tianwen.ccsds
Tianwen.tianwen1_tm

Tianwen-1-parse-netzob.py 是未知协议探索教学脚本。

它关注的是:

不知道协议时,如何开始观察数据?
如何用 Netzob 包装消息?
如何用 Netzob 初步切字段?
如何用候选字段聚类?
如何写候选模型?

新手总结

这个脚本可以理解成一个“协议逆向入门练习”。

它的工作流是:

原始二进制文件
  -> 估计帧长
  -> 切成一帧一帧
  -> 包装成 Netzob RawMessage
  -> 放入 Netzob Symbol
  -> 用 splitStatic 看固定/变化字段
  -> 用 clusterByKeyField 按候选字段分组
  -> 用 Field/Raw 写候选协议模型

如果你是新手,建议按这个顺序理解:

  1. 先看 raw:它只是原始字节。
  2. 再看 frames:它是按候选帧长切出来的帧列表。
  3. 再看 RawMessage:每帧被包装成 Netzob 消息。
  4. 再看 Symbol:一组消息被认为属于同一类格式。
  5. 再看 splitStaticNetzob 开始帮你找字段变化规律。
  6. 再看 clusterByKeyFieldNetzob 按候选字段值分组。
  7. 最后看 Field(Raw(...)):你开始把观察结果写成候选协议结构。

这就是这个文件最重要的教学目的。

Description
No description provided
Readme 29 MiB
Languages
Python 94.6%
C 5%
Jupyter Notebook 0.3%