enet-validator/src/aech.rs
OpenCode 9995335d74 feat: show mode for standalone 1-32 rainbow DMX + RDM status polling
- Merge show/hybrid paths: use dmx_a template for DMX payload and replay-extracted SL frame skeleton for RDM.
- HOSTDEMO_MODE=show runs indefinitely when secs=0; wave period slowed for smoother breathing.
- Update README with show mode usage.
2026-09-11 12:03:43 +08:00

1227 lines
59 KiB
Rust
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.

//! AECH 密钥协商密码学分析 (aech 子命令)
//!
//! 已知结构 (§3.7): 主机发 552B sub=05 "AECH" 质询 (0x2c-0x8b 96B + 0x90-0x123 148B
//! 随机 blob, 共 244B), 真机回 552B sub=06 "AERE", 跨轮唯一变化 = 0xC0-0xC3 (4B)。
//! 核心问题: resp32 = f(challenge, 设备密钥) 是什么函数?
//!
//! 分析策略 (对全部质询/应答对):
//! A. 无密钥直接命中: CRC32(任意切片) / MD5/SHA1(任意切片) 截断 / 简单和
//! B. 密钥化 CRC 仿射测试: 定长消息下 resp = CRC32_k(slice) = CRC32_0(slice) ^ Δ(len)
//! → 对每个切片起点/长度, 若 resp^CRC32_0(slice) 跨全部样本为常数 Δ 则命中
//! (Δ 吸收 init/xorout 密钥, 无需事先知道密钥即可判定+恢复有效密钥)
//! C. XOR 常量测试: resp32 ^ chal32@off 跨样本恒定 → 异或掩码方案
//! D. K 相关: 同一 TSV 的 DMX 保护字节导出会话 K, 与 resp32 字节比对
use crate::dmxhash::{crc32, h2b, md5, read_tsv, sha1};
// ---------- dmxsend: 主机侧等效播放 (认领 + 加密 DMX, 全协议自证, 2026-09-10) ----------
// 流程: sub=01 宣告 → 收 89B 注册应答 → AECH 认领 (nonce/B12 自选) → 验证 AERE 0xC0==G(nonce)
// → 30fps 发送 zPprv0TXRRW 加密 DMX (可识别图案), 由接收端日志验证解密正确性
pub fn dmxsend(target: &str, secs: u64) {
use std::net::UdpSocket;
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
let sock = UdpSocket::bind("0.0.0.0:0").expect("bind");
sock.set_read_timeout(Some(Duration::from_millis(400))).unwrap();
let dst = format!("{}:31772", target);
let mut buf = [0u8; 2048];
// 简易 xorshift32 (避免外部依赖)
let mut rng: u32 = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().subsec_nanos() | 1;
let mut next_r = move || { rng ^= rng << 13; rng ^= rng >> 17; rng ^= rng << 5; rng & 0x7FFF_FFFF };
// --- 1) sub=01 宣告 → 89B 注册应答 ---
let ann = crate::enet::load_template("announce144");
sock.send_to(&ann, &dst).unwrap();
let mut reg_ok = false;
for _ in 0..6 {
if let Ok((n, _)) = sock.recv_from(&mut buf) {
if n == 89 || (n >= 8 && n < 200 && buf[4..8] == [2, 0, 0, 0]) { reg_ok = true; break; }
} else { break; }
}
println!("1) 宣告 sub=01 → 注册应答: {}", if reg_ok { "OK (89B)" } else { "未收到 (不阻塞)" });
// --- 2) AECH 认领: nonce/B12 自选, 验证 AERE 0xC0-0xC3 == G(nonce) ---
let nonce: u32 = next_r();
let b12: u32 = next_r();
let mut ch = crate::enet::load_template("claim_h112");
ch[0x88..0x8C].copy_from_slice(&nonce.to_le_bytes());
ch[0x8C..0x90].copy_from_slice(&b12.to_le_bytes());
let mut claim_ok = false;
for attempt in 0..3 {
sock.send_to(&ch, &dst).unwrap();
for _ in 0..6 {
if let Ok((n, src)) = sock.recv_from(&mut buf) {
let b = &buf[..n];
if n > 0xC4 && b.windows(4).any(|w| w == b"AERE") {
let resp = u32::from_le_bytes([b[0xC0], b[0xC1], b[0xC2], b[0xC3]]);
let want = agios_g(nonce);
claim_ok = resp == want;
println!("2) AECH 认领 (第{}轮): AERE from {} resp=0x{:08x} G(nonce)=0x{:08x} {}",
attempt + 1, src, resp, want, if claim_ok { "★PASS" } else { "FAIL" });
break;
}
} else { break; }
}
if claim_ok { break; }
}
if !claim_ok { println!("认领失败, 终止"); return; }
println!(" B12=0x{:08x} nonce=0x{:08x}", b12, nonce);
// --- 3) DMX 加密发送: 30fps, 高识别度图案 ---
let tpl = crate::enet::load_template("dmx_a");
let mut p1 = [0u8; 512];
p1[0] = 0xAA; p1[1] = 0x55;
for i in 0..8 { p1[2 + i] = 1u8 << i; }
p1[100] = 0xEF; // ch101
let mut p2 = [0u8; 512];
p2[0] = 0x77; p2[511] = 0x33; // ch513 / ch1024
let expect = "aa550102040810204080"; // 接收端解密后 CH1-10 应为
println!("3) DMX 发送 {}s @30fps: port1 ch1=AA ch2=55 ch3-10=1..128 ch101=EF; port2 ch513=77 ch1024=33", secs);
println!(" 期望接收端日志 CH1-16 以 {} 开头", expect);
let start = Instant::now();
let mut seq: u32 = 1;
let mut sent = 0u64;
while start.elapsed() < Duration::from_secs(secs) {
let r1 = next_r();
let r2 = next_r();
let mut f = tpl.clone();
f[0x1A..0x1E].copy_from_slice(&seq.to_le_bytes());
f[0x22..0x26].copy_from_slice(&r1.to_le_bytes());
f[0x26..0x2A].copy_from_slice(&r2.to_le_bytes());
let e1 = agios_dmx_encrypt(&p1, r1, b12);
let e2 = agios_dmx_encrypt(&p2, r2, b12);
f[0x3C..0x23C].copy_from_slice(&e1);
f[0x23C..0x43C].copy_from_slice(&e2);
if sock.send_to(&f, &dst).is_ok() { sent += 1; }
seq += 1;
std::thread::sleep(Duration::from_millis(33));
}
println!("完成: 发送 {} 帧", sent);
}
// ---------- AgiosElias: AECH 应答算法 (源自 SymphoLight Ecue.Devices.dll 反编译) ----------
// 主机验证逻辑 (AgiosElias.i4GrvIBQ6xF): 检查 AERE blob[152] == G(主机 nonce)
// 质询结构 (kxKrvvdOGhh 生成): "AECH" + 92B 随机 + 4B nonce(LE)@0x88 + 4B 实例(LE)@0x8C + 148B 随机
// G(x) = x ^ TABLE[(x%32 + 1 + (x*13)%30) % 32] — 无密钥, 表内嵌于 DLL (0A8F2B10... 静态数组)
pub const AGIOS_TABLE: [u32; 32] = [
0x1008de00, 0xe3b77ff6, 0x31183e1c, 0x90fb53c8, 0xff10ba5c, 0x030bc4b6, 0xe22935c8, 0x10e8ede1,
0x55f409a0, 0x3eb0b9a3, 0xf7333308, 0xedf07810, 0x8abf4078, 0x60ec0545, 0x1f4645c8, 0x54094148,
0x04c0f500, 0xfabe50bb, 0xf3f3e54c, 0x6c2a02cb, 0xa2546e60, 0x4f6ae10e, 0x51dfe8d8, 0x2eedb0ec,
0x74006928, 0x48fade70, 0xe5346e9c, 0x1c3fe0bc, 0xf6700700, 0x6b7f1ba0, 0xa1143cf4, 0x3be8426c,
];
pub fn agios_g(x: u32) -> u32 {
let n = 32u32;
let idx = (x % n + 1 + (x.wrapping_mul(13)) % (n - 2)) % n;
x ^ AGIOS_TABLE[idx as usize]
}
// ---------- AgiosElias::zPprv0TXRRW — DMX 端口数据逐帧混淆 (旧算法, Butler S2 使用) ----------
// 反编译 + 数据实证 (2026-09-10, cold2 3893 帧 + fix1 1781 帧全部解密为稳定全零场景):
// rot = (B12 ^ r) & 511 (B12 = AECH 质询 0x8C-0x8F 实例随机, 会话不变;
// r = 帧内 0x22-0x25/0x26-0x29 LE u32, Random.Next() 每帧每端口)
// 加密: out[i] = src[(i+rot)%512] ^ cnt[i]
// cnt[i] = (512-rot-i)&255 (i < 512-rot)
// = (rot-(i-(512-rot)))&255 (i >= 512-rot)
/// zPprv0TXRRW 加密 (主机侧): src 512B → wire 512B
pub fn agios_dmx_encrypt(src: &[u8], r: u32, b12: u32) -> Vec<u8> {
let rot = ((b12 ^ r) & 511) as usize;
(0..512).map(|i| {
let cnt = if i < 512 - rot { (512 - rot - i) & 0xFF } else { (rot - (i - (512 - rot))) & 0xFF };
src[(i + rot) % 512] ^ cnt as u8
}).collect()
}
/// zPprv0TXRRW 解密 (接收方视角): wire 512B → src 512B
pub fn agios_dmx_decrypt(wire: &[u8], r: u32, b12: u32) -> Vec<u8> {
let rot = ((b12 ^ r) & 511) as usize;
let mut src = vec![0u8; 512];
for i in 0..512 {
let cnt = if i < 512 - rot { (512 - rot - i) & 0xFF } else { (rot - (i - (512 - rot))) & 0xFF };
src[(i + rot) % 512] = wire[i] ^ cnt as u8;
}
src
}
// ---------- dmxdec: DMX 帧加密假说验证 (2026-09-10 反编译突破) ----------
// 反编译实证: iey7k4rDmrsMD1TvRywm::ScheduleUpdate 是唯一 1084B 帧构建者;
// 帧 0x22-0x25 = BBFrDpcLsuZ[0] = EncryptFrameData 返回值 r1 (Random.Next(), MSB=0 已实证),
// 0x26-0x29 = r2。若走加密路径 (jTErDRqkhb1()==true), 通道数据经:
// kWarv47o79k (新): out[i] = src[i] ^ ((512-i)&255) ^ key[(512-i)&3], key = r ^ B12rvMuR8pT
// zPprv0TXRRW (旧): out[i] = src[(i+rot)%512] ^ cnt[i], rot = (B12 ^ r) & 511
// B12rvMuR8pT = AgiosElias 实例随机 = AECH 质询 0x8C-0x8F (会话内不变)。
// 验证: 用 r (帧内 0x22-0x25) + B12 (TSV 内 AECH 质询) 解密, 若明文跨帧稳定/稀疏 → 假说成立。
pub fn dmxdec(tsv: &str) {
let content = read_tsv(tsv);
let mut b12: Option<u32> = None;
let mut frames: Vec<Vec<u8>> = Vec::new();
for line in content.lines() {
let p: Vec<&str> = line.split('\t').collect();
if p.len() < 5 || p[4].is_empty() { continue; }
let b = match h2b(p[4]) { Some(b) => b, None => continue };
if b.len() == 552 && &b[0x28..0x2C] == b"AECH" && b12.is_none() {
b12 = Some(u32::from_le_bytes([b[0x8C], b[0x8D], b[0x8E], b[0x8F]]));
}
if b.len() == 1084 && b[0x20] == 0x01 && b[0x21] == 0x01 {
frames.push(b);
}
}
let b12 = match b12 { Some(v) => v, None => { println!("TSV 内无 AECH 质询, 无法取 B12"); return; } };
if frames.is_empty() { println!("无 DMX 帧"); return; }
println!("B12 = 0x{:08x}, DMX 帧 {} 个", b12, frames.len());
// --- kWarv47o79k 逆运算 (XOR 自逆): src[i] = out[i] ^ ((512-i)&255) ^ key[(512-i)&3] ---
let dec_new = |b: &Vec<u8>, off: usize, r: u32| -> Vec<u8> {
let key = r ^ b12;
let kb = key.to_le_bytes();
(0..512).map(|i| {
let j = 512 - i;
b[off + i] ^ (j & 0xFF) as u8 ^ kb[j & 3]
}).collect()
};
// --- zPprv0TXRRW 逆运算 (共享实现, 见 agios_dmx_decrypt) ---
let dec_old = |b: &Vec<u8>, off: usize, r: u32| -> Vec<u8> {
agios_dmx_decrypt(&b[off..off + 512], r, b12)
};
for (name, is_new) in [("kWarv47o79k(新)", true), ("zPprv0TXRRW(旧)", false)] {
// 解密前 3 帧端口1, 看明文是否稳定/稀疏
let mut stable = 0;
let mut prev: Option<Vec<u8>> = None;
let mut first_plain: Vec<u8> = Vec::new();
for (fi, f) in frames.iter().take(50).enumerate() {
let r1 = u32::from_le_bytes([f[0x22], f[0x23], f[0x24], f[0x25]]);
let plain = if is_new { dec_new(f, 0x3C, r1) } else { dec_old(f, 0x3C, r1) };
if fi == 0 { first_plain = plain.clone(); }
if let Some(p) = &prev {
if *p == plain { stable += 1; }
}
prev = Some(plain);
}
let nonzero = first_plain.iter().filter(|&&v| v != 0).count();
println!("\n{}: 相邻帧明文相同 {}/49, 首帧明文非零字节 {}/512", name, stable, nonzero);
print!(" 首帧明文 CH1-32: ");
for v in first_plain.iter().take(32) { print!("{:02x}", v); }
println!();
}
// --- 全捕获扫描: 双端口解密, 统计非零明文帧 (用户是否发过真实场景) ---
// --- 同时做 encrypt∘decrypt 回环验证 (主机侧复现的正确性) ---
let mut nz_frames = 0;
let mut roundtrip_ok = 0usize;
let mut sample: Option<(usize, Vec<u8>, Vec<u8>)> = None;
for (fi, f) in frames.iter().enumerate() {
let r1 = u32::from_le_bytes([f[0x22], f[0x23], f[0x24], f[0x25]]);
let r2 = u32::from_le_bytes([f[0x26], f[0x27], f[0x28], f[0x29]]);
let p1 = agios_dmx_decrypt(&f[0x3C..0x3C + 512], r1, b12);
let p2 = agios_dmx_decrypt(&f[0x23C..0x23C + 512], r2, b12);
if agios_dmx_encrypt(&p1, r1, b12) == f[0x3C..0x3C + 512]
&& agios_dmx_encrypt(&p2, r2, b12) == f[0x23C..0x23C + 512]
{
roundtrip_ok += 1;
}
let nz = p1.iter().chain(p2.iter()).filter(|&&v| v != 0).count();
if nz > 0 {
nz_frames += 1;
if sample.is_none() { sample = Some((fi, p1, p2)); }
}
}
println!("\n全捕获双端口解密: 非零明文帧 {}/{}, 加解密回环 {}/{}", nz_frames, frames.len(), roundtrip_ok, frames.len());
if let Some((fi, p1, p2)) = sample {
let show = |v: &Vec<u8>| -> String {
v.iter().enumerate().filter(|(_, &x)| x != 0).take(16)
.map(|(i, &x)| format!("ch{}={}", i + 1, x)).collect::<Vec<_>>().join(" ")
};
println!(" 首个非零帧 #{}: port1[{}] port2[{}]", fi, show(&p1), show(&p2));
}
}
// ---------- dmxroll: DMX 帧滚动值/保护字节数据驱动分析 ----------
// 帧布局 (§4.1): seq@0x1A LE u16, guard1@0x22=(CH1-1)^K, roll1@0x23-0x25,
// guard2@0x26=(CH513-1)^K, roll2@0x27-0x29, node@0x2E
// 检验: roll 是否 = G(seq^K) 之类的公开函数; roll1/roll2 关系; 跨帧步进规律
pub fn dmxroll(tsv: &str) {
let content = read_tsv(tsv);
let mut frames: Vec<(u16, u8, [u8; 3], u8, [u8; 3], u8, u8)> = Vec::new(); // seq,g1,r1,g2,r2,node,ch1
for line in content.lines() {
let p: Vec<&str> = line.split('\t').collect();
if p.len() < 5 || p[4].is_empty() { continue; }
let b = match h2b(p[4]) { Some(b) => b, None => continue };
if b.len() != 1084 || b[0x20] != 0x01 || b[0x21] != 0x01 { continue; }
let seq = u16::from_le_bytes([b[0x1A], b[0x1B]]);
frames.push((seq, b[0x22], [b[0x23], b[0x24], b[0x25]], b[0x26], [b[0x27], b[0x28], b[0x29]], b[0x2E], b[0x3C]));
}
if frames.is_empty() { println!("无 DMX 帧"); return; }
println!("DMX 帧 {} 个", frames.len());
// K 导出 (guard1 ^ (CH1-1))
let k = frames[0].1 ^ frames[0].6.wrapping_sub(1);
println!("会话 K = 0x{:02x}", k);
// roll1 vs roll2 关系
let eq = frames.iter().filter(|f| f.2 == f.4).count();
println!("roll1==roll2: {}/{}", eq, frames.len());
// roll 是否随 seq 确定性变化: 同 seq 是否同 roll
let mut by_seq: std::collections::HashMap<u16, Vec<([u8; 3], [u8; 3])>> = std::collections::HashMap::new();
for f in &frames { by_seq.entry(f.0).or_default().push((f.2, f.4)); }
let mut det = 0; let mut nondet = 0;
for (_, v) in &by_seq {
if v.iter().all(|x| *x == v[0]) { det += 1; } else { nondet += 1; }
}
println!("同seq同roll(确定性): {} seqs; 同seq异roll(含随机): {} seqs", det, nondet);
// 前 16 帧明细
println!("\nseq | g1 | roll1 | g2 | roll2 | node | G(seq)^? 对照");
for f in frames.iter().take(16) {
let g = agios_g(f.0 as u32);
println!("{:06} | {:02x} | {:02x}{:02x}{:02x} | {:02x} | {:02x}{:02x}{:02x} | {:02x} | G(seq)={:08x} G(seq^K)={:08x}",
f.0, f.1, f.2[0], f.2[1], f.2[2], f.3, f.4[0], f.4[1], f.4[2], f.5, g, agios_g((f.0 as u32) ^ (k as u32)));
}
// roll1 24bit 与 seq 的直接关系扫描: roll1 ^ seq / roll1 - seq 恒定?
let r1_24 = |f: &(u16, u8, [u8; 3], u8, [u8; 3], u8, u8)| ->
u32 { (f.2[0] as u32) | (f.2[1] as u32) << 8 | (f.2[2] as u32) << 16 };
let x0 = r1_24(&frames[0]) ^ frames[0].0 as u32;
let xor_const = frames.iter().all(|f| (r1_24(f) ^ f.0 as u32) == x0);
let d0 = r1_24(&frames[0]).wrapping_sub(frames[0].0 as u32);
let sub_const = frames.iter().all(|f| r1_24(f).wrapping_sub(f.0 as u32) == d0);
println!("\nroll1^seq 恒定: {} (0x{:06x}); roll1-seq 恒定: {} (0x{:06x})", xor_const, x0, sub_const, d0);
// roll1 逐帧差分规律
let mut diffs: std::collections::HashMap<u32, u32> = std::collections::HashMap::new();
for w in frames.windows(2) {
if w[1].0 == w[0].0.wrapping_add(1) {
let d = r1_24(&w[1]).wrapping_sub(r1_24(&w[0]));
*diffs.entry(d).or_default() += 1;
}
}
let mut dv: Vec<_> = diffs.iter().collect();
dv.sort_by_key(|(_, c)| std::cmp::Reverse(**c));
println!("roll1 逐帧差分 top5: {:?}", dv.iter().take(5).map(|(d, c)| format!("0x{:06x}x{}", d, c)).collect::<Vec<_>>());
// G(seq), G(seq)^K, G(seq+k) 等与 roll 的匹配率
let test_fn = |name: &str, f: &dyn Fn(u16) -> u32| {
let hit = frames.iter().filter(|fr| {
let v = f(fr.0);
let lo3 = [v as u8, (v >> 8) as u8, (v >> 16) as u8];
let hi3 = [(v >> 8) as u8, (v >> 16) as u8, (v >> 24) as u8];
lo3 == fr.2 || hi3 == fr.2
}).count();
println!(" {} 匹配 roll1: {}/{}", name, hit, frames.len());
};
println!("\n候选函数 vs roll1:");
test_fn("G(seq)", &|s| agios_g(s as u32));
test_fn("G(seq^K)", &|s| agios_g((s as u32) ^ (k as u32)));
test_fn("G(seq+K)", &|s| agios_g((s as u32).wrapping_add(k as u32)));
test_fn("G(seq)^G(K)", &|s| agios_g(s as u32) ^ agios_g(k as u32));
test_fn("(seq^K)*7", &|s| ((s as u32) ^ (k as u32)).wrapping_mul(7));
// --- roll2(seq) == roll1(seq+512)? (端口2 = 端口1 偏移 512) ---
let map1: std::collections::HashMap<u16, [u8; 3]> = frames.iter().map(|f| (f.0, f.2)).collect();
let mut off_hit = 0; let mut off_tot = 0;
for f in &frames {
if let Some(r) = map1.get(&f.0.wrapping_add(512)) {
off_tot += 1;
if *r == f.4 { off_hit += 1; }
}
}
println!("\nroll2(seq) == roll1(seq+512): {}/{}", off_hit, off_tot);
// --- PRNG 流分析 (按到达顺序; seq 不重复, "同seq同roll" 判定无效) ---
let r1s: Vec<u32> = frames.iter().map(|f| (f.2[0] as u32) | (f.2[1] as u32) << 8 | (f.2[2] as u32) << 16).collect();
let r2s: Vec<u32> = frames.iter().map(|f| (f.4[0] as u32) | (f.4[1] as u32) << 8 | (f.4[2] as u32) << 16).collect();
// 交叉: roll2[i] == roll1[i+1]? (共享 PRNG, 端口2 取下一个)
let cross = (0..r1s.len() - 1).filter(|&i| r2s[i] == r1s[i + 1]).count();
println!("roll2[i]==roll1[i+1]: {}/{}", cross, r1s.len() - 1);
// LCG mod 2^24 求解: r1 = (a*r0+c) mod 2^24 — 从 3 个连续值解 a,c 并全验证
let solve_lcg = |v: &[u32]| {
let m = 1u64 << 24;
let mut hits = 0;
for start in 0..v.len().saturating_sub(3).min(200) {
let (r0, r1, r2) = (v[start] as u64, v[start + 1] as u64, v[start + 2] as u64);
let d1 = (r1 + m - r0) % m; // r1-r0
let d2 = (r2 + m - r1) % m; // r2-r1
if d1 % 2 == 0 { continue; } // 不可逆, 跳过
// d1 的模逆 (牛顿迭代, mod 2^24)
let mut inv = d1;
for _ in 0..5 { inv = inv.wrapping_mul(2u64.wrapping_sub(d1.wrapping_mul(inv))) % m; }
let a = d2 * inv % m;
let c = (r1 + m - a * r0 % m) % m;
let mut ok = true;
let mut cur = v[start] as u64;
for j in start + 1..v.len().min(start + 500) {
cur = (a * cur + c) % m;
if cur != v[j] as u64 { ok = false; break; }
}
if ok {
println!(" ★ LCG(2^24) 命中 @{}: a=0x{:06x} c=0x{:06x}", start, a, c);
hits += 1;
if hits > 3 { return; }
}
}
if hits == 0 { println!(" LCG(2^24) 无命中"); }
};
println!("roll1 流 LCG 测试:"); solve_lcg(&r1s);
println!("roll2 流 LCG 测试:"); solve_lcg(&r2s);
// LCG mod 2^32 (4B 状态, roll 为低 24 位?): 用 r1s 与 r2s 交错序列试
// 交错序列: roll1[0], roll2[0], roll1[1], roll2[1], ...
let inter: Vec<u32> = r1s.iter().zip(r2s.iter()).flat_map(|(a, b)| [*a, *b]).collect();
println!("交错流 LCG 测试:"); solve_lcg(&inter);
// xorshift 粗测: r[i+1] ^ r[i] 的汉明重量分布
let hw: Vec<u32> = r1s.windows(2).map(|w| (w[1] ^ w[0]).count_ones()).collect();
let avg = hw.iter().sum::<u32>() as f64 / hw.len() as f64;
println!("roll1 相邻 XOR 平均汉明重: {:.1} (随机期望 12.0)", avg);
// --- K vs AECH 实例随机 (0x8C-0x8F) / 首个 nonce (0x88-0x8B) 相关测试 ---
// (仅含认领交换的 TSV 有效, 如 cold2/fix1)
let mut inst: Option<u32> = None;
let mut nonce1: Option<u32> = None;
{
let content2 = read_tsv(tsv);
for line in content2.lines() {
let p: Vec<&str> = line.split('\t').collect();
if p.len() < 5 || p[4].is_empty() { continue; }
let b = match h2b(p[4]) { Some(b) => b, None => continue };
if b.len() == 552 && &b[0x28..0x2C] == b"AECH" {
if inst.is_none() { inst = Some(u32::from_le_bytes([b[0x8C], b[0x8D], b[0x8E], b[0x8F]])); }
if nonce1.is_none() { nonce1 = Some(u32::from_le_bytes([b[0x88], b[0x89], b[0x8A], b[0x8B]])); }
if inst.is_some() && nonce1.is_some() { break; }
}
}
}
if let (Some(iv), Some(nv)) = (inst, nonce1) {
println!("\nAECH 实例=0x{:08x} nonce1=0x{:08x} vs K=0x{:02x}", iv, nv, k);
let tests: Vec<(&str, u32)> = vec![
("G(inst)", agios_g(iv)),
("G(nonce1)", agios_g(nv)),
("(inst^nonce1)*7", (iv ^ nv).wrapping_mul(7)),
("inst*7", iv.wrapping_mul(7)),
("G(inst)^G(nonce1)", agios_g(iv) ^ agios_g(nv)),
("G(inst^nonce1)", agios_g(iv ^ nv)),
("G(inst+nonce1)", agios_g(iv.wrapping_add(nv))),
];
for (name, v) in tests {
let hit = (0..4).any(|s| ((v >> (s * 8)) & 0xFF) == k as u32);
println!(" {}=0x{:08x} {}", name, v, if hit { "★ 含 K 字节!" } else { "" });
}
}
// --- roll1(seq) 的 GF(2) 仿射性检验 (16bit 输入空间) ---
// 仿射 iff 对任意 a,b,c: roll(a)^roll(b)^roll(c) == roll(a^b^c)
{
let keys: Vec<u16> = map1.keys().cloned().collect();
let mut rng = 0x12345678u32;
let mut ok = 0; let mut tot = 0;
for _ in 0..2000 {
rng = rng.wrapping_mul(1664525).wrapping_add(1013904223);
let a = keys[(rng >> 8) as usize % keys.len()];
rng = rng.wrapping_mul(1664525).wrapping_add(1013904223);
let b = keys[(rng >> 8) as usize % keys.len()];
rng = rng.wrapping_mul(1664525).wrapping_add(1013904223);
let c = keys[(rng >> 8) as usize % keys.len()];
if let Some(abc) = map1.get(&(a ^ b ^ c)) {
tot += 1;
let (ra, rb, rc) = (map1[&a], map1[&b], map1[&c]);
let x = [ra[0] ^ rb[0] ^ rc[0], ra[1] ^ rb[1] ^ rc[1], ra[2] ^ rb[2] ^ rc[2]];
if x == *abc { ok += 1; }
}
}
println!("roll1(seq) 仿射性: {}/{} 组通过", ok, tot);
if tot > 0 && ok == tot {
// 解仿射映射: roll(s) = M·s ^ c, 用单比特样本解 16 列
let zero = map1.get(&0).cloned();
let ref_pt = keys[0];
let c0 = map1[&ref_pt];
let mut cols: Vec<[u8; 3]> = Vec::new();
let mut solved = true;
for bit in 0..16 {
let want = ref_pt ^ (1u16 << bit);
match map1.get(&want) {
Some(r) => cols.push([r[0] ^ c0[0], r[1] ^ c0[1], r[2] ^ c0[2]]),
None => { solved = false; break; }
}
}
if solved {
println!(" 仿射映射已解出 (参考点 seq={})", ref_pt);
// 全样本验证
let predict = |s: u16| -> [u8; 3] {
let d = s ^ ref_pt;
let mut r = c0;
for bit in 0..16 {
if d & (1 << bit) != 0 {
r[0] ^= cols[bit][0]; r[1] ^= cols[bit][1]; r[2] ^= cols[bit][2];
}
}
r
};
let hit = frames.iter().filter(|f| predict(f.0) == f.2).count();
println!(" 全样本预测 roll1: {}/{}", hit, frames.len());
if let Some(z) = zero { println!(" roll(0) = {:02x}{:02x}{:02x}", z[0], z[1], z[2]); }
} else {
println!(" 单比特样本不足, 未解 (捕获窗口内 seq 范围有限)");
}
}
}
}
// ---------- 选择密文探测 (aechprobe 子命令): 对真机做 GF(2) 仿射性分析 ----------
//
// 原理: 若 resp32 = f(challenge) 是 GF(2) 仿射函数 (CRC 类/线性反馈类算法的共性),
// 则 f(x) = L(x) ^ c, 其中 c = f(0), L 为线性映射。用 1952 次单比特翻转查询
// 可完整学习 L 的矩阵 → 无需密钥即可对任意质询离线计算合法应答。
// 若不是仿射 (真 MAC), 线性验证步骤会立即暴露。
use std::net::UdpSocket;
use std::time::Duration;
/// 发送一帧质询, 返回应答 0xC0-0xC3 (u32 原生字节序保持 4B 数组)
fn query(s: &UdpSocket, target: &str, frame: &[u8]) -> Option<[u8; 4]> {
for _attempt in 0..2 {
if s.send_to(frame, target).is_err() { return None; }
let mut buf = [0u8; 2048];
match s.recv_from(&mut buf) {
Ok((n, _)) => {
let b = &buf[..n];
if n == 552 && &b[0x28..0x2C] == b"AERE" {
return Some([b[0xC0], b[0xC1], b[0xC2], b[0xC3]]);
}
}
Err(_) => continue,
}
}
None
}
pub fn probe(tsv: &str, target: &str) {
// 从 TSV 取首个 AECH 质询作模板
let content = read_tsv(tsv);
let mut tmpl: Option<Vec<u8>> = None;
let mut tmpl_resp: Option<[u8; 4]> = None;
for line in content.lines() {
let p: Vec<&str> = line.split('\t').collect();
if p.len() < 5 || p[4].is_empty() { continue; }
let b = match h2b(p[4]) { Some(b) => b, None => continue };
if b.len() == 552 && &b[0..4] == b"ENet" && &b[0x28..0x2C] == b"AECH" && tmpl.is_none() {
tmpl = Some(b.clone());
}
if b.len() == 552 && &b[0x28..0x2C] == b"AERE" && tmpl_resp.is_none() {
tmpl_resp = Some([b[0xC0], b[0xC1], b[0xC2], b[0xC3]]);
}
if tmpl.is_some() && tmpl_resp.is_some() { break; }
}
let tmpl = tmpl.expect("TSV 中无 AECH 质询模板");
println!("模板质询已载入, 抓包时应答 resp32={}", tmpl_resp.map_or("?".into(), |r| hexr(&r)));
let s = UdpSocket::bind("0.0.0.0:0").expect("bind 失败");
s.set_read_timeout(Some(Duration::from_millis(300))).ok();
let tgt = format!("{}:31772", target);
// --- 阶段 1: 确定性验证 (原样重放 ×3) ---
println!("\n--- 阶段 1: 确定性 (原样重放×3) ---");
let mut base: Option<[u8; 4]> = None;
for i in 0..3 {
match query(&s, &tgt, &tmpl) {
Some(r) => {
println!(" 重放#{}: resp32={}", i + 1, hexr(&r));
if let Some(b0) = base { if b0 != r { println!(" ✗ 不确定性! 含设备侧随机"); return; } }
base = Some(r);
}
None => { println!(" 重放#{}: 无应答 — 模板被拒?", i + 1); return; }
}
}
let base = base.unwrap();
if let Some(tr) = tmpl_resp {
println!(" 与抓包值 {} => {}", hexr(&tr), if tr == base { "一致 ✓ (完全确定)" } else { "不同 ✗" });
}
// --- 阶段 2: 输入域结构 (哪些区域影响 resp) ---
println!("\n--- 阶段 2: 输入域探测 ---");
let probe_region = |name: &str, f: &mut dyn FnMut(&mut Vec<u8>)| {
let mut m = tmpl.clone();
f(&mut m);
match query(&s, &tgt, &m) {
Some(r) => println!(" {}: resp32={} {}", name, hexr(&r),
if r == base { "(=基线, 不影响)" } else { "(≠基线, ★影响)" }),
None => println!(" {}: 无应答", name),
}
std::thread::sleep(Duration::from_millis(5));
};
probe_region("全 blob 清零 (0x2C-0x123)", &mut |m| { for i in 0x2C..0x124 { m[i] = 0; } });
probe_region("blob 全 FF", &mut |m| { for i in 0x2C..0x124 { m[i] = 0xFF; } });
probe_region("仅 part1 清零 (0x2C-0x8B)", &mut |m| { for i in 0x2C..0x8C { m[i] = 0; } });
probe_region("仅 part2 清零 (0x90-0x123)", &mut |m| { for i in 0x90..0x124 { m[i] = 0; } });
probe_region("静态缝 0x8C-0x8F 翻bit0", &mut |m| { m[0x8C] ^= 1; });
probe_region("头部 0x00 翻bit", &mut |m| { m[0x00] ^= 1; });
probe_region("长度域 0x14 翻bit", &mut |m| { m[0x14] ^= 1; });
probe_region("主机IP 0x1A 翻bit", &mut |m| { m[0x1A] ^= 1; });
probe_region("主机MAC 0x1E 翻bit", &mut |m| { m[0x1E] ^= 1; });
probe_region("0x24 翻bit", &mut |m| { m[0x24] ^= 1; });
probe_region("尾字节 0x227 翻bit", &mut |m| { m[0x227] ^= 1; });
// --- 阶段 3: 仿射性抽样验证 ---
// f(a)^f(b) == f(a^b)^f(0) 对随机 a,b 成立 ⇒ 仿射
println!("\n--- 阶段 3: 仿射性抽样验证 (8 组) ---");
let mut seed = 0x9e3779b9u32;
let mut rnd = move || { seed = seed.wrapping_mul(2654435761).wrapping_add(0x9e3779b9); seed };
// f(0): blob 全零
let mut z = tmpl.clone();
for i in 0x2C..0x124 { z[i] = 0; }
let f0 = match query(&s, &tgt, &z) {
Some(r) => r,
None => { println!(" 零 blob 无应答, 仿射验证跳过"); return; }
};
println!(" f(零blob) = {}", hexr(&f0));
let mut affine_ok = 0;
for t in 0..8 {
let mut a = tmpl.clone(); let mut b = tmpl.clone();
for i in 0x2C..0x124 {
if rnd() & 1 != 0 { a[i] ^= 1 << ((rnd() >> 8) & 7); }
if rnd() & 1 != 0 { b[i] ^= 1 << ((rnd() >> 8) & 7); }
}
let mut axb = a.clone();
for i in 0x2C..0x124 { axb[i] = a[i] ^ b[i] ^ tmpl[i]; } // axb = a^b (相对模板)
// 需要绝对: a^b 的 blob = a.blob ^ b.blob ^ 0? 直接用 a.blob ^ b.blob 作为新 blob
for i in 0x2C..0x124 { axb[i] = a[i] ^ b[i]; }
let fa = query(&s, &tgt, &a);
let fb = query(&s, &tgt, &b);
let faxb = query(&s, &tgt, &axb);
std::thread::sleep(Duration::from_millis(5));
match (fa, fb, faxb) {
(Some(fa), Some(fb), Some(faxb)) => {
// f(a)^f(b)^f(a^b) 应 == f(0) (仿射)
let mut lhs = [0u8; 4];
for i in 0..4 { lhs[i] = fa[i] ^ fb[i] ^ faxb[i]; }
let ok = lhs == f0;
if ok { affine_ok += 1; }
println!(" 组{}: f(a)^f(b)^f(a^b)={} vs f(0)={} {}", t + 1, hexr(&lhs), hexr(&f0),
if ok { "✓" } else { "✗ 非仿射!" });
}
_ => println!(" 组{}: 有无应答缺失, 跳过", t + 1),
}
}
if affine_ok < 8 {
println!("\n仿射性未全过 ({}/8) — 大概率为真 MAC (HMAC/AES 类), 样本无法离线伪造", affine_ok);
println!("退路: 继续中继方案, 或分析固件/dongle 算法");
return;
}
// --- 阶段 4: 全量单比特翻转, 学习线性映射矩阵 ---
println!("\n--- 阶段 4: 学习线性映射 (1952 比特 × 1 查询) ---");
// L(e_i) = f(e_i) ^ f(0), 对 blob 0x2C..0x124 的每比特
let nbits = (0x124 - 0x2C) * 8;
let mut cols: Vec<[u8; 4]> = Vec::with_capacity(nbits);
let mut fails = 0;
for bit in 0..nbits {
let off = 0x2C + bit / 8;
let mut m = z.clone(); // 基于零 blob
m[off] = 1 << (bit % 8);
match query(&s, &tgt, &m) {
Some(r) => {
let mut c = [0u8; 4];
for i in 0..4 { c[i] = r[i] ^ f0[i]; }
cols.push(c);
}
None => { fails += 1; cols.push([0; 4]); }
}
if bit % 200 == 0 { println!(" 进度 {}/{} (失败 {})", bit, nbits, fails); }
std::thread::sleep(Duration::from_millis(3));
}
println!(" 矩阵学习完成: {} 列, 失败 {}", cols.len(), fails);
// 验证: 对模板质询用矩阵计算, 应得 base
let mut calc = f0;
for bit in 0..nbits {
let off = 0x2C + bit / 8;
if tmpl[off] & (1 << (bit % 8)) != 0 {
for i in 0..4 { calc[i] ^= cols[bit][i]; }
}
}
println!(" 矩阵验证: 计算={} vs 实测={} {}", hexr(&calc), hexr(&base),
if calc == base { "✓✓ 仿射模型成立!" } else { "✗" });
// 保存矩阵 (f0 + 列) 供 .7 离线应答用
let mut out = String::new();
out.push_str(&format!("# AECH 仿射映射: resp32 = f0 ^ XOR_{{blob bit=1}} col[bit] (目标 {})\n", target));
out.push_str(&format!("f0={}\n", hexr(&f0)));
for (i, c) in cols.iter().enumerate() {
out.push_str(&format!("{}\n", hexr(c)));
let _ = i;
}
let path = "aech_affine_map.txt";
std::fs::write(path, &out).ok();
println!(" 映射已保存: {} ({} 列)", path, cols.len());
}
// ---------- probe2: 96B part1 的块/字节影响图谱 ----------
// resp32 = f(chal[0x2C..0x8C]) 已实证 (probe 阶段2)。96B = 12×8B 分组密码直觉。
// 测绘: 每字节翻转 1bit 看 resp 雪崩模式; 每 8B 块清零看是否影响; 换序测试。
pub fn probe2(tsv: &str, target: &str) {
let content = read_tsv(tsv);
let mut tmpl: Option<Vec<u8>> = None;
for line in content.lines() {
let p: Vec<&str> = line.split('\t').collect();
if p.len() < 5 || p[4].is_empty() { continue; }
let b = match h2b(p[4]) { Some(b) => b, None => continue };
if b.len() == 552 && &b[0..4] == b"ENet" && &b[0x28..0x2C] == b"AECH" {
tmpl = Some(b); break;
}
}
let tmpl = tmpl.expect("无模板");
let s = UdpSocket::bind("0.0.0.0:0").expect("bind 失败");
s.set_read_timeout(Some(Duration::from_millis(300))).ok();
let tgt = format!("{}:31772", target);
let base = match query(&s, &tgt, &tmpl) {
Some(r) => r,
None => { println!("基线无应答"); return; }
};
println!("基线 resp32={}", hexr(&base));
// --- 字节影响图谱: part1 每字节翻 bit0 ---
println!("\n--- 字节影响图谱 (0x2C-0x8B, 翻转 bit0) ---");
println!("off | resp32 | 变化bit数 | 变化字节位置");
let mut infl: Vec<(usize, u32)> = Vec::new();
for off in 0x2C..0x8C {
let mut m = tmpl.clone();
m[off] ^= 1;
match query(&s, &tgt, &m) {
Some(r) => {
let x = u32::from_be_bytes([r[0] ^ base[0], r[1] ^ base[1], r[2] ^ base[2], r[3] ^ base[3]]);
let nbits = x.count_ones();
let pos: Vec<String> = (0..4).filter(|&i| r[i] != base[i]).map(|i| format!("B{}", i)).collect();
println!("0x{:02x} | {} | {:2} | {}", off, hexr(&r), nbits, pos.join(""));
infl.push((off, nbits));
}
None => println!("0x{:02x} | 无应答", off),
}
std::thread::sleep(Duration::from_millis(3));
}
let zero_infl: Vec<usize> = infl.iter().filter(|(_, n)| *n == 0).map(|(o, _)| *o).collect();
println!("不影响的字节: {:?}", zero_infl.iter().map(|o| format!("0x{:02x}", o)).collect::<Vec<_>>());
// --- 8B 块清零图谱 ---
println!("\n--- 8B 块清零图谱 ---");
for blk in 0..12 {
let mut m = tmpl.clone();
for i in 0..8 { m[0x2C + blk * 8 + i] = 0; }
match query(&s, &tgt, &m) {
Some(r) => println!(" 块{:2} (0x{:02x}-0x{:02x}) 清零: {} {}", blk, 0x2C + blk * 8, 0x2C + blk * 8 + 7,
hexr(&r), if r == base { "(不影响)" } else { "★影响" }),
None => println!(" 块{:2}: 无应答", blk),
}
std::thread::sleep(Duration::from_millis(3));
}
// --- 换序测试 ---
println!("\n--- 换序测试 ---");
let mut m1 = tmpl.clone();
m1.swap(0x2C, 0x2D);
match query(&s, &tgt, &m1) {
Some(r) => println!(" 相邻字节交换 (0x2C<->0x2D): {} {}", hexr(&r), if r == base { "(顺序无关!)" } else { "顺序相关" }),
None => println!(" 交换: 无应答"),
}
let mut m2 = tmpl.clone();
m2[0x2C..0x8C].reverse();
match query(&s, &tgt, &m2) {
Some(r) => println!(" part1 整体反转: {} {}", hexr(&r), if r == base { "(顺序无关!)" } else { "顺序相关" }),
None => println!(" 反转: 无应答"),
}
// 块交换: 块0 <-> 块1
let mut m3 = tmpl.clone();
for i in 0..8 { m3.swap(0x2C + i, 0x34 + i); }
match query(&s, &tgt, &m3) {
Some(r) => println!(" 块0<->块1 交换: {} {}", hexr(&r), if r == base { "(块序无关!)" } else { "块序相关" }),
None => println!(" 块交换: 无应答"),
}
// --- 整数加性测试: 首字节 +1 (整数) vs 翻 bit0 ---
println!("\n--- 整数加性粗测 ---");
let mut m4 = tmpl.clone();
m4[0x2C] = m4[0x2C].wrapping_add(1);
match query(&s, &tgt, &m4) {
Some(r) => println!(" chal[0x2C]+=1: {}", hexr(&r)),
None => println!(" 无应答"),
}
}
// ---------- identify: f(last4) 的离线候选函数鉴别 ----------
// probe2 实证: resp32 = f(chal[0x88..0x8C]), 仅 4B 输入。用手头全部 (输入,输出) 对
// 测试常见 32 位构造 (哈希截断/finalizer/零密钥分组密码), 命中即可离线伪造。
pub fn identify(tsv: &str) {
// 从 TSV 取 3 轮质询的 last4 + 对应 resp32 (抓包对)
let content = read_tsv(tsv);
let mut chals: Vec<(f64, Vec<u8>)> = Vec::new();
let mut resps: Vec<(f64, Vec<u8>)> = Vec::new();
for line in content.lines() {
let p: Vec<&str> = line.split('\t').collect();
if p.len() < 5 || p[4].is_empty() { continue; }
let b = match h2b(p[4]) { Some(b) => b, None => continue };
if b.len() != 552 { continue; }
let t: f64 = match p[0].parse() { Ok(t) => t, Err(_) => continue };
if &b[0x28..0x2C] == b"AECH" { chals.push((t, b)); }
else if &b[0x28..0x2C] == b"AERE" { resps.push((t, b)); }
}
let mut pairs: Vec<([u8; 4], [u8; 4])> = Vec::new();
for (rt, rb) in &resps {
let mut best: Option<&(f64, Vec<u8>)> = None;
for c in &chals { if c.0 <= *rt && rt - c.0 < 0.5 && best.map_or(true, |b| c.0 > b.0) { best = Some(c); } }
if let Some((_, cb)) = best {
pairs.push(([cb[0x88], cb[0x89], cb[0x8A], cb[0x8B]], [rb[0xC0], rb[0xC1], rb[0xC2], rb[0xC3]]));
}
}
// 追加 probe2 实测对: 零 / 全FF
pairs.push(([0, 0, 0, 0], [0xf6, 0x7f, 0xb7, 0xe3]));
pairs.push(([0xFF, 0xFF, 0xFF, 0xFF], [0x37, 0xac, 0x04, 0x6f]));
println!("鉴别样本 {} 对:", pairs.len());
let mut g_ok = 0;
for (x, y) in &pairs {
let xu = u32::from_le_bytes(*x);
let g = agios_g(xu).to_le_bytes();
let ok = g == *y;
if ok { g_ok += 1; }
println!(" {} -> {} | G()={} {}", hexr(x), hexr(y), hexr(&g), if ok { "✓" } else { "✗" });
}
println!("AgiosElias G 验证: {}/{} 命中", g_ok, pairs.len());
let t32 = {
let mut t = [0u32; 256];
for i in 0..256u32 { let mut c = i; for _ in 0..8 { c = if c & 1 != 0 { (c >> 1) ^ 0xEDB88320 } else { c >> 1 }; } t[i as usize] = c; }
t
};
// TEA / XTEA (零密钥)
fn tea(v0: u32, v1: u32, rounds: u32) -> (u32, u32) {
let (mut a, mut b, mut sum) = (v0, v1, 0u32);
for _ in 0..rounds {
sum = sum.wrapping_add(0x9E3779B9);
a = a.wrapping_add((b << 4) ^ b.wrapping_add(sum) ^ (b >> 5));
b = b.wrapping_add((a << 4) ^ a.wrapping_add(sum) ^ (a >> 5));
}
(a, b)
}
fn xtea(v0: u32, v1: u32, rounds: u32) -> (u32, u32) {
let (mut a, mut b, mut sum) = (v0, v1, 0u32);
let k = [0u32; 4];
for _ in 0..rounds {
a = a.wrapping_add(((b << 4) ^ (b >> 5)).wrapping_add(b) ^ sum.wrapping_add(k[(sum & 3) as usize]));
sum = sum.wrapping_add(0x9E3779B9);
b = b.wrapping_add(((a << 4) ^ (a >> 5)).wrapping_add(a) ^ sum.wrapping_add(k[((sum >> 11) & 3) as usize]));
}
(a, b)
}
// 32 位 finalizer 系列
fn fmix32(mut h: u32) -> u32 { h ^= h >> 16; h = h.wrapping_mul(0x85ebca6b); h ^= h >> 13; h = h.wrapping_mul(0xc2b2ae35); h ^= h >> 16; h }
fn splitmix32(mut x: u32) -> u32 { x = x.wrapping_add(0x9e3779b9); x = (x ^ (x >> 16)).wrapping_mul(0x21f0aaad); x = (x ^ (x >> 15)).wrapping_mul(0x735a2d97); x ^ (x >> 15) }
fn xxh_avalanche(mut h: u32) -> u32 { h ^= h >> 15; h = h.wrapping_mul(0x85EBCA77); h ^= h >> 13; h = h.wrapping_mul(0xC2B2AE3D); h ^= h >> 16; h }
fn wang(mut h: u32) -> u32 { h = (h ^ 61) ^ (h >> 16); h = h.wrapping_add(h << 3); h ^= h >> 4; h = h.wrapping_mul(0x27d4eb2d); h ^= h >> 15; h }
fn joaatf(b: &[u8]) -> u32 { let mut h = 0u32; for &x in b { h = h.wrapping_add(x as u32); h = h.wrapping_add(h << 10); h ^= h >> 6; } h = h.wrapping_add(h << 3); h ^= h >> 11; h.wrapping_add(h << 15) }
fn fnv(b: &[u8]) -> u32 { let mut h = 0x811c9dc5u32; for &x in b { h ^= x as u32; h = h.wrapping_mul(0x01000193); } h }
fn murmur3(b: &[u8]) -> u32 {
let mut h = 0u32; let c1 = 0xcc9e2d51u32; let c2 = 0x1b873593u32;
if b.len() >= 4 {
let mut k = u32::from_le_bytes([b[0], b[1], b[2], b[3]]);
k = k.wrapping_mul(c1); k = k.rotate_left(15); k = k.wrapping_mul(c2);
h ^= k; h = h.rotate_left(13); h = h.wrapping_mul(5).wrapping_add(0xe6546b64);
}
h ^= b.len() as u32; fmix32(h)
}
fn xxh32one(b: &[u8]) -> u32 {
// xxHash32 对 4B 输入 (seed=0): h = seed+P5+len; h += k*P3; rotl17; *P4; avalanche
let mut h = 0x165667B1u32.wrapping_add(4);
let k = u32::from_le_bytes([b[0], b[1], b[2], b[3]]);
h = h.wrapping_add(k.wrapping_mul(0xC2B2AE3D)).rotate_left(17).wrapping_mul(0x27D4EB2F);
xxh_avalanche(h)
}
type F = Box<dyn Fn([u8; 4]) -> u32>;
let cands: Vec<(&str, F)> = vec![
("MD5[0..4]LE", Box::new(move |x| { let m = md5(&x); u32::from_le_bytes([m[0], m[1], m[2], m[3]]) })),
("MD5[0..4]BE", Box::new(move |x| { let m = md5(&x); u32::from_be_bytes([m[0], m[1], m[2], m[3]]) })),
("SHA1[0..4]LE", Box::new(move |x| { let m = sha1(&x); u32::from_le_bytes([m[0], m[1], m[2], m[3]]) })),
("SHA1[0..4]BE", Box::new(move |x| { let m = sha1(&x); u32::from_be_bytes([m[0], m[1], m[2], m[3]]) })),
("CRC32", Box::new(move |x| crc32(&t32, &x))),
("Murmur3seed0", Box::new(move |x| murmur3(&x))),
("xxHash32seed0", Box::new(move |x| xxh32one(&x))),
("FNV1a32", Box::new(move |x| fnv(&x))),
("Jenkins", Box::new(move |x| joaatf(&x))),
("fmix32(xLE)", Box::new(move |x| fmix32(u32::from_le_bytes(x)))),
("fmix32(xBE)", Box::new(move |x| fmix32(u32::from_be_bytes(x)))),
("splitmix32(xLE)", Box::new(move |x| splitmix32(u32::from_le_bytes(x)))),
("splitmix32(xBE)", Box::new(move |x| splitmix32(u32::from_be_bytes(x)))),
("xxhAvalanche(xLE)", Box::new(move |x| xxh_avalanche(u32::from_le_bytes(x)))),
("xxhAvalanche(xBE)", Box::new(move |x| xxh_avalanche(u32::from_be_bytes(x)))),
("Wang(xLE)", Box::new(move |x| wang(u32::from_le_bytes(x)))),
("Wang(xBE)", Box::new(move |x| wang(u32::from_be_bytes(x)))),
("TEA16zerok(out0LE)", Box::new(move |x| tea(u32::from_le_bytes(x), 0, 16).0)),
("TEA32zerok(out0LE)", Box::new(move |x| tea(u32::from_le_bytes(x), 0, 32).0)),
("XTEA32zerok(out0LE)", Box::new(move |x| xtea(u32::from_le_bytes(x), 0, 32).0)),
("TEA32zerok(out0BE)", Box::new(move |x| tea(u32::from_be_bytes(x), 0, 32).0)),
("恒等LE", Box::new(move |x| u32::from_le_bytes(x))),
("恒等BE", Box::new(move |x| u32::from_be_bytes(x))),
];
println!("\n--- 候选鉴别 (须命中全部 {} 对) ---", pairs.len());
let mut any = false;
for (name, f) in &cands {
for endian in ["BEout", "LEout"] {
let ok = pairs.iter().all(|(x, y)| {
let v = f(*x);
let out = if endian == "BEout" { v.to_be_bytes() } else { v.to_le_bytes() };
out == *y
});
if ok { println!(" ★★★ 命中: {} {}", name, endian); any = true; }
}
}
if !any { println!(" (全部候选均未命中 — f 为带密钥函数的可能性大)"); }
}
fn hexr(b: &[u8]) -> String {
b.iter().map(|x| format!("{:02x}", x)).collect::<String>()
}
/// 从 TSV 提取 (质询, 应答) 对: AECH(552B, 含 "AECH"@0x28) 后 50ms 内的 AERE(552B, "AERE"@0x28)
fn load_pairs(tsv: &str) -> Vec<(f64, Vec<u8>, Vec<u8>)> {
let content = read_tsv(tsv);
let mut chals: Vec<(f64, Vec<u8>)> = Vec::new();
let mut resps: Vec<(f64, Vec<u8>)> = Vec::new();
for line in content.lines() {
let p: Vec<&str> = line.split('\t').collect();
if p.len() < 5 || p[4].is_empty() { continue; }
let b = match h2b(p[4]) { Some(b) => b, None => continue };
if b.len() != 552 { continue; }
if &b[0..4] != b"ENet" { continue; }
let t: f64 = match p[0].parse() { Ok(t) => t, Err(_) => continue };
if &b[0x28..0x2C] == b"AECH" { chals.push((t, b)); }
else if &b[0x28..0x2C] == b"AERE" { resps.push((t, b)); }
}
let mut pairs = Vec::new();
for (rt, rb) in &resps {
// 最近的先前质询 (500ms 窗, 兼容中继路径 ~140ms 延迟)
let mut best: Option<&(f64, Vec<u8>)> = None;
for c in &chals {
if c.0 <= *rt && rt - c.0 < 0.5 {
if best.map_or(true, |b| c.0 > b.0) { best = Some(c); }
}
}
if let Some((ct, cb)) = best {
pairs.push((*ct, cb.clone(), rb.clone()));
}
}
println!(" {}: AECH={} AERE={} -> 配对 {}", tsv, chals.len(), resps.len(), pairs.len());
pairs
}
/// 同一 TSV 内提取 DMX 会话 K (保护字节 ^ (CH-1), §4.1)
fn session_k(tsv: &str) -> Option<u8> {
let content = read_tsv(tsv);
for line in content.lines() {
let p: Vec<&str> = line.split('\t').collect();
if p.len() < 5 || p[4].is_empty() { continue; }
let b = match h2b(p[4]) { Some(b) => b, None => continue };
// DMX 帧: 1084B, 0x20-0x21=0101, 保护字节 0x22 = (CH1-1)^K
if b.len() == 1084 && b[0x20] == 0x01 && b[0x21] == 0x01 {
let ch1 = b[0x3C];
return Some(b[0x22] ^ ch1.wrapping_sub(1));
}
}
None
}
pub fn run(tsvs: &[String]) {
let mut pairs: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
let mut ks: Vec<(String, Option<u8>)> = Vec::new();
for tsv in tsvs {
let ps = load_pairs(tsv);
let k = session_k(tsv);
ks.push((tsv.clone(), k));
for (_, c, r) in ps { pairs.push((c, r)); }
}
if pairs.len() < 2 {
println!("样本不足 ({} 对), 无法做跨样本测试", pairs.len());
return;
}
println!("\n=== 共 {} 对样本 ===", pairs.len());
for (i, (c, r)) in pairs.iter().enumerate() {
println!(" #{}: chal[0x2c..0x30]={} resp32={}", i,
hexr(&c[0x2c..0x30]), hexr(&r[0xC0..0xC4]));
}
for (t, k) in &ks {
println!(" 会话 K: {} -> {}", t, k.map_or("无 DMX".into(), |k| format!("0x{:02x}", k)));
}
// --- 结构: 应答跨样本变化字节确认 ---
let mut vary = Vec::new();
for i in 0..552 {
let v = pairs[0].1[i];
if pairs.iter().any(|(_, r)| r[i] != v) { vary.push(i); }
}
println!("\n应答跨样本变化字节: {:?}", vary.iter().map(|o| format!("0x{:02x}", o)).collect::<Vec<_>>());
// resp32 两种端序
let resp_le: Vec<u32> = pairs.iter().map(|(_, r)|
u32::from_le_bytes([r[0xC0], r[0xC1], r[0xC2], r[0xC3]])).collect();
let resp_be: Vec<u32> = pairs.iter().map(|(_, r)|
u32::from_be_bytes([r[0xC0], r[0xC1], r[0xC2], r[0xC3]])).collect();
// 多种 CRC32 多项式表 (反射式): IEEE / Castagnoli / Koopman / CRC-32M
let polys: [(&str, u32); 4] = [
("IEEE", 0xEDB88320), ("CRC32C", 0x82F63B78), ("CRC32K", 0xEB31D82E), ("CRC32M", 0xD5828281)];
let tables: Vec<(&str, [u32; 256])> = polys.iter().map(|(name, p)| {
let mut t = [0u32; 256];
for i in 0..256u32 {
let mut c = i;
for _ in 0..8 { c = if c & 1 != 0 { (c >> 1) ^ p } else { c >> 1 }; }
t[i as usize] = c;
}
(*name, t)
}).collect();
let n = pairs.len();
// --- A0: 同轮 质询→应答 字节回显扫描 ---
println!("--- A0: 同轮 chal→resp 回显/复制扫描 ---");
{
let mut hits = 0;
for ro in 0..552usize {
for co in 0..552usize {
if (0..n).all(|i| pairs[i].1[ro] == pairs[i].0[co]) {
// 排除恒等静态字节 (要求该字节跨样本有变化, 否则无信息量)
let v = pairs[0].1[ro];
if pairs.iter().any(|(_, r)| r[ro] != v) {
println!(" ★ resp[0x{:02x}] == chal[0x{:02x}] (全样本)", ro, co);
hits += 1;
}
}
}
}
if hits == 0 { println!(" (变化字节无直接回显)"); }
}
// --- A1: 无密钥 CRC32(多多项式, 任意切片) 直接命中 ---
println!("\n--- A1: 无密钥 CRC32(多項式×切片) 直接匹配 ---");
let mut hits = 0;
for (pname, tab) in &tables {
for start in 0..552usize {
for len in 1..=(552 - start) {
let c0 = crc32(tab, &pairs[0].0[start..start + len]);
for (ename, resp) in [("LE", &resp_le), ("BE", &resp_be)] {
if c0 == resp[0] && (1..n).all(|i| crc32(tab, &pairs[i].0[start..start + len]) == resp[i]) {
println!(" ★ 命中 {} [{}..{}] {} CRC32=resp32", pname, start, start + len, ename);
hits += 1;
}
}
}
}
}
if hits == 0 { println!(" (无)"); }
// --- B1: 密钥化 CRC32 仿射测试 (Δ 跨样本恒定, 多項式) ---
println!("--- B1: 密钥化 CRC32 仿射测试 (resp ^ CRC32_0(slice) = Δ 恒定) ---");
let mut hits = 0;
'outer: for (pname, tab) in &tables {
for start in 0..552usize {
for len in 1..=(552 - start) {
let d0le = resp_le[0] ^ crc32(tab, &pairs[0].0[start..start + len]);
let d0be = resp_be[0] ^ crc32(tab, &pairs[0].0[start..start + len]);
let okle = (1..n).all(|i| (resp_le[i] ^ crc32(tab, &pairs[i].0[start..start + len])) == d0le);
let okbe = !okle && (1..n).all(|i| (resp_be[i] ^ crc32(tab, &pairs[i].0[start..start + len])) == d0be);
if okle || okbe {
println!(" ★ 命中 {} [{}..{}] {} Δ=0x{:08x}", pname, start, start + len,
if okle { "LE" } else { "BE" }, if okle { d0le } else { d0be });
hits += 1;
if hits > 20 { println!(" (更多命中省略)"); break 'outer; }
}
}
}
}
if hits == 0 { println!(" (无)"); }
// --- A2: MD5/SHA1 任意切片, first4/last4 LE/BE ---
println!("--- A2: MD5/SHA1(切片) 截断匹配 ---");
let mut hits = 0;
for start in 0..552usize {
for len in 1..=(552 - start) {
let m0 = md5(&pairs[0].0[start..start + len]);
let s0 = sha1(&pairs[0].0[start..start + len]);
// 候选值: first4le/be, last4le/be (md5), first4le/be (sha1)
let cands: [(&str, u32); 6] = [
("MD5first4LE", u32::from_le_bytes([m0[0], m0[1], m0[2], m0[3]])),
("MD5first4BE", u32::from_be_bytes([m0[0], m0[1], m0[2], m0[3]])),
("MD5last4LE", u32::from_le_bytes([m0[12], m0[13], m0[14], m0[15]])),
("MD5last4BE", u32::from_be_bytes([m0[12], m0[13], m0[14], m0[15]])),
("SHA1first4LE", u32::from_le_bytes([s0[0], s0[1], s0[2], s0[3]])),
("SHA1first4BE", u32::from_be_bytes([s0[0], s0[1], s0[2], s0[3]])),
];
for (cname, cv) in cands {
for (ename, resp) in [("LE", &resp_le), ("BE", &resp_be)] {
if cv != resp[0] { continue; }
let ok = (1..n).all(|i| {
let m = md5(&pairs[i].0[start..start + len]);
let s = sha1(&pairs[i].0[start..start + len]);
let v = match cname {
"MD5first4LE" => u32::from_le_bytes([m[0], m[1], m[2], m[3]]),
"MD5first4BE" => u32::from_be_bytes([m[0], m[1], m[2], m[3]]),
"MD5last4LE" => u32::from_le_bytes([m[12], m[13], m[14], m[15]]),
"MD5last4BE" => u32::from_be_bytes([m[12], m[13], m[14], m[15]]),
"SHA1first4LE" => u32::from_le_bytes([s[0], s[1], s[2], s[3]]),
_ => u32::from_be_bytes([s[0], s[1], s[2], s[3]]),
};
v == resp[i]
});
if ok {
println!(" ★ 命中 [{}..{}] {} = resp32 {}", start, start + len, cname, ename);
hits += 1;
}
}
}
}
}
if hits == 0 { println!(" (无)"); }
// --- A3: 简单加和 (u8/u16LE/u16BE/u32LE/u32BE 累加, 任意切片) ---
println!("--- A3: 加和校验(切片) ---");
let mut hits = 0;
for start in 0..552usize {
for len in 1..=(552 - start) {
let sums = |b: &[u8]| -> [u32; 5] {
let mut r = [0u32; 5];
for (i, &x) in b.iter().enumerate() {
r[0] = r[0].wrapping_add(x as u32);
if i + 1 < b.len() && i % 2 == 0 { r[1] = r[1].wrapping_add(u16::from_le_bytes([x, b[i+1]]) as u32); }
if i + 1 < b.len() && i % 2 == 0 { r[2] = r[2].wrapping_add(u16::from_be_bytes([x, b[i+1]]) as u32); }
if i + 3 < b.len() && i % 4 == 0 { r[3] = r[3].wrapping_add(u32::from_le_bytes([x, b[i+1], b[i+2], b[i+3]])); }
if i + 3 < b.len() && i % 4 == 0 { r[4] = r[4].wrapping_add(u32::from_be_bytes([x, b[i+1], b[i+2], b[i+3]])); }
}
r
};
let s0 = sums(&pairs[0].0[start..start + len]);
for k in 0..5 {
for (ename, resp) in [("LE", &resp_le), ("BE", &resp_be)] {
if s0[k] != resp[0] { continue; }
if (1..n).all(|i| sums(&pairs[i].0[start..start + len])[k] == resp[i]) {
println!(" ★ 命中 [{}..{}] sum{} {}", start, start + len, k, ename);
hits += 1;
}
}
}
}
}
if hits == 0 { println!(" (无)"); }
// --- C: XOR 常量: resp32 ^ chal32@off (LE/BE) 跨样本恒定 ---
println!("--- C: XOR 常量掩码测试 ---");
let mut hits = 0;
for off in 0..548usize {
let cle0 = u32::from_le_bytes([pairs[0].0[off], pairs[0].0[off+1], pairs[0].0[off+2], pairs[0].0[off+3]]);
let cbe0 = u32::from_be_bytes([pairs[0].0[off], pairs[0].0[off+1], pairs[0].0[off+2], pairs[0].0[off+3]]);
let dle = resp_le[0] ^ cle0; let dbe = resp_be[0] ^ cbe0;
let okle = (1..n).all(|i| resp_le[i] ^ u32::from_le_bytes([pairs[i].0[off], pairs[i].0[off+1], pairs[i].0[off+2], pairs[i].0[off+3]]) == dle);
let okbe = (1..n).all(|i| resp_be[i] ^ u32::from_be_bytes([pairs[i].0[off], pairs[i].0[off+1], pairs[i].0[off+2], pairs[i].0[off+3]]) == dbe);
if okle { println!(" ★ resp32LE ^ chal32LE@0x{:02x} = 0x{:08x} (恒定)", off, dle); hits += 1; }
if okbe { println!(" ★ resp32BE ^ chal32BE@0x{:02x} = 0x{:08x} (恒定)", off, dbe); hits += 1; }
}
if hits == 0 { println!(" (无)"); }
// --- D: resp32 与会话 K 相关 ---
println!("--- D: resp32 字节 vs 会话 K ---");
for (i, (t, k)) in ks.iter().enumerate() {
if let Some(k) = k {
// 该 tsv 贡献的样本 (按顺序假设每 tsv 样本连续)
let _ = i;
for (j, (_, r)) in pairs.iter().enumerate() {
let hit = (0xC0..0xC4).any(|o| r[o] == *k);
if hit {
println!(" {} K=0x{:02x} 出现在样本#{} resp32 中", t, k, j);
}
}
}
}
// --- A4: 32 位非加密哈希 (直接匹配 + XOR-Δ 恒定) ---
println!("--- A4: FNV1a32/FNV132/djb2/sdbm/Jenkins/Murmur3/xxHash32 ---");
fn fnv1a32(b: &[u8]) -> u32 { let mut h = 0x811c9dc5u32; for &x in b { h ^= x as u32; h = h.wrapping_mul(0x01000193); } h }
fn fnv132(b: &[u8]) -> u32 { let mut h = 0x811c9dc5u32; for &x in b { h = h.wrapping_mul(0x01000193); h ^= x as u32; } h }
fn djb2(b: &[u8]) -> u32 { let mut h = 5381u32; for &x in b { h = h.wrapping_mul(33).wrapping_add(x as u32); } h }
fn sdbm(b: &[u8]) -> u32 { let mut h = 0u32; for &x in b { h = (x as u32).wrapping_add(h << 6).wrapping_add(h << 16).wrapping_sub(h); } h }
fn joaat(b: &[u8]) -> u32 { let mut h = 0u32; for &x in b { h = h.wrapping_add(x as u32); h = h.wrapping_add(h << 10); h ^= h >> 6; } h = h.wrapping_add(h << 3); h ^= h >> 11; h.wrapping_add(h << 15) }
fn murmur3_32(b: &[u8], seed: u32) -> u32 {
let mut h = seed;
let c1 = 0xcc9e2d51u32; let c2 = 0x1b873593u32;
let nblocks = b.len() / 4;
for i in 0..nblocks {
let mut k = u32::from_le_bytes([b[i*4], b[i*4+1], b[i*4+2], b[i*4+3]]);
k = k.wrapping_mul(c1); k = k.rotate_left(15); k = k.wrapping_mul(c2);
h ^= k; h = h.rotate_left(13); h = h.wrapping_mul(5).wrapping_add(0xe6546b64);
}
let tail = &b[nblocks*4..];
let mut k = 0u32;
for (i, &x) in tail.iter().enumerate() { k |= (x as u32) << (8 * i); }
if !tail.is_empty() { k = k.wrapping_mul(c1); k = k.rotate_left(15); k = k.wrapping_mul(c2); h ^= k; }
h ^= b.len() as u32;
h ^= h >> 16; h = h.wrapping_mul(0x85ebca6b); h ^= h >> 13; h = h.wrapping_mul(0xc2b2ae35); h ^= h >> 16;
h
}
fn xxh32(b: &[u8], seed: u32) -> u32 {
const P1: u32 = 0x9E3779B1; const P2: u32 = 0x85EBCA77; const P3: u32 = 0xC2B2AE3D;
const P4: u32 = 0x27D4EB2F; const P5: u32 = 0x165667B1;
let mut i = 0usize;
let mut h: u32;
if b.len() >= 16 {
let mut v1 = seed.wrapping_add(P1).wrapping_add(P2);
let mut v2 = seed.wrapping_add(P2);
let mut v3 = seed;
let mut v4 = seed.wrapping_sub(P1);
while i + 16 <= b.len() {
let rd = |o: usize| u32::from_le_bytes([b[o], b[o+1], b[o+2], b[o+3]]);
v1 = v1.wrapping_add(rd(i).wrapping_mul(P2)).rotate_left(13).wrapping_mul(P1);
v2 = v2.wrapping_add(rd(i+4).wrapping_mul(P2)).rotate_left(13).wrapping_mul(P1);
v3 = v3.wrapping_add(rd(i+8).wrapping_mul(P2)).rotate_left(13).wrapping_mul(P1);
v4 = v4.wrapping_add(rd(i+12).wrapping_mul(P2)).rotate_left(13).wrapping_mul(P1);
i += 16;
}
h = v1.rotate_left(1).wrapping_add(v2.rotate_left(7)).wrapping_add(v3.rotate_left(12)).wrapping_add(v4.rotate_left(18));
} else {
h = seed.wrapping_add(P5);
}
h = h.wrapping_add(b.len() as u32);
while i + 4 <= b.len() {
h = h.wrapping_add(u32::from_le_bytes([b[i], b[i+1], b[i+2], b[i+3]]).wrapping_mul(P3)).rotate_left(17).wrapping_mul(P4);
i += 4;
}
while i < b.len() {
h = h.wrapping_add((b[i] as u32).wrapping_mul(P5)).rotate_left(11).wrapping_mul(P1);
i += 1;
}
h ^= h >> 15; h = h.wrapping_mul(P2); h ^= h >> 13; h = h.wrapping_mul(P3); h ^= h >> 16;
h
}
let hashes: [(&str, fn(&[u8]) -> u32); 7] = [
("FNV1a32", fnv1a32), ("FNV132", fnv132), ("djb2", djb2), ("sdbm", sdbm),
("Jenkins", joaat), ("Murmur3", |b| murmur3_32(b, 0)), ("xxHash32", |b| xxh32(b, 0))];
let mut hits = 0;
for (hname, hf) in &hashes {
for start in 0..552usize {
for len in 1..=(552 - start) {
let h0 = hf(&pairs[0].0[start..start + len]);
for (ename, resp) in [("LE", &resp_le), ("BE", &resp_be)] {
if h0 == resp[0] && (1..n).all(|i| hf(&pairs[i].0[start..start + len]) == resp[i]) {
println!(" ★ 直接命中 {} [{}..{}] {}", hname, start, start + len, ename);
hits += 1;
}
// XOR-Δ (hash^key 构造)
let d0 = resp[0] ^ h0;
if (1..n).all(|i| (resp[i] ^ hf(&pairs[i].0[start..start + len])) == d0) {
println!(" ★ XOR-Δ 命中 {} [{}..{}] {} Δ=0x{:08x}", hname, start, start + len, ename, d0);
hits += 1;
}
}
}
}
}
if hits == 0 { println!(" (无)"); }
println!("\n=== 分析完成 ===");
}