Files
CCSDS_study/README.md
2026-05-05 21:54:35 +08:00

363 lines
8.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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(...))`:你开始把观察结果写成候选协议结构。
这就是这个文件最重要的教学目的。