snakeCTF logo

Annuitka

REVERSING

1 file available


Description

Python is slow but simple. C is fast but complex.

What if you could have the best of both worlds?

To run the chall use this Dockerfile

FROM python:3.12.5-bullseye

RUN pip3 install --no-cache-dir cryptography
COPY chall /chall

ENTRYPOINT ["/chall"]

Solution

Step 1: Analysis

Trying to statically analyse the binary will prove to be harder than usual, as the binary is compiled with nuitka. Dynamic analysis instead will prove more effective, running the program with strace will show this:

openat(AT_FDCWD, "/usr/local/lib/libpython3.12.so.1.0", O_RDONLY|O_CLOEXEC) = 3
...
openat(AT_FDCWD, "/usr/local/lib/python3.12/site-packages/cryptography/__pycache__/__init__.cpython-312.pyc", O_RDONLY|O_CLOEXEC) = 3

Which means that the python libraries are loaded from the user's system and not from the binary itself. This means that code can be injected into the binary by modifying the python libraries.

Step 2: Injecting code

Modifying the __init__.py file in the cryptography package to inject code will allow running arbitrary code in the binary. Probably the main function uses input() to get the user input, so it can be overwritten.

The following code will print the main module's directory:

def inject(*x,**y):
    import __main__
    print(dir(__main__))
import builtins
builtins.input=inject

# output: ['Cipher', '__annotations__', '__builtins__', '__cached__', '__compiled__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'algorithms', 'base64', 'check', 'decrypt', 'default_backend', 'encrypt', 'flag', 'key', 'main', 'modes', 'pad', 'padding', 'sha256', 'unpad', 'xor']

Just printing the flag variable will result in random bytes, but the decrypt function can help in recovering the flag:

def inject(*x,**y):
    import __main__
    print(__main__.decrypt(__main__.key,__main__.flag))
import builtins
builtins.input=inject

Running the binary will give the flag.