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}