code wiki / (root) / nx_pe_compile_win_write.nx

nx_pe_compile_win_write.nx source

↩ module page · 235 lines · 13419 B

1// nx_pe_compile_win_write.nx -- W3b-3: a compiled organ's OWN sys_write reaches WriteFile. 2// 3// Composes W3b-1 (proven GetStdHandle+WriteFile MS-x64 ABI) with W3b-2 (keystone-as-linker thunk 4// redirect). The nxc2-COMPILED `main` calls a NishiLang shim `nx_win_write(fd, buf, n)`; the keystone 5// redirects that call to a synthesized thunk that performs the FULL kernel32 console write -- with 6// fd/buf/n flowing from the compiled code's SysV registers into the MS-x64 call. So an organ that 7// writes `nx_win_write(1, "...", N)` in NishiLang prints "..." natively on Windows. No compiler edits. 8// 9// shim entry (overwritten): jmp <thunk> ; E9 rel32 10// thunk (appended .text; SysV in rdi=fd[ignored, stdout], rsi=buf, rdx=n): 11// sub rsp, 0x38 ; shadow(0x20)+arg5+&written+saved-n; keeps 16-byte align at call 12// mov [rsp+0x30], rdx ; save n (rdx is MS-x64 VOLATILE -> clobbered by GetStdHandle) 13// mov ecx, -11 ; STD_OUTPUT_HANDLE 14// call [rip+GetStdHandle] ; rax=handle; rsi(buf) survives (rsi is MS-x64 NON-volatile) 15// mov rcx, rax ; arg1 handle 16// mov rdx, rsi ; arg2 buf 17// mov r8, [rsp+0x30] ; arg3 n 18// lea r9, [rsp+0x28] ; arg4 &bytesWritten (stack scratch) 19// mov qword [rsp+0x20], 0 ; arg5 lpOverlapped = NULL 20// call [rip+WriteFile] ; rax = BOOL 21// add rsp, 0x38 22// ret ; back to the SysV caller (rsi/rdi are SysV-volatile, fine) 23// 24// NO-FALSE-GREEN: source `{ let w = nx_win_write(1, "NISHI-W3B3\n", N); return w + 92 }`. The `+92` 25// forces a real `call` (not a tail-call jmp) and keeps the call live (not DCE'd). The string lives in 26// the COMPILED program's .rodata and is passed via rsi; the count N is passed via rdx. POS prints 27// "NISHI-W3B3\n" (11). LEN-CONTROL passes N=5 -> prints only "NISHI" (5 bytes) => the count flows from 28// the compiled call into WriteFile's r8. Tamper (corrupt WriteFile import name) => no output / fail. 29// 30// PIPELINE (build WSL sovereign, run native): src.nx 31// -> ./_offc/nx_compile_x86_native.elf <src> > /tmp/nxwin.s 32// -> ./_offc/nx_sov_build_run.elf nx_pe_compile_win_write (reads /tmp/nxwin.s) 33// -> _offc/nx_win_compiled_write.exe -> run native on Windows 11. 34// 35// HONEST SCOPE: stdout only (fd ignored; STD_OUTPUT hardcoded), one shim name (nx_win_write). Next: 36// fd->handle mapping (stderr), a shim-name->import table for read/open/close, then nx_syscalls' own 37// sys_write lowers here so any I/O organ compiles native. Replicates keystone assemble (reuses 38// axc_pass; no shared-assembler edit). lineage_id: substrate_pe_compile_win_write_v1 39 40import "nx_syscalls.nx" 41import "nxasm_x86.nx" 42import "nx_pe_writer.nx" 43 44const NXW_CODE_CAP: i64 = 1048576 45const NXW_FILE_SIZE: i64 = 0x800 // headers + .text + .rdata + .idata (W3b-1 3-import layout) 46const NXW_STUB_LEN: i64 = 18 // entry stub: sub rsp,0x28;call main;mov ecx,eax;call ExitProcess;int3 47const NXW_THUNK_LEN: i64 = 56 // GetStdHandle+WriteFile returning thunk 48const NXW_TEXT_CAP: i64 = 0x200 // one .text file chunk (stub + compiled code + thunk) 49const NXW_RVA_TEXT: i64 = 0x1000 50const NXW_RVA_RDATA: i64 = 0x2000 51const NXW_RVA_IDATA: i64 = 0x3000 52const NXW_FOFF_TEXT: i64 = 0x200 53const NXW_FOFF_RDATA:i64 = 0x400 54const NXW_FOFF_IDATA:i64 = 0x600 55const NXW_IAT_GSH: i64 = 0x3048 // IAT GetStdHandle RVA (W3b-1 layout) 56const NXW_IAT_WF: i64 = 0x3050 // IAT WriteFile RVA 57const NXW_IAT_EXIT: i64 = 0x3058 // IAT ExitProcess RVA 58 59// Assemble AT&T x86_64 `.s`; report `main` AND `nx_win_write` offsets (reuses axc_pass; no shared edit). 60func nxw_assemble(src: *u8, n: i64, out: *u8, out_cap: i64, p_main: *i64, p_shim: *i64) -> i64 { 61 let lab_off: *i64 = sys_mmap(ASM_MAX_LABELS * 8) as *i64 62 let lab_len: *i64 = sys_mmap(ASM_MAX_LABELS * 8) as *i64 63 let lab_addr: *i64 = sys_mmap(ASM_MAX_LABELS * 8) as *i64 64 let lab_sec: *i64 = sys_mmap(ASM_MAX_LABELS * 8) as *i64 65 let op0: *i64 = sys_mmap(72) as *i64 // 9 slots for SIB (matches nxasm_x86) 66 let op1: *i64 = sys_mmap(72) as *i64 67 let op2: *i64 = sys_mmap(72) as *i64 // API DRIFT FIX: axc_pass gained op2 68 let scratch: *u8 = sys_mmap(64) 69 let posbox: *i64 = sys_mmap(16) as *i64 70 let n_lab_box: *i64 = sys_mmap(16) as *i64 71 n_lab_box[0] = 0 72 let lh: *i64 = sys_mmap(ASM_LH_SIZE * 8) as *i64 73 74 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) 75 if text_size < 0 { return text_size } 76 let n_lab: i64 = n_lab_box[0] 77 78 var main_a: i64 = 0 - 1 79 var shim_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 if axc_tok_is(src, lab_off[k], lab_len[k], "nx_win_write") == 1 { shim_a = lab_addr[k] } 85 k = k + 1 86 } 87 axc_lh_build(src, lab_off, lab_len, n_lab, lh) 88 89 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) 90 if total < 0 { return total } 91 if total > out_cap { return 0 - 200 } 92 p_main[0] = main_a 93 p_shim[0] = shim_a 94 return total 95} 96 97// Wrap compiled `code` in a PE (W3b-1 3-import layout) and link the compiled nx_win_write shim to a 98// synthesized GetStdHandle+WriteFile thunk appended after the code. 99func nxw_emit_pe(buf: *u8, code: *u8, code_len: i64, main_off: i64, shim_off: i64) -> i64 { 100 if (buf as i64) == 0 { return 0 - NX_PE_BAD_INPUT } 101 if shim_off < 0 { return 0 - NX_PE_BAD_INPUT } 102 let thunk_off: i64 = NXW_STUB_LEN + code_len 103 let text_vsize: i64 = thunk_off + NXW_THUNK_LEN 104 if text_vsize > NXW_TEXT_CAP { return 0 - NX_PE_BAD_INPUT } 105 106 // ===== DOS + PE sig ===== 107 _w16(buf, 0, 0x5A4D) 108 _w32(buf, 0x3C, FOFF_PE_SIG) 109 _w32(buf, FOFF_PE_SIG, 0x00004550) 110 // ===== COFF (3 sections) ===== 111 _w16(buf, FOFF_COFF + 0, PE_MACHINE_AMD64) 112 _w16(buf, FOFF_COFF + 2, 3) 113 _w16(buf, FOFF_COFF + 16, 0xF0) 114 _w16(buf, FOFF_COFF + 18, PE_CHAR_EXEC | PE_CHAR_LARGE_ADDR) 115 // ===== Optional Header (PE32+) -- W3b-1 values ===== 116 _w16(buf, FOFF_OPT + 0, PE_OH_MAGIC_PEPLUS) 117 _w8(buf, FOFF_OPT + 2, 1) 118 _w32(buf, FOFF_OPT + 4, 0x200) 119 _w32(buf, FOFF_OPT + 8, 0x400) 120 _w32(buf, FOFF_OPT + 16, NXW_RVA_TEXT) 121 _w32(buf, FOFF_OPT + 20, NXW_RVA_TEXT) 122 _w64(buf, FOFF_OPT + 24, IMG_BASE_LO, IMG_BASE_HI) 123 _w32(buf, FOFF_OPT + 32, 0x1000) 124 _w32(buf, FOFF_OPT + 36, 0x200) 125 _w16(buf, FOFF_OPT + 40, 6) 126 _w16(buf, FOFF_OPT + 48, 6) 127 _w32(buf, FOFF_OPT + 56, 0x4000) 128 _w32(buf, FOFF_OPT + 60, 0x200) 129 _w16(buf, FOFF_OPT + 68, PE_SUBSYSTEM_CONSOLE) 130 _w64(buf, FOFF_OPT + 72, 0x100000, 0) 131 _w64(buf, FOFF_OPT + 80, 0x1000, 0) 132 _w64(buf, FOFF_OPT + 88, 0x100000, 0) 133 _w64(buf, FOFF_OPT + 96, 0x1000, 0) 134 _w32(buf, FOFF_OPT + 108, 16) 135 _w32(buf, FOFF_OPT + 112 + 8, NXW_RVA_IDATA) 136 _w32(buf, FOFF_OPT + 112 + 12, 0x28) 137 // ===== Section headers: .text (dynamic vsize) + .rdata (unused) + .idata ===== 138 _emit_section_header(buf, FOFF_SECT_TBL, 46, 116, 101, 120, 116, 0, 0, 0, text_vsize, NXW_RVA_TEXT, 0x200, NXW_FOFF_TEXT, PE_SECT_CODE_X_R) 139 _emit_section_header(buf, FOFF_SECT_TBL + 40, 46, 114, 100, 97, 116, 97, 0, 0, 16, NXW_RVA_RDATA, 0x200, NXW_FOFF_RDATA, PE_SECT_DATA_R) 140 _emit_section_header(buf, FOFF_SECT_TBL + 80, 46, 105, 100, 97, 116, 97, 0, 0, 0xA1, NXW_RVA_IDATA, 0x200, NXW_FOFF_IDATA, PE_SECT_DATA_R) 141 142 // ===== .text: minimal entry stub (call main; exit with its return) ===== 143 let t: i64 = NXW_FOFF_TEXT 144 _w8(buf, t+0, 0x48); _w8(buf, t+1, 0x83); _w8(buf, t+2, 0xEC); _w8(buf, t+3, 0x28) // sub rsp,0x28 145 _w8(buf, t+4, 0xE8); _w32(buf, t+5, 0x9 + main_off) // call main 146 _w8(buf, t+9, 0x89); _w8(buf, t+10, 0xC1) // mov ecx,eax 147 _w8(buf, t+11, 0xFF); _w8(buf, t+12, 0x15); _w32(buf, t+13, NXW_IAT_EXIT - (NXW_RVA_TEXT + 17)) // call [rip] ExitProcess 148 _w8(buf, t+17, 0xCC) // int3 149 // copy assembled compiled code after the 18-byte stub 150 var i: i64 = 0 151 while i < code_len { buf[t + NXW_STUB_LEN + i] = code[i]; i = i + 1 } 152 153 // ===== LINKER STEP: synthesize the GetStdHandle+WriteFile thunk + redirect the shim ===== 154 let thunk_rva: i64 = NXW_RVA_TEXT + thunk_off 155 let th: i64 = NXW_FOFF_TEXT + thunk_off 156 _w8(buf, th+0, 0x48); _w8(buf, th+1, 0x83); _w8(buf, th+2, 0xEC); _w8(buf, th+3, 0x38) // sub rsp,0x38 157 _w8(buf, th+4, 0x48); _w8(buf, th+5, 0x89); _w8(buf, th+6, 0x54); _w8(buf, th+7, 0x24); _w8(buf, th+8, 0x30) // mov [rsp+0x30],rdx 158 _w8(buf, th+9, 0xB9); _w8(buf, th+10, 0xF5); _w8(buf, th+11, 0xFF); _w8(buf, th+12, 0xFF); _w8(buf, th+13, 0xFF) // mov ecx,-11 159 _w8(buf, th+14, 0xFF); _w8(buf, th+15, 0x15); _w32(buf, th+16, NXW_IAT_GSH - (thunk_rva + 20)) // call [rip] GetStdHandle 160 _w8(buf, th+20, 0x48); _w8(buf, th+21, 0x89); _w8(buf, th+22, 0xC1) // mov rcx,rax 161 _w8(buf, th+23, 0x48); _w8(buf, th+24, 0x89); _w8(buf, th+25, 0xF2) // mov rdx,rsi 162 _w8(buf, th+26, 0x4C); _w8(buf, th+27, 0x8B); _w8(buf, th+28, 0x44); _w8(buf, th+29, 0x24); _w8(buf, th+30, 0x30) // mov r8,[rsp+0x30] 163 _w8(buf, th+31, 0x4C); _w8(buf, th+32, 0x8D); _w8(buf, th+33, 0x4C); _w8(buf, th+34, 0x24); _w8(buf, th+35, 0x28) // lea r9,[rsp+0x28] 164 _w8(buf, th+36, 0x48); _w8(buf, th+37, 0xC7); _w8(buf, th+38, 0x44); _w8(buf, th+39, 0x24); _w8(buf, th+40, 0x20); _w32(buf, th+41, 0) // mov qword[rsp+0x20],0 165 _w8(buf, th+45, 0xFF); _w8(buf, th+46, 0x15); _w32(buf, th+47, NXW_IAT_WF - (thunk_rva + 51)) // call [rip] WriteFile 166 _w8(buf, th+51, 0x48); _w8(buf, th+52, 0x83); _w8(buf, th+53, 0xC4); _w8(buf, th+54, 0x38) // add rsp,0x38 167 _w8(buf, th+55, 0xC3) // ret 168 // OVERWRITE the shim's first 5 bytes with `jmp rel32` -> thunk. 169 let shim_rva: i64 = NXW_RVA_TEXT + NXW_STUB_LEN + shim_off 170 let sh: i64 = NXW_FOFF_TEXT + NXW_STUB_LEN + shim_off 171 _w8(buf, sh+0, 0xE9); _w32(buf, sh+1, thunk_rva - (shim_rva + 5)) 172 173 // ===== .idata: 3 kernel32 imports (GetStdHandle, WriteFile, ExitProcess) -- W3b-1 layout ===== 174 let d: i64 = NXW_FOFF_IDATA 175 _w32(buf, d + 0, 0x3028) 176 _w32(buf, d + 12, 0x3092) 177 _w32(buf, d + 16, 0x3048) 178 _w64(buf, d + 0x28, 0x3068, 0) 179 _w64(buf, d + 0x30, 0x3078, 0) 180 _w64(buf, d + 0x38, 0x3084, 0) 181 _w64(buf, d + 0x40, 0, 0) 182 _w64(buf, d + 0x48, 0x3068, 0) 183 _w64(buf, d + 0x50, 0x3078, 0) 184 _w64(buf, d + 0x58, 0x3084, 0) 185 _w64(buf, d + 0x60, 0, 0) 186 _w16(buf, d + 0x68, 0) 187 _w8(buf,d+0x6A,71);_w8(buf,d+0x6B,101);_w8(buf,d+0x6C,116);_w8(buf,d+0x6D,83) // GetS 188 _w8(buf,d+0x6E,116);_w8(buf,d+0x6F,100);_w8(buf,d+0x70,72);_w8(buf,d+0x71,97) // tdHa 189 _w8(buf,d+0x72,110);_w8(buf,d+0x73,100);_w8(buf,d+0x74,108);_w8(buf,d+0x75,101) // ndle 190 _w8(buf,d+0x76,0);_w8(buf,d+0x77,0) 191 _w16(buf, d + 0x78, 0) 192 _w8(buf,d+0x7A,87);_w8(buf,d+0x7B,114);_w8(buf,d+0x7C,105);_w8(buf,d+0x7D,116) // Writ 193 _w8(buf,d+0x7E,101);_w8(buf,d+0x7F,70);_w8(buf,d+0x80,105);_w8(buf,d+0x81,108) // eFil 194 _w8(buf,d+0x82,101);_w8(buf,d+0x83,0) // e\0 195 _w16(buf, d + 0x84, 0) 196 _w8(buf,d+0x86,69);_w8(buf,d+0x87,120);_w8(buf,d+0x88,105);_w8(buf,d+0x89,116) // Exit 197 _w8(buf,d+0x8A,80);_w8(buf,d+0x8B,114);_w8(buf,d+0x8C,111);_w8(buf,d+0x8D,99) // Proc 198 _w8(buf,d+0x8E,101);_w8(buf,d+0x8F,115);_w8(buf,d+0x90,115);_w8(buf,d+0x91,0) // ess\0 199 _w8(buf,d+0x92,107);_w8(buf,d+0x93,101);_w8(buf,d+0x94,114);_w8(buf,d+0x95,110) // kern 200 _w8(buf,d+0x96,101);_w8(buf,d+0x97,108);_w8(buf,d+0x98,51);_w8(buf,d+0x99,50) // el32 201 _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 202 203 return NX_PE_OK 204} 205 206func main() -> i64 { 207 let lenbox: *i64 = sys_mmap(16) as *i64 208 let src: *u8 = sys_read_file("/tmp/nxwin.s" as *u8, lenbox) 209 if (src as i64) == 0 { return 1 } 210 let n: i64 = lenbox[0] 211 if n <= 0 { return 2 } 212 213 let code: *u8 = sys_mmap(NXW_CODE_CAP) 214 let mainbox: *i64 = sys_mmap(16) as *i64 215 let shimbox: *i64 = sys_mmap(16) as *i64 216 mainbox[0] = 0 - 1 217 shimbox[0] = 0 - 1 218 let code_len: i64 = nxw_assemble(src, n, code, NXW_CODE_CAP, mainbox, shimbox) 219 if code_len < 0 { return 3 } 220 let main_off: i64 = mainbox[0] 221 if main_off < 0 { return 4 } 222 let shim_off: i64 = shimbox[0] 223 if shim_off < 0 { return 6 } // shim nx_win_write not found 224 225 let buf: *u8 = sys_mmap(NXW_FILE_SIZE) 226 let rc: i64 = nxw_emit_pe(buf, code, code_len, main_off, shim_off) 227 if rc != NX_PE_OK { return 5 } 228 229 let outp: *u8 = "/mnt/c/Users/elder/nishi-core/nxc2/_offc/nx_win_compiled_write.exe" as *u8 230 if nx_pe_write_to_file(outp, buf, NXW_FILE_SIZE) != NX_PE_OK { return 70 } 231 232 let msg: *u8 = "[substrate] nxc2-compiled native sys_write PE written: nx_win_compiled_write.exe\n" as *u8 233 sys_write(1, msg, 80) 234 return 0 235}