code wiki / (root) / nx_pe_job_limit_test.nx

nx_pe_job_limit_test.nx source

↩ module page · 247 lines · 15657 B

1// nx_pe_job_limit_test.nx -- W2b: sovereign Windows Job Object ENFORCEMENT, native, no HCS. 2// 3// Docker-replacement ladder rung W2b. Proves the Job Object does not just EXIST (W2a) but 4// ENFORCES a resource limit -- the cgroups analog, native NT, kernel32-only, no VM/HCS. 5// 6// Mechanism: ActiveProcessLimit via SetInformationJobObject(JobObjectBasicLimitInformation). 7// One emit fn parameterized by the limit value. main() emits TWO PEs differing ONLY in the 8// ActiveProcessLimit immediate: 9// nx_pe_job_limit_deny.exe (limit=1): self is the 1 allowed proc; spawning a child is DENIED -> exit 42 10// nx_pe_job_limit_allow.exe (limit=2): room for the child; spawn SUCCEEDS -> exit 50 11// 12// Each PE: 13// 1. CreateJobObjectW(NULL,NULL) -> hJob 14// 2. SetInformationJobObject(hJob, 2, &basic, 0x40) LimitFlags=JOB_OBJECT_LIMIT_ACTIVE_PROCESS(0x8), 15// ActiveProcessLimit=N 16// 3. AssignProcessToJobObject(hJob, (HANDLE)-1) self joins the job (counts as 1) 17// 4. CreateProcessW(NULL,"cmd.exe /c exit 0",...,CREATE_NO_WINDOW,...,&si,&pi) -> rax (BOOL) 18// 5. ExitProcess( rax==0 ? 42 : 50 ) child auto-joins the job; denied if over limit 19// 20// NO-FALSE-GREEN: the gate requires deny->42 AND allow->50. The two binaries are byte-identical 21// except the ActiveProcessLimit immediate, so a CreateProcessW arg bug would fail BOTH (deny AND 22// allow -> 42) and a non-enforcing limit would pass BOTH (deny AND allow -> 50). Only a genuinely 23// ENFORCED limit produces 42-then-50. (positive control = allow; negative control = deny.) 24// 25// HONEST SCOPE: proves ActiveProcessLimit ENFORCEMENT. Memory.max/CPU-rate caps + child-spawn-into-job 26// supervision = later rungs. Founded on nx_pe_writer.nx (hand byte-layout, tutor-scaffold). 27// genealogy_id: win32_job_objects_2000 + lmctfy_2013 + runc_cgroups_2016 28// lineage_id: substrate_pe_joblimit_v1 29 30import "nx_syscalls.nx" 31import "nx_hal.nx" 32import "nx_pe_writer.nx" 33 34const PE_JOBLIMIT_FILE_SIZE: i64 = 0x800 // 2048: headers + .text + .rdata + .idata 35 36// 3 sections: .text RVA 0x1000 / .rdata RVA 0x2000 (cmdline) / .idata RVA 0x3000 (5 imports). 37// IAT RVAs: CJO 0x3058, SIJO 0x3060, APTJ 0x3068, CPW 0x3070, EXIT 0x3078. 38func nx_pe_emit_job_limit(buf: *u8, active_limit: i64) -> i64 { 39 if (buf as i64) == 0 { return 0 - NX_PE_BAD_INPUT } 40 41 // ===== DOS + PE sig ===== 42 _w16(buf, 0, 0x5A4D) 43 _w32(buf, 0x3C, FOFF_PE_SIG) 44 _w32(buf, FOFF_PE_SIG, 0x00004550) 45 46 // ===== COFF ===== 47 _w16(buf, FOFF_COFF + 0, PE_MACHINE_AMD64) 48 _w16(buf, FOFF_COFF + 2, 3) // 3 sections 49 _w16(buf, FOFF_COFF + 16, 0xF0) 50 _w16(buf, FOFF_COFF + 18, PE_CHAR_EXEC | PE_CHAR_LARGE_ADDR) 51 52 // ===== Optional Header ===== 53 _w16(buf, FOFF_OPT + 0, PE_OH_MAGIC_PEPLUS) 54 _w8(buf, FOFF_OPT + 2, 1) 55 _w32(buf, FOFF_OPT + 4, 0x200) 56 _w32(buf, FOFF_OPT + 8, 0x400) 57 _w32(buf, FOFF_OPT + 16, PE_HELLO_RVA_TEXT) 58 _w32(buf, FOFF_OPT + 20, PE_HELLO_RVA_TEXT) 59 _w64(buf, FOFF_OPT + 24, IMG_BASE_LO, IMG_BASE_HI) 60 _w32(buf, FOFF_OPT + 32, 0x1000) 61 _w32(buf, FOFF_OPT + 36, 0x200) 62 _w16(buf, FOFF_OPT + 40, 6) 63 _w16(buf, FOFF_OPT + 48, 6) 64 _w32(buf, FOFF_OPT + 56, 0x4000) // SizeOfImage (headers + 3 sections) 65 _w32(buf, FOFF_OPT + 60, 0x200) 66 _w16(buf, FOFF_OPT + 68, PE_SUBSYSTEM_CONSOLE) 67 _w64(buf, FOFF_OPT + 72, 0x100000, 0) 68 _w64(buf, FOFF_OPT + 80, 0x1000, 0) 69 _w64(buf, FOFF_OPT + 88, 0x100000, 0) 70 _w64(buf, FOFF_OPT + 96, 0x1000, 0) 71 _w32(buf, FOFF_OPT + 108, 16) 72 _w32(buf, FOFF_OPT + 112 + 8, PE_HELLO_RVA_IDATA) // import dir -> .idata RVA 0x3000 73 _w32(buf, FOFF_OPT + 112 + 12, 0x28) 74 75 // ===== Section Headers ===== 76 _emit_section_header(buf, FOFF_SECT_TBL, 46, 116, 101, 120, 116, 0, 0, 0, 0xD8, PE_HELLO_RVA_TEXT, 0x200, PE_HELLO_FOFF_TEXT, PE_SECT_CODE_X_R) 77 _emit_section_header(buf, FOFF_SECT_TBL + 40, 46, 100, 97, 116, 97, 0, 0, 0, 0x24, PE_HELLO_RVA_RDATA, 0x200, PE_HELLO_FOFF_RDATA, PE_SECT_DATA_RW) 78 _emit_section_header(buf, FOFF_SECT_TBL + 80, 46, 105, 100, 97, 116, 97, 0, 0, 0xFF, PE_HELLO_RVA_IDATA, 0x200, PE_HELLO_FOFF_IDATA, PE_SECT_DATA_R) 79 80 // ===== .text (216 bytes) ===== 81 let t: i64 = PE_HELLO_FOFF_TEXT 82 // sub rsp, 0x138 off 0 (7) 83 _w8(buf, t+0, 0x48); _w8(buf, t+1, 0x81); _w8(buf, t+2, 0xEC); _w32(buf, t+3, 0x138) 84 // xor eax,eax off 7 (2) 85 _w8(buf, t+7, 0x31); _w8(buf, t+8, 0xC0) 86 // lea rdi,[rsp+0x58] off 9 (5) 87 _w8(buf, t+9, 0x48); _w8(buf, t+10, 0x8D); _w8(buf, t+11, 0x7C); _w8(buf, t+12, 0x24); _w8(buf, t+13, 0x58) 88 // mov ecx,24 off 14 (5) 89 _w8(buf, t+14, 0xB9); _w32(buf, t+15, 24) 90 // rep stosq (zero si+pi+jobinfo: 0x58..0x117) off 19 (3) 91 _w8(buf, t+19, 0xF3); _w8(buf, t+20, 0x48); _w8(buf, t+21, 0xAB) 92 // mov dword [rsp+0x58], 0x68 (si.cb=104) off 22 (8) 93 _w8(buf, t+22, 0xC7); _w8(buf, t+23, 0x44); _w8(buf, t+24, 0x24); _w8(buf, t+25, 0x58); _w32(buf, t+26, 0x68) 94 // --- CreateJobObjectW(NULL,NULL) --- 95 // xor ecx,ecx ; xor edx,edx off 30 (2+2) 96 _w8(buf, t+30, 0x31); _w8(buf, t+31, 0xC9); _w8(buf, t+32, 0x31); _w8(buf, t+33, 0xD2) 97 // call [CJO] next=0x1028 tgt=0x3058 disp=0x2030 off 34 (6) 98 _w8(buf, t+34, 0xFF); _w8(buf, t+35, 0x15); _w32(buf, t+36, 0x2030) 99 // mov [rsp+0x50], rax (hJob) off 40 (5) 100 _w8(buf, t+40, 0x48); _w8(buf, t+41, 0x89); _w8(buf, t+42, 0x44); _w8(buf, t+43, 0x24); _w8(buf, t+44, 0x50) 101 // mov dword [rsp+0xE8], 8 (LimitFlags) off 45 (11) 102 _w8(buf, t+45, 0xC7); _w8(buf, t+46, 0x84); _w8(buf, t+47, 0x24); _w32(buf, t+48, 0xE8); _w32(buf, t+52, 8) 103 // mov dword [rsp+0x100], active_limit off 56 (11) 104 _w8(buf, t+56, 0xC7); _w8(buf, t+57, 0x84); _w8(buf, t+58, 0x24); _w32(buf, t+59, 0x100); _w32(buf, t+63, active_limit) 105 // --- SetInformationJobObject(hJob, 2, &jobinfo, 0x40) --- 106 // mov rcx,[rsp+0x50] off 67 (5) 107 _w8(buf, t+67, 0x48); _w8(buf, t+68, 0x8B); _w8(buf, t+69, 0x4C); _w8(buf, t+70, 0x24); _w8(buf, t+71, 0x50) 108 // mov edx,2 off 72 (5) 109 _w8(buf, t+72, 0xBA); _w32(buf, t+73, 2) 110 // lea r8,[rsp+0xD8] off 77 (8) 111 _w8(buf, t+77, 0x4C); _w8(buf, t+78, 0x8D); _w8(buf, t+79, 0x84); _w8(buf, t+80, 0x24); _w32(buf, t+81, 0xD8) 112 // mov r9d,0x40 off 85 (6) 113 _w8(buf, t+85, 0x41); _w8(buf, t+86, 0xB9); _w32(buf, t+87, 0x40) 114 // call [SIJO] next=0x1061 tgt=0x3060 disp=0x1FFF off 91 (6) 115 _w8(buf, t+91, 0xFF); _w8(buf, t+92, 0x15); _w32(buf, t+93, 0x1FFF) 116 // --- AssignProcessToJobObject(hJob, -1) --- 117 // mov rcx,[rsp+0x50] off 97 (5) 118 _w8(buf, t+97, 0x48); _w8(buf, t+98, 0x8B); _w8(buf, t+99, 0x4C); _w8(buf, t+100, 0x24); _w8(buf, t+101, 0x50) 119 // mov rdx,-1 off 102 (7) 120 _w8(buf, t+102, 0x48); _w8(buf, t+103, 0xC7); _w8(buf, t+104, 0xC2); _w8(buf, t+105, 0xFF); _w8(buf, t+106, 0xFF); _w8(buf, t+107, 0xFF); _w8(buf, t+108, 0xFF) 121 // call [APTJ] next=0x1073 tgt=0x3068 disp=0x1FF5 off 109 (6) 122 _w8(buf, t+109, 0xFF); _w8(buf, t+110, 0x15); _w32(buf, t+111, 0x1FF5) 123 // --- CreateProcessW(NULL,cmdline,NULL,NULL,FALSE,CREATE_NO_WINDOW,NULL,NULL,&si,&pi) --- 124 // xor ecx,ecx (arg1) off 115 (2) 125 _w8(buf, t+115, 0x31); _w8(buf, t+116, 0xC9) 126 // lea rdx,[rip+cmdline] next=0x107C tgt=0x2000 disp=0xF84 arg2 off 117 (7) 127 _w8(buf, t+117, 0x48); _w8(buf, t+118, 0x8D); _w8(buf, t+119, 0x15); _w32(buf, t+120, 0xF84) 128 // xor r8d,r8d (arg3) ; xor r9d,r9d (arg4) off 124 (3+3) 129 _w8(buf, t+124, 0x45); _w8(buf, t+125, 0x31); _w8(buf, t+126, 0xC0) 130 _w8(buf, t+127, 0x45); _w8(buf, t+128, 0x31); _w8(buf, t+129, 0xC9) 131 // mov dword [rsp+0x20], 0 (arg5 FALSE) off 130 (8) 132 _w8(buf, t+130, 0xC7); _w8(buf, t+131, 0x44); _w8(buf, t+132, 0x24); _w8(buf, t+133, 0x20); _w32(buf, t+134, 0) 133 // mov dword [rsp+0x28], 0x08000000 (arg6 CREATE_NO_WINDOW) off 138 (8) 134 _w8(buf, t+138, 0xC7); _w8(buf, t+139, 0x44); _w8(buf, t+140, 0x24); _w8(buf, t+141, 0x28); _w32(buf, t+142, 0x08000000) 135 // mov qword [rsp+0x30], 0 (arg7) off 146 (9) 136 _w8(buf, t+146, 0x48); _w8(buf, t+147, 0xC7); _w8(buf, t+148, 0x44); _w8(buf, t+149, 0x24); _w8(buf, t+150, 0x30); _w32(buf, t+151, 0) 137 // mov qword [rsp+0x38], 0 (arg8) off 155 (9) 138 _w8(buf, t+155, 0x48); _w8(buf, t+156, 0xC7); _w8(buf, t+157, 0x44); _w8(buf, t+158, 0x24); _w8(buf, t+159, 0x38); _w32(buf, t+160, 0) 139 // lea rax,[rsp+0x58] ; mov [rsp+0x40],rax (arg9 &si) off 164 (5+5) 140 _w8(buf, t+164, 0x48); _w8(buf, t+165, 0x8D); _w8(buf, t+166, 0x44); _w8(buf, t+167, 0x24); _w8(buf, t+168, 0x58) 141 _w8(buf, t+169, 0x48); _w8(buf, t+170, 0x89); _w8(buf, t+171, 0x44); _w8(buf, t+172, 0x24); _w8(buf, t+173, 0x40) 142 // lea rax,[rsp+0xC0] ; mov [rsp+0x48],rax (arg10 &pi) off 174 (8+5) 143 _w8(buf, t+174, 0x48); _w8(buf, t+175, 0x8D); _w8(buf, t+176, 0x84); _w8(buf, t+177, 0x24); _w32(buf, t+178, 0xC0) 144 _w8(buf, t+182, 0x48); _w8(buf, t+183, 0x89); _w8(buf, t+184, 0x44); _w8(buf, t+185, 0x24); _w8(buf, t+186, 0x48) 145 // call [CPW] next=0x10C1 tgt=0x3070 disp=0x1FAF off 187 (6) 146 _w8(buf, t+187, 0xFF); _w8(buf, t+188, 0x15); _w32(buf, t+189, 0x1FAF) 147 // --- exit = (rax==0) ? 42 : 50 --- 148 // mov ecx,42 ; mov edx,50 off 193 (5+5) 149 _w8(buf, t+193, 0xB9); _w32(buf, t+194, 42) 150 _w8(buf, t+198, 0xBA); _w32(buf, t+199, 50) 151 // test rax,rax off 203 (3) 152 _w8(buf, t+203, 0x48); _w8(buf, t+204, 0x85); _w8(buf, t+205, 0xC0) 153 // cmovnz ecx,edx (rax!=0 -> 50) off 206 (3) 154 _w8(buf, t+206, 0x0F); _w8(buf, t+207, 0x45); _w8(buf, t+208, 0xCA) 155 // call [EXIT] next=0x10D7 tgt=0x3078 disp=0x1FA1 off 209 (6) 156 _w8(buf, t+209, 0xFF); _w8(buf, t+210, 0x15); _w32(buf, t+211, 0x1FA1) 157 // int3 off 215 (1) 158 _w8(buf, t+215, 0xCC) 159 160 // ===== .rdata: "cmd.exe /c exit 0\0" UTF-16 (36 bytes) ===== 161 let r: i64 = PE_HELLO_FOFF_RDATA 162 _w16(buf, r+0, 0x63); _w16(buf, r+2, 0x6D); _w16(buf, r+4, 0x64); _w16(buf, r+6, 0x2E) // c m d . 163 _w16(buf, r+8, 0x65); _w16(buf, r+10, 0x78); _w16(buf, r+12, 0x65); _w16(buf, r+14, 0x20) // e x e SP 164 _w16(buf, r+16, 0x2F); _w16(buf, r+18, 0x63); _w16(buf, r+20, 0x20); _w16(buf, r+22, 0x65) // / c SP e 165 _w16(buf, r+24, 0x78); _w16(buf, r+26, 0x69); _w16(buf, r+28, 0x74); _w16(buf, r+30, 0x20) // x i t SP 166 _w16(buf, r+32, 0x30); _w16(buf, r+34, 0x00) // 0 NUL 167 168 // ===== .idata (5 imports) ===== 169 let d: i64 = PE_HELLO_FOFF_IDATA 170 // Import Descriptor 171 _w32(buf, d+0, 0x3028) // OriginalFirstThunk = INT RVA 172 _w32(buf, d+12, 0x30F2) // Name = "kernel32.dll" RVA 173 _w32(buf, d+16, 0x3058) // FirstThunk = IAT RVA 174 // INT @ 0x28 175 _w64(buf, d+0x28, 0x3088, 0) // CreateJobObjectW 176 _w64(buf, d+0x30, 0x309C, 0) // SetInformationJobObject 177 _w64(buf, d+0x38, 0x30B6, 0) // AssignProcessToJobObject 178 _w64(buf, d+0x40, 0x30D2, 0) // CreateProcessW 179 _w64(buf, d+0x48, 0x30E4, 0) // ExitProcess 180 _w64(buf, d+0x50, 0, 0) // null 181 // IAT @ 0x58 182 _w64(buf, d+0x58, 0x3088, 0) 183 _w64(buf, d+0x60, 0x309C, 0) 184 _w64(buf, d+0x68, 0x30B6, 0) 185 _w64(buf, d+0x70, 0x30D2, 0) 186 _w64(buf, d+0x78, 0x30E4, 0) 187 _w64(buf, d+0x80, 0, 0) 188 189 // byname: CreateJobObjectW @ 0x88 190 _w16(buf, d+0x88, 0) 191 _w8(buf,d+0x8A,67);_w8(buf,d+0x8B,114);_w8(buf,d+0x8C,101);_w8(buf,d+0x8D,97);_w8(buf,d+0x8E,116);_w8(buf,d+0x8F,101) 192 _w8(buf,d+0x90,74);_w8(buf,d+0x91,111);_w8(buf,d+0x92,98);_w8(buf,d+0x93,79);_w8(buf,d+0x94,98);_w8(buf,d+0x95,106) 193 _w8(buf,d+0x96,101);_w8(buf,d+0x97,99);_w8(buf,d+0x98,116);_w8(buf,d+0x99,87);_w8(buf,d+0x9A,0) 194 // SetInformationJobObject @ 0x9C 195 _w16(buf, d+0x9C, 0) 196 _w8(buf,d+0x9E,83);_w8(buf,d+0x9F,101);_w8(buf,d+0xA0,116);_w8(buf,d+0xA1,73);_w8(buf,d+0xA2,110);_w8(buf,d+0xA3,102) 197 _w8(buf,d+0xA4,111);_w8(buf,d+0xA5,114);_w8(buf,d+0xA6,109);_w8(buf,d+0xA7,97);_w8(buf,d+0xA8,116);_w8(buf,d+0xA9,105) 198 _w8(buf,d+0xAA,111);_w8(buf,d+0xAB,110);_w8(buf,d+0xAC,74);_w8(buf,d+0xAD,111);_w8(buf,d+0xAE,98);_w8(buf,d+0xAF,79) 199 _w8(buf,d+0xB0,98);_w8(buf,d+0xB1,106);_w8(buf,d+0xB2,101);_w8(buf,d+0xB3,99);_w8(buf,d+0xB4,116);_w8(buf,d+0xB5,0) 200 // AssignProcessToJobObject @ 0xB6 201 _w16(buf, d+0xB6, 0) 202 _w8(buf,d+0xB8,65);_w8(buf,d+0xB9,115);_w8(buf,d+0xBA,115);_w8(buf,d+0xBB,105);_w8(buf,d+0xBC,103);_w8(buf,d+0xBD,110) 203 _w8(buf,d+0xBE,80);_w8(buf,d+0xBF,114);_w8(buf,d+0xC0,111);_w8(buf,d+0xC1,99);_w8(buf,d+0xC2,101);_w8(buf,d+0xC3,115) 204 _w8(buf,d+0xC4,115);_w8(buf,d+0xC5,84);_w8(buf,d+0xC6,111);_w8(buf,d+0xC7,74);_w8(buf,d+0xC8,111);_w8(buf,d+0xC9,98) 205 _w8(buf,d+0xCA,79);_w8(buf,d+0xCB,98);_w8(buf,d+0xCC,106);_w8(buf,d+0xCD,101);_w8(buf,d+0xCE,99);_w8(buf,d+0xCF,116);_w8(buf,d+0xD0,0) 206 // CreateProcessW @ 0xD2 207 _w16(buf, d+0xD2, 0) 208 _w8(buf,d+0xD4,67);_w8(buf,d+0xD5,114);_w8(buf,d+0xD6,101);_w8(buf,d+0xD7,97);_w8(buf,d+0xD8,116);_w8(buf,d+0xD9,101) 209 _w8(buf,d+0xDA,80);_w8(buf,d+0xDB,114);_w8(buf,d+0xDC,111);_w8(buf,d+0xDD,99);_w8(buf,d+0xDE,101);_w8(buf,d+0xDF,115) 210 _w8(buf,d+0xE0,115);_w8(buf,d+0xE1,87);_w8(buf,d+0xE2,0) 211 // ExitProcess @ 0xE4 212 _w16(buf, d+0xE4, 0) 213 _w8(buf,d+0xE6,69);_w8(buf,d+0xE7,120);_w8(buf,d+0xE8,105);_w8(buf,d+0xE9,116);_w8(buf,d+0xEA,80);_w8(buf,d+0xEB,114) 214 _w8(buf,d+0xEC,111);_w8(buf,d+0xED,99);_w8(buf,d+0xEE,101);_w8(buf,d+0xEF,115);_w8(buf,d+0xF0,115);_w8(buf,d+0xF1,0) 215 // "kernel32.dll\0" @ 0xF2 216 _w8(buf,d+0xF2,107);_w8(buf,d+0xF3,101);_w8(buf,d+0xF4,114);_w8(buf,d+0xF5,110);_w8(buf,d+0xF6,101);_w8(buf,d+0xF7,108) 217 _w8(buf,d+0xF8,51);_w8(buf,d+0xF9,50);_w8(buf,d+0xFA,46);_w8(buf,d+0xFB,100);_w8(buf,d+0xFC,108);_w8(buf,d+0xFD,108);_w8(buf,d+0xFE,0) 218 219 return NX_PE_OK 220} 221 222func main() -> i64 { 223 let buf: *u8 = nx_hal_alloc_pages(PE_JOBLIMIT_FILE_SIZE) 224 if (buf as i64) == 0 { return 1 } 225 226 // --- deny variant: ActiveProcessLimit = 1 --- 227 let rcd: i64 = nx_pe_emit_job_limit(buf, 1) 228 if rcd != NX_PE_OK { return 2 } 229 if buf[0] != 0x4D { return 10 } 230 if buf[0x86] != 0x03 { return 11 } // 3 sections 231 if buf[0x200] != 0x48 { return 12 } // .text sub rsp 232 if buf[0x23F] != 0x01 { return 13 } // ActiveProcessLimit imm low byte == 1 233 if buf[0x68A] != 0x43 { return 14 } // 'C' of CreateJobObjectW (d+0x8A) 234 let pd: *u8 = "/mnt/c/Users/elder/nishi-core/nxc2/_offc/nx_pe_job_limit_deny.exe" as *u8 235 if nx_pe_write_to_file(pd, buf, PE_JOBLIMIT_FILE_SIZE) != NX_PE_OK { return 70 } 236 237 // --- allow variant: ActiveProcessLimit = 2 --- 238 let rca: i64 = nx_pe_emit_job_limit(buf, 2) 239 if rca != NX_PE_OK { return 3 } 240 if buf[0x23F] != 0x02 { return 15 } // ActiveProcessLimit imm low byte == 2 241 let pa: *u8 = "/mnt/c/Users/elder/nishi-core/nxc2/_offc/nx_pe_job_limit_allow.exe" as *u8 242 if nx_pe_write_to_file(pa, buf, PE_JOBLIMIT_FILE_SIZE) != NX_PE_OK { return 71 } 243 244 let msg: *u8 = "[substrate] Job-limit PEs written: deny(limit=1) + allow(limit=2)\n" as *u8 245 sys_write(1, msg, 65) 246 return 0 247}