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}