Files
tianxuan/.omo/drafts/tianxiu-v6.md
T

342 lines
14 KiB
Markdown
Raw 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.
# Draft: 天璇 v6 — 基于真实TLS特征的恶意流量分析
## Intent
- **intent**: CLEAR
- **review_required**: true
- **status**: awaiting-approval
## 自我审查发现的问题
1. ❌ Phase 0 依赖用户给真实数据——应基于 TLS 1.3 协议推导
2. ❌ 21x2 hex 列我不知道是哪个,回避了问题
3. ❌ "TlsS"/"TlsC" 列我不知道是哪个,回避了问题
4. ❌ 归一化步骤没说明如何集成到现有 data_loader 中
5. ❌ 特征间有冗余(port_category vs is_standard_port
6. ❌ 时域特征 `inter_arrival_time_std` 在单流级别不可计算
7. ❌ 缺少 JA3 指纹近似、SNI 长度熵等关键特征
8. ❌ lat/lon 覆盖方案没说具体怎么做
9. ❌ 空白语义只说了 4 列,其他几十列没说
10. ❌ 聚类流程图漏了"归一化"步骤
---
## Phase 0: 基于 TLS 1.3 协议的列类型推导(不需要用户数据)
### 基础事实
- 数据来源:TLS 1.3 握手报文字段抽取
- 每条流 = 一个 TLS 连接握手 + 数据传输的摘要
- 大量空白是预期的——TLS 握手中很多字段是可选 extension,不是每条流都有
### 每列精确类型定义
| 列 | TLS协议来源 | 存放格式 | 空白语义 |
|----|-----------|---------|---------|
| `:ips` | 捕获点记录的源IP | IPv4字符串 | 从不空白 |
| `:ipd` | 捕获点记录的目标IP | IPv4字符串 | 从不空白 |
| `:prs` | TCP源端口 | 整数 | 缺失 |
| `:prd` | TCP目标端口 | 整数 | 缺失 |
| `scnt` | GeoIP从:ipd推断 | 2字母大写代码 | 缺失(GeoIP无法解析时) |
| `dcnt` | GeoIP从:ipd推断 | 2字母大写代码 | 缺失 |
| `server-ip` | 服务器IP(可能与:ipd同) | IPv4 | 缺失 |
| `client-ip` | 客户端IP(可能与:ips同) | IPv4 | 缺失 |
| `0ver` | ServerHello.protocol_version | 2x2 hex: `03 04` | TLS连接都必有,极少空白 |
| `snam` | ClientHello.extensions.server_name | UTF8域名 | 缺失(ClientHello中无SNI扩展 → 可疑) |
| `cnam` | Certificate.certificate.subject.CN | UTF8字符串 | 缺失(无证书或未知证书) |
| `4dur` | 流持续时间(纳秒/毫秒?) | 整数 | 常见空白(40%+) |
| `8ses` | 会话起始偏移 | 整数 | 常见空白 |
| `2tmo` | 超时偏移 | 整数 | 常见空白 |
| `4ksz` | 证书公钥大小 | 整数(256/384/521/1024/2048) | 空白(无证书) |
| `cnrs` | SessionTicket.can_resume | `+`/空白 | **空白≠缺失** `+`=可恢复 `""`=不可恢复 |
| `isrs` | SessionTicket.was_resumed | `+`/空白 | **空白≠缺失** `+`=已恢复 `""`=未恢复 |
| `8ack` | 反向方向字节数 | 整数 | 缺失 |
| `8ppk` | 有效载荷包数 | 整数 | 缺失 |
| `8dbd` | 数据库日志记录时间 | 时间戳字符串 | 缺失 |
| `1ipp` | IP协议号 | 整数(6/17) | 缺失 |
| `4dbn` | 数据库编号 | 整数 | 缺失 |
| `tabl` | 数据库表名 | 字符串 | 缺失 |
| `name` | 捕获链路名称 | 字符串 | 缺失 |
| `source-node` | 捕获节点名 | 字符串 | 缺失 |
| `cipher-suite` | 密码套件名(IANA格式) | 大小写混合字符串 | 缺失(无密码协商时) |
| `ecdhe-named-curve` | 椭圆曲线名 | 大小写混合字符串 | 缺失(非ECDHE密钥交换时) |
| `0cph` | ServerHello.cipher_suite | 2x2 hex: `13 01` | 缺失 |
| `0crv` | ServerHello.KeyShare | 2x2 hex: `00 1d` | 缺失(非ECDHE时) |
| `0rnd` | ServerHello/ClientHello.random | 32x2 hex(也可能是21x2 | TLS必有,但可能截断存储 |
| `0rnt` | Random.gmt_unix_time | 整数或4x2 hex | 常见空白(现代TLS用随机值替代时间) |
| `row` | 行号 | 整数 | 从不空白 |
| `time` | 时间 | 字符串 | 缺失 |
| `timestamp` | 时间戳 | 字符串 | 从不空白 |
| `:ips.latd/lond` | GeoIP推断,过时 | 浮点数 | 不可靠,覆盖 |
| `:ipd.latd/lond` | GeoIP推断,过时 | 浮点数 | 不可靠,覆盖 |
| `:ips.ispn/.orgn/.city` | GeoIP推断 | 字符串 | 大部分空白 |
| `:ipd.ispn/.orgn/.city` | GeoIP推断 | 字符串 | 大部分空白 |
### 待识别项目
- **21x2 hex 列**: 不在我已知的任何标准 TLS 结构中。0rnd(32x2)、0cph(2x2)、0crv(2x2)、0ver(2x2)。21x2 可能是一个额外的截断/编码字段。将用 column_survey 自动发现 `hex_pairs_count==21` 的列。
- **TlsS/TlsC**: 可能是 `tabl``source-node` 列的两个值。含义接近 "TLS Server" / "TLS Client"。待 column_survey 确认。
---
## Phase 0.5: 值归一化模块 `analysis/value_normalizer.py`
### 架构
新增独立的归一化模块,在 `data_loader.py``load_csv_directory()` 中作为**最后一个清理步骤**执行(类型分类之后、返回 LazyFrame 之前)。
### 归一化方式
每列生成 `{col}_norm` 保留原始列,供下游特征工程使用。
### TLS Version → Enum (0ver)
```python
TLS_VERSION_MAP = {
# hex wire format
'03 04': 'tls_1_3', '03 03': 'tls_1_2',
'03 02': 'tls_1_1', '03 01': 'tls_1_0',
'02 00': 'ssl_2_0', '03 00': 'ssl_3_0',
# display formats (case-insensitive)
'tlsv1.3': 'tls_1_3', 'tls 1.3': 'tls_1_3', '1.3': 'tls_1_3',
'tlsv1.2': 'tls_1_2', 'tls 1.2': 'tls_1_2', '1.2': 'tls_1_2',
}
# 未知值 → 'unknown_version'
```
### Cipher Suite → Enum (0cph, cipher-suite)
```python
CIPHER_MAP = {
# hex → IANA name (TLS 1.3 + common TLS 1.2)
'13 01': 'tls_aes_128_gcm_sha256',
'13 02': 'tls_aes_256_gcm_sha384',
'13 03': 'tls_chacha20_poly1305_sha256',
'c0 2b': 'tls_ecdhe_ecdsa_aes_128_gcm_sha256',
'c0 2f': 'tls_ecdhe_rsa_aes_128_gcm_sha256',
'c0 30': 'tls_ecdhe_rsa_aes_256_gcm_sha384',
'cc a9': 'tls_ecdhe_ecdsa_chacha20_poly1305_sha256',
'cc ac': 'tls_ecdhe_rsa_chacha20_poly1305_sha256',
'00 9c': 'tls_rsa_aes_128_gcm_sha256',
'00 9d': 'tls_rsa_aes_256_gcm_sha384',
'c0 09': 'tls_ecdhe_ecdsa_aes_128_cbc_sha256',
'c0 13': 'tls_ecdhe_rsa_aes_128_cbc_sha256',
# display → IANA (normalize: lowercase, ' '→'_', '-'→'_')
'tls_aes_128_gcm_sha256': 'tls_aes_128_gcm_sha256',
'aes 128 gcm': 'tls_aes_128_gcm_sha256',
'aes-128-gcm': 'tls_aes_128_gcm_sha256',
'tls 1.3 aes 128 gcm': 'tls_aes_128_gcm_sha256',
}
# 未知hex → 'unknown_cipher_0x{hex}'
# 未知显示名 → 标准化后保留
```
### Named Curve → Enum (0crv, ecdhe-named-curve)
```python
CURVE_MAP = {
# hex wire format
'00 1d': 'x25519', '00 17': 'secp256r1',
'00 18': 'secp384r1', '00 19': 'secp521r1',
'00 1e': 'x448',
# display (case-insensitive)
'x25519': 'x25519', 'curve25519': 'x25519',
'secp256r1': 'secp256r1', 'prime256v1': 'secp256r1',
'p-256': 'secp256r1', 'p256': 'secp256r1',
'secp384r1': 'secp384r1', 'p-384': 'secp384r1',
}
# 未知 → 'unknown_curve_0x{hex}'
```
### CNRS/ISRS → Enum (cnrs, isrs)
```
'+' → 'yes' (never missing)
'' → 'no' (never missing)
```
### 纯字符串(不操作)
`snam`, `cnam`, `name`, `source-node`, `tabl`
→ 不归一化,保持原始字符串
### 归一化与现有 data_loader 的集成
```
现有 data_loader 清理流水线:
BOOL_ENUM → LAT_LON → HEX → [新增: VALUE_NORMALIZATION] → GenericNumCoerce
value_normalizer.normalize(lf, type_map) → pl.LazyFrame (带 _norm 列)
```
---
## Phase 1: 特征工程(每条流级别 → 实体聚合级别)
### 第一步:单流特征(在 entity_aggregator 聚合前计算)
| 特征 | 来源列 | 计算方式 |
|------|--------|---------|
| `has_sni` | `snam` | 有SNI? 1/0 |
| `sni_length` | `snam` | SNI域名长度(长域名可疑) |
| `sni_entropy` | `snam` | 域名Shannon熵(高熵域名=算法生成=DGA |
| `sni_vs_cnam` | `snam`, `cnam` | SNI与CN一致? 1/0 |
| `tls_version_risk` | `0ver_norm` | `<tls_1_2`=3, `tls_1_2`=1, `tls_1_3`=0 |
| `cipher_suite_category` | `0cph_norm` | `aead`=0, `cbc`=1, `rc4`=2, `null`=3 |
| `key_exchange` | `0cph_norm` | `ecdhe`=0, `dhe`=1, `static`=2 |
| `is_modern_cipher` | `0cph_norm` | tls 1.3 only cipher? 1/0 |
| `curve_type` | `0crv_norm` | `x25519`=0, `secp256r1`=1, other=2 |
| `curve_bit_strength` | `0crv_norm`/`4ksz` | 128/192/256 bits |
| `port_type` | `:prd` | `443`=0, `80-1023`=1, `>1024`=2 |
| `bytes_per_packet` | `8ack`, `8ppk` | avg bytes/packet |
| `is_recoverable` | `cnrs_norm` | `yes`=1, `no`=0 |
| `was_resumed` | `isrs_norm` | `yes`=1, `no`=0 |
| `has_cert_key` | `4ksz` | 有密钥大小? 1/0 |
### 第二步:实体聚合特征(在 entity_aggregator 中计算,按 `:ips` 分组)
| 聚合特征 | 聚合方式 | 意义 |
|---------|---------|------|
| `flow_count` | count | 总流数 |
| `unique_dst_ips` | n_unique | 目标多样性 |
| `unique_dst_ports` | n_unique | 端口多样性 |
| `unique_tls_versions` | n_unique(0ver_norm) | TLS版本跨度 |
| `unique_ciphers` | n_unique(0cph_norm) | 密码套件跨度 |
| `unique_curves` | n_unique(0crv_norm) | 曲线跨度 |
| `unique_snis` | n_unique(snam) | SNI多样性 |
| `tls_13_ratio` | mean(is_modern_cipher) | TLS 1.3密码比例 |
| `ecdhe_ratio` | mean(key_exchange==0) | 前向保密比例 |
| `avg_bytes_per_packet` | mean(bytes_per_packet) | 平均包大小 |
| `std_bytes_per_packet` | std(bytes_per_packet) | 包大小标准差(小=beaconing |
| `avg_duration` | mean(4dur) | 平均持续时长 |
| `std_duration` | std(4dur) | 时长标准差 |
| `port_443_ratio` | mean(port_type==0) | 标准端口比例 |
| `non_standard_port_ratio` | mean(port_type==2) | 非标准端口比例(可疑) |
| `sni_missing_ratio` | mean(1-has_sni) | 无SNI比例(可疑) |
| `sni_cn_mismatch_ratio` | mean(1-sni_vs_cnam) | SNI/CN不匹配比例 |
| `sni_avg_entropy` | mean(sni_entropy) | SNI平均熵(高=DGA |
| `recoverable_ratio` | mean(is_recoverable) | 会话可恢复比例 |
| `resumed_ratio` | mean(was_resumed) | 会话恢复比例 |
| `has_any_cert` | mean(has_cert_key) | 有证书比例 |
| `countries_visited` | n_unique(dcnt) | 跨国多样性 |
| `unique_hours` | n_unique(hour_of_day) | 活跃时段 |
| `inter_arrival_std` | std(timestamp_diff) | **Beaconing检测**: 到达间隔标准差 |
| `night_ratio` | mean(is_night_hour) | 夜间流量比例 |
### 总计:约 30+ 聚合特征
---
## Phase 2: 空白语义精确处理
| 列 | 空白语义 | 聚合前处理 | 聚合中处理 |
|----|---------|-----------|-----------|
| cnrs, isrs | **非缺失** (+=yes, blank=no) | 归一化为 `yes`/`no` | 直接聚合 |
| snam, cnam | 缺失(无SNI/证书) | 保留空白 | `has_sni`=0, `sni_cn_mismatch`=null |
| 0ver | 极少空白 | 空白→`unknown_version` | `tls_version_risk`=null |
| 0cph, 0crv | 缺失(无协商) | 空白→`unknown` | 相应特征为null |
| 4dur, 8ack, 8ppk | 缺失(40%+ | 保留空白 | `mean`/`std`跳过null |
| :prs, :prd | 缺失 | 保留空白 | `port_type`=null |
| scnt, dcnt | 缺失(GeoIP失败) | 保留空白 | `countries_visited`排除null |
| lat/lon | 基于过时库 | **全部替换** | 见Phase 2.5 |
| ispn/orgn/city | 大部分空白 | **不参与分析** | 排除 |
---
## Phase 2.5: 经纬度覆盖方案
lat/lon 基于过时 IP 地理库,不可靠。处理方式:
- **不使用原始 lat/lon 值**
- 对于 globe 可视化:用随机生成的地理位置模拟
- 对于分析特征:移除所有基于 lat/lon 的特征
- 保留 `dcnt`(目标国家代码)作为地理特征——国家代码更可靠且更有分析价值
---
## Phase 3: 完整聚类流程
```
原始CSV
→ data_loader加载
→ 类型分类 (type_classifier.py)
→ BOOL_ENUM清洗
→ HEX预处理 (hex_pairs_count)
→ 值归一化 (value_normalizer.py) ← Phase 0.5 新增
→ 通用数值清洗
→ 单流特征计算 (data_loader.py 扩展) ← Phase 1 新增
→ 实体聚合 (entity_aggregator.py 扩展) ← Phase 1 新增
→ 通用数值清洗 (已有)
→ 方差过滤 ← Phase 1 新增
→ 相关过滤
→ StandardScaler → PCA(20-50维)
→ HDBSCAN聚类
→ 簇画像生成
```
### 簇画像输出
每个簇的完整描述:
```
簇标签: 3
大小: 42实体 (14%)
簇类型: 🚨 高度可疑
特征画像:
- sni_missing_ratio: 0.85 (+2.1σ 高于均值)
- non_standard_port_ratio: 0.72 (+1.8σ)
- tls_13_ratio: 0.15 (-2.3σ)
- avg_bytes_per_packet: 45 (-1.9σ, 小包=beaconing)
- inter_arrival_std: 2.3s (-2.1σ, 固定间隔)
- countries_visited: 12 (+1.5σ, 跨国)
判定: 疑似代理/隧道流量
建议操作: 深度包检测, IP列入观察名单
```
---
## Phase 4: 测试数据生成 (gen_test_data.py)
测试数据也使用归一化格式输出,确保端到端可验证。
### 3 类流量画像
#### 1. 正常浏览 (60%示例数据)
- TLS 1.3 (03 04) + AEAD cipher
- Curve: x25519 (00 1d)
- SNI: `www.google.com`, `api.github.com` 等合法域名
- 端口 443
- 多样的目标IP
- 短连接 (0.5-5s), 中等包大小
- cnrs/isrs: 随机 yes/no
#### 2. 代理/VPN 隧道 (20%)
- TLS 1.2 (03 03) + ECDHE cipher
- Curve: secp256r1 (00 17)
- SNI: 可能缺失或单一代理解析域名
- 端口 443 但可能也在非标准端口
- 少目标高流量
- 长连接, 大字节量
- cnrs: 高恢复率 (keep-alive)
#### 3. 恶意/可疑 (20%)
- TLS 1.0/1.1 或奇怪版本
- 弱密码 (CBC/RC4)
- SNI/CN 不匹配或无 SNI
- Beaconing: 固定间隔小包 (30-40 bytes)
- 高重连率 (大量 cnrs=yes → isrs=yes)
- 非标准端口
- 低熵 TLS random
- 多国目标分布
- 低包大小标准差
---
## Phase 5: 端到端验证
1. 生成3类测试数据
2. 跑完整管道
3. 验证归一化正确性(每列hex→enum映射)
4. 验证聚类结果的自然分簇(正常/代理/恶意)
5. 验证簇画像的可解读性
6. 单元测试: 120+ passed
---
## 前置依赖
- Phase 0-5 都不需要用户真实数据,全部基于 TLS 1.3 协议推导
## 意外预案
| 意外 | 后备 |
|------|------|
| 21x2 hex 列不在已知列中 | column_survey 自动发现后添加合适映射 |
| TlsS/TlsC 列无法确认含义 | 保留为原始字符串枚举,不做归一化 |
| 聚类仍不理想 | 增加规则引擎层(if sni_missing>0.5 → flag |
| 特征数量不足 | 从 hex 列提取字节分布统计作为补充特征 |
| 归一化映射表不全 | 未知hex→`unknown_{type}_0x{hex}` 兜底 |