code wiki / (root) / nx_pe_compile_win_io.nx

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}