enet-validator/src/aech.rs

1227 lines
59 KiB
Rust
Raw Normal View History

//! 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=== 分析完成 ===");
}