- 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.
1227 lines
59 KiB
Rust
1227 lines
59 KiB
Rust
//! 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=== 分析完成 ===");
|
||
}
|