nx_pe_compile_win_io.nx source
↩ module page · 251 lines · 13415 B
1// nx_pe_compile_win_io.nx -- W3b-1: nxc2-compiled NishiLang -> native Windows PE
2// that performs REAL kernel32 console I/O before running the compiled code.
3//
4// The docker-replacement ladder's first I/O rung. The W3 keystone (nx_pe_compile_win)
5// proved a leaf compiled `main` RUNS native (1 import: ExitProcess, arg in ecx). This rung
6// GENERALIZES the compiled-organ PE to an N-entry kernel32 import table (3 imports:
7// GetStdHandle, WriteFile, ExitProcess) and exercises the full MS-x64 ABI (rcx/rdx/r8/r9 +
8// a 5th stack arg + 0x20 shadow + 16-byte alignment) by writing a banner to stdout via
9// WriteFile -- THEN running the nxc2-compiled `main` and exiting with its return value.
10//
11// entry stub (machine code, RVA 0x1000):
12// sub rsp, 0x38
13// mov ecx, -11 ; STD_OUTPUT_HANDLE
14// call [rip+GetStdHandle]
15// mov rcx, rax ; arg1 = handle
16// lea rdx, [rip+MSG] ; arg2 = banner ptr (.rdata RVA 0x2000)
17// mov r8d, <MSGLEN> ; arg3 = byte count
18// xor r9, r9 ; arg4 = NULL (lpNumberOfBytesWritten)
19// mov qword [rsp+0x20], 0 ; arg5 = NULL (lpOverlapped) -- stack arg
20// call [rip+WriteFile]
21// call main ; <-- nxc2-COMPILED code, result in eax
22// mov ecx, eax
23// call [rip+ExitProcess] ; exit(main())
24// int3
25//
26// The first 49 stub bytes are BYTE-IDENTICAL to the run-proven nx_pe_emit_hello stub; only the
27// tail (xor ecx,ecx;call ExitProcess) is swapped for (call main;mov ecx,eax;call ExitProcess) and
28// the compiled code is appended after the stub. The header/section/.idata layout is the proven
29// hello layout verbatim (same 3 kernel32 imports). MSG length is data-driven (copied from a literal).
30//
31// PIPELINE (build WSL sovereign, run native): src.nx
32// -> ./_offc/nx_compile_x86_native.elf <src> > /tmp/nxwin.s
33// -> ./_offc/nx_sov_build_run.elf nx_pe_compile_win_io (reads /tmp/nxwin.s)
34// -> _offc/nx_win_compiled_io.exe -> run native on Windows 11.
35//
36// HONEST SCOPE: the I/O call is issued by the entry STUB, not yet by the compiled code itself.
37// The compiled code's OUTPUT (its return -> exit code) is the no-false-green witness that real
38// codegen ran; the banner is the witness that native kernel32 I/O ran. The remaining W3b work
39// (W3b-2): the compiler/a thunk so an I/O organ's OWN sys_write maps to this WriteFile path.
40// Replicates nxpcw_assemble_main from nx_pe_compile_win (reuses axc_pass, no shared-assembler edit;
41// back-fill = a named-entry assemble API folded into nxasm_x86). lineage_id: substrate_pe_compile_win_io_v1
42
43import "nx_syscalls.nx"
44import "nxasm_x86.nx"
45import "nx_pe_writer.nx"
46
47const NXIO_CODE_CAP: i64 = 1048576 // 1 MiB assembled code
48const NXIO_FILE_SIZE: i64 = 0x800 // headers + .text + .rdata + .idata (4 file-aligned chunks)
49const NXIO_STUB_LEN: i64 = 63 // I/O stub: 49 proven hello bytes + (call main;mov;call exit;int3)
50const NXIO_TEXT_CAP: i64 = 0x200 // one .text file chunk (stub + compiled code)
51const NXIO_RVA_TEXT: i64 = 0x1000
52const NXIO_RVA_RDATA: i64 = 0x2000
53const NXIO_RVA_IDATA: i64 = 0x3000
54const NXIO_FOFF_TEXT: i64 = 0x200
55const NXIO_FOFF_RDATA:i64 = 0x400
56const NXIO_FOFF_IDATA:i64 = 0x600
57
58// Assemble AT&T x86_64 `.s` -> machine code in `out`; report `main`'s offset in p_main[0].
59// Mirrors nxasm_x86_assemble but resolves `main` instead of `_start` (additive, no shared edit).
60// Replicated from nx_pe_compile_win (the W3 keystone) -- same proven logic.
61func nxio_assemble_main(src: *u8, n: i64, out: *u8, out_cap: i64, p_main: *i64) -> i64 {
62 let lab_off: *i64 = sys_mmap(ASM_MAX_LABELS * 8) as *i64
63 let lab_len: *i64 = sys_mmap(ASM_MAX_LABELS * 8) as *i64
64 let lab_addr: *i64 = sys_mmap(ASM_MAX_LABELS * 8) as *i64
65 let lab_sec: *i64 = sys_mmap(ASM_MAX_LABELS * 8) as *i64
66 let op0: *i64 = sys_mmap(72) as *i64 // 9 slots for SIB (matches nxasm_x86)
67 let op1: *i64 = sys_mmap(72) as *i64
68 let op2: *i64 = sys_mmap(72) as *i64 // API DRIFT FIX: axc_pass gained op2
69 let scratch: *u8 = sys_mmap(64)
70 let posbox: *i64 = sys_mmap(16) as *i64
71 let n_lab_box: *i64 = sys_mmap(16) as *i64
72 n_lab_box[0] = 0
73 let lh: *i64 = sys_mmap(ASM_LH_SIZE * 8) as *i64
74
75 let text_size: i64 = axc_pass(src, n, out, 0, 0, lab_off, lab_len, lab_addr, lab_sec, n_lab_box, lh, op0, op1, op2, scratch, posbox)
76 if text_size < 0 { return text_size }
77 let n_lab: i64 = n_lab_box[0]
78
79 var main_a: i64 = 0 - 1
80 var k: i64 = 0
81 while k < n_lab {
82 if lab_sec[k] == 1 { lab_addr[k] = lab_addr[k] + text_size }
83 if axc_tok_is(src, lab_off[k], lab_len[k], "main") == 1 { main_a = lab_addr[k] }
84 k = k + 1
85 }
86 axc_lh_build(src, lab_off, lab_len, n_lab, lh)
87
88 let total: i64 = axc_pass(src, n, out, text_size, 1, lab_off, lab_len, lab_addr, lab_sec, n_lab_box, lh, op0, op1, op2, scratch, posbox)
89 if total < 0 { return total }
90 if total > out_cap { return 0 - 200 }
91 p_main[0] = main_a
92 return total
93}
94
95// Copy NUL-terminated literal `s` into buf at `off`; return its length (excl NUL).
96func nxio_put_str(buf: *u8, off: i64, s: *u8) -> i64 {
97 var i: i64 = 0
98 while s[i] != 0 {
99 buf[off + i] = s[i]
100 i = i + 1
101 }
102 return i
103}
104
105// Wrap assembled `code` (len `code_len`, with `main` at `main_off`) in a native PE that does
106// GetStdHandle+WriteFile(banner) then call main then ExitProcess(eax). Mirrors nx_pe_emit_hello's
107// proven header/section/.idata layout; .text = [63B I/O stub][compiled code]; banner in .rdata.
108func nxio_emit_pe(buf: *u8, code: *u8, code_len: i64, main_off: i64) -> i64 {
109 if (buf as i64) == 0 { return 0 - NX_PE_BAD_INPUT }
110 let text_vsize: i64 = NXIO_STUB_LEN + code_len
111 if text_vsize > NXIO_TEXT_CAP { return 0 - NX_PE_BAD_INPUT } // minimal-slice cap (one .text chunk)
112
113 // ===== DOS + PE sig =====
114 _w16(buf, 0, 0x5A4D)
115 _w32(buf, 0x3C, FOFF_PE_SIG)
116 _w32(buf, FOFF_PE_SIG, 0x00004550)
117 // ===== COFF (3 sections) =====
118 _w16(buf, FOFF_COFF + 0, PE_MACHINE_AMD64)
119 _w16(buf, FOFF_COFF + 2, 3)
120 _w16(buf, FOFF_COFF + 16, 0xF0)
121 _w16(buf, FOFF_COFF + 18, PE_CHAR_EXEC | PE_CHAR_LARGE_ADDR)
122 // ===== Optional Header (PE32+) -- hello values =====
123 _w16(buf, FOFF_OPT + 0, PE_OH_MAGIC_PEPLUS)
124 _w8(buf, FOFF_OPT + 2, 1)
125 _w32(buf, FOFF_OPT + 4, 0x200) // SizeOfCode
126 _w32(buf, FOFF_OPT + 8, 0x400) // SizeOfInitializedData (.rdata + .idata)
127 _w32(buf, FOFF_OPT + 16, NXIO_RVA_TEXT) // AddressOfEntryPoint = stub @ 0x1000
128 _w32(buf, FOFF_OPT + 20, NXIO_RVA_TEXT)
129 _w64(buf, FOFF_OPT + 24, IMG_BASE_LO, IMG_BASE_HI)
130 _w32(buf, FOFF_OPT + 32, 0x1000) // SectionAlignment
131 _w32(buf, FOFF_OPT + 36, 0x200) // FileAlignment
132 _w16(buf, FOFF_OPT + 40, 6)
133 _w16(buf, FOFF_OPT + 48, 6)
134 _w32(buf, FOFF_OPT + 56, 0x4000) // SizeOfImage (headers + 3 sections * 0x1000)
135 _w32(buf, FOFF_OPT + 60, 0x200) // SizeOfHeaders
136 _w16(buf, FOFF_OPT + 68, PE_SUBSYSTEM_CONSOLE)
137 _w64(buf, FOFF_OPT + 72, 0x100000, 0)
138 _w64(buf, FOFF_OPT + 80, 0x1000, 0)
139 _w64(buf, FOFF_OPT + 88, 0x100000, 0)
140 _w64(buf, FOFF_OPT + 96, 0x1000, 0)
141 _w32(buf, FOFF_OPT + 108, 16)
142 _w32(buf, FOFF_OPT + 112 + 8, NXIO_RVA_IDATA) // DataDirectory[1] Import Table
143 _w32(buf, FOFF_OPT + 112 + 12, 0x28)
144 // ===== Section headers: .text (dynamic vsize) + .rdata + .idata =====
145 _emit_section_header(buf, FOFF_SECT_TBL, 46, 116, 101, 120, 116, 0, 0, 0, text_vsize, NXIO_RVA_TEXT, 0x200, NXIO_FOFF_TEXT, PE_SECT_CODE_X_R)
146 _emit_section_header(buf, FOFF_SECT_TBL + 40, 46, 114, 100, 97, 116, 97, 0, 0, 64, NXIO_RVA_RDATA, 0x200, NXIO_FOFF_RDATA, PE_SECT_DATA_R)
147 _emit_section_header(buf, FOFF_SECT_TBL + 80, 46, 105, 100, 97, 116, 97, 0, 0, 0xA1, NXIO_RVA_IDATA, 0x200, NXIO_FOFF_IDATA, PE_SECT_DATA_R)
148
149 // ===== .rdata: banner (data-driven length) =====
150 let msg: *u8 = "[nx-win-io] nxc2-compiled organ: native kernel32 WriteFile\n" as *u8
151 let msglen: i64 = nxio_put_str(buf, NXIO_FOFF_RDATA, msg)
152
153 // ===== .text: I/O entry stub (first 49 bytes == proven hello stub) =====
154 let t: i64 = NXIO_FOFF_TEXT
155 // sub rsp, 0x38
156 _w8(buf, t+0, 0x48); _w8(buf, t+1, 0x83); _w8(buf, t+2, 0xEC); _w8(buf, t+3, 0x38)
157 // mov ecx, -11 (STD_OUTPUT_HANDLE)
158 _w8(buf, t+4, 0xB9); _w8(buf, t+5, 0xF5); _w8(buf, t+6, 0xFF); _w8(buf, t+7, 0xFF); _w8(buf, t+8, 0xFF)
159 // call [rip+0x2039] -> IAT_GetStdHandle (RVA 0x3048)
160 _w8(buf, t+9, 0xFF); _w8(buf, t+10, 0x15); _w32(buf, t+11, 0x2039)
161 // mov rcx, rax
162 _w8(buf, t+15, 0x48); _w8(buf, t+16, 0x89); _w8(buf, t+17, 0xC1)
163 // lea rdx, [rip+0xFE7] -> MSG (RVA 0x2000)
164 _w8(buf, t+18, 0x48); _w8(buf, t+19, 0x8D); _w8(buf, t+20, 0x15); _w32(buf, t+21, 0xFE7)
165 // mov r8d, <msglen>
166 _w8(buf, t+25, 0x41); _w8(buf, t+26, 0xB8); _w32(buf, t+27, msglen)
167 // xor r9, r9
168 _w8(buf, t+31, 0x4D); _w8(buf, t+32, 0x31); _w8(buf, t+33, 0xC9)
169 // mov qword [rsp+0x20], 0 (arg5 lpOverlapped = NULL)
170 _w8(buf, t+34, 0x48); _w8(buf, t+35, 0xC7); _w8(buf, t+36, 0x44); _w8(buf, t+37, 0x24); _w8(buf, t+38, 0x20); _w32(buf, t+39, 0)
171 // call [rip+0x201F] -> IAT_WriteFile (RVA 0x3050)
172 _w8(buf, t+43, 0xFF); _w8(buf, t+44, 0x15); _w32(buf, t+45, 0x201F)
173 // --- W3b tail (new): run nxc2-compiled code, exit with its return ---
174 // call main (E8 rel32); call at byte 49, next-insn RVA 0x1036; main RVA = 0x103F+main_off; rel = 0x9+main_off
175 _w8(buf, t+49, 0xE8); _w32(buf, t+50, 0x9 + main_off)
176 // mov ecx, eax
177 _w8(buf, t+54, 0x89); _w8(buf, t+55, 0xC1)
178 // call [rip+0x201A] -> IAT_ExitProcess (RVA 0x3058); call at byte 56, next-insn RVA 0x103E; disp 0x3058-0x103E
179 _w8(buf, t+56, 0xFF); _w8(buf, t+57, 0x15); _w32(buf, t+58, 0x201A)
180 // int3
181 _w8(buf, t+62, 0xCC)
182 // copy assembled compiled code after the 63-byte stub
183 var i: i64 = 0
184 while i < code_len { buf[t + NXIO_STUB_LEN + i] = code[i]; i = i + 1 }
185
186 // ===== .idata: 3 kernel32 imports (GetStdHandle, WriteFile, ExitProcess) -- proven hello layout =====
187 let d: i64 = NXIO_FOFF_IDATA
188 // Import Descriptor for kernel32
189 _w32(buf, d + 0, 0x3028) // OriginalFirstThunk = INT RVA
190 _w32(buf, d + 12, 0x3092) // Name = "kernel32.dll" RVA
191 _w32(buf, d + 16, 0x3048) // FirstThunk = IAT RVA
192 // INT (RVA 0x3028)
193 _w64(buf, d + 0x28, 0x3068, 0) // GetStdHandle hint/name
194 _w64(buf, d + 0x30, 0x3078, 0) // WriteFile
195 _w64(buf, d + 0x38, 0x3084, 0) // ExitProcess
196 _w64(buf, d + 0x40, 0, 0) // null terminator
197 // IAT (RVA 0x3048) -- loader overwrites with real addresses
198 _w64(buf, d + 0x48, 0x3068, 0)
199 _w64(buf, d + 0x50, 0x3078, 0)
200 _w64(buf, d + 0x58, 0x3084, 0)
201 _w64(buf, d + 0x60, 0, 0)
202 // IMAGE_IMPORT_BY_NAME GetStdHandle (RVA 0x3068)
203 _w16(buf, d + 0x68, 0)
204 _w8(buf,d+0x6A,71);_w8(buf,d+0x6B,101);_w8(buf,d+0x6C,116);_w8(buf,d+0x6D,83) // GetS
205 _w8(buf,d+0x6E,116);_w8(buf,d+0x6F,100);_w8(buf,d+0x70,72);_w8(buf,d+0x71,97) // tdHa
206 _w8(buf,d+0x72,110);_w8(buf,d+0x73,100);_w8(buf,d+0x74,108);_w8(buf,d+0x75,101) // ndle
207 _w8(buf,d+0x76,0);_w8(buf,d+0x77,0) // \0 pad
208 // IMAGE_IMPORT_BY_NAME WriteFile (RVA 0x3078)
209 _w16(buf, d + 0x78, 0)
210 _w8(buf,d+0x7A,87);_w8(buf,d+0x7B,114);_w8(buf,d+0x7C,105);_w8(buf,d+0x7D,116) // Writ
211 _w8(buf,d+0x7E,101);_w8(buf,d+0x7F,70);_w8(buf,d+0x80,105);_w8(buf,d+0x81,108) // eFil
212 _w8(buf,d+0x82,101);_w8(buf,d+0x83,0) // e\0
213 // IMAGE_IMPORT_BY_NAME ExitProcess (RVA 0x3084)
214 _w16(buf, d + 0x84, 0)
215 _w8(buf,d+0x86,69);_w8(buf,d+0x87,120);_w8(buf,d+0x88,105);_w8(buf,d+0x89,116) // Exit
216 _w8(buf,d+0x8A,80);_w8(buf,d+0x8B,114);_w8(buf,d+0x8C,111);_w8(buf,d+0x8D,99) // Proc
217 _w8(buf,d+0x8E,101);_w8(buf,d+0x8F,115);_w8(buf,d+0x90,115);_w8(buf,d+0x91,0) // ess\0
218 // "kernel32.dll\0" (RVA 0x3092)
219 _w8(buf,d+0x92,107);_w8(buf,d+0x93,101);_w8(buf,d+0x94,114);_w8(buf,d+0x95,110) // kern
220 _w8(buf,d+0x96,101);_w8(buf,d+0x97,108);_w8(buf,d+0x98,51);_w8(buf,d+0x99,50) // el32
221 _w8(buf,d+0x9A,46);_w8(buf,d+0x9B,100);_w8(buf,d+0x9C,108);_w8(buf,d+0x9D,108);_w8(buf,d+0x9E,0) // .dll\0
222
223 return NX_PE_OK
224}
225
226func main() -> i64 {
227 let lenbox: *i64 = sys_mmap(16) as *i64
228 let src: *u8 = sys_read_file("/tmp/nxwin.s" as *u8, lenbox)
229 if (src as i64) == 0 { return 1 }
230 let n: i64 = lenbox[0]
231 if n <= 0 { return 2 }
232
233 let code: *u8 = sys_mmap(NXIO_CODE_CAP)
234 let mainbox: *i64 = sys_mmap(16) as *i64
235 mainbox[0] = 0 - 1
236 let code_len: i64 = nxio_assemble_main(src, n, code, NXIO_CODE_CAP, mainbox)
237 if code_len < 0 { return 3 }
238 let main_off: i64 = mainbox[0]
239 if main_off < 0 { return 4 } // no `main` label found
240
241 let buf: *u8 = sys_mmap(NXIO_FILE_SIZE)
242 let rc: i64 = nxio_emit_pe(buf, code, code_len, main_off)
243 if rc != NX_PE_OK { return 5 }
244
245 let outp: *u8 = "/mnt/c/Users/elder/nishi-core/nxc2/_offc/nx_win_compiled_io.exe" as *u8
246 if nx_pe_write_to_file(outp, buf, NXIO_FILE_SIZE) != NX_PE_OK { return 70 }
247
248 let msg: *u8 = "[substrate] nxc2-compiled native I/O PE written: nx_win_compiled_io.exe\n" as *u8
249 sys_write(1, msg, 71)
250 return 0
251}