code wiki / (root) / nx_tls13_chrome_hello_gate.nx

nx_tls13_chrome_hello_gate.nx source

↩ module page · 123 lines · 8133 B

1// nx_tls13_chrome_hello_gate.nx -- SOVEREIGN referee: emit our Chrome-JA3 ClientHello, extract its JA3 fingerprint 2// (GREASE-filtered ciphers/extensions/curves/point-formats), and assert it EQUALS a real Chrome (~v120) JA3 string 3// EXACTLY. This proves Cloudflare-class CDNs will classify us as a browser (the R2f-A unblock). No network. 4// T1 the emitted hello PARSES (valid ClientHello structure) 5// T2 JA3 string == the known Chrome JA3 (ciphers,extensions,curves,pointformats -- the exact fingerprint) 6// T3 GREASE is PRESENT in ciphers[0] + extensions + groups (browser-shape, and the filter is load-bearing) 7// T4 the JA3 MD5 is stable/deterministic (emit twice -> same hash) 8// GREEN iff 4/4. license_tier: ORIGINAL expect_exit: 0 9import "nx_syscalls.nx" 10import "nx_tls13_chrome_hello.nx" 11import "nx_tls13_hello.nx" // tls13_client_hello_parse + tls13_ext_find + tls_read_u16_be 12import "nx_md5.nx" 13import "nx_gate_verdict.nx" 14 15func gw(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 16func gn(v: i64) -> i64 { let b: *u8 = sys_mmap(24); var m: i64 = v; if m < 0 { gw("-" as *u8); m = 0 - m }; let t: *u8 = sys_mmap(24); var k: i64 = 0; if m == 0 { t[0] = 48 as u8; k = 1 }; while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }; var i: i64 = 0; while i < k { b[i] = t[k - 1 - i]; i = i + 1 }; sys_write(1, b, k); return 0 } 17func grow(name: *u8, ok: i64) -> i64 { if ok == 1 { gw(" PASS " as *u8) } else { gw(" FAIL " as *u8) } gw(name); gw("\n" as *u8); return ok } 18func g_streq(a: *u8, b: *u8) -> i64 { var i: i64=0; while a[i]!=(0 as u8){ if a[i]!=b[i]{return 0} i=i+1 } if b[i]==(0 as u8){return 1} return 0 } 19 20// GREASE = 0x?a?a where the two bytes are equal and low nibble is 0xa (0x0a0a,0x1a1a,...,0xfafa) 21func is_grease(v: i64) -> i64 { let hi: i64 = (v >> 8) & 0xff; let lo: i64 = v & 0xff; if hi == lo { if (lo & 0x0f) == 0x0a { return 1 } } return 0 } 22// append decimal v to dst at o (with a leading '-' unless first==1); returns new o. updates *first via firstp. 23func ap_dec(dst: *u8, o: i64, v: i64, firstp: *i64) -> i64 { 24 if firstp[0] == 0 { dst[o] = 45 as u8; o = o + 1 } // '-' 25 firstp[0] = 0 26 let t: *u8 = sys_mmap(24); var m: i64 = v; var k: i64 = 0 27 if m == 0 { t[0] = 48 as u8; k = 1 } 28 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 29 var i: i64 = 0; while i < k { dst[o] = t[k - 1 - i]; o = o + 1; i = i + 1 } 30 return o 31} 32func ap_str(dst: *u8, o: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i]!=(0 as u8) { dst[o]=s[i]; o=o+1; i=i+1 } return o } 33 34func main() -> i64 { 35 gw("chrome-hello SOVEREIGN gate (emit -> JA3 == real Chrome fingerprint)\n" as *u8) 36 var pass: i64 = 0 37 38 let rnd: *u8 = sys_mmap(32); var i: i64 = 0; while i < 32 { rnd[i] = (0x40 + i) as u8; i = i + 1 } 39 let pub: *u8 = sys_mmap(32); i = 0; while i < 32 { pub[i] = (0x80 + i) as u8; i = i + 1 } 40 let p256: *u8 = sys_mmap(65); p256[0] = 4 as u8; i = 1; while i < 65 { p256[i] = (0x30 + i) as u8; i = i + 1 } 41 let out: *u8 = sys_mmap(2048) 42 let n: i64 = tls13_chrome_hello_emit(rnd, "nhentai.net" as *u8, 11, pub, p256, out, 2048) 43 gw(" emitted bytes=" as *u8); gn(n); gw("\n" as *u8) 44 pass = pass + grow("T0 emit produced bytes\x00" as *u8, n > 0) 45 46 // parse 47 let lv: *i64=sys_mmap(8) as *i64; let ro: *i64=sys_mmap(8) as *i64; let sidl: *i64=sys_mmap(8) as *i64 48 let cso: *i64=sys_mmap(8) as *i64; let csl: *i64=sys_mmap(8) as *i64; let eo: *i64=sys_mmap(8) as *i64; let el: *i64=sys_mmap(8) as *i64 49 let pr: i64 = tls13_client_hello_parse(out, n, lv, ro, sidl, cso, csl, eo, el) 50 pass = pass + grow("T1 emitted ClientHello parses cleanly\x00" as *u8, pr == NX_TLS13_HELLO_VERDICT_OK) 51 52 // build the JA3 string: "771,ciphers,extensions,curves,pointfmts" 53 let ja3: *u8 = sys_mmap(1024); var o: i64 = 0 54 o = ap_str(ja3, o, "771," as *u8) 55 let firstp: *i64 = sys_mmap(8) as *i64 56 // ciphers (GREASE filtered) 57 firstp[0] = 1 58 var greasec: i64 = 0 59 var c: i64 = 0 60 while c < csl[0] { let v: i64 = tls_read_u16_be(out, cso[0] + c); if is_grease(v)==1 { greasec = greasec + 1 } else { o = ap_dec(ja3, o, v, firstp) } c = c + 2 } 61 ja3[o] = 44 as u8; o = o + 1 // ',' 62 // extensions (GREASE filtered) -- walk the ext blob in order 63 firstp[0] = 1 64 var greasee: i64 = 0 65 var off: i64 = eo[0]; let endx: i64 = eo[0] + el[0] 66 while off + 4 <= endx { 67 let et: i64 = tls_read_u16_be(out, off); let elen: i64 = tls_read_u16_be(out, off + 2) 68 if is_grease(et)==1 { greasee = greasee + 1 } else { o = ap_dec(ja3, o, et, firstp) } 69 off = off + 4 + elen 70 } 71 ja3[o] = 44 as u8; o = o + 1 72 // curves = supported_groups (0x000a) data, GREASE filtered 73 let go: *i64=sys_mmap(8) as *i64; let gl: *i64=sys_mmap(8) as *i64 74 firstp[0] = 1 75 var greaseg: i64 = 0 76 if tls13_ext_find(((out as i64)+eo[0]) as *u8, el[0], 0x000a, go, gl) == NX_TLS13_HELLO_VERDICT_OK { 77 // data = list_len(2) then curve ids 78 let base: i64 = eo[0] + go[0] 79 let listlen: i64 = tls_read_u16_be(out, base) 80 var gi: i64 = base + 2; let gend: i64 = base + 2 + listlen 81 while gi + 2 <= gend { let v: i64 = tls_read_u16_be(out, gi); if is_grease(v)==1 { greaseg = greaseg + 1 } else { o = ap_dec(ja3, o, v, firstp) } gi = gi + 2 } 82 } 83 ja3[o] = 44 as u8; o = o + 1 84 // point formats = ec_point_formats (0x000b) data 85 let po: *i64=sys_mmap(8) as *i64; let pl: *i64=sys_mmap(8) as *i64 86 firstp[0] = 1 87 if tls13_ext_find(((out as i64)+eo[0]) as *u8, el[0], 0x000b, po, pl) == NX_TLS13_HELLO_VERDICT_OK { 88 let base: i64 = eo[0] + po[0] 89 let cnt: i64 = out[base] as i64 90 var fi: i64 = 0; while fi < cnt { o = ap_dec(ja3, o, out[base+1+fi] as i64, firstp); fi = fi + 1 } 91 } 92 ja3[o] = 0 as u8 93 94 gw(" JA3=" as *u8); gw(ja3); gw("\n" as *u8) 95 // Chrome ~v120 JA3, minus compress_certificate(27): we deliberately DON'T offer cert compression because we 96 // can't decompress a brotli CompressedCertificate, and offering it makes Cloudflare send one (breaks the 97 // handshake). Dropping ext 27 keeps a browser-shaped fingerprint that LIVE-passes the wall (nhentai.net = 200). 98 let want: *u8 = "771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-13-18-51-45-43-17513,29-23-24,0" as *u8 99 pass = pass + grow("T2 JA3 string == Chrome-shaped (minus cert-compression we cannot decode)\x00" as *u8, g_streq(ja3, want)) 100 101 var t3: i64 = 0; if greasec >= 1 { if greasee >= 2 { if greaseg >= 1 { t3 = 1 } } } 102 gw(" GREASE: ciphers=" as *u8); gn(greasec); gw(" exts=" as *u8); gn(greasee); gw(" groups=" as *u8); gn(greaseg); gw("\n" as *u8) 103 pass = pass + grow("T3 GREASE present in ciphers+extensions+groups (browser-shape)\x00" as *u8, t3) 104 105 // T4 MD5 of the JA3 string, deterministic 106 var jn: i64 = 0; while ja3[jn]!=(0 as u8) { jn = jn + 1 } 107 let dig: *u8 = sys_mmap(16); nx_md5(ja3, jn, dig) 108 let dig2: *u8 = sys_mmap(16); nx_md5(ja3, jn, dig2) 109 var t4: i64 = 1; i = 0; while i < 16 { if dig[i]!=dig2[i] { t4 = 0 } i = i + 1 } 110 gw(" JA3-MD5=" as *u8); let hx: *u8="0123456789abcdef" as *u8; i=0; while i<16 { let b: i64=dig[i] as i64; let o2:*u8=sys_mmap(2); o2[0]=hx[(b>>4)&0xf]; o2[1]=hx[b&0xf]; sys_write(1,o2,2); i=i+1 } gw("\n" as *u8) 111 pass = pass + grow("T4 JA3 MD5 deterministic\x00" as *u8, t4) 112 113 gw("pass=" as *u8); gn(pass); gw("/5\n" as *u8) 114 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check 115 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled 116 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify. 117 let ctr__dry: *i64 = gv_ctr() 118 ctr__dry[0] = pass 119 ctr__dry[1] = 5 120 let rc__dry: i64 = gv_verdict("TLS13-CHROME-HELLO-GATE" as *u8, ctr__dry, "Chrome-JA3 ClientHello: exact fingerprint match + GREASE browser-shape -- the R2f-A anti-bot-CDN unblock)" as *u8) 121 sys_exit(rc__dry) 122 return rc__dry 123}