panthadori lion mascot

Sunshine CTF 2023 RE Challenge

cybersecurity posted 2023-10-10 (updated 2024-01-23)
decompiling .pyc
Table of Contents

Overview

Sunshine CTF 2023 had two reverse-engineering challenges that my team completed (just before time ran out, too!). This post describes my thought process behind Dill, a challenge worth 100 points (which is relatively easy).

Dill

‘Dill’ provides players with a .pyc file that is unreadable to users. A .pyc file contains the bytecode of the python file, not the source code. The source code has already been compiled, and we need to decompile it to read it. We can also understand what the .pyc file is doing by running it in a debugger,

Luckily for us, this .pyc file can be easily decompiled by a tool called uncompyle.

  1. It can be downloaded here: https://pypi.org/project/uncompyle6/#files
  2. Choose the latest .whl file and install like: pip install uncompyle6-3.2.3-py27-none-any.whl.
  3. You should now be able to type uncompyle6 --version via commandline and receive a version number.
  4. With this tool, we can decompile dill.pyc like so: uncompyle6 dill.pyc.
Now we have the source code:
class Dill:
    prefix = 'sun{'
    suffix = '}'
    o = [5, 1, 3, 4, 7, 2, 6, 0]

    def __init__(self) -> None:
        self.encrypted = 'bGVnbGxpaGVwaWNrdD8Ka2V0ZXRpZGls'

    def validate(self, value: str) -> bool:
        return value.startswith(Dill.prefix) and value.endswith(Dill.suffix) or False
        value = value[len(Dill.prefix):-len(Dill.suffix)]
        if len(value) != 32:
            return False
        c = [value[i:i + 4] for i in range(0, len(value), 4)]
        value = ''.join([c[i] for i in Dill.o])
        if value != self.encrypted:
            return False
        return True

Here is what I notice when reading through the class:

I want to run this script locally to test it first. I removed the class and added a main function. I also wanted to understand what `c` and `value` were doing, so I printed them out.
prefix = 'sun{'
suffix = '}'
o = [5, 1, 3, 4, 7, 2, 6, 0]

encrypted = 'bGVnbGxpaGVwaWNrdD8Ka2V0ZXRpZGls'

def validate(value: str) -> bool:
    print(value)
    if len(value) != 32:
        print("not right length")
    #for every group of 4 letters letter in value
    c = [value[i:i + 4] for i in range(0, len(value), 4)]
    print(c)
    value = ''.join([c[i] for i in o]) # add back the quartets in a strange order
    if value != encrypted:
        return False
    return True

def main():
    string = encrypted
    validate(string)

if __name__ == "__main__":
    main()

Output

challenges screenshot

At this point, I’m starting to understand what’s going on. These blocks of 4 letters have been rearranged according to the order listed in o. To put them back, we have to create a new array that organizes them in order. Since 0 should be the first block and is listed last in the array o, we know that the last index 7 should be printed first. Similarly, since 1 is the second block and is printed at index 1. 2 is the third block and is printed at index 5.

o = [5, 1, 3, 4, 7, 2, 6, 0]

To print the blocks in the correct order again, we will use this new array:

a = [7, 1, 5, 2, 3, 0, 6, 4]

By reordering the blocks, we arrive at the solution:
prefix = 'sun{'
suffix = '}'
o = [5, 1, 3, 4, 7, 2, 6, 0]
a = [7, 1, 5, 2, 3, 0, 6, 4]

encrypted = 'bGVnbGxpaGVwaWNrdD8Ka2V0ZXRpZGls'

def validate(value: str) -> bool:
    print(value)
    if len(value) != 32:
        print("not right length")
    #for every group of 4 letters letter in value
    c = [value[i:i + 4] for i in range(0, len(value), 4)]
    print(c)
    # add back the quartets out of order to 'encrypt' them
    value = ''.join([c[i] for i in o])
    # reorder the quartets to decrypt them
    unencrypt = ''.join([c[i] for i in a])
    print("here", unencrypt)
    if value != encrypted:
        return False
    return True

def main():
    string = encrypted
    validate(string)

if __name__ == "__main__":
    main()
    

Solution output:

solution output

Just add the prefix back to the string, and we have the flag!