Odna pika
Odna pika
Platform: CUSTOM | Category: Reversing | Type: Challenge | Difficulty: Easy | OS: N/A | **Author: XESXOR Techniques: bytecode_decompilation, headerless_pyc_recovery, interleaved_index_constraint_solve, pyc_header_reconstruction, python27_marshal_identification
Summary
Walkthrough of the CUSTOM Reversing Odna pika. Task: a 909-byte .pyc with erased header (magic+timestamp stripped), leaving raw marshal starting at 0x63. Solution: identify Python 2.7 via marshal code-object field layout and raw_input, rebuild the
Description
Привет от Тайной кошачьей! Стёрты заголовки. ("Hello from the Secret Cat! Headers erased.")
English summary: We are given a 909-byte file pyc_spbctf_1.pyc. file reports
only "data". The description hints that the file's headers were erased. The goal
is to recover the flag validation logic and compute the correct flag.
Analysis
Recon
$ file pyc_spbctf_1.pyc
pyc_spbctf_1.pyc: data
xxd shows the file begins immediately with byte 0x63 — that is the marshal
TYPE_CODE ('c') for a code object. In a normal .pyc the marshal blob is
preceded by an 8-byte header (4-byte magic number + 4-byte timestamp). Here that
header is gone: this is exactly what "Стёрты заголовки" (headers erased) and
the task name "Odna pika" (a lone .pyc) hint at.
strings reveals the interesting content:
Enter your flag:
Noooooo!
Closer, but no
YAY!
raw_input
Version identification — Python 2.7
Two independent signals point to Python 2.7:
-
raw_inputis a Python 2 builtin (removed in Python 3). -
2-byte-wide bytecode arguments (e.g.
64 00 00=LOAD_CONST) — pre-3.6 wordcode. -
The code-object marshal layout right after
0x63:00 00 00 00 | 00 00 00 00 | 09 00 00 00 | 40 00 00 00 argcount=0 nlocals=0 stacksize=9 flags=0x40
That's 4 leading ints — the Python 2.7 code-object layout. Python 3.0–3.7
would carry 5 ints (an extra kwonlyargcount), so this is Python 2.7.
- Running
marshal.loadson modern Python fails withValueError: bad marshal data (unknown type code), confirming the old format.
Solution
1. Reconstruct a valid Python 2.7 .pyc
Prepend the Python 2.7 magic \x03\xf3\x0d\x0a plus a 4-byte zero timestamp:
python read-only • copy allowedimport struct magic = b'\x03\xf3\x0d\x0a' ts = struct.pack('<L', 0) data = open('pyc_spbctf_1.pyc', 'rb').read() open('fixed_27.pyc', 'wb').write(magic + ts + data)
2. Decompile / disassemble
Run these under a Python 3.9 host (they understand Python 2.7 bytecode):
-
uncompyle6 fixed_27.pycrecovers the source, BUT it misrenders single-character string constants as integers (theirco_constsindices), producing misleading lists such asc = [4,5,6,...]. -
xdisshows the REAL constants:python3.9 -c "from xdis.disasm import disassemble_file; disassemble_file('fixed_27.pyc')"
The reconstructed logic:
python read-only • copy alloweda = raw_input('Enter your flag: ') assert len(a) == 18 b = a[::2] # even indices 0,2,...,16 c = ['s','b','t','{','1','s','_','4','k'] if any(b[i] != c[i] for i in range(len(c))): print 'Noooooo!' else: d = ['}','S','7','7','r','f','f','c','p'] b = a[1::2] # odd indices 1,3,...,17 if any(b[i] != d[::-1][i] for i in range(len(d))): print 'Closer, but no' else: print 'YAY!'
3. Solve the interleaved constraint
The 18-char flag interleaves two halves:
- Even positions must equal
['s','b','t','{','1','s','_','4','k']. - Odd positions must equal
dreversed =['p','c','f','f','r','7','7','S','}'].
python read-only • copy allowedeven = ['s','b','t','{','1','s','_','4','k'] odd = ['}','S','7','7','r','f','f','c','p'][::-1] flag = ''.join(even[i] + odd[i] for i in range(9)) print(flag) # spbctf{REDACTED}
Key Takeaways / Lessons
- Mastering bytecode_decompilation, headerless_pyc_recovery, interleaved_index_constraint_solve, pyc_header_reconstruction, python27_marshal_identification was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-07-05 • CUSTOM • Odna pika • Difficulty: Easy