Back to writeups

International Hack10 CTF 2026

Easy RE 2

International HACK@10 CTF 2026 hack10, forensics, reverse engineering writeup covering Easy RE 2 with analysis, solution steps, and final recovery notes.

Date
Platform
CTF
Category
CTF
Difficulty
Medium
#ctf#hack10#forensics#reverse-engineering#malware-analysis#cryptography#binary-exploitation#mobile

Challenge Overview

Challenge Name: Easy RE 2 Category: Reverse Engineering Points: 493 Flag Format: hack10{...}

The challenge provided an Android APK and related extracted files. The goal was to reverse the challenge assets and recover the hidden flag.

The important provided files were:

chall.apk
background.txt
background.bkp
background.txt.decrypted.dex
image_check.py

The final flag was hidden inside an encrypted image asset.

Initial Analysis

The challenge looked like a simple reverse engineering warm-up. Instead of complex binary exploitation, the key observation was that the provided files included suspicious background-related assets:

background.txt
background.bkp

A helper script, image_check.py, also showed that the challenge likely involved XOR-based image recovery. The script checked whether XORing file bytes could produce valid image headers such as PNG or JPG.

Important image headers:

JPG: ff d8 ff
PNG: 89 50 4e 47 0d 0a 1a 0a

Since background.bkp did not open normally as an image, it was likely encrypted or obfuscated.

Vulnerability / Weakness Identification

The weakness was weak single-byte XOR encryption.

The file background.bkp was not strongly encrypted. Every byte was XORed with the same key:

0xEF

Single-byte XOR is trivial to brute-force because there are only 256 possible keys.

Once the correct key is used, the decrypted output starts with a valid JPG header:

ff d8 ff

Exploitation Strategy

The solving plan:

  1. Read the suspicious files as raw bytes.

  2. Try every possible single-byte XOR key from 0x00 to 0xFF.

  3. XOR the file content with each key.

  4. Check whether the decrypted result starts with a known image header.

  5. Save the valid recovered image.

  6. Open the recovered image and read the flag visually.

This works because image formats have predictable magic bytes at the beginning of the file.

Proof of Concept

Minimal proof of concept:

python3 - <<'PY'
data = open("background.bkp", "rb").read()
decoded = bytes(b ^ 0xEF for b in data)
open("recovered.jpg", "wb").write(decoded)
print("[+] Wrote recovered.jpg")
PY

file recovered.jpg

Expected output:

[+] Wrote recovered.jpg
recovered.jpg: JPEG image data

Then open the recovered image:

xdg-open recovered.jpg

Full Python Solver

#!/usr/bin/env python3
from pathlib import Path

# Files to test
INPUT_FILES = [
    "background.bkp",
    "background.txt",
]

# Common image magic headers
MAGIC_HEADERS = {
    b"\xff\xd8\xff": "jpg",
    b"\x89PNG\r\n\x1a\n": "png",
    b"GIF87a": "gif",
    b"GIF89a": "gif",
    b"BM": "bmp",
}


def detect_file_type(data: bytes):
    """
    Check whether decrypted data starts with a known image header.
    """
    for magic, extension in MAGIC_HEADERS.items():
        if data.startswith(magic):
            return extension
    return None


def xor_single_byte(data: bytes, key: int) -> bytes:
    """
    XOR every byte with a single-byte key.
    """
    return bytes(byte ^ key for byte in data)


def brute_force_xor(filename: str):
    """
    Try all possible single-byte XOR keys against a file.
    Save the file if a valid image header is found.
    """
    path = Path(filename)

    if not path.exists():
        print(f"[-] File not found: {filename}")
        return False

    data = path.read_bytes()
    print(f"[*] Testing {filename} ({len(data)} bytes)")

    for key in range(256):
        decrypted = xor_single_byte(data, key)
        extension = detect_file_type(decrypted)

        if extension:
            output_name = f"{path.stem}_xor_{key:02x}.{extension}"
            Path(output_name).write_bytes(decrypted)

            print(f"[+] Valid {extension.upper()} found")
            print(f"[+] Source file : {filename}")
            print(f"[+] XOR key     : 0x{key:02X}")
            print(f"[+] Saved as    : {output_name}")
            return True

    print(f"[-] No valid image found in {filename}")
    return False


def main():
    found = False

    for filename in INPUT_FILES:
        if brute_force_xor(filename):
            found = True

    if not found:
        print("[-] No encrypted image recovered.")
        return

    print("\n[+] Open the recovered image to read the flag.")
    print("[+] Useful result for this challenge: background.bkp XOR 0xEF")


if __name__ == "__main__":
    main()

Walkthrough

Save the solver:

nano solve.py

Run it:

python3 solve.py

Expected output:

[*] Testing background.bkp (...)
[+] Valid JPG found
[+] Source file : background.bkp
[+] XOR key     : 0xEF
[+] Saved as    : background_xor_ef.jpg

Verify the recovered file:

file background_xor_ef.jpg

Expected result:

background_xor_ef.jpg: JPEG image data

Open it:

xdg-open background_xor_ef.jpg

Troubleshooting:

ls -lah

Make sure these files are in the same folder:

solve.py
background.bkp
background.txt

No external Python packages are required.

Flag

The recovered image reveals the flag:

hack10{minato_namikaze}

Conclusion

The challenge used weak XOR obfuscation to hide an image inside background.bkp. Because the same single-byte key was reused for the whole file, the key space was only 256 possibilities.

The key lesson is that file magic bytes are powerful indicators during reverse engineering and forensics. When a suspicious file does not open normally, checking for XOR-obfuscated headers is a fast and reliable first step.

Authorized security practice only. These notes are for lab, CTF, and explicitly permitted environments.