# Tianwen-1-parse-netzob.py 教学说明 这个脚本是一个 **Netzob 入门教学脚本**。它使用天问一号原始二进制帧数据做例子,但故意不使用 `Tianwen.ccsds` 里已经写好的协议解析器。 它的核心假设是: > 我们只拿到一个 `.u8` 原始二进制文件,不知道 Tianwen-1 的空间帧具体协议,想先用 Netzob 和基础统计方法观察数据结构。 脚本位置: ```text /home/zjz/CCSDS_study/test/Tianwen-1-parse-netzob.py ``` 默认输入数据: ```text /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. 如何用 `Field` 和 `Raw` 手工写一个候选帧结构。 6. 如何用简单统计辅助 Netzob 分析,比如熵、唯一值数量、最常见字节值。 ## 运行方式 推荐在项目根目录运行: ```bash /home/zjz/CCSDS_study/ccsds/bin/python /home/zjz/CCSDS_study/test/Tianwen-1-parse-netzob.py ``` 查看帮助: ```bash /home/zjz/CCSDS_study/ccsds/bin/python /home/zjz/CCSDS_study/test/Tianwen-1-parse-netzob.py --help ``` 常用参数: ```bash --frame-size 220 ``` 如果你已经知道候选帧长,可以直接指定,跳过帧长估计。 ```bash --sample-size 32 ``` 控制参与统计和 Netzob 分析的帧数。数值越大,结果越稳定,但运行更慢。 ```bash --cluster-sample-size 8 ``` 只控制 `clusterByKeyField` 演示使用多少帧。Netzob 聚类比较耗时,所以默认较小。 ## Step 1:读取未知二进制数据 脚本首先读取整个 `.u8` 文件: ```python raw = load_raw_bytes(args.input) ``` 此时 `raw` 只是一个很长的 `bytes`,没有帧边界,也没有字段含义。 新手可以理解为: ```text raw = 一大串连续字节 ``` ## Step 2:估计固定帧长 如果不知道每帧多长,脚本会尝试多个候选帧长,例如默认的 180 到 260。 核心思路: 1. 用候选帧长把字节流切成多行。 2. 看每一行开头若干字节的统计特征。 3. 如果帧长猜对了,帧头字段会对齐。 4. 对齐后的帧头通常比随机载荷更稳定。 5. 稳定字段的平均熵更低。 因此脚本会打印类似: ```text Top frame-size candidates by low average header entropy: size=220 avg_entropy=... ``` 这里 `220` 通常会排在最前面。 注意:这不是数学证明,只是未知协议分析中的启发式方法。 ## Step 3:创建 Netzob RawMessage 和 Symbol 脚本把切好的每一帧包装成 Netzob `RawMessage`: ```python messages = [RawMessage(data=frame) for frame in frames] ``` 然后把这些消息放进一个 `Symbol`: ```python symbol = Symbol(messages=messages, name=name) ``` 新手可以这样理解: - `RawMessage`:一条原始消息。 - `Symbol`:一组格式相似的消息。 刚开始 Netzob 不知道字段边界,所以一个 Symbol 通常只有一个大字段。 ## Step 4:字节位置统计 在使用 Netzob 自动推断前,脚本先做基础统计。 每个字节位置会统计: - `offset`:字节偏移。 - `unique`:这个位置出现过多少种不同字节值。 - `entropy`:这个位置的熵。 - `most common byte values`:最常见的几个字节值。 例如: ```text offset unique entropy most common byte values 0 2 0.811 0x7d:72, 0x54:24 ``` 可以粗略理解为: - `unique` 很小:可能是版本、类型、ID、固定标记。 - `entropy` 很小:字段比较稳定。 - `unique` 很大:可能是计数器、时间戳、载荷或校验。 脚本还会把连续位置合并成候选区间: ```text bytes 000-003 low-var bytes 004-004 dynamic bytes 005-007 low-var ``` 这一步不是 Netzob 必需的,但很适合新手理解数据形态。 ## Step 5:使用 Format.splitStatic 这一段是 Netzob 的核心教学点之一。 脚本调用: ```python Format.splitStatic(...) ``` 它会比较同一个 Symbol 中所有消息的同一字节位置: - 如果所有样本都一样,可能是 static field。 - 如果样本之间不同,可能是 dynamic field。 脚本演示两种模式。 ### 合并相邻字段 ```python mergeAdjacentStaticFields=True mergeAdjacentDynamicFields=True ``` 这种模式会把连续的静态字段或动态字段合并。 如果样本中几乎每个字节位置都变化过,那么可能会合并成一个很大的 dynamic field。 ### 不合并相邻字段 ```python mergeAdjacentStaticFields=False mergeAdjacentDynamicFields=False ``` 这种模式会让每个字节都变成独立字段。 这样做的好处是: ```text fields[0] 对应 byte 0 fields[1] 对应 byte 1 fields[2] 对应 byte 2 ``` 后面做 `clusterByKeyField` 时就可以直接选择某个 byte offset。 ## Step 6:使用 clusterByKeyField 聚类 未知协议分析中,经常需要找一个“分类字段”。 例如真实协议里可能有: - 版本字段 - 消息类型字段 - spacecraft ID - virtual channel ID - 命令类型 脚本先用统计方法找出低变化候选 offset,然后选择一个字段调用: ```python Format.clusterByKeyField(bytewise_symbol, bytewise_symbol.fields[key_offset]) ``` 这会把 key 字段值相同的消息放到同一组。 输出类似: ```text clusters created: 2 key=0x7d messages=6 key=0x54 messages=2 ``` 新手可以理解为: ```text 按某个字节值把帧分组 ``` 这个过程不能直接证明字段含义,但可以帮助我们发现数据中是否存在不同类别。 ## Step 7:手工建立候选模型 脚本最后演示如何用 Netzob 手工定义一个候选结构: ```python 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_`,意思是: > 这只是猜测,不是已经确认的协议规范。 然后脚本创建: ```python 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 `Field` 和 `Raw` 写候选协议结构。 ## 和 Tianwen-1-frame-analysis-v2.py 的区别 `Tianwen-1-frame-analysis-v2.py` 是在已知 Tianwen-1 / CCSDS 解析结构的基础上分析数据。 它会使用: ```python Tianwen.ccsds Tianwen.tianwen1_tm ``` 而 `Tianwen-1-parse-netzob.py` 是未知协议探索教学脚本。 它关注的是: ```text 不知道协议时,如何开始观察数据? 如何用 Netzob 包装消息? 如何用 Netzob 初步切字段? 如何用候选字段聚类? 如何写候选模型? ``` ## 新手总结 这个脚本可以理解成一个“协议逆向入门练习”。 它的工作流是: ```text 原始二进制文件 -> 估计帧长 -> 切成一帧一帧 -> 包装成 Netzob RawMessage -> 放入 Netzob Symbol -> 用 splitStatic 看固定/变化字段 -> 用 clusterByKeyField 按候选字段分组 -> 用 Field/Raw 写候选协议模型 ``` 如果你是新手,建议按这个顺序理解: 1. 先看 `raw`:它只是原始字节。 2. 再看 `frames`:它是按候选帧长切出来的帧列表。 3. 再看 `RawMessage`:每帧被包装成 Netzob 消息。 4. 再看 `Symbol`:一组消息被认为属于同一类格式。 5. 再看 `splitStatic`:Netzob 开始帮你找字段变化规律。 6. 再看 `clusterByKeyField`:Netzob 按候选字段值分组。 7. 最后看 `Field(Raw(...))`:你开始把观察结果写成候选协议结构。 这就是这个文件最重要的教学目的。