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}