Tunneling SSH over ICMPv6
What started as curiosity about firewall evasion turned into a small Go tool that carries SSH inside ICMPv6 — and a much better understanding of how protocols actually behave.
I didn’t intend to build a tool. I wanted to learn something.
A while ago I started questioning myself: What packets get through firewalls, and why? That’s the question that eventually became sshoi — a small Go project that tunnels SSH over ICMPv6. But the tool is almost beside the point. Nowadays, the state of the art coding agents enable hackers to convert the idea into an actual tool with much smaller effort. The real thing I got out of it was a much better feel for how protocols work, where assumptions hide, and why firewall policies that look airtight on paper may not be.
This post is about that journey.
Where it started: timing-based evasion and the idea of protocol abuse
The rabbit hole opened when I came across SlowDNS and similar timing-based evasion techniques. The idea is very simple: most intrusion-detection rules fire on rate, shape, and known signatures. If you slow a connection down enough, split it oddly enough, or dress it up as something else, the detectors blink and miss it. SlowDNS was my first proper “oh, protocols should not always be what they were meant to” moment.
From there I fell into a whole ecosystem of evasion tricks, each one exploiting a different assumption:
┌─────────────────────────────────────────────┐
│ THE "ALLOWED" PROTOCOL │
└─────────────────────────────────────────────┘
│
┌────────────────────┼────────────────────┐
▼ ▼ ▼
┌────────┐ ┌─────────┐ ┌──────────┐
│ DNS │ │ HTTPS │ │ ICMP │
│tunnels │ │ (SNI, │ │ tunnels │
│ │ │ domain │ │ │
│iodine, │ │ front.) │ │ptunnel-ng│
│dnscat2 │ │ │ │ hans │
└────────┘ └─────────┘ └──────────┘
│ │ │
▼ ▼ ▼
"it's just "it's just "it's just
a lookup" a webpage" a ping"
Every one of those is the same story told differently: a transport that the firewall has already decided is harmless becomes a wrapper for a transport it would have blocked. The interesting bit is not that it’s possible — it’s why: because firewalls classify packets by headers, not by intention, and because the protocols they trust were designed in an era when nobody expected them to carry arbitrary payloads.
Why ICMPv6, specifically
ICMPv4 tunneling is old news. Tools like ptunnel-ng, hans, and
icmptunnel have been around for ages. But when I started looking at
ICMPv6, something felt different.
IPv6 leans on ICMPv6 for a lot more than classic ping. Neighbour Discovery, Router Advertisement, Path MTU Discovery, Multicast Listener Discovery — it’s all ICMPv6. RFC 4890 recommends that firewalls not drop essential ICMPv6 messages needed for IPv6 connectivity, which means administrators often cannot simply block it wholesale the way they might block ICMPv4. Blocking it outright breaks IPv6 in subtle ways, so many networks leave large categories of it open. Administrators who cheerfully drop all ICMPv4 may leave ICMPv6 wide open, because the alternative is a network that mysteriously falls over at 3 AM.
That asymmetry is where sshoi lives.
The design, at a diagram
The mental model is very simple:
┌──────────────┐ ┌──────────────┐
│ SSH client │ │ sshd │
└──────┬───────┘ └──────▲───────┘
│ TCP :2222 │ TCP :22
▼ │
┌──────────────┐ ICMPv6 Echo Request/Reply ┌──────┴───────┐
│ sshoi-client │◄══════════════════════════════►│ sshoi-server │
└──────────────┘ └──────────────┘
▲ ▲
│ │
└──── firewall sees: "just a ping, let it go" ──┘
sshoi-client is a local TCP listener. Your ssh client connects to it
like any other local port. Everything you send gets chopped up, encrypted,
and stuffed into the payload of ICMPv6 Echo Requests aimed at
sshoi-server. The server unwraps them and hands the bytes to a real
sshd. Replies come back inside Echo Replies. To the network, it’s the
world’s chattiest ping session. (Rate-limiting could be implemented to
further hide this characteristic.)
What I actually learned building it
If you allow for any communication, it can deliver any message.
1. You need to respect how protocols work
The very first version of sshoi did not work at all. The Linux kernel,
being helpful, automatically replies to Echo Requests on its own — this
is standard ICMPv6 behavior, not a quirk. Which meant the server’s
sshoi-server and the kernel’s built-in ping-replier were fighting over the
same packets, and my tunnel was getting corrupted by its own host OS.
The fix was to use two distinct ICMP identifiers:
client ──► server Identifier = 0x5348 ("SH") ← Echo Request
server ──► client Identifier = 0x5349 ("SI") ← Echo Reply
The client ignores anything with the request identifier, so the kernel’s well-meaning auto-replies get dropped on the floor. That’s the whole trick. Luckily — in the age of AI, you can identify those issues in hours or even minutes.
2. Unreliable transports need a reliability layer, and that layer is a tiny TCP
ICMP gives you nothing. No ordering, no retransmits, no flow control, no duplicate detection. If you want an SSH session to survive on top of it, you end up rebuilding the TCP ideas.
┌──────────────────────────────────────────────────────┐
│ sshoi reliability │
├──────────────────────────────────────────────────────┤
│ • 16-bit session IDs (multiplex many SSH conns) │
│ • 64-packet sliding send window │
│ • Cumulative ACKs, TCP-style │
│ • 500 ms retransmit, up to 10 tries │
│ • 512-slot duplicate-detect bitmap per session │
│ • 15 s keepalives to hold firewall state open │
└──────────────────────────────────────────────────────┘
3. Sealing the payload
Payloads are encrypted with XChaCha20-Poly1305. The key is derived from a passphrase via Argon2id (64 MiB, 4 threads), and the header fields — session ID, sequence, ACK, flags — are bound in as AAD so they can’t be tampered with or replayed.
┌─────────┬─────────┬─────────┬─────────┬──────────────────┐
│ session │ seq │ ack │ flags │ ciphertext │
│ (16) │ (32) │ (32) │ (8) │ + Poly1305 tag │
└─────────┴─────────┴─────────┴─────────┴──────────────────┘
└──────────── bound as AAD ────────────┘
In the scenario where the firewall allows the ICMPv6 traffic, the evasion doesn’t end there. This additional encryption makes the traffic resistant to signature-based detection. This also allows the server to answer only when the pre-shared key is correct.
4. The evasion that you didn’t have to design
The thing that surprised me most is how little sshoi has to try. Nothing about it is clever in the adversarial sense. It’s not fragmenting oddly. It’s not mimicking real ping timing. It just uses a protocol that security tools were already told to trust, and lets their own policy do the work.
That is the pattern I see again and again in firewall-evasion field: the attacker’s best friend is the defender’s own assumption. If you assume ICMP is harmless, you will allow ICMP. If you allow ICMP, someone will carry a shell on it. The problem is not the protocol; the problem is the mental model.
What I take away
The main takeaway for me was that the way to get good at network security is to stop treating protocols as sealed boxes and start treating them as conventions you can choose to honour or not. Slowloris taught me that rates are a convention. ICMP tunnels taught me that headers are a convention. Once you see it that way, firewall rules look less like walls and more like polite suggestions.
Another outcome of this project is the idea of leveraging AI in such projects. Any idea that can be made will be made with enough supervision and tokens. This greatly expands the threat landscape as well as the capability to defend. Managing firewalls and tools of this nature requires a similarly significant upgrade that artificial intelligence offers.
The source code: github.com/FrEeMiKi/sshoi.