code wiki / (root) / nx_pe_job_self_test.nx

nx_pe_job_self_test.nx source

↩ module page · 278 lines · 13154 B

1// nx_pe_job_self_test.nx -- W2a: sovereign Windows Job Object, native, no HCS. 2// 3// Docker-replacement ladder rung W2a (isolation model = sovereign Job Objects, 4// operator decision 2026-06-13). The substrate emits a native Windows PE that: 5// 1. CreateJobObjectW(NULL, NULL) -> hJob (our sovereign control boundary) 6// 2. AssignProcessToJobObject(hJob, self) -> put THIS process under the job 7// 3. IsProcessInJob(self, hJob, &inJob) -> VERIFY membership (no-false-green) 8// 4. ExitProcess(inJob ? 42 : 99) 9// self = GetCurrentProcess() = (HANDLE)-1 (constant pseudo-handle, no import needed). 10// 11// PROOF: exit 42 iff the process is genuinely inside OUR job (governed). If the 12// assignment did not take (e.g. nesting unavailable), inJob=FALSE -> exit 99 -> RED. 13// This is the cgroups-analog BOUNDARY on Windows -- the thing Docker needs a Linux 14// VM (WSL2/Hyper-V) for. We do it as a native NT process. No HCS daemon, no MinGW, 15// no MSVC, no VM. 4 kernel32 imports. 16// 17// HONEST SCOPE: this proves Job CREATION + MEMBERSHIP (the boundary exists & governs). 18// It does NOT yet enforce a resource LIMIT -- that is W2b (SetInformationJobObject 19// memory.max/ActiveProcessLimit + tamper proof). Child-spawn-into-job = W2c (reuse 20// nx_pe_emit_spawn42 machinery). Founded on nx_pe_writer.nx (hand-authored byte-layout, 21// tutor-scaffold lineage); back-fill = nxc2 PE/MS-x64 backend (W3). 22// 23// genealogy_id: win32_job_objects_2000 + lmctfy_2013 + runc_cgroups_2016 24// lineage_id: substrate_pe_jobself_v1 25 26import "nx_syscalls.nx" 27import "nx_hal.nx" 28import "nx_pe_writer.nx" 29 30const PE_JOBSELF_FILE_SIZE: i64 = 0x600 // 1536 bytes: headers + .text + .idata 31 32// idata base (file 0x400, RVA 0x2000). 4 imports: 33// slot 0 CreateJobObjectW IAT RVA 0x2050 byname RVA 0x2078 34// slot 1 AssignProcessToJobObject IAT RVA 0x2058 byname RVA 0x208C 35// slot 2 IsProcessInJob IAT RVA 0x2060 byname RVA 0x20A8 36// slot 3 ExitProcess IAT RVA 0x2068 byname RVA 0x20BA 37// dll name "kernel32.dll" RVA 0x20C8 38func nx_pe_emit_job_self(buf: *u8) -> i64 { 39 if (buf as i64) == 0 { return 0 - NX_PE_BAD_INPUT } 40 41 // ===== DOS Header ===== 42 _w16(buf, 0, 0x5A4D) 43 _w32(buf, 0x3C, FOFF_PE_SIG) 44 45 // ===== PE Signature ===== 46 _w32(buf, FOFF_PE_SIG, 0x00004550) 47 48 // ===== COFF File Header ===== 49 _w16(buf, FOFF_COFF + 0, PE_MACHINE_AMD64) 50 _w16(buf, FOFF_COFF + 2, 2) // 2 sections (.text + .idata) 51 _w16(buf, FOFF_COFF + 16, 0xF0) 52 _w16(buf, FOFF_COFF + 18, PE_CHAR_EXEC | PE_CHAR_LARGE_ADDR) 53 54 // ===== Optional Header (PE32+) ===== 55 _w16(buf, FOFF_OPT + 0, PE_OH_MAGIC_PEPLUS) 56 _w8(buf, FOFF_OPT + 2, 1) 57 _w32(buf, FOFF_OPT + 4, 0x200) // SizeOfCode 58 _w32(buf, FOFF_OPT + 8, 0x200) // SizeOfInitializedData (.idata) 59 _w32(buf, FOFF_OPT + 16, RVA_TEXT) // AddressOfEntryPoint 60 _w32(buf, FOFF_OPT + 20, RVA_TEXT) // BaseOfCode 61 _w64(buf, FOFF_OPT + 24, IMG_BASE_LO, IMG_BASE_HI) 62 _w32(buf, FOFF_OPT + 32, 0x1000) // SectionAlignment 63 _w32(buf, FOFF_OPT + 36, 0x200) // FileAlignment 64 _w16(buf, FOFF_OPT + 40, 6) // MajorOSVersion 65 _w16(buf, FOFF_OPT + 48, 6) // MajorSubsystemVersion 66 _w32(buf, FOFF_OPT + 56, 0x3000) // SizeOfImage (headers + 2 sections) 67 _w32(buf, FOFF_OPT + 60, 0x200) // SizeOfHeaders 68 _w16(buf, FOFF_OPT + 68, PE_SUBSYSTEM_CONSOLE) 69 _w64(buf, FOFF_OPT + 72, 0x100000, 0) // StackReserve 70 _w64(buf, FOFF_OPT + 80, 0x1000, 0) // StackCommit 71 _w64(buf, FOFF_OPT + 88, 0x100000, 0) // HeapReserve 72 _w64(buf, FOFF_OPT + 96, 0x1000, 0) // HeapCommit 73 _w32(buf, FOFF_OPT + 108, 16) // NumberOfRvaAndSizes 74 // DataDirectory[1] = Import Table -> .idata RVA 0x2000, 2 descriptors (0x28) 75 _w32(buf, FOFF_OPT + 112 + 8, RVA_IDATA) 76 _w32(buf, FOFF_OPT + 112 + 12, 0x28) 77 78 // ===== Section Headers ===== 79 // .text vsize 0x5E (94 bytes of code) 80 _emit_section_header(buf, FOFF_SECT_TBL, 46, 116, 101, 120, 116, 0, 0, 0, 0x5E, RVA_TEXT, 0x200, FOFF_TEXT, PE_SECT_CODE_X_R) 81 // .idata vsize 0xD5 (213 bytes) 82 _emit_section_header(buf, FOFF_SECT_TBL + 40, 46, 105, 100, 97, 116, 97, 0, 0, 0xD5, RVA_IDATA, 0x200, FOFF_IDATA, PE_SECT_DATA_R) 83 84 // ===== .text (MS x64) ===== 85 let t: i64 = FOFF_TEXT 86 // sub rsp, 0x38 (48 83 EC 38) off 0 87 _w8(buf, t + 0, 0x48); _w8(buf, t + 1, 0x83); _w8(buf, t + 2, 0xEC); _w8(buf, t + 3, 0x38) 88 // --- CreateJobObjectW(NULL, NULL) --- 89 // xor ecx, ecx (31 C9) off 4 90 _w8(buf, t + 4, 0x31); _w8(buf, t + 5, 0xC9) 91 // xor edx, edx (31 D2) off 6 92 _w8(buf, t + 6, 0x31); _w8(buf, t + 7, 0xD2) 93 // call [rip+disp] IAT_CJO 0x2050; next RVA 0x100E; disp=0x1042 off 8 94 _w8(buf, t + 8, 0xFF); _w8(buf, t + 9, 0x15); _w32(buf, t + 10, 0x1042) 95 // mov [rsp+0x28], rax (hJob) (48 89 44 24 28) off 14 (0x0E) 96 _w8(buf, t + 14, 0x48); _w8(buf, t + 15, 0x89); _w8(buf, t + 16, 0x44); _w8(buf, t + 17, 0x24); _w8(buf, t + 18, 0x28) 97 // --- AssignProcessToJobObject(hJob, (HANDLE)-1) --- 98 // mov rcx, [rsp+0x28] (48 8B 4C 24 28) off 19 (0x13) 99 _w8(buf, t + 19, 0x48); _w8(buf, t + 20, 0x8B); _w8(buf, t + 21, 0x4C); _w8(buf, t + 22, 0x24); _w8(buf, t + 23, 0x28) 100 // mov rdx, -1 (48 C7 C2 FF FF FF FF) off 24 (0x18) 101 _w8(buf, t + 24, 0x48); _w8(buf, t + 25, 0xC7); _w8(buf, t + 26, 0xC2) 102 _w8(buf, t + 27, 0xFF); _w8(buf, t + 28, 0xFF); _w8(buf, t + 29, 0xFF); _w8(buf, t + 30, 0xFF) 103 // call [rip+disp] IAT_APTJ 0x2058; next RVA 0x1025; disp=0x1033 off 31 (0x1F) 104 _w8(buf, t + 31, 0xFF); _w8(buf, t + 32, 0x15); _w32(buf, t + 33, 0x1033) 105 // --- IsProcessInJob((HANDLE)-1, hJob, &inJob) --- 106 // mov dword [rsp+0x30], 0 (init inJob) (C7 44 24 30 00000000) off 37 (0x25) 107 _w8(buf, t + 37, 0xC7); _w8(buf, t + 38, 0x44); _w8(buf, t + 39, 0x24); _w8(buf, t + 40, 0x30); _w32(buf, t + 41, 0) 108 // mov rcx, -1 (48 C7 C1 FF FF FF FF) off 45 (0x2D) 109 _w8(buf, t + 45, 0x48); _w8(buf, t + 46, 0xC7); _w8(buf, t + 47, 0xC1) 110 _w8(buf, t + 48, 0xFF); _w8(buf, t + 49, 0xFF); _w8(buf, t + 50, 0xFF); _w8(buf, t + 51, 0xFF) 111 // mov rdx, [rsp+0x28] (hJob) (48 8B 54 24 28) off 52 (0x34) 112 _w8(buf, t + 52, 0x48); _w8(buf, t + 53, 0x8B); _w8(buf, t + 54, 0x54); _w8(buf, t + 55, 0x24); _w8(buf, t + 56, 0x28) 113 // lea r8, [rsp+0x30] (&inJob) (4C 8D 44 24 30) off 57 (0x39) 114 _w8(buf, t + 57, 0x4C); _w8(buf, t + 58, 0x8D); _w8(buf, t + 59, 0x44); _w8(buf, t + 60, 0x24); _w8(buf, t + 61, 0x30) 115 // call [rip+disp] IAT_IPIJ 0x2060; next RVA 0x1044; disp=0x101C off 62 (0x3E) 116 _w8(buf, t + 62, 0xFF); _w8(buf, t + 63, 0x15); _w32(buf, t + 64, 0x101C) 117 // --- exit = inJob ? 42 : 99 --- 118 // mov ecx, 42 (B9 2A 00 00 00) off 68 (0x44) 119 _w8(buf, t + 68, 0xB9); _w32(buf, t + 69, 42) 120 // mov edx, 99 (BA 63 00 00 00) off 73 (0x49) 121 _w8(buf, t + 73, 0xBA); _w32(buf, t + 74, 99) 122 // mov eax, [rsp+0x30] (inJob) (8B 44 24 30) off 78 (0x4E) 123 _w8(buf, t + 78, 0x8B); _w8(buf, t + 79, 0x44); _w8(buf, t + 80, 0x24); _w8(buf, t + 81, 0x30) 124 // test eax, eax (85 C0) off 82 (0x52) 125 _w8(buf, t + 82, 0x85); _w8(buf, t + 83, 0xC0) 126 // cmovz ecx, edx (if inJob==0 -> 99) (0F 44 CA) off 84 (0x54) 127 _w8(buf, t + 84, 0x0F); _w8(buf, t + 85, 0x44); _w8(buf, t + 86, 0xCA) 128 // call [rip+disp] IAT_EXIT 0x2068; next RVA 0x105D; disp=0x100B off 87 (0x57) 129 _w8(buf, t + 87, 0xFF); _w8(buf, t + 88, 0x15); _w32(buf, t + 89, 0x100B) 130 // int3 (CC) off 93 (0x5D) 131 _w8(buf, t + 93, 0xCC) 132 133 // ===== .idata ===== 134 let d: i64 = FOFF_IDATA 135 // Import Descriptor for kernel32 136 _w32(buf, d + 0, 0x2028) // OriginalFirstThunk = INT RVA 137 _w32(buf, d + 12, 0x20C8) // Name = "kernel32.dll" RVA 138 _w32(buf, d + 16, 0x2050) // FirstThunk = IAT RVA 139 // (null descriptor d+0x14.. already zero) 140 141 // INT at 0x28 (RVA 0x2028) 142 _w64(buf, d + 0x28, 0x2078, 0) // CreateJobObjectW 143 _w64(buf, d + 0x30, 0x208C, 0) // AssignProcessToJobObject 144 _w64(buf, d + 0x38, 0x20A8, 0) // IsProcessInJob 145 _w64(buf, d + 0x40, 0x20BA, 0) // ExitProcess 146 _w64(buf, d + 0x48, 0, 0) // null 147 148 // IAT at 0x50 (RVA 0x2050) -- loader overwrites with real addrs 149 _w64(buf, d + 0x50, 0x2078, 0) 150 _w64(buf, d + 0x58, 0x208C, 0) 151 _w64(buf, d + 0x60, 0x20A8, 0) 152 _w64(buf, d + 0x68, 0x20BA, 0) 153 _w64(buf, d + 0x70, 0, 0) 154 155 // IMAGE_IMPORT_BY_NAME: CreateJobObjectW @ 0x78 (RVA 0x2078) 156 _w16(buf, d + 0x78, 0) 157 _w8(buf, d + 0x7A, 67) // C 158 _w8(buf, d + 0x7B, 114) // r 159 _w8(buf, d + 0x7C, 101) // e 160 _w8(buf, d + 0x7D, 97) // a 161 _w8(buf, d + 0x7E, 116) // t 162 _w8(buf, d + 0x7F, 101) // e 163 _w8(buf, d + 0x80, 74) // J 164 _w8(buf, d + 0x81, 111) // o 165 _w8(buf, d + 0x82, 98) // b 166 _w8(buf, d + 0x83, 79) // O 167 _w8(buf, d + 0x84, 98) // b 168 _w8(buf, d + 0x85, 106) // j 169 _w8(buf, d + 0x86, 101) // e 170 _w8(buf, d + 0x87, 99) // c 171 _w8(buf, d + 0x88, 116) // t 172 _w8(buf, d + 0x89, 87) // W 173 _w8(buf, d + 0x8A, 0) // null 174 175 // AssignProcessToJobObject @ 0x8C (RVA 0x208C) 176 _w16(buf, d + 0x8C, 0) 177 _w8(buf, d + 0x8E, 65) // A 178 _w8(buf, d + 0x8F, 115) // s 179 _w8(buf, d + 0x90, 115) // s 180 _w8(buf, d + 0x91, 105) // i 181 _w8(buf, d + 0x92, 103) // g 182 _w8(buf, d + 0x93, 110) // n 183 _w8(buf, d + 0x94, 80) // P 184 _w8(buf, d + 0x95, 114) // r 185 _w8(buf, d + 0x96, 111) // o 186 _w8(buf, d + 0x97, 99) // c 187 _w8(buf, d + 0x98, 101) // e 188 _w8(buf, d + 0x99, 115) // s 189 _w8(buf, d + 0x9A, 115) // s 190 _w8(buf, d + 0x9B, 84) // T 191 _w8(buf, d + 0x9C, 111) // o 192 _w8(buf, d + 0x9D, 74) // J 193 _w8(buf, d + 0x9E, 111) // o 194 _w8(buf, d + 0x9F, 98) // b 195 _w8(buf, d + 0xA0, 79) // O 196 _w8(buf, d + 0xA1, 98) // b 197 _w8(buf, d + 0xA2, 106) // j 198 _w8(buf, d + 0xA3, 101) // e 199 _w8(buf, d + 0xA4, 99) // c 200 _w8(buf, d + 0xA5, 116) // t 201 _w8(buf, d + 0xA6, 0) // null 202 203 // IsProcessInJob @ 0xA8 (RVA 0x20A8) 204 _w16(buf, d + 0xA8, 0) 205 _w8(buf, d + 0xAA, 73) // I 206 _w8(buf, d + 0xAB, 115) // s 207 _w8(buf, d + 0xAC, 80) // P 208 _w8(buf, d + 0xAD, 114) // r 209 _w8(buf, d + 0xAE, 111) // o 210 _w8(buf, d + 0xAF, 99) // c 211 _w8(buf, d + 0xB0, 101) // e 212 _w8(buf, d + 0xB1, 115) // s 213 _w8(buf, d + 0xB2, 115) // s 214 _w8(buf, d + 0xB3, 73) // I 215 _w8(buf, d + 0xB4, 110) // n 216 _w8(buf, d + 0xB5, 74) // J 217 _w8(buf, d + 0xB6, 111) // o 218 _w8(buf, d + 0xB7, 98) // b 219 _w8(buf, d + 0xB8, 0) // null 220 221 // ExitProcess @ 0xBA (RVA 0x20BA) 222 _w16(buf, d + 0xBA, 0) 223 _w8(buf, d + 0xBC, 69) // E 224 _w8(buf, d + 0xBD, 120) // x 225 _w8(buf, d + 0xBE, 105) // i 226 _w8(buf, d + 0xBF, 116) // t 227 _w8(buf, d + 0xC0, 80) // P 228 _w8(buf, d + 0xC1, 114) // r 229 _w8(buf, d + 0xC2, 111) // o 230 _w8(buf, d + 0xC3, 99) // c 231 _w8(buf, d + 0xC4, 101) // e 232 _w8(buf, d + 0xC5, 115) // s 233 _w8(buf, d + 0xC6, 115) // s 234 _w8(buf, d + 0xC7, 0) // null 235 236 // "kernel32.dll\0" @ 0xC8 (RVA 0x20C8) 237 _w8(buf, d + 0xC8, 107) // k 238 _w8(buf, d + 0xC9, 101) // e 239 _w8(buf, d + 0xCA, 114) // r 240 _w8(buf, d + 0xCB, 110) // n 241 _w8(buf, d + 0xCC, 101) // e 242 _w8(buf, d + 0xCD, 108) // l 243 _w8(buf, d + 0xCE, 51) // 3 244 _w8(buf, d + 0xCF, 50) // 2 245 _w8(buf, d + 0xD0, 46) // . 246 _w8(buf, d + 0xD1, 100) // d 247 _w8(buf, d + 0xD2, 108) // l 248 _w8(buf, d + 0xD3, 108) // l 249 _w8(buf, d + 0xD4, 0) // null 250 251 return NX_PE_OK 252} 253 254func main() -> i64 { 255 let buf: *u8 = nx_hal_alloc_pages(PE_JOBSELF_FILE_SIZE) 256 if (buf as i64) == 0 { return 1 } 257 258 let rc: i64 = nx_pe_emit_job_self(buf) 259 if rc != NX_PE_OK { return 2 } 260 261 // Spot-checks (catch layout drift before native run). 262 if buf[0] != 0x4D { return 10 } // 'M' 263 if buf[0x80] != 0x50 { return 11 } // 'P' (PE sig) 264 if buf[0x86] != 0x02 { return 12 } // 2 sections 265 if buf[0x200] != 0x48 { return 13 } // .text: sub rsp 266 if buf[0x203] != 0x38 { return 14 } // imm8 0x38 267 if buf[0x47A] != 0x43 { return 15 } // 'C' of CreateJobObjectW (d+0x7A=0x47A) 268 if buf[0x48E] != 0x41 { return 16 } // 'A' of AssignProcessToJobObject (d+0x8E=0x48E) 269 if buf[0x4C8] != 0x6B { return 17 } // 'k' of kernel32.dll (d+0xC8=0x4C8) 270 271 let out_path: *u8 = "/mnt/c/Users/elder/nishi-core/nxc2/_offc/nx_pe_job_self.exe" as *u8 272 let wrc: i64 = nx_pe_write_to_file(out_path, buf, PE_JOBSELF_FILE_SIZE) 273 if wrc != NX_PE_OK { return 70 } 274 275 let msg: *u8 = "[substrate] Job-Object PE32+ written (nx_pe_job_self.exe, 1536 bytes)\n" as *u8 276 sys_write(1, msg, 68) 277 return 0 278}