Windows API For Red Team 102
Windows API For Red Team 102
Author
Joas Antonio dos Santos
https://www.linkedin.com/in/joas-antonio-dos-santos/
AUTHOR
04 MANIPULATION OF PROCESSES
AND THREADS
These APIs allow everything from manipulating security tokens to interacting with
system processes and modules. Let's get to know some of them:
VirtualAlloc: Part of the family of functions that manage virtual memory. Allows
the allocation of memory space within the virtual address space of a process. It is
widely used to create space for injecting malicious code or for expanding buffers
needed during the execution of complex operations.
MalAPI
Link:https://malapi.io/
When software runs on Windows, it starts as a process that can contain one or more
threads. Each thread executes part of the program code in its own execution
context, allowing multiple operations to occur simultaneously within the same
process. The APIs that manage these processes and threads are vital not only for
optimizing application performance and efficiency, but are also often exploited in
offensive security techniques to perform code injection, privilege escalation, and
other forms of process manipulation.
A process is an instance of a running program that contains its own isolated virtual
address space, code, data, and other system resources. Each process in Windows
operates within its own context, ensuring that processes do not interfere with each
other without appropriate permissions.
A thread is an entity within a process that can be scheduled to execute. It is the basic
execution unit used by the operating system to execute the program. A process can
contain multiple threads that share the same memory space and system resources,
but can run independently of each other to perform multiple tasks simultaneously.
In this case, we will read information from a process by PID using Windows
APIs. Here's an example of how you can do this in C++:
To read information from a process using its Process ID (PID), we will use the
OpenProcess and GetProcessImageFileNameA functions.
voidPrintDetailedProcessInfo(DWORD processID) {
HANDLEhProcess =
OpenProcess(PROCESS_QUERY_INFORMATION|PROCESS_VM_READ,FALSE,processID);
if(hProcess ==NULL) {
std::cerr<< "Failed to open process with PID: " << processID << ".Error:
" <<GetLastError()<<std::endl;
return;
This code attempts to open a process in Windows using the process ID (PID).
The OpenProcess function is called with permissions to query information and
read process memory. If the function fails (i.e. returns NULL), it prints an error
message showing the PID and specific error code. This message helps you
identify why the process could not be accessed. If the handle is obtained
successfully, it will be used for subsequent operations in the process, such as
reading data or querying information. If it fails, the function terminates
prematurely.
TCHARprocessPath[MAX_PATH];
if(GetModuleFileNameEx(hProcess,NULL, processPath,MAX_PATH) == 0) {
std::cerr<< "Failed to get process path for PID: " << processID <<
".Error: " <<GetLastError()<<std::endl;
}
else{
std::wcout<< "Process ID: " << processID << ". Executable Path: "
<<processPath<<std::endl;
This code attempts to get the executable file path of a process using
GetModuleFileNameEx. First, it sets processPath to store the path, using the
maximum allowed size, MAX_PATH. The function tries to fill this array with the
path of the process's executable. If it fails (returns 0), it prints an error message
with the error code obtained by GetLastError(). If successful, displays the
process PID and executable path.
// Obtaining process memory information
PROCESS_MEMORY_COUNTERSpmc;
if(GetProcessMemoryInfo(hProcess, &pmc,sizeof(pmc))) {
std::cout<< "Memory Usage: " <<pmc.WorkingSetSize<< "bytes" <<std::endl;
}
CloseHandle(hProcess);
This code fetches detailed information about the memory and CPU usage of
a specific process.
After collecting this information, the code ends by closing the process handle
with CloseHandle, freeing associated resources.
intmain() {
DWORDpid;
std::cout<< "Enter the PID of the process: ";
std::cin>>pid;
PrintDetailedProcessInfo(pid);
return0;
This code is the main function that asks the user to enter a process ID (PID).
After receiving the PID, it calls the PrintDetailedProcessInfo function to display
detailed information about this process. Then, the program ends and returns
0, indicating that it ran without errors.
Complete code
#include <windows.h>
#include <iostream>
#include <psapi.h>
#include <tchar.h>
voidPrintDetailedProcessInfo(DWORD processID) {
HANDLEhProcess =
OpenProcess(PROCESS_QUERY_INFORMATION|PROCESS_VM_READ,FALSE,processID);
if(hProcess ==NULL) {
std::cerr<< "Failed to open process with PID: " << processID << ".Error:
" <<GetLastError()<<std::endl;
return;
}
CloseHandle(hProcess);
}
intmain() {
DWORDpid;
std::cout<< "Enter the PID of the process: ";
std::cin>>pid;
PrintDetailedProcessInfo(pid);
return0;
}
This code assumes you have the PID and want to get the name of the
executable, collect additional information such as memory usage, base
priority, and CPU time used by the process.
This is just some information I can collect from a process, you can adapt the
code above to extract more information.
To use the process name instead of the PID, you can iterate over all active
processes on the machine, compare the executable names, and call the
voidPrintProcessInfo(const TCHAR*processName) {
DWORDprocesses[1024], needed, cProcesses;
unsigned inti;
if(!EnumProcesses(processes,sizeof(processes), &needed)) {
std::cerr<< "Failed to enumerate processes." <<std::endl;
return;
This code lists all active processes on the system. It uses the EnumProcesses
function to fill an array with process IDs. If the function fails, it displays an error
message indicating that the processes could not be enumerated.
if(_tcsicmp(baseName,processName) == 0) {
// Print details if matched
std::wcout<< "Process found! Name: " <<baseName<< ", PID: "
<<processes[i]<<std::endl;
CloseHandle(hProcess);
return;
}
}
CloseHandle(hProcess);
}
}
}
This code goes through the list of active processes obtained previously and
tries to open each process to access its information. Uses OpenProcess with
permissions to query information and read process memory. If it can open the
process, try to obtain the full path of the executable file using
GetModuleFileNameEx. If it fails, it shows an error; if successful, it checks
whether the file's base name matches the name provided by the user. This is
done by separating the base name from the full path. If it finds a match, it
prints the process details and ends the search. If it does not find the specified
process after checking them all, it displays a message that the process was
not found.
intmain() {
TCHARprocessName[MAX_PATH];
std::wcout<< "Enter the name of the process (eg, 'notepad.exe'): ";
std::wcin>>processName;
PrintProcessInfo(processName);
return0;
This code is the main function that asks the user to enter the name of a
process. After obtaining this name, it calls the PrintProcessInfo function to
fetch and display information about the process. If found, show details; If not,
it reports that it was not found. The program ends by returning 0, indicating
that it ran without errors.
Complete code
#include <windows.h>
#include <iostream>
#include <psapi.h>
#include <tchar.h>
voidPrintProcessInfo(const TCHAR*processName) {
DWORDprocesses[1024], needed, cProcesses;
unsigned inti;
if(!EnumProcesses(processes,sizeof(processes), &needed)) {
std::cerr<< "Failed to enumerate processes." <<std::endl;
return;
}
if(_tcsicmp(baseName,processName) == 0) {
// Print details if matched
std::wcout<< "Process found! Name: " <<baseName<< ", PID: "
<<processes[i]<<std::endl;
CloseHandle(hProcess);
return;
}
}
CloseHandle(hProcess);
}
}
}
intmain() {
TCHARprocessName[MAX_PATH];
std::wcout<< "Enter the name of the process (eg, 'notepad.exe'): ";
std::wcin>>processName;
PrintProcessInfo(processName);
return0;
}
When executing the code you obtain information about the PID of the
process or processes with the same name
To create a specific process by the name "cmd.exe" in C++, you can use the
Windows API called CreateProcessW. Let's demonstrate how to launch
cmd.exe.
intmain() {
STARTUPINFOyes;
PROCESS_INFORMATIONpi;
// Process creation
if(!CreateProcess(
NULL,// Program module name
cmdLine,// Modifiable command line
NULL,// Process safety attributes
NULL,// Thread safety attributes
FALSE,// Handle inheritance
0,// Creation flags
NULL,// Environment
NULL,// Current directory
&si,// Initialization information
&pi// Process information
)) {
std::wcerr<< L"CreateProcess failed (" <<GetLastError()<< L").\n";
return1;
}
return0;
This code runs cmd.exe and creates a process, as long as you don't close
cmd.exe it doesn't kill the process.
Using Windows run, I put the path of the compiled code to open a cmd.exe
DWORDFindProcessId(conststd::wstring&processName) {
PROCESSENTRY32processInfo;
processInfo.dwSize =sizeof(processInfo);
HANDLEprocessesSnapshot =
CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,NULL);
if(processesSnapshot ==INVALID_HANDLE_VALUE)
return0;
Process32First(processesSnapshot, &processInfo);
if(!processName.compare(processInfo.szExeFile)) {
CloseHandle(processesSnapshot);
returnprocessInfo.th32ProcessID;
}
while(Process32Next(processesSnapshot, &processInfo)) {
if(!processName.compare(processInfo.szExeFile)) {
CloseHandle(processesSnapshot);
returnprocessInfo.th32ProcessID;
}
}
CloseHandle(processesSnapshot);
return0;
The code starts with the FindProcessId function, which searches for the
Notepad process ID. It takes a snapshot of all processes running on the system
using CreateToolhelp32Snapshot and iterates over them with Process32First
and Process32Next. When it finds a process whose name matches
"notepad.exe", it returns the PID of that process.
BOOLInjectThreadIntoProcess(DWORD pid) {
HANDLEhProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,pid);
if(hProcess ==NULL) {
std::cerr<< "OpenProcess failed: " <<GetLastError()<<std::endl;
return FALSE;
}
Open the Process: Uses OpenProcess to obtain a handle with full access to
the Notepad process.
if(pid == 0) {
std::cerr<< "Notepad.exe not found running." <<std::endl;
return1;
if(!InjectThreadIntoProcess(pid)) {
return1;
}
return0;
}
The main function is quite simple. It calls FindProcessId to get the Notepad's
PID and, if successful, calls InjectThreadIntoProcess to inject the thread. If any
of these operations fail, the program displays an error message and
terminates.
Full Code:
#include <windows.h>
#include <iostream>
#include <tlhelp32.h>
DWORDFindProcessId(conststd::wstring&processName) {
PROCESSENTRY32processInfo;
processInfo.dwSize =sizeof(processInfo);
HANDLEprocessesSnapshot =
CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,NULL);
if(processesSnapshot ==INVALID_HANDLE_VALUE)
return0;
Process32First(processesSnapshot, &processInfo);
if(!processName.compare(processInfo.szExeFile)) {
CloseHandle(processesSnapshot);
returnprocessInfo.th32ProcessID;
}
while(Process32Next(processesSnapshot, &processInfo)) {
if(!processName.compare(processInfo.szExeFile)) {
CloseHandle(processesSnapshot);
returnprocessInfo.th32ProcessID;
}
}
CloseHandle(processesSnapshot);
return0;
}
BOOLInjectThreadIntoProcess(DWORD pid) {
HANDLEhProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,pid);
if(hProcess ==NULL) {
std::cerr<< "OpenProcess failed: " <<GetLastError()<<std::endl;
return FALSE;
}
intmain() {
std::wstringnotepad =L"notepad.exe";
DWORDpid = FindProcessId(notepad);
if(pid == 0) {
std::cerr<< "Notepad.exe not found running." <<std::endl;
return1;
}
if(!InjectThreadIntoProcess(pid)) {
return1;
}
return0;
}
To create a simple runner shellcode in C++ that uses memory with read-write
(RW) permissions and then switches to read-and-execute (RX), you can make
use of several Windows APIs for memory manipulation and creation of
threads.
Shellcode
Payload
The term payload refers to the software component that performs a malicious
action as part of a cyberattack after a successful exploit. In terms of
cybersecurity, a payload can do anything from creating a simple on-screen
message to installing backdoors, stealing data or encrypting files (like
Msfvenom
Msfvenomis a tool from the Metasploit project used to generate shellcodes for
exploits and payloads. It combines functionality that was previously provided
by the msfpayload and msfencode tools. Msfvenom supports the generation
of payloads in many different formats (such as executables, scripts and raw
shells) and can be used to encode these payloads in order to evade security
controls. It is a very versatile tool in penetration testing and security research,
allowing users to create specific payloads for a wide range of vulnerabilities
and system configurations.
For our example, I will use msfvenom to generate our messagebox shellcode
in C, to be inserted into our runner shellcode
After generating the runner shellcode, simply insert unsigned char shellcode
into the section.
#include <windows.h>
#include <iostream>
intmain() {
SIZE_TshellcodeSize =sizeof(shellcode);
This code snippet uses the Windows API to allocate a page of memory where
data can be written and read. The VirtualAlloc function is called to reserve a
memory space the size of the shellcode with read and write permissions. If the
allocation fails, the program displays an error message and ends with a return
code of 1.
memcpy(execMemory, shellcode, shellcodeSize);
// Cleaning
CloseHandle(hThread);
VirtualFree(execMemory, 0,MEM_RELEASE);
This code snippet creates a new thread to execute the shellcode that has
been stored in memory, using the CreateThread function. If thread creation
fails, it displays an error, frees memory, and terminates the program. If the
thread is created successfully, the code waits for the thread to finish using
WaitForSingleObject, then cleans up by closing the thread's handle and
freeing the memory, and ends by reporting that the shellcode was executed
successfully.
Complete Code
#include <windows.h>
#include <iostream>
intmain() {
SIZE_TshellcodeSize =sizeof(shellcode);
// Cleaning
CloseHandle(hThread);
VirtualFree(execMemory, 0,MEM_RELEASE);
6. Stealth API Usage: Use lesser-known API calls or indirect ways of calling
APIs to perform actions such as allocating memory or creating threads.
This may include using inline assembly to make system calls directly.
Bibliographic references
Credits for the custom Windows used in the demos,João Paulo de Andrade