dtrh.net~/blog
KBS
│KBS all posts

Reverse Engineering the Wii U GamePad — Part 1: Recovering the Console's Secrets

Reverse Engineering the Wii U GamePad — Part 1: Recovering the Console's Secrets

Reverse Engineering the Wii U GamePad — Part 1: Recovering the Console's Secrets

The Wii U GamePad is a controller with a screen that has no wired path to it. No USB port, no video input, nothing but a proprietary 5 GHz radio. To use it as a display for my linux box I would have to impersonate the console it belongs to — which meant extracting a PSK that Nintendo deliberately made very difficult to get at, from a WPS handshake they had quietly sabotaged. This part is about how far I got: the GamePad now joins my box's network, takes an IP address, and drives my mouse cursor over its own wireless link. It still refuses to show me a picture. That's Part 2.


Published: 2026-11-03
Author: KBS admin@dtrh.net
Subject: Wii U GamePad (DRC) — my own hardware, my own console, my own box
Method: Passive protocol research, protocol reimplementation, one patched kernel module, one patched Wi-Fi driver
Evidence: One recovered PSK, one box serving as a fake console, several hundred lines of build and driver output


Contents

  1. What the GamePad actually is
  2. The link, and why there is no cable
  3. The plan
  4. Getting the PSK
  5. The radio wars, part one: the Pi's firmware says no
  6. The radio wars, part two: patching the driver three times
  7. The pivot: a kernel patch and the box's own radio
  8. Where it stands
  9. What's coming in Part 2

1. What the GamePad actually is

The Wii U GamePad (Nintendo's internal name for it is DRC — Display Remote Controller) is a 6.2-inch tablet with a resistive touchscreen, and it is nearly unique among console accessories in that the console streams a full video signal to it.

The first thing to establish is what you cannot do. The physical interfaces are:

Connector Reality
Proprietary charge jack Power in only. No data lines.
3.5 mm jack Analog audio out.
IrDA window + IR LED TV-remote blaster / toy IR. No video.
4-pad UART header (inside the shell, near the battery) The only data port. 115200 8N1, marked RXD / UART-boot / TXD / Vcc 2.8 V. It is the serial console of the pad's own firmware.

There is no USB port and no video input on the pad. The front-panel USB ports belong to the console and are host-only. So "stream to the GamePad over a cable" does not exist as a concept — it would require either driving the panel's firmware over that UART or gutting the pad for an HDMI-to-RGB888 board. Both are destructive, and both were ruled out early.

That leaves the radio.

2. The link, and why there is no cable

The GamePad talks to the console over a custom 802.11n 5 GHz link with a slightly obfuscated WPA2 and a slightly obfuscated WPS. The documentation for this (libdrc, wiiubrew) is old but accurate, and the single most useful sentence in it is this one:

the required modifications for support of this obfuscated Wi-Fi protocol can be applied in userland without any kernel modification.

That is the whole shape of the project. The obfuscation lives in the daemons, not the kernel.

The normal-mode access point

In normal operation the console raises a WPA2-PSK access point with an empty SSID, advertising cipher selectors that are not standard:

Selector Value
Pairwise cipher a4:c0:e1 (Nintendo OUI) AES-CCM
Group cipher a4:c0:e1 AES-CCM
Auth key management a4:c0:e1 PSK

The actual cryptographic difference from standard CCMP is small and specific: the PTK is rotated three bytes to the left after it is computed. Everything else is ordinary WPA2.

Because those selectors are not standard, a stock supplicant will not associate, and — the useful part — a stock access point will not be accepted by a GamePad that has the Nintendo selectors stored.

The addressing

Once associated, the DRC network is fixed:

  • console (in this project, my box): 192.168.1.10
  • GamePad: 192.168.1.11, handed out by DHCP
  • interface MTU 1800 (the video packets exceed 1500 bytes)

And the services, on documented ports:

Stream Us Pad
video (ephemeral) 50120
audio 50021 50121
input 50022 50122
command 50023 50123
message 50010 —

That table matters more than it looks: it is the diagnostic. Ping proves nothing, but 50122 → 50022 traffic proves the GamePad's DRC stack is live and talking to you.

The pairing, and the trick inside it

When a console enters GamePad pairing mode it raises an open access point named WiiU<mac2><mac>_STA1 and runs a modified WPS handshake. Nintendo's change is one rotation, in the same idiom as the PTK:

a normal WPS implementation will not compute the same AuthKey and will fail at stage M2. The standard specifies that the AuthKey is computed from KDF(KDK). In Nintendo's implementation, AuthKey is computed from KDF(KDK_ROT) where KDK_ROT is KDK rotated 3 bytes to the left.

The pairing PIN is eight digits: the first four are displayed on the TV as symbols (♠=0, ♥=1, ♦=2, ♣=3) and the last four are hardcoded to 5678.

And then the sentence that makes this project possible at all:

If you are trying to associate a gamepad to your computer (and not your computer to a Wii U), you get to choose the code, as long as you enter the same on the GamePad it should work.

In other words: the console is not special. It is just a WPS registrar that happens to rotate a key three bytes. Be the registrar, choose the PIN, and the GamePad will hand you the credentials.

3. The plan

Being concrete about this mattered, because the naive plan does not work.

The naive plan is: raise an access point with the GamePad's SSID and let it join. That fails, and watching it fail is instructive. Using a monitor radio to look at what a GamePad actually does when it is unhappy:

text3 lines
SA:40:d2:8a:17:e2:09  Probe Request (WiiUccfb65b68169)  @5180 MHz, -41dBm
SA:40:d2:8a:17:e2:09  Probe Request (WiiUccfb65b68169)  @5180 MHz, -41dBm
...

The pad is issuing directed probes — not "is anything out there", but "where is the specific network called WiiUccfb65b68169". That string is the console's normal-mode SSID, derived from the console's own Wi-Fi MAC cc:fb:65:b6:81:69. The pad is not looking for a console; it is looking for its console, and it will ignore anything else.

So a fake AP with a made-up SSID is invisible to it, no matter how well it beacons.

The plan therefore became:

  1. Get the real credentials — the console's SSID, its BSSID, and its PSK.
  2. Become that console — same SSID, same BSSID, same Nintendo cipher selectors, same key, same PTK rotation.
  3. Serve the DRC network — 192.168.1.10, DHCP for 192.168.1.11, and the streaming daemons.

Step 2 is where all the difficulty turned out to live, but step 1 is where the actual secret is.

4. Getting the PSK

It is worth being precise about why this step cannot be cheated.

The PSK is not derived from the PIN. The registrar (the console) generates it randomly and ships it to the enrollee (the GamePad) encrypted inside the WPS credential, protected by keys derived from the PIN. So:

  • You cannot compute the PSK from the PIN alone.
  • You cannot read it off the wire without the PIN.
  • Sniffing the exchange only helps if you also have the PIN, and then you may as well run the exchange yourself.

The GamePad was put into pairing mode from its own settings screen, and the console was put into pairing mode with a TV attached. The TV displayed four symbols — ♥ ♦ ♦ ♥ — which is the digits 1221, and the full PIN is therefore 12215678.

Where it got interesting: the console's pairing access point is hidden. Scanning for an SSID beginning WiiU finds nothing:

text3 lines
SCAN 1: no WiiU (saw 20 APs)
SCAN 2: no WiiU (saw 20 APs)
SCAN 3: no WiiU (saw 21 APs)

The AP is there, but it beacons an empty SSID. It is identifiable only by BSSID — and the BSSID is the console's own Wi-Fi MAC, cc:fb:65:b6:81:69, which is the same value encoded into the normal-mode SSID. That is a satisfying little piece of reasoning: the SSID the pad hunts for tells you the MAC of the AP you need to talk to.

With the PIN and the BSSID, a WPS client built with the modified handshake completes the exchange and the console simply hands over the goods:

text5 lines
PIN = 12215678  (heart diamond diamond heart + hardcoded 5678)

CONSOLE_SSID = WiiUccfb65b68169
CONSOLE_BSSID = cc:fb:65:b6:81:69
PSK = 2f1e7799acd4415cf33a8159019ecbe5e4d28b74ead8c7c77a488a74a0f69b71

That is the game. Everything after this is plumbing — hard plumbing, but plumbing.

A note on scope, because it matters: this unpairs the GamePad from its console. It is reversible (press SYNC twice on the console), but it is not a passive operation, and you should decide deliberately before doing it.

5. The radio wars, part one: the Pi's firmware says no

With the credentials in hand, the obvious move is to serve the AP. I started on a Raspberry Pi 5.

The Pi's radio (CYW43455, brcmfmac) advertised everything needed — AP mode, Band 2, 30 channels — and with standard ciphers it did serve a perfectly good access point. But with the Nintendo selectors, which are mandatory, it refused:

text2 lines
brcmf_configure_wpaie: ivalid OUI
brcmf_cfg80211_start_ap: brcmf_parse_configure_security error

Note the typo in the source (ivalid). That string is the whole story of the next several hours: the firmware does not recognise a4:c0:e1 and will not start the AP.

The two requirements are mutually exclusive on that radio:

Configuration AP beacons? GamePad joins?
Standard CCMP yes no — pad has Nintendo selectors stored
Nintendo a4:c0:e1 no — firmware: invalid OUI —

6. The radio wars, part two: patching the driver three times

brcmfmac here is a DKMS out-of-tree module, so it is patchable without rebuilding a kernel. I patched it three times, and each patch worked, which is what made the experience maddening — every success revealed the next layer.

Patch 1 — accept the OUI. brcmf_valid_wpa_oui() is a three-line allow-list that accepts only 00:0f:ac (RSN) or 00:50:f2 (WPA). Adding a4:c0:e1 made the error disappear and the AP come up with the Nintendo selectors, visible on air from another station.

Patch 2 — stop the firmware doing AP SME. With firmware AP SME the pad's association request is parsed by the firmware, which rejects the same OUI from the other direction. Clearing WIPHY_FLAG_HAVE_AP_SME moved association into hostapd in userspace:

text1 line
nl80211: Setup AP(wlan0) - device_ap_sme=0 use_monitor=1

Patch 3 — let the monitor interface exist. Userspace AP SME needs a monitor vif to receive auth/assoc frames, and brcmfmac only advertised monitor in the interface combination that lacked AP, so cfg80211_check_combinations() returned -EBUSY. Adding a monitor limit to the AP combination let hostapd create it — New interface mon.wlan0 created.

And then, at the bottom of the third layer:

text1 line
brcmf_net_mon_stop: BRCMF_C_SET_MONITOR error (-5)

The firmware cannot enter monitor mode. Userspace AP SME requires monitor mode. Both doors closed, in opposite directions:

  • firmware AP SME → the firmware parses the pad's association and rejects the OUI
  • userspace AP SME → needs monitor mode, which this firmware will not provide

That is not something a driver patch can fix. The radio is a black box that will neither speak the cipher nor hand over the frames so we can speak it ourselves.

There is a genuine lesson in this that cost me the most time: a wedged brcmfmac lies to you. After enough failed mode changes it returns -16 EBUSY for operations that would otherwise work, and after enough of those the chip stops answering entirely (brcm_attach: dongle is not responding: err=-84) until a power cycle. Several of my "this configuration is broken" conclusions were really "this driver instance is confused". Reloading the module with modprobe -r brcmfmac && modprobe brcmfmac became a standard first step.

7. The pivot: a kernel patch and the box's own radio

The insight that unlocked it: the Pi failed not because of the OUI per se, but because closed firmware sits in the association path. A radio where mac80211 does that work in software is patchable.

The box's Intel AX210 (iwlwifi) is exactly that, is 5 GHz, and advertises the combination we need:

text1 line
* #{ managed } <= 1, #{ AP, P2P-client, P2P-GO } <= 1, ... #channels <= 1

So the box can serve the fake console while keeping its own uplink — no more blind, connection-killing test cycles.

One obstacle remained, and it is a clean one. cfg80211 validates key ciphers against what the driver advertises, and iwlwifi advertises only the standard suites, so installing a key under Nintendo's selector fails with -EINVAL. The fix is small and surgical — rewrite the selector at validation time:

c10 lines
int cfg80211_validate_key_settings(...)
{
	/* Nintendo use a4:c0:e1 in place of 00:0f:ac. hostapd advertises that
	 * selector on air and performs the PTK rotation itself, but cfg80211
	 * validates key ciphers against the driver's cipher_suites. Rewrite the
	 * Nintendo CCMP selector to standard CCMP: the driver installs a normal
	 * key, the beacon is unaffected, and the rotated PTK still matches. */
	if (params->cipher == 0xa4c0e104)	/* a4:c0:e1:4 Nintendo CCMP */
		params->cipher = WLAN_CIPHER_SUITE_CCMP;
	...

The reasoning is the interesting part: the driver does not need to know that the peer thinks it is a Wii U. It just needs a key. hostapd already rotated the PTK in userspace before handing it over, so the key it installs is correct regardless of which OUI it was called by — and the beacon's selector, which is what the pad checks, is built by hostapd and is untouched.

Two more things were needed to get a stream out:

  • A TSF fallback. libdrc timestamps video frames against the AP's TSF clock read from /sys/class/net/<if>/tsf. iwlwifi has no get_tsf() and reports UINT64_MAX, so every frame would be stamped with the same value. Falling back to a monotonic clock fixes it.
  • pkill -f will kill your own shell if your command line contains the pattern. This cost me two commands before I learned to use pkill -x.

8. Where it stands

With the patched kernel and the real credentials:

text4 lines
hostapd:   AP as cc:fb:65:b6:81:69, SSID WiiUccfb65b68169, Nintendo ciphers, ch149
GamePad:   Station 40:d2:8a:17:e2:09 — authenticated: yes, associated: yes,
           authorized: yes, signal -55 dBm
DHCP:      192.168.1.11

The 4-way handshake completes — including the rotated PTK — and the pad takes an IP address. Then:

text3 lines
192.168.1.11.50122 > 192.168.1.10.50022   UDP length 128    ← the pad's input stream
192.168.1.10.34490 > 192.168.1.11.50120   UDP length 1710   ← our video stream
192.168.1.10.58273 > 192.168.1.11.50123   UDP length 56     ← command protocol

The pad's touchscreen drives my box's cursor. That is half the goal, and it is the half that involves all the cryptography.

Demo. The GamePad driving this box over the DRC link and nothing else — no USB, no dongle. Touch becomes pointer input, buttons become keys, and the cursor moves because the pad is associated to WiiUccfb65b68169 at 192.168.1.11. The pad's own screen is still showing its "not connected" UI, which is exactly the gap Part 2 is about.

Open the video full size ↗

And the other half refuses. The pad renders its own app-picker, joins the network, sends input — and displays "Could not connect to the Wii U console", while quietly hammering us with roughly 27 resync requests per second:

text1 line
192.168.1.11 → 192.168.1.10:50010, 4-byte messages, ~27/sec

That is the pad saying, twenty-seven times a second: "I received frames and I cannot decode them, start over." Every request makes libdrc emit another IDR, which does not help, because the problem is not that the pad missed a keyframe.

The likely cause is the parameter set. libdrc encodes with b_repeat_headers = 0, deliberately shipping no SPS/PPS, on the assumption that the pad holds Nintendo's fixed H.264 configuration internally. My attempt to send them in-band segfaulted libdrc: the extra NAL units overflow its fixed five-chunk array — kH264ChunksPerFrame = 5, matching the DRH format's six-macroblock-row chunking of a 480-line frame.

A detail worth recording for Part 2: turning the debug dump on makes libdrc write the parameter sets it believes the pad wants —

text2 lines
SPS: 67 64 00 20 ac 2b 40 6c 1e f3 68    (profile_idc 0x64 = 100, High; level 0x20 = 3.2)
PPS: 68 ee 06 0c e8

— and those are almost certainly the ground truth to match against.

9. What's coming in Part 2

The remaining problem is bitstream compatibility with a closed decoder, and the plan is to stop guessing.

The pad's video is standard WPA2-CCMP at layer 2 — and we hold the PSK. So the plan is to put a monitor radio on the console's channel, record an actual console-to-GamePad session, decrypt it with the key we extracted, and recover the exact parameter sets, packet-flag sequences, chunk boundaries and frame pacing that a real console produces. Then the fixes become a diff rather than a guess:

  1. Feed the pad a valid parameter set — either inject the known SPS/PPS as an explicit init sequence, or make ProcessNalUnit place SPS/PPS into chunk 0 without overflowing the five-slot array.
  2. Match the slice headers against what the capture shows.
  3. Use the resync rate as the success metric: it going to zero is "it decoded a frame."

I should be honest about the odds. This is where the existing community projects stopped: pc2drc is archived with "fix major x264 encoder issues" and "visible artifacting" as known-open bugs, and libdrc's chunking is intolerant of anything outside its assumptions. But the network layer — the part that looked impossible at the start, because it required a secret that could not be sniffed, derived or brute-forced — is done. What is left is engineering against a known-good reference, and I now have the key to read that reference.


All work in this article was performed on my own hardware: my GamePad, my console, my box. The pairing step deliberately unpairs the GamePad from its console and is reversible by pressing SYNC twice on the console.

normal~/dtrh.net│Reverse Engineering the Wii U GamePad — Part 1: Recovering the Console's Secrets