Using EnumProcesses
TL;DR
The EnumProcesses
API from PSAPI retrieves an array of process identifiers for
all running processes. By iterating over these IDs and opening each process, we
can list loaded modules and gather details such as executable names. Malware uses
this enumeration step to discover potential targets or detect analysis tools.
This example shows how to request the process list, extract module information,
and display the results for later decision making.
Code Walkthrough
main.zig
const std = @import("std");
const windows = std.os.windows;
const WINAPI = windows.WINAPI;
// Windows API constants and types
const DWORD = windows.DWORD;
const HANDLE = windows.HANDLE;
const BOOL = windows.BOOL;
const LPCWSTR = windows.LPCWSTR;
const HMODULE = windows.HMODULE;
const MAX_PATH = windows.MAX_PATH;
// Define a UTF-8 to UTF-16 string converter
const W = std.unicode.utf8ToUtf16LeStringLiteral;
// Process access rights
const PROCESS_ALL_ACCESS = 0x001F0FFF;
const PROCESS_QUERY_INFORMATION = 0x0400;
const PROCESS_VM_READ = 0x0010;
// External Windows API functions
extern "psapi" fn EnumProcesses(lpidProcess: [*]DWORD, cb: DWORD, lpcbNeeded: *DWORD) callconv(WINAPI) BOOL;
extern "psapi" fn EnumProcessModules(hProcess: HANDLE, lphModule: *HMODULE, cb: DWORD, lpcbNeeded: *DWORD) callconv(WINAPI) BOOL;
extern "psapi" fn GetModuleBaseNameW(hProcess: HANDLE, hModule: HMODULE, lpBaseName: [*]u16, nSize: DWORD) callconv(WINAPI) DWORD;
extern "kernel32" fn OpenProcess(dwDesiredAccess: DWORD, bInheritHandle: BOOL, dwProcessId: DWORD) callconv(WINAPI) ?HANDLE;
extern "kernel32" fn CloseHandle(hObject: HANDLE) callconv(WINAPI) BOOL;
extern "kernel32" fn GetLastError() callconv(WINAPI) DWORD;
const ProcessInfo = struct {
pid: DWORD,
handle: HANDLE,
};
fn getRemoteProcessHandle(allocator: std.mem.Allocator, proc_name: []const u16) !?ProcessInfo {
_ = allocator; // suppress unused parameter warning
var processes: [1024 * 2]DWORD = undefined;
var return_len: DWORD = 0;
var return_len2: DWORD = 0;
// Get the array of PIDs in the system
if (EnumProcesses(&processes, @sizeOf(@TypeOf(processes)), &return_len) == 0) {
std.debug.print("[!] EnumProcesses Failed With Error: {}\n", .{GetLastError()});
return null;
}
// Calculate the number of elements in the array returned
const number_of_pids = return_len / @sizeOf(DWORD);
std.debug.print("[i] Number Of Processes Detected: {}\n", .{number_of_pids});
for (0..number_of_pids) |i| {
// If process PID is not NULL
if (processes[i] != 0) {
// Open a process handle
if (OpenProcess(PROCESS_ALL_ACCESS, 0, processes[i])) |h_process| {
var h_module: HMODULE = undefined;
// If handle is valid
// Get a handle of a module in the process
// The module handle is needed for `GetModuleBaseNameW`
if (EnumProcessModules(h_process, &h_module, @sizeOf(HMODULE), &return_len2) != 0) {
var proc_name_buffer: [MAX_PATH]u16 = undefined;
// Get the name of the process
if (GetModuleBaseNameW(h_process, h_module, &proc_name_buffer, proc_name_buffer.len) != 0) {
// Find the null terminator
var name_len: usize = 0;
for (proc_name_buffer) |char| {
if (char == 0) break;
name_len += 1;
}
// Compare process names
if (std.mem.eql(u16, proc_name, proc_name_buffer[0..name_len])) {
return ProcessInfo{
.pid = processes[i],
.handle = h_process,
};
}
} else {
std.debug.print("[!] GetModuleBaseName Failed [At Pid: {}] With Error: {}\n", .{ processes[i], GetLastError() });
}
} else {
std.debug.print("[!] EnumProcessModules Failed [At Pid: {}] With Error: {}\n", .{ processes[i], GetLastError() });
}
_ = CloseHandle(h_process);
}
}
}
return null;
}
fn printProcesses() !void {
var processes: [1024 * 2]DWORD = undefined;
var return_len: DWORD = 0;
var return_len2: DWORD = 0;
// Get the array of PIDs in the system
if (EnumProcesses(&processes, @sizeOf(@TypeOf(processes)), &return_len) == 0) {
std.debug.print("[!] EnumProcesses Failed With Error: {}\n", .{GetLastError()});
return;
}
// Calculate the number of elements in the array returned
const number_of_pids = return_len / @sizeOf(DWORD);
std.debug.print("[i] Number Of Processes Detected: {}\n", .{number_of_pids});
for (0..number_of_pids) |i| {
if (processes[i] != 0) {
// Open a process handle with limited access
if (OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, 0, processes[i])) |h_process| {
var h_module: HMODULE = undefined;
// Get a handle of a module in the process
if (EnumProcessModules(h_process, &h_module, @sizeOf(HMODULE), &return_len2) != 0) {
var proc_name_buffer: [MAX_PATH]u16 = undefined;
// Get the name of the process
if (GetModuleBaseNameW(h_process, h_module, &proc_name_buffer, proc_name_buffer.len) != 0) {
// Find the null terminator
var name_len: usize = 0;
for (proc_name_buffer) |char| {
if (char == 0) break;
name_len += 1;
}
// Convert UTF-16 to UTF-8 for printing
var utf8_name: [MAX_PATH * 2]u8 = undefined;
if (std.unicode.utf16LeToUtf8(&utf8_name, proc_name_buffer[0..name_len])) |utf8_len| {
std.debug.print("[{:0>3}] Process \"{s}\" - Of Pid: {}\n", .{ i, utf8_name[0..utf8_len], processes[i] });
} else |_| {
std.debug.print("[{:0>3}] Process [encoding error] - Of Pid: {}\n", .{ i, processes[i] });
}
} else {
std.debug.print("[!] GetModuleBaseName Failed [At Pid: {}] With Error: {}\n", .{ processes[i], GetLastError() });
}
} else {
std.debug.print("[!] EnumProcessModules Failed [At Pid: {}] With Error: {}\n", .{ processes[i], GetLastError() });
}
_ = CloseHandle(h_process);
}
}
}
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Define target process name as UTF-16 array
const target_name = W("svchost.exe");
if (getRemoteProcessHandle(allocator, target_name)) |maybe_process_info| {
if (maybe_process_info) |process_info| {
var utf8_name: [MAX_PATH * 2]u8 = undefined;
if (std.unicode.utf16LeToUtf8(&utf8_name, target_name)) |utf8_len| {
std.debug.print("[+] FOUND \"{s}\" - Of Pid: {}\n", .{ utf8_name[0..utf8_len], process_info.pid });
} else |_| {
std.debug.print("[+] FOUND [encoding error] - Of Pid: {}\n", .{process_info.pid});
}
// Don't forget to close the handle
_ = CloseHandle(process_info.handle);
} else {
std.debug.print("[!] Target process not found\n", .{});
return;
}
} else |err| {
std.debug.print("[!] Error occurred: {}\n", .{err});
return;
}
// NOTE: Uncomment this to print all processes
// try printProcesses();
std.debug.print("[#] Press <Enter> To Quit ... ", .{});
const stdin = std.io.getStdIn().reader();
_ = try stdin.readByte();
}