In this Reverse Engineering challenge, we’re presented with a video game, in the form of an executable file.
We can download it and analyze it – it’s an ELF, which means it runs on Linux systems. We can run it on our own machine or in a Linux virtual machine. You might need a more recent VM, since it looks like it was built using a relatively new glibc version – the Kali VM in our Cloud Labs environment does the job fine.

When we run it, it says something about pygame, then displays a GUI window. It looks like it’s a slot machine game – fun! We can try to play it, but we’ll find we quickly get nowhere – the house always wins, after all.
Let’s do some research. pygame (https://github.com/pygame/pygame) is a Python game development toolkit, so somehow, this is running Python code, despite being a compiled ELF binary. The most common way of achieving this is with a tool called pyinstaller. There are several tools out there that can open up pyinstaller binaries for us. One is even available on the web: https://pyinstxtractor-web.netlify.app/
Using that tool, we can upload our ELF, and get back a ZIP containing various .pyc (Python Bytecode) files. The one that’s relevant to us is shamrock.pyc, our main entry point.
There are various disassembly tools available to turn the .pyc file – which is unreadable machine code – back into human-readable Python opcodes. pydisasm is a popular choice. pycdas should also work.
pydisasm -S -F extended ../shamrock_extracted/shamrock.pycThis yields what looks like relevant bytecode:

Let’s try searching for flags?

Some of this is background noise, but a few spots – ENCRYPTED_FLAG, flag_found, attempt_flag_decrypt, etc. – look relevant. This bytecode is a mess, though, so let’s see if there’s any tools that can convert it back to something more readable.
We can try PyLingual: https://pylingual.io/ It unfortunately crashes with a syntax error:

And indeed, on inspection, we find that the resulting code has a lot of issues. It does decode some of the constants, one of which might be particularly useful:
ENCRYPTED_FLAG = b'\x89\x955W\xfa?\xd4\xf66\x84\xb3\xcc\x16\xcf<\xd7p\xfc\xbb#\x0ci\x00\xa1\x0b9w[?t\x05\x1an\x0cm\x13vy\x92<\xabS:\xc4\xdb\x85\xb1\x811@\xd0\x97T=i\xb5A!z\x81\x91\x00\xbd\xb7'But ENCRYPTED_FLAG doesn’t actually get used anywhere in this code, and that’s ignoring all the other glaring issues with it.
We can try a few other decompilation toolkits. Unfortunately, for this author and as of October 2025, pycdc, decompyle3, and uncompyle6 all failed with some variant of a version-too-new (This is Python 3.13.0 bytecode) or unknown-opcode (likely because the version is too new) error. All online tools we tried also failed.
In the end, we took the coward’s way out.

And after some fiddling with the file extension, believe it or not:

(Yes, this is ChatGPT 5.) We hate to admit it, but this is indeed a fairly accurate analysis of the disassembly. We can even ask it to tell us what text gets output after the decryption – it tries its best to write a script, but ultimately fails, at least in my attempt. But it’s useful as boilerplate, and with or without it, we can write our own script pretty easily. The crucial bits are:
- b1, b2, and b3 – each one byte – are taken from the game state in some way.
- A hardcoded suffix is appended to these three bytes.
- The result is used as the key to AES decrypt the encrypted flag.
- The flag is then unpadded and displayed.
We could trace the logic backwards – we even have variable names, which is handy – to try to figure out what b1, b2, and b3 need to be. But given that each is one byte and 255 * 255 * 255 = 16,581,375 which is a reasonable enough number to brute force, it’s probably easier to just brute force it. Conveniently, unpadding will fail unless we decrypt it correctly, so we have an easy way to know when we’ve got the right key.
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
ENCRYPTED_FLAG = b'\x89\x955W\xfa?\xd4\xf66\x84\xb3\xcc\x16\xcf<\xd7p\xfc\xbb#\x0ci\x00\xa1\x0b9w[?t\x05\x1an\x0cm\x13vy\x92<\xabS:\xc4\xdb\x85\xb1\x811@\xd0\x97T=i\xb5A!z\x81\x91\x00\xbd\xb
7'
tail = b'\x137\xca\xfe\xba\xbe\x06f\x00\x11"3D'
for b1 in range(255):
    print(b1) # Progress
    for b2 in range(255):
        for b3 in range(255):
            key = bytes([b1, b2, b3]) + tail
            x = AES.new(key, AES.MODE_ECB).decrypt(ENCRYPTED_FLAG)
            try:
                y = unpad(x, 16).decode("utf-8")
                print(y)
            except:
                passAnd when we run this:
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
MetaCTF{r3al_sl07s_h4v3_n3gat1v3_ev_d0n7_be_7rick3d}
20
21
22
23Happy Halloween.