Teaser Dragon CTF 2019: "PlayCAP" writeup

Given was PlayCAP.pcapng and app.html. First checking the PCAP, we see that an USB connection is captured there. In the Response DEVICE, the vendor Nintendo Co., Ltd is shown. Later the device name is sent as string: Pro Controller.

At https://github.com/ToadKing/switch-pro-x/blob/master/switch-pro-x/ProControllerDevice.cpp there is a user-space driver for the Pro Controller for PC.

Now, we extract all data from the PCAP and parse them. In the app.html, we see that they use the direction buttons for changing the selection, A to select and X to reset (set current selection to [0,0]).

Using the Bitmasks from the driver, only reading a button once (by using a state-machine), and using the same functionality as app.html, we can decode the flag.

Script here:

#!/usr/bin/env python3

b = []
with open("data.txt", "r") as f:
    for i in f.read().split("\n"):
        if len(i) == 0:
            continue
        b.append([int(f"{i[j]}{i[j+1]}", 16) for j in range(0, len(i), 2)])

curr_x = 0
curr_y = 0
flag = []
buttons = []

# state is saved because the controller sends a cyclic status message so we get multiple messages with the same state which we can safely ignore
#        UP     DOWN   LEFT   RIGHT  A      X
state = [False, False, False, False, False, False]
for i in b:
    type = i[0]
    # only use type 30
    if type != 0x30:
        continue
    serial = i[1] # ignored
    button_pressed = [
        0x02 & i[5] != 0, # UP
        0x01 & i[5] != 0, # DOWN
        0x08 & i[5] != 0, # LEFT
        0x04 & i[5] != 0, # RIGHT
        0x08 & i[3] != 0, # A
        0x02 & i[3] != 0, # X
    ]

    for i in range(6):
        # button is now pressed and wasn't before
        if button_pressed[i] and not state[i]:
            state[i] = True
            if i == 0: # UP
                buttons.append("↑")
                curr_y = (curr_y - 1 + 6) % 6
            if i == 1: # DOWN
                buttons.append("↓")
                curr_y = (curr_y + 1) % 6
            if i == 2: # LEFT
                buttons.append("←")
                curr_x = (curr_x - 1 + 10) % 10
            if i == 3: # RIGHT
                buttons.append("→")
                curr_x = (curr_x + 1) % 10
            if i == 4: # A
                flag.append("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.,-=:;{}"[curr_y * 10 + curr_x])
                buttons.append("A")
            if i == 5: # X
                curr_x = 0
                curr_y = 0
                buttons.append("X")

        # button is not longer pressed, but was before
        if not button_pressed[i] and state[i]:
            state[i] = False

print("Buttons: " + "".join(buttons))
print("Flag: " + "".join(flag))

Buttons: XXX→→→A↓↓↓↓A↑←A→→→→→→→A→→→→→↑↑→→→→A↓↓↓↓A↑↑↑↑←←←←←←←A↓↓←A→→→→→↓A←A↑↑↑→A↓↓→→→←A↑←A↓↓←←←←←←↓A↑↑↑↑↑A→→→→→→A↓↓A→→↓A→→A→↓A↑↑→→→→→A→→→A↓↓↓AX

Flag: DrgnS{LetsPlayAGamepad}

Bonus: Running the challenge on a regular Switch

PlayCAP running on a real Switch