Skip to content

Using EnumProcesses

TL;DR

See the code example

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();
}