Alles CTF 2020 - PWNkemon (hardware)

Hardware - PWNKemon

Disclaimer : my hardware skills are near zero, sorry for the mistakes and obvious shit if applicable. 20H before the end of the CTF, 28 team have flagged this hardware challenge and so weights 168 points. Here’s the description of the task :

The picture should explain everything.

Careful, flag format is in a different format: CSCG(...)

files identification

Once the challenge files downloaded we get the previously mentioned image :

And the following file.

λ ackira ~/nextcloud/CTF/alles2020/hwd/pwnkemon  » file pwnkemon.logicdata
pwnkemon.logicdata: CLIPPER COFF executable C1 R1 not stripped - version 15 -Ctnc -Cdnc

Two Game Boys Pocket are connected through their Game Link Cable and a device is plugged like a man in the middle in between.

I started by the principal tools like binwalk, strings and an hex editor and gathered the following string :

Timing Marker Pair

On Google, the first link bring us to https://www.saleae.com/, their products are logic analyzers. The aim of this kind of tool is to sniff multiples signals from a digital system or circuit and to later identify extracted data seeking for protocol, machine state or every data relative to the system execution.

If you zoom on the black device on the top of the picture, you will see saleae written on it, also the .logicdata extension is the name of the default output file extension generated by the associated software.

So we can assume that it’s a capture of the exchange between the two game boys made by the logic analyzer.

hardware specification

You can load the file in the saleae client (available through AUR), and then see 4 channels. I spent so much time wondering what I have to do here, I had no idea of which kind of protocol where used nor encoding.

Without these information you can’t extract data as you don’t know how 1 or 0 are transmitted. You have to know if the used protocol is about clock rising or falling edge for example.

Then I came across the following blog post Sniffing Game Boy serial traffic with an STM32F4 and Hooking up a Logic Analyzer. I learned that the protocol used was Serial Peripheral Interface (SPI) which consists of 4 wires (more VCC and GND ofc).

  • SCLK : the internal clock of the master which is 8192 Hz
  • SD : not used in games
  • SOUT or MOSI : master out data to slave input
  • SIN or MISO : master input data from slave output

With these information we can try to identify the different channels, the clock and SD are easily spotable as the clock has regular pattern and SD is continuous and not activated. However, SOUT and SIN can’t be differenced.

You can use the following configuration I stole from a blog post ( check the sources at the end), to add an analyzer on the panel at the right of the window. Thanks to the analyzer we can decode the logical data to get some numerical values.

I you pay attention the first byte you see are 0xFD, I remembered read this value in one of my opened tab

The first few bytes appear to be preamble bytes (0xFD). After that the names of the players are transferred (up to 11 bytes, but both players had a name of only 4 characters, terminated by 0x50). The names are followed by the number of Pokémons in their party (6 for both players). The next 6 bytes represent the species of the Pokémon, terminated by 0xFF, and so on…

Pwnkemon exchange party m8 !

So we are just placed at the beginning of a Pokemon exchange ! Reading more about this kind of exchange I got a ton of information from this blog post : Arbitrary Code Execution in Pokemon Red.

So, some Pokemons names or trainers names should be presents. I looked for it in the decoded ASCII in Saleae software but nothing readable grabbed my eyes.

So I exported the output byte to a file thanks to the CSV export and a python parsing script

for data in open("value.csv", "r").read().split("\n")[1:-1]:
    x = data.split(",")[2]
    extracted = findall(r"0x[a-zA-Z0-9]{2}", x)[1] # 0 is MOSI and 1 MISO 
    tmp +=  pack("B", int(extracted, 16))
    
open("out-miso.bin", "wb").write(tmp)

Once dumped the data look like this :

Generation I char encoding

Following the blog posts the structure starts with some 0xFD and then the name of the player (11 chars max) which is ending with the byte 0x50. This seems to happen at 0x7A with \x91\x84\x83\x50, but this string is not a valid ASCII name of a trainer. I struggled here for a while looking for others structures until I came across this video of LiveOverflow (the actual author of this challenge).

The name on this data save seems to be encoded and A is 0x80. With a bit of googling and some references you can find the char encoding according to your pokemon generation

So \x91\x84\x83 means RED, seems promising. As I knew the flag format it was really easy to spot the flag in this encoded form which is present, at least, in the miso data transfer.

$ print(tmp.index(b"\x82\x92\x82\x86")) # CSCG encoded
471
$ encoded_flag = tmp[471:].split(b"\x9b")[0] # ')' encoded
$ 
b'\x82\x92\x82\x86\x9a\x86\x8e\x8d\x8d\x80P\xe3\xa7\xa0\xa2\xaa\xe3\xa4\xac\xe3\x80P\xab\xab\xe3\x8f\x96\x8d\xaa\xa4\xac\xaeP\xad\xe7\xe7\xe7'

0x9B is ) in it encoded form, so the end of the flag

I did not found a script to decoded it so I wrote an ugly keymap table and just decoded it !

#!/usr/bin/python3

from re import findall
from struct import pack
from string import ascii_lowercase, ascii_uppercase

table = ascii_uppercase + "():;[]" + ascii_lowercase

k = 0
dic = {0x50 : "\x00", 0xe3 : "-", 0xe6 : "?", 0xe7 : "!"}

for i in range(0x8, 0xc):
    for j in range(0, 0x10):
        nb = i * 16 + j
        if nb < 0xba:
            dic[nb] = table[k]
            k +=1
k = 0

for i in range(0xf6, 0x100):
    dic[i] = str(k)
    k+=1

assert dic[0xff] == "9"

def decode_prop(encoded):
    tmp = ""
    for char in encoded:
        tmp += dic[char]
    return tmp

exchange_dump = b""

for data in open("fff.csv", "r").read().split("\n")[1:-1]:
    x = data.split(",")[2]
    extracted = findall(r"0x[a-zA-Z0-9]{2}", x)[1]
    exchange_dump +=  pack("B", int(extracted, 16))

print(exchange_dump.index(b"\x82\x92\x82\x86"))
encoded_flag = tmp[471:].split(b"\x9b")[0]

print(encoded_flag)

# open("out-miso.bin", "wb").write(tmp)
print(decode_prop(encoded_flag))
CSCG(GONNA-hack-em-All-PWNkemon!!!)

sources