Post

APT CTF Write Up

Write Up of the Reverse Engineering challenges I made for the APT CTF at UM6P - 1337

APT CTF Write Up

CrackMe!

In this challenge, I gave the participants a binary file. Upon execution, it presents a simple GUI with a text input box and a “Check” button — essentially a flag checker. The goal is to reverse-engineer the binary and recover the correct input that will produce the flag.

Step 1: Initial Binary Analysis

We begin by using the file command to inspect the binary:

1
2
$ file CrackMe!
CrackMe!: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=fc89f558d158bb1cc6e5e463d6fe7c536da15abb, stripped

So it’s a 64-bit ELF binary, dynamically linked, and stripped (no symbols).

Next, we want to determine if it’s packed and what language it was originally written in.

Step 2: Identifying the Language and Packer

We use Detect It Easy (DIE) to analyze the binary:

DIE tells us this is a Python application bundled with PyInstaller. PyInstaller packages Python applications into standalone executables, embedding the Python interpreter and required libraries.

Step 3: Extracting the PyInstaller Archive

To decompile the executable, we use pyinstxtractor.py — a Python script that extracts PyInstaller-packed binaries.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ python3 pyinstxtractor.py CrackMe!

[+] Processing CrackMe!
[+] Pyinstaller version: 2.1+
[+] Python version: 3.12
[+] Length of package: 12277682 bytes
[+] Found 365 files in CArchive
[+] Beginning extraction...please standby
[+] Possible entry point: pyiboot01_bootstrap.pyc
[+] Possible entry point: pyi_rth__tkinter.pyc
[+] Possible entry point: pyi_rth_inspect.pyc
[+] Possible entry point: check.pyc
[+] Found 104 files in PYZ archive
[+] Successfully extracted pyinstaller archive: CrackMe!

Among the extracted files, we find our entry point: check.pyc

Step 4: Decompiling the Obfuscated Python Code

We decompile check.pyc using pylingual and discover it’s obfuscated:

The obfuscation was done using Py-Fuscate — a tool that compresses and marshals Python bytecode.

To reverse it, you can manually:

  • Decompress with zlib

  • Unmarshal the code with marshal

  • Repeat the process if the next layer is also obfuscated or uses a different compression method

This can become a time-consuming and repetitive task because multiple layers of obfuscation are used.

Or save time and use my online deobfuscator tool pydeobf.xyz or use the Python Deobfuscator for Py-Fuscate on my GitHub, which deobfuscates Py-Fuscate code and outputs the raw bytecode. Why bytecode? because the marshal module works at the bytecode level, not the source level.

Deobfuscated bytecode using my online tool:

Step 5: Deobfuscation to Source Code

Once we extract the bytecode, we can either:

  • Manually reverse it (takes a lot of time), or

  • Let an LLM (like ChatGPT) regenerate the readable Python code from bytecode.

Here’s the recovered source code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import tkinter as tk

def encrypt_text_to_coords(text, key):
    encrypted = [ord(b) ^ ord(key[i % len(key)]) for i, b in enumerate(text.encode())]
    coords = []
    for byte in encrypted:
        rank = byte // 8 + 1
        file = 'abcdefgh'[byte % 8]
        coords.append(f"{file}{rank}")
    return coords

def check_encryption(input_text, coords, key):
    encrypted_coords = encrypt_text_to_coords(input_text, key)
    return encrypted_coords == coords

coords = [
    'e4', 'a3', 'c3', 'f2', 'd1', 'b1', 'b6', 'h1',
    'f6', 'a4', 'b1', 'b2', 'd2', 'd7', 'a4', 'b6',
    'h1', 'g4', 'f2', 'g7', 'c3', 'f2', 'd1', 'b1'
]

key = b"lifelover"

def validate_input():
    user_input = entry.get()
    if check_encryption(user_input, coords, key):
        result_label.config(
            text=f"Correct! here is your flag: APT}",
            fg="green"
        )
    else:
        result_label.config(text="Incorrect! ghiyerha", fg="red")

# GUI setup
root = tk.Tk()
root.title("Crack me!")
label = tk.Label(root, text="Enter the secret:", font=("Courier", 14))
label.pack(pady=10)
entry = tk.Entry(root, font=("Courier", 14), width=30)
entry.pack(pady=10)
validate_button = tk.Button(
    root,
    text="Check",
    font=("Courier", 14),
    command=validate_input
)
validate_button.pack(pady=10)
result_label = tk.Label(root, text="", font=("Courier", 14))
result_label.pack(pady=10)
root.mainloop()

Step 6: Reverse Engineering the Algorithm

The encryption function XORs the input string with a repeating key lifelover, then maps each result byte to a chessboard-style coordinate using:

1
2
Rank = (byte // 8) + 1
File = 'abcdefgh'[byte % 8]

To reverse it:

  • Convert each coordinate back into its byte representation.

  • XOR with the key.

  • Decode into a UTF-8 string.

Example :

1
e4 → file e → index 4, rank 4 → (4-1)*8 + 4 = 28

Step 7: Solver Script

Here’s a solver python code that will give us the correct secret :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def coords_to_encrypted_bytes(coords):
    files = 'abcdefgh'
    encrypted = []
    for coord in coords:
        file_char = coord[0]
        rank = int(coord[1])
        file_index = files.index(file_char)
        byte = (rank - 1) * 8 + file_index
        encrypted.append(byte)
    return encrypted

def decrypt_coords(encrypted_bytes, key):
    return bytes([b ^ key[i % len(key)] for i, b in enumerate(encrypted_bytes)])

coords = [
    'e4', 'a3', 'c3', 'f2', 'd1', 'b1', 'b6', 'h1',
    'f6', 'a4', 'b1', 'b2', 'd2', 'd7', 'a4', 'b6',
    'h1', 'g4', 'f2', 'g7', 'c3', 'f2', 'd1', 'b1'
]
key = b"lifelover"

encrypted = coords_to_encrypted_bytes(coords)
secret = decrypt_coords(encrypted, key).decode()
print(secret)

Output : python_b_thon_w_bla_thon

FLAG : APT{python_b_thon_w_bla_thon}

Second Challenge: ransomware

In this challenge, I gave participants a Windows executable ransomware and an encrypted flag file. Their objective was to reverse engineer the binary and its encryption method to recover the original file.

This is my approach to solving this challenge.

Static Analysis

First, I performed static analysis using DIE (Detect It Easy):

The analysis revealed a 64-bit console executable compiled in C++ using Visual Studio 2022. It also uses OpenSSL, which likely handles the file encryption functionality.

Decompilation

I used IDA to decompile the binary. Examining the main function revealed:

The execution flow is:

1 - The binary prints “[+] WELCOME TO WINDOWS ACTIVATIOR”

2 - It calls sub_140006300 with an argument (time calculations suggest this is a sleep function, confirmed by sandbox execution showing a delay between messages)

3 - It prints “ “[-] Activating Windows…”

4 - It calls two important functions: sub_140001600 and sub_1400017F0

Looking deeper at these two functions:

Both functions:

  • Load binary resources named “IV_RESOURCE” and “KEY_RESOURCE”

  • XOR-decrypt them with the byte 0x45 (decimal 69)

  • Append the result to a std::string

This indicates the ransomware uses hardcoded XOR-obfuscated encryption keys stored in the .rsrc section.

Resource Analysis

To extract these resources, I used Resource Hacker to examine the .rsrc section:

After obtaining the XOR-encrypted KEY and IV values, I performed XOR decryption:

KEY : CHABANI_CHABANA!

IV : !CHABANI_LABANI!

Decryption

With the key and initialization vector identified, I determined that the ransomware likely uses AES encryption. I attempted decryption with these values:

FLAG: APT{w_ch4k_ch4b4n1_ch4b4n4}

This post is licensed under CC BY 4.0 by the author.