code wiki / (root) / nx_pe_writer.nx

nx_pe_writer.nx source

↩ module page · 952 lines · 37359 B

1// nx_pe_writer.nx -- substrate-emitted Windows PE32+ bytes. 2// 3// Arc Step 1 of the nxc2 Windows PE backend. Per cardinal 2026-05-21 4// ("always do the bits up harder investment"): substrate produces a 5// valid, runnable Windows PE32+ executable from NishiLang source + 6// the substrate's existing byte-writing primitives + sys_openat 7// to flush to disk. No assembler, no linker, no MinGW, no MSVC, 8// no third-party PE writer. 9// 10// What this Step 1 ships: 11// - nx_pe_emit_exit42(buf): writes the bytes of a complete PE32+ 12// executable that calls kernel32!ExitProcess(42) and returns 13// to the Windows loader cleanly 14// - Bytes are byte-for-byte deterministic for this minimal case; 15// subsequent steps generalize (variable .text, multiple imports, 16// real codegen integration with nxc2 x86_64 backend) 17// - sys_openat + sys_write flush bytes to a Windows-visible path 18// (/mnt/c/...) so PowerShell / cmd can execute it 19// 20// What this Step 1 does NOT ship (queued for next steps): 21// Step 2: generalize -- accept (code_bytes, code_len, imports[]) 22// and lay out sections dynamically; supports any kernel32 23// function the substrate wants to call 24// Step 3: integrate with nxc2 x86_64 backend so a NishiLang 25// program compiles to PE directly (--target pe32plus); 26// emits MS x64 calling convention instead of SysV 27// Step 4: substrate-side .obj+.lib equivalents (still no MS toolchain) 28// Step 5: retire WSL2/qemu/binutils from the Win-native build path 29// 30// Why a minimal PE matters as foundation: 31// - Validates byte layout against the real Windows loader 32// - Establishes import-table mechanics (Import Descriptor + INT + 33// IAT + IMAGE_IMPORT_BY_NAME + dll-name string) 34// - Lets the substrate's HAL gain a Windows shim that calls 35// kernel32 functions (the future NX_HAL_SHIM_WINDOWS sealed-enum 36// option finally has an implementation path) 37// - Proves the substrate doesn't need MinGW/MSVC to produce 38// Windows-native binaries 39// 40// PE32+ layout for this minimal case (file size = 0x600 = 1536 bytes): 41// 42// 0x0000 DOS Header (64 bytes; only MZ + e_lfanew = 0x80 nonzero) 43// 0x0040 DOS Stub (zeros) 44// 0x0080 PE Signature "PE\0\0" 45// 0x0084 COFF File Header (20 bytes) 46// 0x0098 Optional Header PE32+ (240 bytes) 47// 0x0188 Section Header .text (40 bytes) 48// 0x01B0 Section Header .idata (40 bytes) 49// 0x01D8 ...padding to 0x200... 50// 0x0200 .text section (file): 16 bytes of code + padding 51// RVA 0x1000, virtual size 0x1000 52// 0x0400 .idata section (file): import descriptors + INT/IAT 53// + IMAGE_IMPORT_BY_NAME + "kernel32.dll" string 54// RVA 0x2000, virtual size 0x1000 55// 0x0600 end of file 56// 57// SizeOfImage = 0x3000 (headers 0x1000 + .text 0x1000 + .idata 0x1000 58// at SectionAlignment 0x1000 each). 59// 60// genealogy_id: pe_coff_spec_2010 + cardinal_2026-05-21_bits_up_harder_investment 61// lineage_id: substrate_pe_writer_v1_exit42 62 63import "nx_syscalls.nx" 64import "nx_hal.nx" 65 66// ===== Byte writers (little-endian) ================================================= 67func _w8(buf: *u8, off: i64, v: i64) -> i64 { 68 buf[off] = (v & 0xFF) as u8 69 return off + 1 70} 71func _w16(buf: *u8, off: i64, v: i64) -> i64 { 72 buf[off] = (v & 0xFF) as u8 73 buf[off+1] = ((v >> 8) & 0xFF) as u8 74 return off + 2 75} 76func _w32(buf: *u8, off: i64, v: i64) -> i64 { 77 buf[off] = (v & 0xFF) as u8 78 buf[off+1] = ((v >> 8) & 0xFF) as u8 79 buf[off+2] = ((v >> 16) & 0xFF) as u8 80 buf[off+3] = ((v >> 24) & 0xFF) as u8 81 return off + 4 82} 83func _w64(buf: *u8, off: i64, lo32: i64, hi32: i64) -> i64 { 84 // Write as two 32-bit halves to avoid relying on >>32 codegen. 85 _w32(buf, off, lo32) 86 _w32(buf, off + 4, hi32) 87 return off + 8 88} 89 90// ===== Constants (PE/COFF spec) ================================================= 91const PE_FILE_SIZE: i64 = 0x600 92const PE_MACHINE_AMD64: i64 = 0x8664 93const PE_OH_MAGIC_PEPLUS: i64 = 0x020B 94const PE_SUBSYSTEM_CONSOLE: i64 = 3 95const PE_CHAR_EXEC: i64 = 0x0002 96const PE_CHAR_LARGE_ADDR: i64 = 0x0020 97const PE_SECT_CODE_X_R: i64 = 0x60000020 98const PE_SECT_DATA_R: i64 = 0x40000040 99const PE_SECT_DATA_RW: i64 = 0xC0000040 // INITIALIZED_DATA | READ | WRITE 100 101// File offsets 102const FOFF_PE_SIG: i64 = 0x80 103const FOFF_COFF: i64 = 0x84 104const FOFF_OPT: i64 = 0x98 105const FOFF_SECT_TBL: i64 = 0x188 106const FOFF_TEXT: i64 = 0x200 107const FOFF_IDATA: i64 = 0x400 108 109// RVAs (post-load image addresses) 110const RVA_TEXT: i64 = 0x1000 111const RVA_IDATA: i64 = 0x2000 112const RVA_IMP_DESC: i64 = 0x2000 113const RVA_INT: i64 = 0x2028 114const RVA_IAT: i64 = 0x2038 115const RVA_IMP_NAME: i64 = 0x2048 116const RVA_DLL_STR: i64 = 0x2056 117 118// ImageBase = 0x140000000 -- typical PE32+ user-mode preferred base. 119const IMG_BASE_LO: i64 = 0x40000000 120const IMG_BASE_HI: i64 = 0x1 121 122// ===== Verdicts ================================================= 123const NX_PE_OK: i64 = 0 124const NX_PE_BAD_INPUT: i64 = 1 125const NX_PE_FAULT: i64 = 2 126const NX_PE_N_VERDICTS: i64 = 3 127 128func nx_pe_verdict_is_valid(v: i64) -> i64 { 129 if v < 0 { return 0 } 130 if v >= NX_PE_N_VERDICTS { return 0 } 131 return 1 132} 133 134// ===== Section table writer ================================================= 135func _emit_section_header(buf: *u8, off: i64, name0: i64, name1: i64, name2: i64, name3: i64, name4: i64, name5: i64, name6: i64, name7: i64, vsize: i64, vaddr: i64, rawsize: i64, rawptr: i64, chars: i64) -> i64 { 136 _w8(buf, off + 0, name0) 137 _w8(buf, off + 1, name1) 138 _w8(buf, off + 2, name2) 139 _w8(buf, off + 3, name3) 140 _w8(buf, off + 4, name4) 141 _w8(buf, off + 5, name5) 142 _w8(buf, off + 6, name6) 143 _w8(buf, off + 7, name7) 144 _w32(buf, off + 8, vsize) 145 _w32(buf, off + 12, vaddr) 146 _w32(buf, off + 16, rawsize) 147 _w32(buf, off + 20, rawptr) 148 _w32(buf, off + 24, 0) // PointerToRelocations 149 _w32(buf, off + 28, 0) // PointerToLinenumbers 150 _w16(buf, off + 32, 0) // NumberOfRelocations 151 _w16(buf, off + 34, 0) // NumberOfLinenumbers 152 _w32(buf, off + 36, chars) 153 return off + 40 154} 155 156// ===== Main emitter ================================================= 157// Returns NX_PE_OK + writes PE_FILE_SIZE bytes into buf, or -verdict. 158// Caller must ensure buf has >= PE_FILE_SIZE bytes of zero-init storage. 159func nx_pe_emit_exit42(buf: *u8) -> i64 { 160 if (buf as i64) == 0 { return 0 - NX_PE_BAD_INPUT } 161 162 // ===== DOS Header ===== 163 _w16(buf, 0, 0x5A4D) // 'MZ' 164 _w32(buf, 0x3C, FOFF_PE_SIG) // e_lfanew -> PE signature 165 166 // ===== PE Signature ===== 167 _w32(buf, FOFF_PE_SIG, 0x00004550) // 'PE\0\0' 168 169 // ===== COFF File Header (20 bytes) ===== 170 _w16(buf, FOFF_COFF + 0, PE_MACHINE_AMD64) 171 _w16(buf, FOFF_COFF + 2, 2) // NumberOfSections (.text + .idata) 172 _w32(buf, FOFF_COFF + 4, 0) // TimeDateStamp 173 _w32(buf, FOFF_COFF + 8, 0) // PointerToSymbolTable 174 _w32(buf, FOFF_COFF + 12, 0) // NumberOfSymbols 175 _w16(buf, FOFF_COFF + 16, 0xF0) // SizeOfOptionalHeader (240) 176 _w16(buf, FOFF_COFF + 18, PE_CHAR_EXEC | PE_CHAR_LARGE_ADDR) 177 178 // ===== Optional Header (PE32+) ===== 179 _w16(buf, FOFF_OPT + 0, PE_OH_MAGIC_PEPLUS) 180 _w8(buf, FOFF_OPT + 2, 1) // MajorLinkerVersion 181 _w8(buf, FOFF_OPT + 3, 0) // MinorLinkerVersion 182 _w32(buf, FOFF_OPT + 4, 0x200) // SizeOfCode 183 _w32(buf, FOFF_OPT + 8, 0x200) // SizeOfInitializedData 184 _w32(buf, FOFF_OPT + 12, 0) // SizeOfUninitializedData 185 _w32(buf, FOFF_OPT + 16, RVA_TEXT) // AddressOfEntryPoint 186 _w32(buf, FOFF_OPT + 20, RVA_TEXT) // BaseOfCode 187 _w64(buf, FOFF_OPT + 24, IMG_BASE_LO, IMG_BASE_HI) // ImageBase 0x140000000 188 _w32(buf, FOFF_OPT + 32, 0x1000) // SectionAlignment 189 _w32(buf, FOFF_OPT + 36, 0x200) // FileAlignment 190 _w16(buf, FOFF_OPT + 40, 6) // MajorOSVersion 191 _w16(buf, FOFF_OPT + 42, 0) // MinorOSVersion 192 _w16(buf, FOFF_OPT + 44, 0) // MajorImageVersion 193 _w16(buf, FOFF_OPT + 46, 0) // MinorImageVersion 194 _w16(buf, FOFF_OPT + 48, 6) // MajorSubsystemVersion 195 _w16(buf, FOFF_OPT + 50, 0) // MinorSubsystemVersion 196 _w32(buf, FOFF_OPT + 52, 0) // Win32VersionValue 197 _w32(buf, FOFF_OPT + 56, 0x3000) // SizeOfImage 198 _w32(buf, FOFF_OPT + 60, 0x200) // SizeOfHeaders 199 _w32(buf, FOFF_OPT + 64, 0) // CheckSum 200 _w16(buf, FOFF_OPT + 68, PE_SUBSYSTEM_CONSOLE) 201 _w16(buf, FOFF_OPT + 70, 0) // DllCharacteristics 202 _w64(buf, FOFF_OPT + 72, 0x100000, 0) // SizeOfStackReserve = 1 MB 203 _w64(buf, FOFF_OPT + 80, 0x1000, 0) // SizeOfStackCommit 204 _w64(buf, FOFF_OPT + 88, 0x100000, 0) // SizeOfHeapReserve = 1 MB 205 _w64(buf, FOFF_OPT + 96, 0x1000, 0) // SizeOfHeapCommit 206 _w32(buf, FOFF_OPT + 104, 0) // LoaderFlags 207 _w32(buf, FOFF_OPT + 108, 16) // NumberOfRvaAndSizes 208 209 // DataDirectories[16] at FOFF_OPT + 112, each 8 bytes 210 // [0] Export = 0,0 (already zero) 211 // [1] Import = (RVA_IMP_DESC, size=40 bytes for 2 import descriptors) 212 _w32(buf, FOFF_OPT + 112 + 8, RVA_IMP_DESC) 213 _w32(buf, FOFF_OPT + 112 + 12, 0x28) 214 // [2..15] zero (already) 215 216 // ===== Section Headers ===== 217 // .text 218 _emit_section_header(buf, FOFF_SECT_TBL, 46, 116, 101, 120, 116, 0, 0, 0, 0x10, RVA_TEXT, 0x200, FOFF_TEXT, PE_SECT_CODE_X_R) 219 // .idata 220 _emit_section_header(buf, FOFF_SECT_TBL + 40, 46, 105, 100, 97, 116, 97, 0, 0, 0x63, RVA_IDATA, 0x200, FOFF_IDATA, PE_SECT_DATA_R) 221 222 // ===== .text section ===== 223 // sub rsp, 0x28 (48 83 EC 28) -- 4 bytes 224 _w8(buf, FOFF_TEXT + 0, 0x48) 225 _w8(buf, FOFF_TEXT + 1, 0x83) 226 _w8(buf, FOFF_TEXT + 2, 0xEC) 227 _w8(buf, FOFF_TEXT + 3, 0x28) 228 // mov ecx, 42 (B9 2A 00 00 00) -- 5 bytes (zero-extends to RCX, MS x64 arg0) 229 _w8(buf, FOFF_TEXT + 4, 0xB9) 230 _w8(buf, FOFF_TEXT + 5, 0x2A) 231 _w8(buf, FOFF_TEXT + 6, 0x00) 232 _w8(buf, FOFF_TEXT + 7, 0x00) 233 _w8(buf, FOFF_TEXT + 8, 0x00) 234 // call qword ptr [rip + disp32] (FF 15 disp32) -- 6 bytes 235 // disp = RVA(IAT_ExitProcess) - RVA(next_insn) 236 // = 0x2038 - (0x1000 + 4 + 5 + 6) = 0x2038 - 0x100F = 0x1029 237 _w8(buf, FOFF_TEXT + 9, 0xFF) 238 _w8(buf, FOFF_TEXT + 10, 0x15) 239 _w32(buf, FOFF_TEXT + 11, 0x1029) 240 // int3 (safety; never reached if ExitProcess works) 241 _w8(buf, FOFF_TEXT + 15, 0xCC) 242 243 // ===== .idata section ===== 244 // Import Descriptor for kernel32 (20 bytes, RVA 0x2000 = file 0x400) 245 _w32(buf, FOFF_IDATA + 0, RVA_INT) // OriginalFirstThunk -> INT 246 _w32(buf, FOFF_IDATA + 4, 0) // TimeDateStamp 247 _w32(buf, FOFF_IDATA + 8, 0) // ForwarderChain 248 _w32(buf, FOFF_IDATA + 12, RVA_DLL_STR) // Name -> "kernel32.dll" 249 _w32(buf, FOFF_IDATA + 16, RVA_IAT) // FirstThunk -> IAT 250 // Null Import Descriptor (20 bytes of zeros) -- already zero 251 252 // INT at file 0x428 (RVA 0x2028) 253 _w64(buf, FOFF_IDATA + 0x28, RVA_IMP_NAME, 0) // Thunk[0] -> hint/name 254 _w64(buf, FOFF_IDATA + 0x30, 0, 0) // Thunk[1] = 0 terminator 255 256 // IAT at file 0x438 (RVA 0x2038) 257 _w64(buf, FOFF_IDATA + 0x38, RVA_IMP_NAME, 0) // loader replaces with real addr 258 _w64(buf, FOFF_IDATA + 0x40, 0, 0) // terminator 259 260 // IMAGE_IMPORT_BY_NAME at file 0x448 (RVA 0x2048) 261 _w16(buf, FOFF_IDATA + 0x48, 0) // Hint 262 // "ExitProcess\0" 263 _w8(buf, FOFF_IDATA + 0x4A, 69) // 'E' 264 _w8(buf, FOFF_IDATA + 0x4B, 120) // 'x' 265 _w8(buf, FOFF_IDATA + 0x4C, 105) // 'i' 266 _w8(buf, FOFF_IDATA + 0x4D, 116) // 't' 267 _w8(buf, FOFF_IDATA + 0x4E, 80) // 'P' 268 _w8(buf, FOFF_IDATA + 0x4F, 114) // 'r' 269 _w8(buf, FOFF_IDATA + 0x50, 111) // 'o' 270 _w8(buf, FOFF_IDATA + 0x51, 99) // 'c' 271 _w8(buf, FOFF_IDATA + 0x52, 101) // 'e' 272 _w8(buf, FOFF_IDATA + 0x53, 115) // 's' 273 _w8(buf, FOFF_IDATA + 0x54, 115) // 's' 274 _w8(buf, FOFF_IDATA + 0x55, 0) // null 275 276 // "kernel32.dll\0" at file 0x456 (RVA 0x2056), 13 bytes 277 _w8(buf, FOFF_IDATA + 0x56, 107) // 'k' 278 _w8(buf, FOFF_IDATA + 0x57, 101) // 'e' 279 _w8(buf, FOFF_IDATA + 0x58, 114) // 'r' 280 _w8(buf, FOFF_IDATA + 0x59, 110) // 'n' 281 _w8(buf, FOFF_IDATA + 0x5A, 101) // 'e' 282 _w8(buf, FOFF_IDATA + 0x5B, 108) // 'l' 283 _w8(buf, FOFF_IDATA + 0x5C, 51) // '3' 284 _w8(buf, FOFF_IDATA + 0x5D, 50) // '2' 285 _w8(buf, FOFF_IDATA + 0x5E, 46) // '.' 286 _w8(buf, FOFF_IDATA + 0x5F, 100) // 'd' 287 _w8(buf, FOFF_IDATA + 0x60, 108) // 'l' 288 _w8(buf, FOFF_IDATA + 0x61, 108) // 'l' 289 _w8(buf, FOFF_IDATA + 0x62, 0) // null 290 291 return NX_PE_OK 292} 293 294// ===== Hello World PE (Step 2) ================================================= 295// 296// Three sections: .text (RVA 0x1000) + .rdata (RVA 0x2000) + 297// .idata (RVA 0x3000). Three kernel32 imports: GetStdHandle, 298// WriteFile, ExitProcess. Writes "Hello from Nishi PE!\n" to 299// stdout, exits 0. 300// 301// File size = 2048 bytes (4 file-aligned 0x200 chunks). 302// SizeOfImage = 0x4000 (headers + 3 sections * 0x1000). 303// 304// .idata layout (file 0x600, RVA 0x3000): 305// 0x000 Import Descriptor for kernel32 (20 bytes) 306// 0x014 Null Import Descriptor (20 bytes zeros) 307// 0x028 INT: 3 thunks + null (32 bytes) 308// 0x048 IAT: 3 thunks + null (32 bytes) 309// 0x068 IMAGE_IMPORT_BY_NAME GetStdHandle (16 bytes, pad to even) 310// 0x078 IMAGE_IMPORT_BY_NAME WriteFile (12 bytes) 311// 0x084 IMAGE_IMPORT_BY_NAME ExitProcess (14 bytes) 312// 0x092 "kernel32.dll\0" (13 bytes) 313// 314// IAT RVAs: 315// IAT_GetStdHandle = 0x3048 316// IAT_WriteFile = 0x3050 317// IAT_ExitProcess = 0x3058 318// 319// Code at RVA 0x1000 (58 bytes): 320// sub rsp, 0x38 321// mov ecx, -11 ; STD_OUTPUT_HANDLE 322// call [rip + disp_to_GetStdHandle] 323// mov rcx, rax ; arg1 = handle 324// lea rdx, [rip + disp_to_msg] ; arg2 = pointer 325// mov r8d, 21 ; arg3 = bytes 326// xor r9, r9 ; arg4 = NULL (lpNumberOfBytesWritten) 327// mov qword [rsp+0x20], 0 ; arg5 = NULL (lpOverlapped) 328// call [rip + disp_to_WriteFile] 329// xor ecx, ecx ; exit 0 330// call [rip + disp_to_ExitProcess] 331// int3 332const PE_HELLO_FILE_SIZE: i64 = 0x800 // 2048 bytes 333const PE_HELLO_FOFF_TEXT: i64 = 0x200 334const PE_HELLO_FOFF_RDATA: i64 = 0x400 335const PE_HELLO_FOFF_IDATA: i64 = 0x600 336const PE_HELLO_RVA_TEXT: i64 = 0x1000 337const PE_HELLO_RVA_RDATA: i64 = 0x2000 338const PE_HELLO_RVA_IDATA: i64 = 0x3000 339 340func nx_pe_emit_hello(buf: *u8) -> i64 { 341 if (buf as i64) == 0 { return 0 - NX_PE_BAD_INPUT } 342 343 // ===== DOS Header ===== 344 _w16(buf, 0, 0x5A4D) 345 _w32(buf, 0x3C, FOFF_PE_SIG) 346 347 // ===== PE Signature ===== 348 _w32(buf, FOFF_PE_SIG, 0x00004550) 349 350 // ===== COFF File Header ===== 351 _w16(buf, FOFF_COFF + 0, PE_MACHINE_AMD64) 352 _w16(buf, FOFF_COFF + 2, 3) // 3 sections 353 _w16(buf, FOFF_COFF + 16, 0xF0) 354 _w16(buf, FOFF_COFF + 18, PE_CHAR_EXEC | PE_CHAR_LARGE_ADDR) 355 356 // ===== Optional Header ===== 357 _w16(buf, FOFF_OPT + 0, PE_OH_MAGIC_PEPLUS) 358 _w8(buf, FOFF_OPT + 2, 1) 359 _w32(buf, FOFF_OPT + 4, 0x200) // SizeOfCode 360 _w32(buf, FOFF_OPT + 8, 0x400) // SizeOfInitializedData (.rdata + .idata) 361 _w32(buf, FOFF_OPT + 16, PE_HELLO_RVA_TEXT) 362 _w32(buf, FOFF_OPT + 20, PE_HELLO_RVA_TEXT) 363 _w64(buf, FOFF_OPT + 24, IMG_BASE_LO, IMG_BASE_HI) 364 _w32(buf, FOFF_OPT + 32, 0x1000) 365 _w32(buf, FOFF_OPT + 36, 0x200) 366 _w16(buf, FOFF_OPT + 40, 6) 367 _w16(buf, FOFF_OPT + 48, 6) 368 _w32(buf, FOFF_OPT + 56, 0x4000) // SizeOfImage (1 header + 3 sections) 369 _w32(buf, FOFF_OPT + 60, 0x200) // SizeOfHeaders 370 _w16(buf, FOFF_OPT + 68, PE_SUBSYSTEM_CONSOLE) 371 _w64(buf, FOFF_OPT + 72, 0x100000, 0) 372 _w64(buf, FOFF_OPT + 80, 0x1000, 0) 373 _w64(buf, FOFF_OPT + 88, 0x100000, 0) 374 _w64(buf, FOFF_OPT + 96, 0x1000, 0) 375 _w32(buf, FOFF_OPT + 108, 16) 376 // DataDirectory[1] = Import Table 377 _w32(buf, FOFF_OPT + 112 + 8, PE_HELLO_RVA_IDATA) 378 _w32(buf, FOFF_OPT + 112 + 12, 0x28) // 2 descriptors = 40 bytes 379 380 // ===== Section Headers ===== 381 // .text 382 _emit_section_header(buf, FOFF_SECT_TBL, 46, 116, 101, 120, 116, 0, 0, 0, 0x3A, PE_HELLO_RVA_TEXT, 0x200, PE_HELLO_FOFF_TEXT, PE_SECT_CODE_X_R) 383 // .rdata 384 _emit_section_header(buf, FOFF_SECT_TBL + 40, 46, 114, 100, 97, 116, 97, 0, 0, 21, PE_HELLO_RVA_RDATA, 0x200, PE_HELLO_FOFF_RDATA, PE_SECT_DATA_R) 385 // .idata 386 _emit_section_header(buf, FOFF_SECT_TBL + 80, 46, 105, 100, 97, 116, 97, 0, 0, 0xA1, PE_HELLO_RVA_IDATA, 0x200, PE_HELLO_FOFF_IDATA, PE_SECT_DATA_R) 387 388 // ===== .text (58 bytes of MS-x64 code) ===== 389 let t: i64 = PE_HELLO_FOFF_TEXT 390 // sub rsp, 0x38 391 _w8(buf, t + 0, 0x48) 392 _w8(buf, t + 1, 0x83) 393 _w8(buf, t + 2, 0xEC) 394 _w8(buf, t + 3, 0x38) 395 // mov ecx, -11 (0xFFFFFFF5) 396 _w8(buf, t + 4, 0xB9) 397 _w8(buf, t + 5, 0xF5) 398 _w8(buf, t + 6, 0xFF) 399 _w8(buf, t + 7, 0xFF) 400 _w8(buf, t + 8, 0xFF) 401 // call [rip + 0x2039] -> IAT_GetStdHandle at RVA 0x3048 402 _w8(buf, t + 9, 0xFF) 403 _w8(buf, t + 10, 0x15) 404 _w32(buf, t + 11, 0x2039) 405 // mov rcx, rax 406 _w8(buf, t + 15, 0x48) 407 _w8(buf, t + 16, 0x89) 408 _w8(buf, t + 17, 0xC1) 409 // lea rdx, [rip + 0xFE7] -> msg at RVA 0x2000 410 _w8(buf, t + 18, 0x48) 411 _w8(buf, t + 19, 0x8D) 412 _w8(buf, t + 20, 0x15) 413 _w32(buf, t + 21, 0xFE7) 414 // mov r8d, 21 415 _w8(buf, t + 25, 0x41) 416 _w8(buf, t + 26, 0xB8) 417 _w32(buf, t + 27, 21) 418 // xor r9, r9 419 _w8(buf, t + 31, 0x4D) 420 _w8(buf, t + 32, 0x31) 421 _w8(buf, t + 33, 0xC9) 422 // mov qword ptr [rsp+0x20], 0 (9 bytes) 423 _w8(buf, t + 34, 0x48) 424 _w8(buf, t + 35, 0xC7) 425 _w8(buf, t + 36, 0x44) 426 _w8(buf, t + 37, 0x24) 427 _w8(buf, t + 38, 0x20) 428 _w32(buf, t + 39, 0) 429 // call [rip + 0x201F] -> IAT_WriteFile at RVA 0x3050 430 _w8(buf, t + 43, 0xFF) 431 _w8(buf, t + 44, 0x15) 432 _w32(buf, t + 45, 0x201F) 433 // xor ecx, ecx 434 _w8(buf, t + 49, 0x31) 435 _w8(buf, t + 50, 0xC9) 436 // call [rip + 0x201F] -> IAT_ExitProcess at RVA 0x3058 437 _w8(buf, t + 51, 0xFF) 438 _w8(buf, t + 52, 0x15) 439 _w32(buf, t + 53, 0x201F) 440 // int3 441 _w8(buf, t + 57, 0xCC) 442 443 // ===== .rdata: "Hello from Nishi PE!\n" (21 bytes) ===== 444 let r: i64 = PE_HELLO_FOFF_RDATA 445 _w8(buf, r + 0, 72) // 'H' 446 _w8(buf, r + 1, 101) // 'e' 447 _w8(buf, r + 2, 108) // 'l' 448 _w8(buf, r + 3, 108) // 'l' 449 _w8(buf, r + 4, 111) // 'o' 450 _w8(buf, r + 5, 32) // ' ' 451 _w8(buf, r + 6, 102) // 'f' 452 _w8(buf, r + 7, 114) // 'r' 453 _w8(buf, r + 8, 111) // 'o' 454 _w8(buf, r + 9, 109) // 'm' 455 _w8(buf, r + 10, 32) // ' ' 456 _w8(buf, r + 11, 78) // 'N' 457 _w8(buf, r + 12, 105) // 'i' 458 _w8(buf, r + 13, 115) // 's' 459 _w8(buf, r + 14, 104) // 'h' 460 _w8(buf, r + 15, 105) // 'i' 461 _w8(buf, r + 16, 32) // ' ' 462 _w8(buf, r + 17, 80) // 'P' 463 _w8(buf, r + 18, 69) // 'E' 464 _w8(buf, r + 19, 33) // '!' 465 _w8(buf, r + 20, 10) // '\n' 466 467 // ===== .idata ===== 468 let d: i64 = PE_HELLO_FOFF_IDATA 469 // Import Descriptor for kernel32 470 _w32(buf, d + 0, 0x3028) // OriginalFirstThunk = INT RVA 471 _w32(buf, d + 4, 0) 472 _w32(buf, d + 8, 0) 473 _w32(buf, d + 12, 0x3092) // Name = "kernel32.dll" RVA 474 _w32(buf, d + 16, 0x3048) // FirstThunk = IAT RVA 475 476 // Null Import Descriptor (zeros) already zero 477 478 // INT at offset 0x28 (RVA 0x3028) 479 _w64(buf, d + 0x28, 0x3068, 0) // Thunk[0] -> GetStdHandle hint/name 480 _w64(buf, d + 0x30, 0x3078, 0) // Thunk[1] -> WriteFile hint/name 481 _w64(buf, d + 0x38, 0x3084, 0) // Thunk[2] -> ExitProcess hint/name 482 _w64(buf, d + 0x40, 0, 0) // Thunk[3] = null terminator 483 484 // IAT at offset 0x48 (RVA 0x3048) -- same values, loader replaces 485 _w64(buf, d + 0x48, 0x3068, 0) 486 _w64(buf, d + 0x50, 0x3078, 0) 487 _w64(buf, d + 0x58, 0x3084, 0) 488 _w64(buf, d + 0x60, 0, 0) 489 490 // IMAGE_IMPORT_BY_NAME entries 491 // GetStdHandle at offset 0x68 (RVA 0x3068) 492 _w16(buf, d + 0x68, 0) // Hint 493 _w8(buf, d + 0x6A, 71) // 'G' 494 _w8(buf, d + 0x6B, 101) // 'e' 495 _w8(buf, d + 0x6C, 116) // 't' 496 _w8(buf, d + 0x6D, 83) // 'S' 497 _w8(buf, d + 0x6E, 116) // 't' 498 _w8(buf, d + 0x6F, 100) // 'd' 499 _w8(buf, d + 0x70, 72) // 'H' 500 _w8(buf, d + 0x71, 97) // 'a' 501 _w8(buf, d + 0x72, 110) // 'n' 502 _w8(buf, d + 0x73, 100) // 'd' 503 _w8(buf, d + 0x74, 108) // 'l' 504 _w8(buf, d + 0x75, 101) // 'e' 505 _w8(buf, d + 0x76, 0) // null 506 _w8(buf, d + 0x77, 0) // pad to even 507 508 // WriteFile at offset 0x78 (RVA 0x3078) 509 _w16(buf, d + 0x78, 0) // Hint 510 _w8(buf, d + 0x7A, 87) // 'W' 511 _w8(buf, d + 0x7B, 114) // 'r' 512 _w8(buf, d + 0x7C, 105) // 'i' 513 _w8(buf, d + 0x7D, 116) // 't' 514 _w8(buf, d + 0x7E, 101) // 'e' 515 _w8(buf, d + 0x7F, 70) // 'F' 516 _w8(buf, d + 0x80, 105) // 'i' 517 _w8(buf, d + 0x81, 108) // 'l' 518 _w8(buf, d + 0x82, 101) // 'e' 519 _w8(buf, d + 0x83, 0) // null 520 521 // ExitProcess at offset 0x84 (RVA 0x3084) 522 _w16(buf, d + 0x84, 0) // Hint 523 _w8(buf, d + 0x86, 69) // 'E' 524 _w8(buf, d + 0x87, 120) // 'x' 525 _w8(buf, d + 0x88, 105) // 'i' 526 _w8(buf, d + 0x89, 116) // 't' 527 _w8(buf, d + 0x8A, 80) // 'P' 528 _w8(buf, d + 0x8B, 114) // 'r' 529 _w8(buf, d + 0x8C, 111) // 'o' 530 _w8(buf, d + 0x8D, 99) // 'c' 531 _w8(buf, d + 0x8E, 101) // 'e' 532 _w8(buf, d + 0x8F, 115) // 's' 533 _w8(buf, d + 0x90, 115) // 's' 534 _w8(buf, d + 0x91, 0) // null 535 536 // "kernel32.dll\0" at offset 0x92 (RVA 0x3092) 537 _w8(buf, d + 0x92, 107) // 'k' 538 _w8(buf, d + 0x93, 101) // 'e' 539 _w8(buf, d + 0x94, 114) // 'r' 540 _w8(buf, d + 0x95, 110) // 'n' 541 _w8(buf, d + 0x96, 101) // 'e' 542 _w8(buf, d + 0x97, 108) // 'l' 543 _w8(buf, d + 0x98, 51) // '3' 544 _w8(buf, d + 0x99, 50) // '2' 545 _w8(buf, d + 0x9A, 46) // '.' 546 _w8(buf, d + 0x9B, 100) // 'd' 547 _w8(buf, d + 0x9C, 108) // 'l' 548 _w8(buf, d + 0x9D, 108) // 'l' 549 _w8(buf, d + 0x9E, 0) // null 550 551 return NX_PE_OK 552} 553 554// ===== CreateProcessW spawn PE (Step 3) ================================================= 555// 556// Substrate emits a PE that calls 557// CreateProcessW(NULL, "cmd.exe /c exit 42", NULL, NULL, FALSE, 0, 558// NULL, NULL, &startupInfo, &processInfo) 559// WaitForSingleObject(processInfo.hProcess, INFINITE) 560// GetExitCodeProcess(processInfo.hProcess, &exitCode) 561// ExitProcess(exitCode) 562// 563// Successful execution returns 42 -- proving the substrate-emitted 564// PE correctly invoked the 4-step spawn-wait-read-exit pattern that 565// Elder AI's supervisor will need on Windows. 566// 567// 4 kernel32 imports. 3 sections (.text + .rdata + .idata). 568// File size = 2048 bytes. Code size = 163 bytes. 569// 570// Stack frame layout (rsp+0x00 to rsp+0xE8): 571// 0x00..0x1F shadow space for callees 572// 0x20 arg5 (BOOL bInheritHandles = FALSE) 573// 0x28 arg6 (DWORD dwCreationFlags = 0) 574// 0x30 arg7 (LPVOID lpEnvironment = NULL) 575// 0x38 arg8 (LPCWSTR lpCurrentDirectory = NULL) 576// 0x40 arg9 (LPSTARTUPINFOW &startupInfo) 577// 0x48 arg10 (LPPROCESS_INFORMATION &processInfo) 578// 0x50..0x57 unused alignment 579// 0x58..0xBF STARTUPINFOW (104 bytes; cb set to 0x68, rest zeros) 580// 0xC0..0xD7 PROCESS_INFORMATION (24 bytes; loader fills in) 581// 0xD8..0xDB exitCode DWORD (4 bytes) 582// 0xDC..0xE7 alignment padding 583// 584// .idata layout (4 imports from kernel32, RVA 0x3000): 585// 0x000 Import Descriptor + null 586// 0x028 INT: 4 thunks + null (40 bytes) 587// 0x050 IAT: 4 thunks + null (40 bytes) 588// 0x078 IMAGE_IMPORT_BY_NAME CreateProcessW (18 bytes after pad) 589// 0x08A IMAGE_IMPORT_BY_NAME WaitForSingleObject (24 bytes) 590// 0x0A2 IMAGE_IMPORT_BY_NAME GetExitCodeProcess (22 bytes) 591// 0x0B8 IMAGE_IMPORT_BY_NAME ExitProcess (14 bytes) 592// 0x0C6 "kernel32.dll\0" 593// 594// IAT RVAs: 595// IAT_CreateProcessW = 0x3050 596// IAT_WaitForSingleObject = 0x3058 597// IAT_GetExitCodeProcess = 0x3060 598// IAT_ExitProcess = 0x3068 599 600const PE_SPAWN_FILE_SIZE: i64 = 0x800 601 602func nx_pe_emit_spawn42(buf: *u8) -> i64 { 603 if (buf as i64) == 0 { return 0 - NX_PE_BAD_INPUT } 604 605 // ===== DOS Header ===== 606 _w16(buf, 0, 0x5A4D) 607 _w32(buf, 0x3C, FOFF_PE_SIG) 608 609 // ===== PE Signature ===== 610 _w32(buf, FOFF_PE_SIG, 0x00004550) 611 612 // ===== COFF File Header ===== 613 _w16(buf, FOFF_COFF + 0, PE_MACHINE_AMD64) 614 _w16(buf, FOFF_COFF + 2, 3) 615 _w16(buf, FOFF_COFF + 16, 0xF0) 616 _w16(buf, FOFF_COFF + 18, PE_CHAR_EXEC | PE_CHAR_LARGE_ADDR) 617 618 // ===== Optional Header ===== 619 _w16(buf, FOFF_OPT + 0, PE_OH_MAGIC_PEPLUS) 620 _w8(buf, FOFF_OPT + 2, 1) 621 _w32(buf, FOFF_OPT + 4, 0x200) 622 _w32(buf, FOFF_OPT + 8, 0x400) 623 _w32(buf, FOFF_OPT + 16, PE_HELLO_RVA_TEXT) 624 _w32(buf, FOFF_OPT + 20, PE_HELLO_RVA_TEXT) 625 _w64(buf, FOFF_OPT + 24, IMG_BASE_LO, IMG_BASE_HI) 626 _w32(buf, FOFF_OPT + 32, 0x1000) 627 _w32(buf, FOFF_OPT + 36, 0x200) 628 _w16(buf, FOFF_OPT + 40, 6) 629 _w16(buf, FOFF_OPT + 48, 6) 630 _w32(buf, FOFF_OPT + 56, 0x4000) 631 _w32(buf, FOFF_OPT + 60, 0x200) 632 _w16(buf, FOFF_OPT + 68, PE_SUBSYSTEM_CONSOLE) 633 _w64(buf, FOFF_OPT + 72, 0x100000, 0) 634 _w64(buf, FOFF_OPT + 80, 0x1000, 0) 635 _w64(buf, FOFF_OPT + 88, 0x100000, 0) 636 _w64(buf, FOFF_OPT + 96, 0x1000, 0) 637 _w32(buf, FOFF_OPT + 108, 16) 638 _w32(buf, FOFF_OPT + 112 + 8, PE_HELLO_RVA_IDATA) 639 _w32(buf, FOFF_OPT + 112 + 12, 0x28) 640 641 // ===== Section Headers ===== 642 _emit_section_header(buf, FOFF_SECT_TBL, 46, 116, 101, 120, 116, 0, 0, 0, 0xA3, PE_HELLO_RVA_TEXT, 0x200, PE_HELLO_FOFF_TEXT, PE_SECT_CODE_X_R) 643 // .data (writable -- CreateProcessW may modify lpCommandLine) 644 _emit_section_header(buf, FOFF_SECT_TBL + 40, 46, 100, 97, 116, 97, 0, 0, 0, 38, PE_HELLO_RVA_RDATA, 0x200, PE_HELLO_FOFF_RDATA, PE_SECT_DATA_RW) 645 _emit_section_header(buf, FOFF_SECT_TBL + 80, 46, 105, 100, 97, 116, 97, 0, 0, 0xD3, PE_HELLO_RVA_IDATA, 0x200, PE_HELLO_FOFF_IDATA, PE_SECT_DATA_R) 646 647 // ===== .text (163 bytes) ===== 648 let t: i64 = PE_HELLO_FOFF_TEXT 649 // sub rsp, 0xE8 (48 81 EC E8 00 00 00) 650 _w8(buf, t + 0, 0x48) 651 _w8(buf, t + 1, 0x81) 652 _w8(buf, t + 2, 0xEC) 653 _w32(buf, t + 3, 0xE8) 654 // xor eax, eax (31 C0) 655 _w8(buf, t + 7, 0x31) 656 _w8(buf, t + 8, 0xC0) 657 // lea rdi, [rsp+0x58] (48 8D 7C 24 58) 658 _w8(buf, t + 9, 0x48) 659 _w8(buf, t + 10, 0x8D) 660 _w8(buf, t + 11, 0x7C) 661 _w8(buf, t + 12, 0x24) 662 _w8(buf, t + 13, 0x58) 663 // mov ecx, 13 (B9 0D 00 00 00) 664 _w8(buf, t + 14, 0xB9) 665 _w32(buf, t + 15, 13) 666 // rep stosq (F3 48 AB) 667 _w8(buf, t + 19, 0xF3) 668 _w8(buf, t + 20, 0x48) 669 _w8(buf, t + 21, 0xAB) 670 // mov dword [rsp+0x58], 0x68 (C7 44 24 58 68 00 00 00) -- cb=104 671 _w8(buf, t + 22, 0xC7) 672 _w8(buf, t + 23, 0x44) 673 _w8(buf, t + 24, 0x24) 674 _w8(buf, t + 25, 0x58) 675 _w32(buf, t + 26, 0x68) 676 // xor ecx, ecx (31 C9) -- arg1 = NULL 677 _w8(buf, t + 30, 0x31) 678 _w8(buf, t + 31, 0xC9) 679 // lea rdx, [rip + disp_cmdline] (48 8D 15 disp32) -- arg2 680 // disp = RVA(cmdline) - RVA(next) = 0x2000 - 0x1027 = 0xFD9 681 _w8(buf, t + 32, 0x48) 682 _w8(buf, t + 33, 0x8D) 683 _w8(buf, t + 34, 0x15) 684 _w32(buf, t + 35, 0xFD9) 685 // xor r8, r8 (4D 31 C0) -- arg3 686 _w8(buf, t + 39, 0x4D) 687 _w8(buf, t + 40, 0x31) 688 _w8(buf, t + 41, 0xC0) 689 // xor r9, r9 (4D 31 C9) -- arg4 690 _w8(buf, t + 42, 0x4D) 691 _w8(buf, t + 43, 0x31) 692 _w8(buf, t + 44, 0xC9) 693 // mov dword [rsp+0x20], 0 -- arg5 = FALSE 694 _w8(buf, t + 45, 0xC7) 695 _w8(buf, t + 46, 0x44) 696 _w8(buf, t + 47, 0x24) 697 _w8(buf, t + 48, 0x20) 698 _w32(buf, t + 49, 0) 699 // mov dword [rsp+0x28], 0 -- arg6 = 0 700 _w8(buf, t + 53, 0xC7) 701 _w8(buf, t + 54, 0x44) 702 _w8(buf, t + 55, 0x24) 703 _w8(buf, t + 56, 0x28) 704 _w32(buf, t + 57, 0) 705 // mov qword [rsp+0x30], 0 -- arg7 = NULL 706 _w8(buf, t + 61, 0x48) 707 _w8(buf, t + 62, 0xC7) 708 _w8(buf, t + 63, 0x44) 709 _w8(buf, t + 64, 0x24) 710 _w8(buf, t + 65, 0x30) 711 _w32(buf, t + 66, 0) 712 // mov qword [rsp+0x38], 0 -- arg8 = NULL 713 _w8(buf, t + 70, 0x48) 714 _w8(buf, t + 71, 0xC7) 715 _w8(buf, t + 72, 0x44) 716 _w8(buf, t + 73, 0x24) 717 _w8(buf, t + 74, 0x38) 718 _w32(buf, t + 75, 0) 719 // lea rax, [rsp+0x58] (48 8D 44 24 58) 720 _w8(buf, t + 79, 0x48) 721 _w8(buf, t + 80, 0x8D) 722 _w8(buf, t + 81, 0x44) 723 _w8(buf, t + 82, 0x24) 724 _w8(buf, t + 83, 0x58) 725 // mov [rsp+0x40], rax (48 89 44 24 40) -- arg9 = &si 726 _w8(buf, t + 84, 0x48) 727 _w8(buf, t + 85, 0x89) 728 _w8(buf, t + 86, 0x44) 729 _w8(buf, t + 87, 0x24) 730 _w8(buf, t + 88, 0x40) 731 // lea rax, [rsp+0xC0] (48 8D 84 24 C0 00 00 00) 732 _w8(buf, t + 89, 0x48) 733 _w8(buf, t + 90, 0x8D) 734 _w8(buf, t + 91, 0x84) 735 _w8(buf, t + 92, 0x24) 736 _w32(buf, t + 93, 0xC0) 737 // mov [rsp+0x48], rax (48 89 44 24 48) -- arg10 = &pi 738 _w8(buf, t + 97, 0x48) 739 _w8(buf, t + 98, 0x89) 740 _w8(buf, t + 99, 0x44) 741 _w8(buf, t + 100, 0x24) 742 _w8(buf, t + 101, 0x48) 743 // call [rip + disp_CreateProcessW] (FF 15 disp32) 744 // disp = 0x3050 - 0x106C = 0x1FE4 745 _w8(buf, t + 102, 0xFF) 746 _w8(buf, t + 103, 0x15) 747 _w32(buf, t + 104, 0x1FE4) 748 // mov rcx, [rsp+0xC0] (48 8B 8C 24 C0 00 00 00) -- pi.hProcess 749 _w8(buf, t + 108, 0x48) 750 _w8(buf, t + 109, 0x8B) 751 _w8(buf, t + 110, 0x8C) 752 _w8(buf, t + 111, 0x24) 753 _w32(buf, t + 112, 0xC0) 754 // mov edx, -1 (BA FF FF FF FF) -- INFINITE 755 _w8(buf, t + 116, 0xBA) 756 _w8(buf, t + 117, 0xFF) 757 _w8(buf, t + 118, 0xFF) 758 _w8(buf, t + 119, 0xFF) 759 _w8(buf, t + 120, 0xFF) 760 // call [rip + disp_WaitForSingleObject] (FF 15 disp32) 761 // disp = 0x3058 - 0x107F = 0x1FD9 762 _w8(buf, t + 121, 0xFF) 763 _w8(buf, t + 122, 0x15) 764 _w32(buf, t + 123, 0x1FD9) 765 // mov rcx, [rsp+0xC0] (8 bytes) 766 _w8(buf, t + 127, 0x48) 767 _w8(buf, t + 128, 0x8B) 768 _w8(buf, t + 129, 0x8C) 769 _w8(buf, t + 130, 0x24) 770 _w32(buf, t + 131, 0xC0) 771 // lea rdx, [rsp+0xD8] (48 8D 94 24 D8 00 00 00) -- &exitCode 772 _w8(buf, t + 135, 0x48) 773 _w8(buf, t + 136, 0x8D) 774 _w8(buf, t + 137, 0x94) 775 _w8(buf, t + 138, 0x24) 776 _w32(buf, t + 139, 0xD8) 777 // call [rip + disp_GetExitCodeProcess] (FF 15 disp32) 778 // disp = 0x3060 - 0x1095 = 0x1FCB 779 _w8(buf, t + 143, 0xFF) 780 _w8(buf, t + 144, 0x15) 781 _w32(buf, t + 145, 0x1FCB) 782 // mov ecx, [rsp+0xD8] (8B 8C 24 D8 00 00 00) -- 7 bytes 783 _w8(buf, t + 149, 0x8B) 784 _w8(buf, t + 150, 0x8C) 785 _w8(buf, t + 151, 0x24) 786 _w32(buf, t + 152, 0xD8) 787 // call [rip + disp_ExitProcess] (FF 15 disp32) 788 // disp = 0x3068 - 0x10A2 = 0x1FC6 789 _w8(buf, t + 156, 0xFF) 790 _w8(buf, t + 157, 0x15) 791 _w32(buf, t + 158, 0x1FC6) 792 // int3 793 _w8(buf, t + 162, 0xCC) 794 795 // ===== .rdata: "cmd.exe /c exit 42\0" as UTF-16 (38 bytes) ===== 796 let r: i64 = PE_HELLO_FOFF_RDATA 797 _w16(buf, r + 0, 0x63) // 'c' 798 _w16(buf, r + 2, 0x6D) // 'm' 799 _w16(buf, r + 4, 0x64) // 'd' 800 _w16(buf, r + 6, 0x2E) // '.' 801 _w16(buf, r + 8, 0x65) // 'e' 802 _w16(buf, r + 10, 0x78) // 'x' 803 _w16(buf, r + 12, 0x65) // 'e' 804 _w16(buf, r + 14, 0x20) // ' ' 805 _w16(buf, r + 16, 0x2F) // '/' 806 _w16(buf, r + 18, 0x63) // 'c' 807 _w16(buf, r + 20, 0x20) // ' ' 808 _w16(buf, r + 22, 0x65) // 'e' 809 _w16(buf, r + 24, 0x78) // 'x' 810 _w16(buf, r + 26, 0x69) // 'i' 811 _w16(buf, r + 28, 0x74) // 't' 812 _w16(buf, r + 30, 0x20) // ' ' 813 _w16(buf, r + 32, 0x34) // '4' 814 _w16(buf, r + 34, 0x32) // '2' 815 _w16(buf, r + 36, 0) // null 816 817 // ===== .idata ===== 818 let d: i64 = PE_HELLO_FOFF_IDATA 819 // Import Descriptor (kernel32) 820 _w32(buf, d + 0, 0x3028) // OriginalFirstThunk = INT 821 _w32(buf, d + 12, 0x30C6) // Name = "kernel32.dll" 822 _w32(buf, d + 16, 0x3050) // FirstThunk = IAT 823 824 // INT (4 thunks + null at offset 0x28) 825 _w64(buf, d + 0x28, 0x3078, 0) // CreateProcessW 826 _w64(buf, d + 0x30, 0x308A, 0) // WaitForSingleObject 827 _w64(buf, d + 0x38, 0x30A2, 0) // GetExitCodeProcess 828 _w64(buf, d + 0x40, 0x30B8, 0) // ExitProcess 829 _w64(buf, d + 0x48, 0, 0) 830 831 // IAT (offset 0x50) 832 _w64(buf, d + 0x50, 0x3078, 0) 833 _w64(buf, d + 0x58, 0x308A, 0) 834 _w64(buf, d + 0x60, 0x30A2, 0) 835 _w64(buf, d + 0x68, 0x30B8, 0) 836 _w64(buf, d + 0x70, 0, 0) 837 838 // IMAGE_IMPORT_BY_NAME structures 839 // CreateProcessW at offset 0x78 (RVA 0x3078) 840 _w16(buf, d + 0x78, 0) 841 _w8(buf, d + 0x7A, 67) // 'C' 842 _w8(buf, d + 0x7B, 114) // 'r' 843 _w8(buf, d + 0x7C, 101) // 'e' 844 _w8(buf, d + 0x7D, 97) // 'a' 845 _w8(buf, d + 0x7E, 116) // 't' 846 _w8(buf, d + 0x7F, 101) // 'e' 847 _w8(buf, d + 0x80, 80) // 'P' 848 _w8(buf, d + 0x81, 114) // 'r' 849 _w8(buf, d + 0x82, 111) // 'o' 850 _w8(buf, d + 0x83, 99) // 'c' 851 _w8(buf, d + 0x84, 101) // 'e' 852 _w8(buf, d + 0x85, 115) // 's' 853 _w8(buf, d + 0x86, 115) // 's' 854 _w8(buf, d + 0x87, 87) // 'W' 855 _w8(buf, d + 0x88, 0) // null 856 _w8(buf, d + 0x89, 0) // pad 857 858 // WaitForSingleObject at offset 0x8A (RVA 0x308A) 859 _w16(buf, d + 0x8A, 0) 860 _w8(buf, d + 0x8C, 87) // 'W' 861 _w8(buf, d + 0x8D, 97) // 'a' 862 _w8(buf, d + 0x8E, 105) // 'i' 863 _w8(buf, d + 0x8F, 116) // 't' 864 _w8(buf, d + 0x90, 70) // 'F' 865 _w8(buf, d + 0x91, 111) // 'o' 866 _w8(buf, d + 0x92, 114) // 'r' 867 _w8(buf, d + 0x93, 83) // 'S' 868 _w8(buf, d + 0x94, 105) // 'i' 869 _w8(buf, d + 0x95, 110) // 'n' 870 _w8(buf, d + 0x96, 103) // 'g' 871 _w8(buf, d + 0x97, 108) // 'l' 872 _w8(buf, d + 0x98, 101) // 'e' 873 _w8(buf, d + 0x99, 79) // 'O' 874 _w8(buf, d + 0x9A, 98) // 'b' 875 _w8(buf, d + 0x9B, 106) // 'j' 876 _w8(buf, d + 0x9C, 101) // 'e' 877 _w8(buf, d + 0x9D, 99) // 'c' 878 _w8(buf, d + 0x9E, 116) // 't' 879 _w8(buf, d + 0x9F, 0) // null 880 _w8(buf, d + 0xA0, 0) // pad 881 _w8(buf, d + 0xA1, 0) // pad 882 883 // GetExitCodeProcess at offset 0xA2 (RVA 0x30A2) 884 _w16(buf, d + 0xA2, 0) 885 _w8(buf, d + 0xA4, 71) // 'G' 886 _w8(buf, d + 0xA5, 101) // 'e' 887 _w8(buf, d + 0xA6, 116) // 't' 888 _w8(buf, d + 0xA7, 69) // 'E' 889 _w8(buf, d + 0xA8, 120) // 'x' 890 _w8(buf, d + 0xA9, 105) // 'i' 891 _w8(buf, d + 0xAA, 116) // 't' 892 _w8(buf, d + 0xAB, 67) // 'C' 893 _w8(buf, d + 0xAC, 111) // 'o' 894 _w8(buf, d + 0xAD, 100) // 'd' 895 _w8(buf, d + 0xAE, 101) // 'e' 896 _w8(buf, d + 0xAF, 80) // 'P' 897 _w8(buf, d + 0xB0, 114) // 'r' 898 _w8(buf, d + 0xB1, 111) // 'o' 899 _w8(buf, d + 0xB2, 99) // 'c' 900 _w8(buf, d + 0xB3, 101) // 'e' 901 _w8(buf, d + 0xB4, 115) // 's' 902 _w8(buf, d + 0xB5, 115) // 's' 903 _w8(buf, d + 0xB6, 0) // null 904 _w8(buf, d + 0xB7, 0) // pad 905 906 // ExitProcess at offset 0xB8 (RVA 0x30B8) 907 _w16(buf, d + 0xB8, 0) 908 _w8(buf, d + 0xBA, 69) // 'E' 909 _w8(buf, d + 0xBB, 120) // 'x' 910 _w8(buf, d + 0xBC, 105) // 'i' 911 _w8(buf, d + 0xBD, 116) // 't' 912 _w8(buf, d + 0xBE, 80) // 'P' 913 _w8(buf, d + 0xBF, 114) // 'r' 914 _w8(buf, d + 0xC0, 111) // 'o' 915 _w8(buf, d + 0xC1, 99) // 'c' 916 _w8(buf, d + 0xC2, 101) // 'e' 917 _w8(buf, d + 0xC3, 115) // 's' 918 _w8(buf, d + 0xC4, 115) // 's' 919 _w8(buf, d + 0xC5, 0) // null 920 921 // "kernel32.dll\0" at offset 0xC6 (RVA 0x30C6) 922 _w8(buf, d + 0xC6, 107) // 'k' 923 _w8(buf, d + 0xC7, 101) // 'e' 924 _w8(buf, d + 0xC8, 114) // 'r' 925 _w8(buf, d + 0xC9, 110) // 'n' 926 _w8(buf, d + 0xCA, 101) // 'e' 927 _w8(buf, d + 0xCB, 108) // 'l' 928 _w8(buf, d + 0xCC, 51) // '3' 929 _w8(buf, d + 0xCD, 50) // '2' 930 _w8(buf, d + 0xCE, 46) // '.' 931 _w8(buf, d + 0xCF, 100) // 'd' 932 _w8(buf, d + 0xD0, 108) // 'l' 933 _w8(buf, d + 0xD1, 108) // 'l' 934 _w8(buf, d + 0xD2, 0) // null 935 936 return NX_PE_OK 937} 938 939// ===== File-flush helper ================================================= 940// Writes the PE bytes to a Windows-visible path via sys_openat + sys_write. 941// Returns NX_PE_OK or -verdict. 942func nx_pe_write_to_file(path: *u8, buf: *u8, n_bytes: i64) -> i64 { 943 if (path as i64) == 0 { return 0 - NX_PE_BAD_INPUT } 944 if (buf as i64) == 0 { return 0 - NX_PE_BAD_INPUT } 945 if n_bytes <= 0 { return 0 - NX_PE_BAD_INPUT } 946 let fd: i64 = sys_openat_wr(path, 0x1ED) // mode 0755 947 if fd < 0 { return 0 - NX_PE_FAULT } 948 let written: i64 = sys_write(fd, buf, n_bytes) 949 sys_close(fd) 950 if written != n_bytes { return 0 - NX_PE_FAULT } 951 return NX_PE_OK 952}