diff --git a/docs/enet_protocol.md b/docs/enet_protocol.md index 0f8b8df..8ad1f92 100644 --- a/docs/enet_protocol.md +++ b/docs/enet_protocol.md @@ -677,15 +677,20 @@ AECH 质询须源自 192.168.123.113(真机固件过滤,§3.7)。 - **AECH 握手成功 ≠ 会话数据通道打通**——认领(47B/AERE 全过)后,自构造的 type=4 帧(DMX/RDM)仍被静默丢弃;而 verbatim 重放 SL 的历史会话帧立即全通 (遥测/RDM 应答恢复)→ Butler 在帧级校验某种我们尚未破解的会话一致性 -- **RDM 请求帧的消息起始位置可变**(UN_MUTE@0x3A vs UNIQUE_BRANCH@0x3C, - 前+len16 字段位置随之浮动 2B)——固定偏移构造的 RDM 帧必被拒;变长规则未破解 - (实用绕过: 直接重放 SL 原帧) +- **~~RDM 请求帧消息起始位置可变~~(09-11 更正:解析错误,实为固定)**: + 深夜复核证实所有 RDM 请求帧布局恒定——SubSC/msgLen@0x3C、len16(msgLen+1)@0x34、 + flag@0x30(00 独立/01 副本,配对 srcUID 末字节 ff/fe,UN_MUTE 同样成对)、 + counter@0x1A、qseq@0x22、TN@0x4A。此前"0x3A vs 0x3C 变长"系手工解析错位 +- **★单帧 verbatim 在流外仍被拒**(slbisect 突变实验:SL 原版 GET→NFS 帧单独 + 发送 → 零应答;同一帧在完整流重放中 → 正常应答)→ **门控在会话流上下文** + (疑似流级防重放指纹/状态机,具体算法未破解)——实用方案即完整流重放 - 1947 端口"密钥报文"实为 **40B ASCII 许可令牌**(`BlZRQphA…`,恒定值,双目标 21 帧 @200ms 每 ~34s 一轮);EUCE 实为全程每 6s 的许可信标 - 真机认领门控行为(当晚早段观察): 灯具 MUTE 态跨会话持久(SL 遗留的 mute 挡住后续 branch 查询);SymphoLight 关闭无释放帧,DMX 流直接消失 -**遗留**:RDM 帧变长结构规则(破解后可用纯自构造帧完成发现,摆脱对 SL 原帧的依赖)。 +**遗留**:会话流上下文门控的具体算法(破解后可用纯自构造帧完成发现,摆脱对 +SL 原帧重放的依赖——当前 slfull 重放方案已可复现全部功能,不影响使用)。 **历史过程记录(假设-证伪链,供复盘)**:完整冷启动前奏(45s)/绑定帧×4/EUCE 6s 节奏/ 新鲜时间戳宣告/1947 密钥/会话保持 2h 模型——均单因素排除;最终由 verbatim 重放 diff --git a/src/hostdemo.rs b/src/hostdemo.rs index 1ae698a..5634c8a 100644 --- a/src/hostdemo.rs +++ b/src/hostdemo.rs @@ -594,6 +594,119 @@ pub fn run(target: &str, bind_ip: &str, secs: u64) { return; } + // slbisect: SL 原帧逐字段突变 — 定位 Butler 拒收我们 RDM 帧的确切字段 + // 突变序列: verbatim(对照) → counter → TN → qseq → 全部 → 纯我们构造 + if std::env::var("HOSTDEMO_MODE").unwrap_or_default() == "slbisect" { + // 认领 + let o2: Vec = bind_ip.split('.').map(|x| x.parse().unwrap()).collect(); + sock.send_to(&load_template("announce144"), &dst).unwrap(); + std::thread::sleep(Duration::from_millis(300)); + let nonce = rng.next(); + let bc = rng.next(); + let mut ch = load_template("claim_h112"); + ch[0x1A..0x1E].copy_from_slice(&[o2[3], o2[2], o2[1], o2[0]]); + ch[0x88..0x8C].copy_from_slice(&nonce.to_le_bytes()); + ch[0x8C..0x90].copy_from_slice(&bc.to_le_bytes()); + sock.send_to(&ch, &dst).unwrap(); + std::thread::sleep(Duration::from_millis(500)); + println!("[B] 认领完成 B12=0x{:08x}", bc); + // verbatim DMX 背景流 + 突变 branch 查询 + let path = std::env::var("REPLAY_FILE") + .unwrap_or_else(|_| "C:/Users/tonycao/AppData/Local/Temp/opencode/replay_frames.txt".into()); + let data = std::fs::read_to_string(&path).expect("replay file"); + let mut dmxf: Vec> = Vec::new(); + for line in data.lines() { + let p: Vec<&str> = line.split('|').collect(); + if p.len() < 4 { continue; } + let hex = p[3]; + if hex.len() != 1084 * 2 { continue; } + let byte = |o: usize| u8::from_str_radix(&hex[o * 2..o * 2 + 2], 16).unwrap(); + if byte(0x18) == 1 && byte(0x20) == 1 && byte(0x21) == 1 && byte(0x3c) != 1 { + let mut fb = Vec::new(); + for i in (0..hex.len()).step_by(2) { fb.push(u8::from_str_radix(&hex[i..i + 2], 16).unwrap()); } + dmxf.push(fb); + } + } + let sl_branch = std::fs::read_to_string("C:/Users/tonycao/AppData/Local/Temp/opencode/sl_branch_exact.hex") + .unwrap_or_else(|_| String::new()); + let mut slb: Vec = Vec::new(); + { + let h = sl_branch.trim(); + for i in (0..h.len()).step_by(2) { slb.push(u8::from_str_radix(&h[i..i + 2], 16).unwrap()); } + } + // NFS1K2D GET DEVICE_INFO 探针 (被 MUTE 的设备也须应答 GET — 应答可观测) + let sl_get = std::fs::read_to_string("C:/Users/tonycao/AppData/Local/Temp/opencode/sl_get_exact.hex") + .unwrap_or_default(); + let mut slg: Vec = Vec::new(); + { + let h = sl_get.trim(); + for i in (0..h.len()).step_by(2) { slg.push(u8::from_str_radix(&h[i..i + 2], 16).unwrap()); } + } + let known: [[u8; 6]; 3] = [ + [0x01, 0x07, 0x34, 0x67, 0x01, 0x25], + [0x01, 0x07, 0x34, 0x88, 0x09, 0x00], + [0x08, 0x99, 0x50, 0x00, 0x26, 0xc9], + ]; + println!("[B] 开始突变序列 (GET→NFS; 每步 1s DMX 背景 + 发送 + 500ms 侦听)"); + let mut di = 0usize; + let mutations = ["verbatim对照", "counter=ours", "TN=ours", "qseq=ours", "全突变"]; + for round in 0..2 { + for (mi, mname) in mutations.iter().enumerate() { + let seg = Instant::now(); + while seg.elapsed() < Duration::from_millis(1000) { + let f = &dmxf[di % dmxf.len()]; + di += 1; + let _ = sock.send_to(f, &dst); + if di % 30 == 0 { let _ = sock.send_to(&load_template("heartbeat"), &dst); } + std::thread::sleep(Duration::from_millis(33)); + while sock.recv_from(&mut buf).is_ok() {} + } + let mut f = slg.clone(); + let ctr = (di as u32).wrapping_add(9000); + match mi { + 1 => { f[0x1A..0x1E].copy_from_slice(&ctr.to_le_bytes()); } + 2 => { f[0x4A] = 0x77; } + 3 => { f[0x22] = 0x55; f[0x23] = 0x00; } + 4 => { + f[0x1A..0x1E].copy_from_slice(&ctr.to_le_bytes()); + f[0x4A] = 0x78; + f[0x22] = 0x56; f[0x23] = 0x00; + } + _ => {} + } + let _ = sock.send_to(&f, &dst); + let mut f2 = f.clone(); + f2[0x30] = 0x01; + f2[0x49] = f2[0x49].wrapping_sub(1); + let _ = sock.send_to(&f2, &dst); + let mut info = false; + let mut any = 0usize; + let w = Instant::now(); + // ★边发 DMX 边侦听 (Butler 仅在 DMX 流持续时吐应答, 停流即饿死) + while w.elapsed() < Duration::from_millis(500) { + let f = &dmxf[di % dmxf.len()]; + di += 1; + let _ = sock.send_to(f, &dst); + if di % 30 == 0 { let _ = sock.send_to(&load_template("heartbeat"), &dst); } + while let Ok((n, src)) = sock.recv_from(&mut buf) { + if src.ip().to_string() != target { continue; } + let b = &buf[..n]; + any += 1; + if let Some((false, d)) = rdm_region(b) { + if d.len() >= 0x18 + 19 && d[0] == 0xCC && d[0x15..0x17] == [0x00, 0x60] { + info = true; + } + } + } + std::thread::sleep(Duration::from_millis(33)); + } + println!(" [B] r{} {:<14} → 收帧 {} DEVICE_INFO={}", round, mname, any, info); + } + } + let _ = known; + return; + } + let mut b12 = rng.next(); let mut claim_ok = false;