code wiki / (root) / nx_iot_kasa_win.nx

nx_iot_kasa_win.nx source

↩ module page · 159 lines · 6254 B

1// nx_iot_kasa_win.nx -- Pairs or probes Kasa bulbs via TCP/9999 using autokey-XOR encryption and credentials from a local file. 2const K_MAGIC_9999: i64 = 9999 3const K_MAGIC_2048: i64 = 2048 4const K_MAGIC_4096: i64 = 4096 5// nx_iot_kasa_win.nx -- SOVEREIGN native-Windows Kasa client: PAIR (+ probe fallback). 6// Compiled to a native Windows PE (ws2_32) by nx_pe_compile_win_iotcli. Shims below are 7// redirected by the emitter to WinSock + kernel32 thunks; same source is portable to NishiOS. 8// 9// THE BLINK-STOPPER: if a local file `iot_creds.txt` ("SSID" on line 1, "PASSWORD" on line 2) 10// is present, this PAIRS the bulb -- it pushes Kasa set_stainfo over the SoftAP so the bulb 11// leaves pairing mode, joins that Wi-Fi, and goes SOLID (then the hub + watchdog keep it 12// stable). With no creds file it PROBES (read-only get_sysinfo) -- the original proof. 13// 14// Protocol: TCP/9999, 4-byte BE length prefix + autokey-XOR (init 0xAB). Target 192.168.0.1:9999 15// (HS210 SoftAP; the emitter's connect thunk hardcodes that IP). 16// NEVER-BRICK #26: set_stainfo writes only a REVERSIBLE Wi-Fi credential (a rejected push just 17// leaves the device safe in pairing mode); there is NO firmware-write path here. 18// Exit: 0 = ok (PAIRED if creds present, else sysinfo read); 10 sock; 11 connect; 13 no/short 19// reply; 15 reply-not-sysinfo; 21 pair-send-fail. 20 21// ---- shims (emitter redirects to ws2_32 + kernel32 thunks) ---- 22func sys_mmap(n: i64) -> i64 { return 0 } 23func nx_wsa() -> i64 { return 0 } 24func nx_sock() -> i64 { return 0 } 25func nx_connect(s: i64, port: i64) -> i64 { return 0 } 26func nx_send(c: i64, buf: *u8, n: i64) -> i64 { return 0 } 27func nx_recv(c: i64, buf: *u8, n: i64) -> i64 { return 0 } 28func nx_close(s: i64) -> i64 { return 0 } 29func nx_fopen(path: *u8) -> i64 { return 0 } 30func nx_fread(h: i64, buf: *u8, n: i64) -> i64 { return 0 } 31 32// ---- Kasa autokey XOR ---- 33func k_enc(plain: *u8, n: i64, out: *u8) -> i64 { 34 var key: i64 = 0xAB 35 var i: i64 = 0 36 while i < n { 37 let c: i64 = ((plain[i] as i64) & 0xff) ^ key 38 out[i] = (c & 0xff) as u8 39 key = c & 0xff 40 i = i + 1 41 } 42 return n 43} 44func k_dec(cipher: *u8, n: i64, out: *u8) -> i64 { 45 var key: i64 = 0xAB 46 var i: i64 = 0 47 while i < n { 48 let c: i64 = (cipher[i] as i64) & 0xff 49 out[i] = ((c ^ key) & 0xff) as u8 50 key = c 51 i = i + 1 52 } 53 return n 54} 55// copy a NUL-terminated literal into out at off; return new off 56func k_puts(out: *u8, off: i64, s: *u8) -> i64 { 57 var o: i64 = off 58 var i: i64 = 0 59 while s[i] != (0 as u8) { out[o] = s[i]; o = o + 1; i = i + 1 } 60 return o 61} 62// TCP frame: 4-byte BE length + autokey-encrypted payload; returns total bytes 63func k_frame(plain: *u8, n: i64, out: *u8) -> i64 { 64 out[0] = ((n >> 24) & 0xff) as u8 65 out[1] = ((n >> 16) & 0xff) as u8 66 out[2] = ((n >> 8) & 0xff) as u8 67 out[3] = (n & 0xff) as u8 68 let ba: i64 = (out as i64) + 4 69 let b: *u8 = ba as *u8 70 k_enc(plain, n, b) 71 return n + 4 72} 73func k_find(buf: *u8, len: i64, pat: *u8, pat_len: i64) -> i64 { 74 if pat_len > len { return 0 - 1 } 75 var i: i64 = 0 76 let end: i64 = len - pat_len + 1 77 var hit: i64 = 0 - 1 78 while i < end { 79 if hit == (0 - 1) { 80 var j: i64 = 0 81 var ok: i64 = 1 82 while j < pat_len { 83 if (buf[i + j] as i64) != (pat[j] as i64) { ok = 0; j = pat_len } else { j = j + 1 } 84 } 85 if ok == 1 { hit = i } 86 } 87 i = i + 1 88 } 89 return hit 90} 91 92func main() -> i64 { 93 nx_wsa() 94 let sock: i64 = nx_sock() 95 if sock < 0 { return 10 } 96 if nx_connect(sock, K_MAGIC_9999) < 0 { return 11 } 97 98 let clear: *u8 = sys_mmap(512) as *u8 99 let frame: *u8 = sys_mmap(640) as *u8 100 var pn: i64 = 0 101 102 // ---- PAIR path: read iot_creds.txt ("SSID\nPASSWORD") and push set_stainfo ---- 103 let ch: i64 = nx_fopen("iot_creds.txt" as *u8) 104 var paired: i64 = 0 105 if ch >= 0 { 106 let cbuf: *u8 = sys_mmap(512) as *u8 107 var cn: i64 = nx_fread(ch, cbuf, 500) 108 // trim trailing CR/LF 109 var keep: i64 = 1 110 while keep == 1 { 111 if cn <= 0 { keep = 0 } 112 else { 113 let lc: i64 = cbuf[cn - 1] as i64 114 if lc == 10 { cn = cn - 1 } else { if lc == 13 { cn = cn - 1 } else { keep = 0 } } 115 } 116 } 117 if cn > 3 { 118 // first '\n' splits SSID | PASSWORD 119 var sl: i64 = 0 - 1 120 var i: i64 = 0 121 while i < cn { if sl == (0 - 1) { if cbuf[i] == (10 as u8) { sl = i } } i = i + 1 } 122 if sl > 0 { 123 if sl + 1 < cn { 124 var o: i64 = k_puts(clear, 0, "{\"netif\":{\"set_stainfo\":{\"ssid\":\"" as *u8) 125 var a: i64 = 0 126 while a < sl { clear[o] = cbuf[a]; o = o + 1; a = a + 1 } 127 o = k_puts(clear, o, "\",\"password\":\"" as *u8) 128 var p: i64 = sl + 1 129 while p < cn { clear[o] = cbuf[p]; o = o + 1; p = p + 1 } 130 o = k_puts(clear, o, "\",\"key_type\":3}}}" as *u8) 131 let flen: i64 = k_frame(clear, o, frame) 132 if nx_send(sock, frame, flen) < 0 { nx_close(sock); return 21 } 133 let rb: *u8 = sys_mmap(K_MAGIC_2048) as *u8 134 nx_recv(sock, rb, K_MAGIC_2048) 135 nx_close(sock) 136 return 0 137 } 138 } 139 } 140 } 141 142 // ---- PROBE path (no creds): read-only get_sysinfo ---- 143 pn = k_puts(clear, 0, "{\"system\":{\"get_sysinfo\":{}}}" as *u8) 144 let flen2: i64 = k_frame(clear, pn, frame) 145 nx_send(sock, frame, flen2) 146 let rbuf: *u8 = sys_mmap(K_MAGIC_4096) as *u8 147 let got: i64 = nx_recv(sock, rbuf, K_MAGIC_4096) 148 nx_close(sock) 149 if got <= 4 { return 13 } 150 var dn: i64 = got - 4 151 let plain: *u8 = sys_mmap(K_MAGIC_4096) as *u8 152 let cba: i64 = (rbuf as i64) + 4 153 let cb: *u8 = cba as *u8 154 k_dec(cb, dn, plain) 155 if k_find(plain, dn, "relay_state" as *u8, 11) >= 0 { return 0 } 156 if k_find(plain, dn, "mic_type" as *u8, 8) >= 0 { return 0 } 157 if k_find(plain, dn, "sw_ver" as *u8, 6) >= 0 { return 0 } 158 return 15 159}