Elfstaller
Elfstaller
Platform: CUSTOM | Category: Reversing | Type: Challenge | Difficulty: Easy | OS: N/A | **Author: XESXOR Techniques: known_key_from_flag_prefix, pyc_decompilation, pyinstaller_extraction, repeating_key_xor
Summary
Walkthrough of the CUSTOM Reversing Elfstaller. Task: 12MB stripped PyInstaller ELF (Python 3.8) validating a flag via repeating-key XOR. Solution: extract with pyinstxtractor, decompile the 448-byte .pyc with decompyle3, then decrypt the hardcoded
Description
Elfstaller — reverse challenge. A single ELF binary
pyinstallerelf.elfis provided.
The challenge name "Elfstaller" is a pun on ELF + (Py)Installer, hinting
directly at the technique: a PyInstaller-bundled Python application shipped as an
ELF executable. Goal: recover the flag (SPbCTF{...}) accepted by the binary.
Challenge file: https://rev-kids20.forkbomb.ru/files/rev/new9/pyinstallerelf.elf
Analysis
Initial recon on the binary:
bash read-only • copy allowedfile pyinstallerelf.elf # ELF 64-bit LSB executable, x86-64, dynamically linked, stripped, ~12 MB strings pyinstallerelf.elf | grep -i python # libpython3.8.so.1.0 # python3.8 ... paths
A ~12 MB stripped ELF that references libpython3.8.so.1.0 is the classic
signature of a PyInstaller bundle built with Python 3.8. The real logic is
Python bytecode packed inside the executable, not native code.
Solution
1. Extract the PyInstaller archive
bash read-only • copy allowedcurl -s -o pyinstxtractor.py https://raw.githubusercontent.com/extremecoders-re/pyinstxtractor/master/pyinstxtractor.py python3 pyinstxtractor.py pyinstallerelf.elf
Output reports PyInstaller 2.1+, Python 3.8, 696 files, extracted to
pyinstallerelf.elf_extracted/. The entry-point candidate is
pyinstallerelf.pyc — only 448 bytes, so it holds the flag check.
2. Decompile the Python 3.8 bytecode
On a modern host (Python 3.14) decompyle3 handles 3.8 bytecode:
bash read-only • copy allowed# installed via homebrew: /opt/homebrew/bin/decompyle3 decompyle3 pyinstallerelf.elf_extracted/pyinstallerelf.pyc
It prints a lot of parser grammar debug noise, but the reconstructed source appears at the end:
python read-only • copy allowedimport os flag = b'(d=\x01eq\x0c8V1\x10\x03!\x0f 6c\x19fgS\x0f8*?<=%1rfa\x00\x0f\x11;' res = b'' c = 0 text = input("Insert flag to continue playing:\n").encode() flagd = text[:6] for i in text[6:]: res += bytes([i ^ flagd[c % 6]]) c += 1 if res == flag: print("Enjoy!") else: print("That is definitely not a flag.")
3. Understand the check
- The user's input is the flag.
flagd = text[:6]takes the first 6 bytes of the flag as a repeating XOR key.- Everything after position 6 (
text[6:]) is XOR'd byte-by-byte against the cycled 6-byte key (c % 6) and must equal the hardcodedflagciphertext.
The key is self-referential: it is the flag prefix itself. But the flag
format is known — SPbCTF{...} — so the first 6 bytes are exactly SPbCTF.
That gives us both the key and the prefix for free.
4. Decrypt
python read-only • copy allowed#!/usr/bin/env python3 flag = b'(d=\x01eq\x0c8V1\x10\x03!\x0f 6c\x19fgS\x0f8*?<=%1rfa\x00\x0f\x11;' key = b'SPbCTF' res = ''.join(chr(b ^ key[i % 6]) for i, b in enumerate(flag)) print('SPbCTF' + res) # SPbCTF{REDACTED}
Lessons / Takeaways
The whole check hinges on a self-referential trick: the XOR key was the first 6
bytes of the flag, and those bytes are dictated by the known flag format
SPbCTF{...}. Whenever a validator uses part of the secret as the key to
transform the rest of the secret, and the format gives you that part, the
scheme collapses instantly — recognizing the known flag prefix as the key
turns "reverse engineering" into a one-line XOR decrypt.
Key Takeaways / Lessons
- Mastering known_key_from_flag_prefix, pyc_decompilation, pyinstaller_extraction, repeating_key_xor was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-07-02 • CUSTOM • Elfstaller • Difficulty: Easy