pwnat: A NAT traversal tool for reverse shells and remote control via NATed networks

pwnat: A NAT traversal tool for reverse shells and remote control via NATed networks

I still remember the first time I ran into a network topology where both my attacking box and my target were sitting behind separate NAT gateways, with no port forwarding, no VPS relay, and no way to punch a hole through either firewall using the usual tricks. That’s the exact problem pwnat was built to solve. It’s an old tool — it hasn’t seen much upstream development in years — but the technique it uses is still worth understanding deeply, because the underlying idea (using ICMP as a rendezvous signal) shows up again and again in more modern NAT traversal and tunneling tools.

pwnat lets a client behind one NAT connect to a server behind a different NAT, with no port forwarding, no STUN/TURN server, and no man-in-the-middle relay involved. It does this by having the server periodically send ICMP echo requests to a well-known “fake” destination (like 3.3.3.3), and having the client sniff for those packets to learn the server’s public IP and use ICMP time-exceeded tricks to signal back. Once the two sides discover each other’s public IP/port mapping, they establish a UDP-based tunnel that behaves like a proxy, and normal TCP traffic gets encapsulated over it.

How It Actually Works Internally

pwnat’s core trick relies on three things:

  1. ICMP as a beacon. The server, sitting behind NAT, has no public IP a client could dial into directly. But it can send outbound ICMP echo requests. It repeatedly pings a fixed address that doesn’t really matter (traditionally 3.3.3.3) — this is never meant to reach anything, it’s a beacon.
  2. Client sniffs the beacon. Because the client also doesn’t know the server’s public NAT-mapped address, it needs a way to discover it. It listens (using raw sockets) for the server’s ICMP echo requests destined for that fixed address, as those pass across the intermediate network segments it can see (this only works when the client can see that traffic — e.g., same broadcast domain, a mirrored span port, or the server periodically informing the client out-of-band). In pwnat’s actual design, the client instead triggers the server to reply, and the client watches for ICMP Time Exceeded or Destination Unreachable messages generated by the server’s own IP stack.
  3. UDP hole punch, then TCP-over-UDP tunnel. Once addresses are known, both sides start sending UDP packets to each other’s public NAT-mapped port. Once the NAT devices see outbound UDP from both directions, they permit inbound replies — this is the “hole” being punched. From that point, pwnat multiplexes a TCP-like proxy protocol on top.

The practical result: you run pwnat in server mode on the “target-side” box you control, and pwnat in client mode on your “attacker-side” box, and you get a local SOCKS-like proxy or forwarded port that tunnels through to the other side — despite neither box having a routable public IP.

Installation

On Debian/Kali-based systems, pwnat is available via apt, though it may need to be pulled from a slightly older repo snapshot in newer Kali releases since it isn’t actively maintained upstream:

sudo apt update
sudo apt install pwnat

If it’s not in your repos, build from source — this is the more reliable path since the tool hasn’t changed in a long time:

git clone https://github.com/samyk/pwnat.git
cd pwnat
make
sudo cp pwnat /usr/local/bin/

Verify it’s installed:

pwnat -h

Expected output (abbreviated):

usage: ./pwnat [-h] [-6] [-v[v]] [-i <int>] [-f <int>] [-4|-p <port>|-s|-u] [<dest-host> <dest-port> [<src-host>] [<src-port>]]
        -h                this help message
        -v                verbose (twice for more verbosity)
        -i interface      set the interface to use (default eth0)
        -f fwd-port       specify a udp port to use, other than default 2222
        -4                do not attempt IPv6 UDP forwarding
        -p src-port       proxy TCP port to bind on client, or on server to be forwarded to

Syntax and Command Examples

Server mode (run on the machine behind the “hidden” NAT, forwarding a service — say SSH on port 22 — out to the client):

sudo pwnat -s 0.0.0.0 22

This tells pwnat: “Act as server, forward connections to 0.0.0.0:22 (local SSH) once a client tunnels in.”

Client mode (run on your attacking machine):

sudo pwnat -p 2222

This opens a local port 2222 on the client. Any connection to localhost:2222 gets tunneled through the NAT-traversal channel to the server’s forwarded service.

Once both are running and have discovered each other via the ICMP beacon exchange, you can do:

ssh -p 2222 user@127.0.0.1

And that SSH session is actually reaching the “server” box behind its own NAT, despite you never touching port forwarding on either router.

Typical console output during a successful session

[server] waiting for ICMP...
[server] got request from client, sending handshake
[server] connection established with 203.0.113.44:41231
[client] sending probe to 3.3.3.3
[client] received handshake response
[client] tunnel established, listening on 127.0.0.1:2222

(Exact log lines vary slightly by version and verbosity level — run with -v or -vv for more detail.)

Real-World Use Cases in Authorized Engagements

I’ve used pwnat-style NAT traversal in a couple of specific authorized red team scenarios:

Integration with Other Tools

pwnat by itself is just a raw pipe. In practice I chain it with:

Troubleshooting and Common Mistakes

Performance Considerations

pwnat’s tunnel isn’t fast — it’s UDP-encapsulated and wasn’t designed with throughput in mind. For interactive shells (SSH, simple C2 beaconing) it’s fine. For bulk data transfer, expect it to be noticeably slower than a direct connection, so I don’t rely on it for exfiltrating large datasets; I use it to get a foothold or a management channel, then pivot to something faster once I have more conventional access.

FAQ

Is pwnat still maintained? Not actively — the last real development was years ago. It still works on most Linux systems, but don’t expect fixes for edge cases.

Does it work between two Windows machines? No — pwnat is Linux-only (relies on raw ICMP socket behavior specific to the Linux network stack).

Is this the same as UDP hole punching used in P2P apps like Skype/WebRTC? Conceptually related (both punch a NAT-to-NAT hole), but pwnat’s discovery mechanism via ICMP is distinct from STUN/TURN/ICE, which typically use a third-party rendezvous server.

Can this bypass a fully symmetric NAT? Not reliably. Symmetric NATs assign a different external port for every destination, which breaks the address-prediction pwnat depends on.

Lab Example

In an isolated lab (two virtual routers doing NAT, two internal hosts, no shared broadcast domain):

# On "internal-server" behind NAT A, exposing SSH
sudo pwnat -s 0.0.0.0 22

# On "attacker" behind NAT B
sudo pwnat -p 2222

# From attacker, once tunnel is up
ssh -p 2222 labuser@127.0.0.1

Confirm the tunnel is actually traversing both NATs (not just local loopback) by checking netstat -tunp on both routers for the UDP hole-punch session.

Summary

pwnat solves a specific, narrow problem — NAT-to-NAT connectivity without a relay — using a genuinely clever abuse of ICMP for peer discovery. It’s not fast, it’s not actively maintained, and it won’t defeat symmetric NAT, but as a red-team fallback channel or a teaching tool for understanding NAT traversal and ICMP-based covert signaling, it’s still worth having in your toolkit and worth knowing how to detect as a defender.

References

Exit mobile version