Featured image of post MidnightCTF 2024 - WiiWii

MidnightCTF 2024 - WiiWii

# Introduction

WiiWii was a challenge from the 2024 edition of the Midnight Flag CTF. This particular challenge was about reversing a Dolphin file.

cf12d806128b1881a48c8566e7a9647d.png

I started by loading this file on a Dolphin Emulator in Debug mode by doing dolphin-emu -d. I then set up my View to have the register, memory and instruction tabs.

f19409a8d2465c1fa2e024a57f156c0d.png

We can see this starts a server on a local IP address. I patched the port so it would be on port 4095 instead of 80 since the emulator didn’t have permissions for that port. Let’s try and curl it.

curl 10.96.0.16:4095/flag

76f0759ec1cc920fabdf7ba804fb4720.png

This printed /STTO to the output, meaning it probably did some encoding/encrypting on our /flag endpoint.

# Disassembly

We can look at the file on the disassembler, for this I used the GameCube DOL Binary Ninja plugin. Looking for the Default response sent string, we find this function :

cb71eb019362642118103364fd7c8ddd.png

If you’ve ever followed Beej’s networking programming guide this function should look very similar to the C implementation of a TCP server. We see that we should strive to get the check_endpoint function to return something different than to get the Initialization response.

6fd350c216c5e6c36a1127863d86060a.png

Some clarification on how I found the strcmp_int32 bd804fca27677f6cc5565d902af47139.png

Well I could reconstruct the string by hand, and although I suspect that var_d0 is the key for the Vigenere-like rotation, I’m not quite sure.

Here we go into disassembly view, and look for the instructions which will contain our wanted values.

14c8cff6b9242dbd0a7e35547bf69d07.png d61023a75456e787412ebad4634a37d6.png

# Debugging with dolphin-emu

Using this as an excuse to learn a bit about the Dolphin Emulator debugger, I placed a few breakpoints, one at 80005928 and one at 8000595c. This would allow me to look in memory for the value and confirm the rotation hypothesis.

880cb520e62f6f955ade3ba5f8a3b155.png

Let’s curl 10.96.0.16:4095/AAAAAAAAAAAA.

We break at the expected functions.

12c6b15afbbeda69408c39bef27c67e4.png

And sure enough, in memory at around 801c8cc0.

878ed42620f1b4ff6ab618c8b1c015f6.png

If we step through the operations around r9, we can confirm some Vigenere like encryption with the INIT variable, where each character from the alphabet is subtracted by one of the letters according to the index.

This could also be deducted by sending some request like /ABCDEF123456abcdefghi and looking at the offsets from the results.

From all this information, we can assume that the wanted input is one that results in the :.{01L_5PP001_64Z3_K0V5013} string. We write a small solution script :

key = [0x13, 0x18, 0xD, 0x18]

def find(string, key):
    rev = []
    for i in range(len(string)):
        c = string[i]
        if 'a' <= c <= 'z':
            rev.append(chr(ord(c) + key[i % 4]))
        elif 'A' <= c <= 'Z':
            rev.append(chr(ord(c) + key[i % 4]))
        else:
            rev.append(c)
    
    return rev

transformed_str = ":.{01L_5PP001_64Z3_K0V5013}"
original_str = find(transformed_str, key)
print("Original string:", ''.join(original_str))

And we get the flag: :.{01d_5ch001_64m3_c0n5013}

Built with coffee holding the wheels and nicotine working the pedals. And Hugo.
Theme Stack designed by Jimmy.