Two laptops. Each runs a headless Chrome joined to a Microsoft Teams call as a guest. Between them sits a real network interface. Ping crosses it. So does a TCP handshake, and iperf3 pushes bulk data across it. The entire path is a Teams video call. The packets ride inside the encoded H.264 picture itself, so to every network device in between, the traffic simply is a video meeting. Microsoft’s own servers relay it, encrypted, on infrastructure that nearly every enterprise network allows.
Why not QR codes
When I describe this project, the first suggestion is always the same: put QR codes on the screen and point the call’s camera at them. Someone has built that, properly. DECIMEN is Evan Crawley’s fountain-coded screen-to-camera file transfer: one device animates Luby-transform QR frames, the other watches with a camera, collects about 15 percent more distinct frames than the file needs, in any order, and peels the payload out. Dropped frames cost time, never correctness. On a 49-inch monitor it sustains 418 KB/s.

For moving a file across a genuine air gap, with no network at all, it is a great tool. It is the wrong tool here, for two reasons.
First, those numbers are screen-to-camera. A video call puts an H.264 encoder between the QR frames and the receiver, and the encoder eats structure. It spends its bits on what human eyes notice and approximates the rest; a dense strobing checkerboard is high-frequency detail that conference bitrates quantize into mush. Fountain coding tolerates the damage, but tolerance is not capacity: every frame the codec mangles is a frame of payload that was transmitted and never received. To make codes survive the trip you drop the density, enlarge the modules, slow the animation, and throughput falls by orders of magnitude. Anything encoded in the visible portion of the frame gets swallowed whole by the encoder.
Second, it is loud. A video tile strobing QR patterns for the length of a transfer is visible to every participant, lands in the meeting recording, and, as DECIMEN’s own README warns, is readable by any camera pointed at the screen. For a channel whose job is to look like nothing, hiding the data in the most-watched pixels of the frame is exactly backwards.
So the approach inverts: stop fighting the codec over the visible image, and ride a part of the bitstream the codec is required to carry bit-exactly.
The carrier is the meeting
The allowlist model won. Corporate networks rarely ask what you are doing; they ask who you are talking to, and teams.live.com is on the list. The media path is DTLS/SRTP that no middlebox terminates, carried in UDP to Microsoft’s conferencing infrastructure. A DLP appliance sees encrypted video. A firewall sees a Teams call.

That meeting is the tunnel. The two guests named tunnel-alice and tunnel-bob are the headless Chromes; their tiles show the fake camera feed, and inside that video, invisible at this scale, rides the SEI.
Hiding data in allowed services is nothing new: DNS tunneling, HTTPS C2 on cloud providers. Those channels are thin. A video call is a fat pipe that nobody inspects, terminated by a first-party relay. If arbitrary bytes could ride inside the video itself, they would pick up every property of the meeting for free: the allowlist entry, the encryption, the relay, and a meeting URL as the only setup step.
On each host a Rust bridge with a TUN interface and KCP session talks over a local WebSocket to a headless Chrome joined as a meeting guest. The two Chromes exchange H.264 carrying SEI inside SRTP with a Teams SFU inside Microsoft 365, which forwards the guest video without transcoding.
Whether the bytes would survive was the open question. A video call is not a wire. It is an encoder, a packetizer, a selective forwarding unit that decides what to forward, and a decoder on the far end. Any of these can decide your data is not video and drop it.
H.264 ships with a comments section
An H.264 stream is a sequence of NAL units, each tagged with a type. Most carry picture data. Type 6 is SEI, Supplemental Enhancement Information, and decoders are required to parse it and permitted to ignore it. Encoders use it for metadata: timing, scene labels, whatever a downstream player might want. One payload type, user_data_unregistered (type 5), is an escape hatch built into the spec: a 16-byte UUID of your choosing followed by arbitrary bytes.
That is a data channel inside the video, sanctioned by the codec. One NAL unit is six fields:
Six fields in sequence: a one-byte NAL header of 0x06, a one-byte SEI payload type of 0x05, a variable-length UVLC length field of one to seventy-nine bytes, a sixteen-byte tunnel UUID, the KCP segment payload of up to twenty kilobytes, and a one-byte trailing-bits byte of 0x80.
The UUID lets us pick our own SEI out of any legitimate ones the encoder emits. The one subtlety is emulation prevention: the format forbids certain byte sequences inside a NAL unit, so 0x03 escape bytes must be inserted and stripped exactly, or you corrupt the video around you.
Whether Teams’ SFU forwards SEI it does not understand was the gate-zero question. Two headless Chromes in one meeting, the sender hooking its own encoded frames to prepend an SEI NAL, the receiver scanning inbound frames. The SFU forwards a guest Chrome’s H.264 without transcoding, so the SEI arrives intact: 2,793 payloads injected, 2,791 extracted, which is 1:1 against delivered video frames, byte-perfect, at up to 20 KB per frame. At 40 KB the encoder falls over. The channel is real.
Chrome has to be the media endpoint
My first architecture was the clean one: speak Teams’ signaling and media directly from a Rust WebRTC stack, no browser. It does not work. After I fixed three bugs of my own in ICE and matched Chrome’s HTTP signaling field for field (capabilities, headers, the two-step join, token scope), the SFU still rejected the call with error 301000, while accepting a byte-identical Chrome SDP offer from the same machine, in the same meeting, a minute later. Eight experimental sessions narrowed this to a gate on client identity upstream of anything observable on the wire. You cannot be a forwardable media source unless you are a real client. That stack is deleted.
The pivot: let a real Chrome be the media endpoint, the thing the SFU already trusts, and reach inside its pipeline. WebRTC Insertable Streams (now called encoded transforms) expose each video frame between encoder and packetizer on the send side, and between depacketizer and decoder on the receive side. A headless Chrome joins as a guest, plays a fake camera, and runs two small transforms. The sender prepends our SEI NAL to every outgoing encoded frame; the receiver scans every inbound frame for our UUID. A Rust process owns everything else and talks to Chrome over a local WebSocket.
A camera frame is encoded to H.264, and a sender transform prepends the tunnel SEI before RTP packetization and SRTP transport. The Teams SFU forwards the NAL units untouched. After depacketization a receiver transform extracts SEI payloads by UUID for the far bridge, while the decoder ignores the SEI and renders the remaining video.
On the Rust side there is a real TUN interface (a /30: 10.99.0.1 and 10.99.0.2), framing and fragmentation for inner packets, and a KCP session on top. KCP is an ARQ protocol that trades bandwidth for latency, which is the right trade here, because the underlying channel is strange.
A video call is a strange link layer
From KCP’s point of view, the channel has three properties that matter.
Bytes move only when a frame is encoded, about 30 slots per second. This is not a wire you can push on; it is a scheduled cargo service.
Each frame carries about 20 KB, and that envelope is shared with the picture. I measured the sharing in both directions. Raising the fake camera’s bitrate cap collapsed delivery from 6,606 payloads to 384, because the SFU drops over-full frames; lowering the cap to starve the video halved throughput too. The SEI and the encoded picture draw from the same per-frame budget, and the encoder’s default is the sweet spot.
And the channel loses things. The SFU drops frames under pressure, which is what KCP’s fast retransmit is for, at 29 to 35 percent retransmission overhead under bonded load.
Ceiling arithmetic: 20 KB at 30 fps is about 4.8 Mbps on paper, and 2.4 Mbps at the ~15 fps the SFU actually delivers. Measured single-call goodput after every tuning pass came in at 1.26–1.35 Mbps.
Verification came in two steps. Single host first: crafted IPv4 packets written into one TUN and read out the other, byte-verified, after a real round trip through the SFU. Then two physical machines, which are required because two TUNs on one macOS host short-circuit through the loopback route. There, ping and a full TCP handshake crossed the tunnel. SSH over Teams works. Idle round-trip time settles at 250 ms, dominated by encode cadence and SFU transit rather than distance; under bonded load the median rises to 376 ms. It feels like a satellite link.
Bonding: one tunnel, several meetings
One call tops out near 1.35 Mbps. The obvious scaling move is several Chrome participants in one meeting, and it fails fast: both carriers forward SEI for about 80 seconds, then both stop. They share a per-meeting SFU budget. The tunnel needs independent meetings.
Bonding mode therefore stripes one durable KCP stream across N carriers, each Chrome in its own meeting, each SEI payload tagged with a 4-byte carrier ID so that endpoint N accepts only peer carrier N. A scheduler prefers carriers that recently produced traffic; a dead meeting is bypassed until it comes back.
Host A's bridge stripes segments to three Chrome carriers. Each carrier joins its own meeting, whose SFU has its own forwarding budget. Host B's three carriers each reassemble into one bridge. Two carriers in the same meeting would share one SFU budget, so the meetings stay independent.
| Profile | Payloads | Delivery | Goodput | Time to usable | Loaded RTT med/p90 |
|---|---|---|---|---|---|
| 1 call | 15,263 / 15,261 | 99.987% | 1.256 Mbps | 9.0 s | 383 / 2,617 ms |
| 2 meetings | 18,671 / 18,669 | 99.989% | 1.484 Mbps | 10.3 s | 365 / 2,163 ms |
| 3 meetings, balanced | 22,917 / 22,917 | 100% | 1.853 Mbps | 10.3 s | 349 / 2,613 ms |
| 3 meetings, max bandwidth | 26,041 / 26,038 | 99.988% | 2.150 Mbps | 11.4 s | 376 / 2,836 ms |
2.150 Mbps
Peak bulk goodput over three bonded meetings
99.988% delivery, 376 ms median loaded RTT, 11.4 s to first usable media.
250 ms
Unloaded round-trip time, single meeting
Dominated by encode cadence and SFU transit, not distance.
That is document-exfiltration bandwidth. The delivery fraction is the more important result: the link is stable enough to hand to TCP.
What can catch it
“Undetectable” is a claim this experiment did not make. Three observers get three different views.
The network cannot see it. The traffic is genuine Teams media from a real Chrome client to Microsoft’s own SFU, DTLS-keyed, on the allowlisted path. There is no signature to match, no SNI to block, and no payload to inspect short of breaking SRTP. From outside, this is a boring two-person call, because it is one.
The endpoint is loud. A headless Chromium driven by automation, a utun interface with a /30 subnet, a setuid helper binary, a plaintext local WebSocket. Every one of those is EDR-visible. The channel evades the network, not the host.
The platform sees the shape. The meetings it needs are distinctive: created with lobby bypass, guest-only, cameras on, no audio, hours long, exactly two participants. Microsoft relays the media and could notice server-side that every frame carries a user_data_unregistered SEI with the same UUID. The channel is opaque to the carrier’s customers and transparent to the carrier.
The defense follows from the detection. Restrict who can bypass lobbies and create guest-accessible meetings. Alert on long guest-only meetings with cameras and no audio. Watch endpoints for TUN interfaces and headless browsers in combinations real users don’t have. None of this requires reading the channel, only noticing the meeting.
The code lives in a research repo, and I have deliberately not packaged it as a tool.