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, participants were given 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}

ransomware

In this challenge, participants were given 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.

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.