rev12
rev12
Platform: CUSTOM | Category: Reversing | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: custom_rc4_decrypt, docker_amd64_run, key_concatenation_recovery, self_reading_binary, validator_constant_extraction
Summary
Walkthrough of the CUSTOM Reversing rev12. Task: 64-bit non-stripped PIE ELF in the train/path/station self-decrypting custom-RC4 family (sibling of RE4_rev02) but with many more struct fields (driver, passport, country, dates, durations). Sol
Description
RE4_rev12 — Reverse. Binary
rev_1_2onrev-kids20.forkbomb.ru.
A 64-bit x86-64 PIE ELF rev_1_2 (NOT stripped) is given. It is the same
"train/path/station" self-decrypting custom-RC4 family as RE4_rev02
(rev_0_2), but with many more struct fields: driver name, passport, country,
date of issue, trip durations, etc. The program asks you to fill all struct
fields, validates each against an embedded constant, then concatenates every
entered value into a single "key" used to decrypt the embedded flag. Goal:
recover the key, get the flag, submit via POST flag.
Analysis
The binary is built from nested structs. For each struct a read_* / create_*
function prompts and stores a field, and a check_*_0 validator compares the
stored field against a hard-coded constant via strncmp/integer compare:
check_driver_name_0,check_driver_passport_0,check_passport_country_0,check_passport_date_of_issue_0check_path(@0xf2d) callscheck_path_begin_0,check_path_end_0,check_path_train_0,check_path_travel_time_0,check_train_driver_0
If check_path passes, main (@0x1eab) prints Enter key:, reads up to 0x800
bytes, strips the newline, and calls drop_flag (@0x1c9d).
drop_flag(filename=argv[0], key) opens the program's own binary "rb":
- Reads the last 4 bytes of the file → ciphertext length
clen = 50. - Seeks to
filesize - 4 - clen = 18536and reads the 50-byte ciphertext. - Runs a custom RC4:
prepare_key(@0x198a) is the KSA,rc4(@0x1a9f) is the PRGA — both standard. The key is the user-typed string. - Decrypts in place. First 4 bytes of plaintext = inner length (
0x2e); the rest is the flag string.
strtoupper exists in the binary but is never called — the key is used
verbatim (no case change). The check_*_0 functions only validate fields;
they do not build the key. The key is the concatenation of every entered
field value in entry (read traversal) order, with no separator.
Expected field values (entry order = key order)
| # | Field | Constant | Decimal/Value |
|---|---|---|---|
| 1 | begin station avg_trains (qword) | 0x50 | 80 |
| 2 | begin station city (str) | — | Savirellia |
| 3 | begin station country (str) | — | Ziyton |
| 4 | end station avg_trains | 0x17 | 23 |
| 5 | end city | — | Contiensa |
| 6 | end country | — | Zhand |
| 7 | train rate (fgets) | — | 5 |
| 8 | user age (qword) | 0x36 | 54 |
| 9 | name surname | — | Wenger |
| 10 | name name | — | Matthieu |
| 11 | passport number (int) | 0x1dc3f3 | 1950707 |
| 12 | passport country name | — | Rohan |
| 13 | country age_country (int) | 0x5ce | 1486 |
| 14 | country population (qword) | 0x2a1c07 | 2759687 |
| 15 | date day (int) | 0x16 | 22 |
| 16 | date month (qword) | 0x4 | 4 |
| 17 | date year (str) | — | 2001 |
| 18 | train passengers_count (qword) | 0x3bf | 959 |
| 19 | train price (int) | 0x958 | 2392 |
| 20 | train train_number (fgets) | — | H142 |
| 21 | duration seconds (str) | — | 46 |
| 22 | duration hours (str) | — | 27 |
| 23 | duration minutes (str) | — | 33 |
| |
Note: read_station / read_country prompt labels are swapped relative to
the storage offset, but the storage order (= entry order) is what feeds the
key, so follow storage order, not the printed label.
Solution
- Reverse each
check_*_0validator (radare2 / Ghidra) to extract every expected constant. - Determine read order from
mainand key concatenation order fromprepare_key. No separators, no case change. - Concatenate field values in entry order to form the RC4 key:
80SavirelliaZiyton23ContiensaZhand554WengerMatthieu1950707Rohan1486275968722420019592392H142462733
- Run the binary under Docker
ubuntu:22.04(linux/amd64), feed the expected field values to each prompt, and paste the full concatenation at theEnter key:prompt → it prints "Well done!" and the flag. Alternatively reproduce the custom RC4 offline on the 50-byte tail ciphertext.
Offline decrypt (custom RC4)
python read-only • copy allowed#!/usr/bin/env python3 # Read the 50-byte appended ciphertext from rev_1_2 and decrypt with the recovered key. KEY = b"80SavirelliaZiyton23ContiensaZhand554WengerMatthieu1950707Rohan1486275968722420019592392H142462733" with open("rev_1_2", "rb") as f: data = f.read() clen = int.from_bytes(data[-4:], "little") # 50 ct = data[len(data) - 4 - clen : len(data) - 4] # offset 18536, 50 bytes # RC4 KSA (prepare_key) + PRGA (rc4) — standard S = list(range(256)) j = 0 for i in range(256): j = (j + S[i] + KEY[i % len(KEY)]) & 0xff S[i], S[j] = S[j], S[i] out = bytearray() i = j = 0 for b in ct: i = (i + 1) & 0xff j = (j + S[i]) & 0xff S[i], S[j] = S[j], S[i] out.append(b ^ S[(S[i] + S[j]) & 0xff]) inner_len = int.from_bytes(out[:4], "little") # 0x2e = 46 flag = out[4:4 + inner_len].decode() print(flag) # spbctf{REDACTED}
Ciphertext (50 bytes @ offset 18536):
71a45d3f7bfed449583a53caba9877cc44cad9a817cd7f38fef6daa799239898f8624b9015f7eb87d0d46b02928612387174
decrypts to \x2e\x00\x00\x00 + spbctf{REDACTED}.
Debugging lesson (failed-then-fixed)
The first run failed with "Try again!". The cause was a hex→decimal slip on the
population constant 0x2a1c07, which was mis-converted to 2759175 instead
of the correct 2759687. A full prompt-trace run (entering each field one at
a time and watching the response) isolated the single mismatched field;
correcting the conversion to 2759687 made the binary accept the key
("Well done!") and print the flag.
Lesson: when a multi-field RC4-key check fails, trace each prompt/response to find the single wrong field, and double-check hex→decimal conversions of numeric struct fields.
Key Takeaways / Lessons
- Mastering custom_rc4_decrypt, docker_amd64_run, key_concatenation_recovery, self_reading_binary, validator_constant_extraction was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-06-15 • CUSTOM • rev12 • Difficulty: Medium