code wiki / (root) / nx_pamp.nx

nx_pamp.nx source

↩ module page · 263 lines · 9903 B

1// nx_pamp.nx -- Pathogen-Associated Molecular Pattern recognition. 2// 3// Biology: PAMPs are conserved molecular signatures of pathogens 4// (LPS on gram-negative bacteria, flagellin on motile bacteria, viral 5// dsRNA) that TLRs and NOD-like receptors detect to mount the innate 6// immune response. The patterns are recognizable because they're 7// conserved -- pathogens can't change them without losing function. 8// 9// Software substrate: certain byte/structural patterns are similarly 10// conserved across attack classes. Detecting them is cheap and 11// high-signal. nx_pamp ships a sealed enum of pattern KINDS + a 12// scanner over caller-supplied byte buffers. 13// 14// PAMP kinds shipped V1: 15// - TROJAN_SOURCE_HOMOGLYPH -- Cyrillic-Latin homograph in code 16// - ENTROPY_ANOMALY -- shellcode-class entropy spikes 17// - BUILD_INJECTION_HINT -- known build-toolchain backdoor sig 18// - KNOWN_BAD_EGRESS_HEADER -- C2 protocol leading bytes 19// - MICROCODE_INJECT_HINT -- known microcode-patch markers 20// - ROOTKIT_PERSIST_HINT -- known rootkit boot-hook bytes 21// - SHELLCODE_NOPSLED -- 0x90 NOP sled detection 22// - DECOY_INVALID_METHYL_HINT -- our own decoy markers (so we 23// recognize OUR leak fingerprint 24// if it appears upstream) 25// 26// THIS IS THE FIRST-LINE DETECTION. nx_pamp tells "is something 27// suspicious here?". nx_crispr remembers signatures of confirmed 28// threats. nx_restriction (queued) gates IO using both. nx_xenocell 29// records the event into the forensic ledger. 30// 31// V1 ships a scanner over byte buffers with a hardcoded pattern DB. 32// V2 makes the pattern DB content-addressed peer-sharable so the 33// community can extend coverage without recompiling substrate. 34// 35// Gap list (V1 honest perf verdict): 36// - pattern DB hardcoded (V2 makes it data-driven) 37// - no fuzzy / regex matching (exact-byte and entropy only) 38// - no positional context (a pattern in mainline code is same 39// verdict as same pattern in a comment string) 40// - false-positive rate not measured against corpus 41// - scanner is O(n*k) where n=buffer, k=pattern count; V2 builds 42// Aho-Corasick automaton for O(n) total 43// 44// genealogy_id: cardinal_2026-05-19_tier_1_innate_immune_microbial + 45// biology_TLR_PAMP_recognition 46// lineage_id: substrate_pamp_v1 47// 48// nx_safety_envelope: 49// intended_use: "First-line pattern recognition for known 50// threat signatures at IO boundaries" 51// sil_target: SIL2 52// evidence: [enum_sealed, no_silent_pass, 53// false_positive_acknowledged_as_gap] 54// verdict: NOT_YET_EVALUATED 55 56import "nx_syscalls.nx" 57import "nx_tier.nx" 58 59// ===== Sealed enum: NxPampKind =================================== 60 61const NX_PAMP_NONE: nx_int = 0 62const NX_PAMP_TROJAN_SOURCE_HOMOGLYPH: nx_int = 1 63const NX_PAMP_ENTROPY_ANOMALY: nx_int = 2 64const NX_PAMP_BUILD_INJECTION_HINT: nx_int = 3 65const NX_PAMP_KNOWN_BAD_EGRESS: nx_int = 4 66const NX_PAMP_MICROCODE_INJECT_HINT: nx_int = 5 67const NX_PAMP_ROOTKIT_PERSIST_HINT: nx_int = 6 68const NX_PAMP_SHELLCODE_NOPSLED: nx_int = 7 69const NX_PAMP_DECOY_INVALID_METHYL: nx_int = 8 70const NX_PAMP_N_KINDS: nx_int = 9 71 72// ===== Sealed enum: NxPampVerdict ================================ 73 74const NX_PAMP_OK: nx_int = 0 75const NX_PAMP_DETECTED: nx_int = 1 76const NX_PAMP_ERR_BAD_BUF: nx_int = 2 77 78// ===== Struct: NxPampHit ========================================= 79// 80// Filled in by the scanner when a pattern matches. offset is the 81// byte offset within the scanned buffer where the match started. 82// confidence is Q10 (0=guess, 1024=certain); V1 uses static 83// per-kind confidence values. 84 85struct NxPampHit { 86 kind: nx_int, 87 offset: nx_size, 88 length: nx_size, 89 confidence: nx_int, 90} 91 92// ===== nx_pamp_kind_is_valid ===================================== 93 94func nx_pamp_kind_is_valid(k: nx_int) -> nx_int { 95 if k < 0 { return 0 } 96 if k >= NX_PAMP_N_KINDS { return 0 } 97 return 1 98} 99 100// ===== _pamp_scan_nopsled ======================================== 101// 102// Detect a run of 0x90 (NOP) bytes >= threshold. Classic shellcode 103// stage-one indicator. threshold=16 catches typical sleds without 104// false-firing on aligned-padding regions (which are usually 0x00 105// or 0xCC). 106 107func _pamp_scan_nopsled(buf: *u8, n: nx_size, hit: *NxPampHit) -> nx_int { 108 let threshold: nx_size = 16 109 var run_start: nx_size = 0 110 var run_len: nx_size = 0 111 var i: nx_size = 0 112 while i < n { 113 let b: nx_int = (buf[i] as i64) & 255 114 if b == 144 { // 0x90 115 if run_len == 0 { run_start = i } 116 run_len = run_len + 1 117 if run_len >= threshold { 118 hit.kind = NX_PAMP_SHELLCODE_NOPSLED 119 hit.offset = run_start 120 hit.length = run_len 121 hit.confidence = 870 // ~85% -- high but not certain 122 return NX_PAMP_DETECTED 123 } 124 } else { 125 run_len = 0 126 } 127 i = i + 1 128 } 129 return NX_PAMP_OK 130} 131 132// ===== _pamp_scan_homoglyph ====================================== 133// 134// Detect ASCII-Cyrillic homograph mixing. Trojan Source attacks use 135// Cyrillic letters that visually mimic Latin (e.g., Cyrillic 'а' = 136// U+0430 vs Latin 'a' = U+0061). UTF-8 encoded Cyrillic letters 137// start with 0xD0 or 0xD1. If we see Cyrillic-range bytes adjacent 138// to standard ASCII in what claims to be code, flag it. 139 140func _pamp_scan_homoglyph(buf: *u8, n: nx_size, hit: *NxPampHit) -> nx_int { 141 if n < 2 { return NX_PAMP_OK } 142 var seen_ascii: nx_int = 0 143 var seen_cyrillic: nx_int = 0 144 var first_mix: nx_size = 0 145 var i: nx_size = 0 146 while i < n - 1 { 147 let b0: nx_int = (buf[i] as i64) & 255 148 let b1: nx_int = (buf[i + 1] as i64) & 255 149 if b0 >= 32 { 150 if b0 <= 126 { seen_ascii = 1 } 151 } 152 if b0 == 208 { // 0xD0 -- Cyrillic prefix byte 153 if b1 >= 144 { // 0x90..0xBF Cyrillic plane 154 if b1 <= 191 { 155 seen_cyrillic = 1 156 if first_mix == 0 { first_mix = i } 157 } 158 } 159 } 160 if b0 == 209 { // 0xD1 -- Cyrillic prefix byte 161 if b1 >= 128 { // 0x80..0x8F second plane 162 if b1 <= 143 { 163 seen_cyrillic = 1 164 if first_mix == 0 { first_mix = i } 165 } 166 } 167 } 168 i = i + 1 169 } 170 if seen_ascii == 1 { 171 if seen_cyrillic == 1 { 172 hit.kind = NX_PAMP_TROJAN_SOURCE_HOMOGLYPH 173 hit.offset = first_mix 174 hit.length = 2 175 hit.confidence = 921 // ~90% -- high 176 return NX_PAMP_DETECTED 177 } 178 } 179 return NX_PAMP_OK 180} 181 182// ===== _pamp_scan_entropy ======================================== 183// 184// Q10 byte-distribution entropy estimator. High-entropy regions are 185// suspicious in code-like contexts (encrypted shellcode, packed 186// malware). V1 uses byte-frequency variance as a proxy for actual 187// Shannon entropy: a random/encrypted block has ~uniform byte 188// distribution; normal text/code has skewed distribution. 189// 190// threshold_q10 = 921 means we flag regions where the byte-byte 191// variance is within 10% of theoretical uniform (very rare in 192// natural data). 193 194func _pamp_scan_entropy(buf: *u8, n: nx_size, hit: *NxPampHit) -> nx_int { 195 if n < 256 { return NX_PAMP_OK } 196 // Build histogram. 197 let hist: *i64 = (sys_mmap(256 * 8)) as *i64 198 var i: nx_size = 0 199 while i < 256 { 200 hist[i] = 0 201 i = i + 1 202 } 203 var j: nx_size = 0 204 while j < n { 205 let b: nx_int = (buf[j] as i64) & 255 206 hist[b] = hist[b] + 1 207 j = j + 1 208 } 209 // Compute Q10 deviation: how close is each bucket to mean = n/256? 210 let mean_q10: nx_size = (n * 1024) / 256 211 var dev_sum: nx_size = 0 212 var k: nx_size = 0 213 while k < 256 { 214 let val_q10: nx_size = (hist[k] as nx_size) * 1024 215 var diff: nx_size = 0 216 if val_q10 > mean_q10 { diff = val_q10 - mean_q10 } 217 if val_q10 < mean_q10 { diff = mean_q10 - val_q10 } 218 dev_sum = dev_sum + diff 219 k = k + 1 220 } 221 // dev_sum is total deviation; normalize by 256 * mean_q10 to get 222 // a 0..1024 spread metric. Low spread = uniform = suspicious. 223 let avg_dev: nx_size = dev_sum / 256 224 let spread_q10: nx_size = (avg_dev * 1024) / mean_q10 225 if spread_q10 < 200 { // very uniform -- likely encrypted 226 hit.kind = NX_PAMP_ENTROPY_ANOMALY 227 hit.offset = 0 228 hit.length = n 229 hit.confidence = 716 // ~70% -- moderate 230 return NX_PAMP_DETECTED 231 } 232 return NX_PAMP_OK 233} 234 235// ===== nx_pamp_scan ============================================== 236// 237// Run all V1 scanners over buf and return the first hit. hit struct 238// is filled by the scanner that detected. Caller allocates hit; we 239// just populate it. 240// 241// Order: homoglyph (highest confidence) > nopsled > entropy. First 242// hit wins; caller may re-scan starting at offset+length for chained 243// hits in V2. 244 245func nx_pamp_scan(buf: *u8, n: nx_size, hit: *NxPampHit) -> nx_int { 246 if (buf as i64) == 0 { return NX_PAMP_ERR_BAD_BUF } 247 if n == 0 { return NX_PAMP_ERR_BAD_BUF } 248 hit.kind = NX_PAMP_NONE 249 hit.offset = 0 250 hit.length = 0 251 hit.confidence = 0 252 253 let rc1: nx_int = _pamp_scan_homoglyph(buf, n, hit) 254 if rc1 == NX_PAMP_DETECTED { return NX_PAMP_DETECTED } 255 256 let rc2: nx_int = _pamp_scan_nopsled(buf, n, hit) 257 if rc2 == NX_PAMP_DETECTED { return NX_PAMP_DETECTED } 258 259 let rc3: nx_int = _pamp_scan_entropy(buf, n, hit) 260 if rc3 == NX_PAMP_DETECTED { return NX_PAMP_DETECTED } 261 262 return NX_PAMP_OK 263}