CIA
Initializing Cyber Invasion
Home Shinigami Soul Society Archives Contact Enlist →
Back to Writeups
Writeup

TJCTF — rev/rotated

Reverse engineering writeup for TJCTF rotated: reversing a per-byte 0x1d shift to recover a hidden ELF, extracting and deobfuscating a shell script payload, and base64-decoding the embedded flag.

CTFReverse EngineeringTJCTFELFWriteup

Category: Reverse Engineering  |  Flag: tjctf{b45h_d3bu6_m4573r}

Executive Summary

rotated is a reverse engineering challenge where the provided file is intentionally transformed so it does not initially resemble a valid executable. The title and hint indicate that the transformation should be reversed at the byte level. Testing byte-wise rotations/shifts reveals that subtracting 0x1d from every byte restores a valid ELF binary. Running or inspecting the recovered executable exposes an obfuscated shell script, which contains a base64-encoded gzip payload. Decompressing that payload reveals another base64 string containing the flag.


Step 1 — Challenge Context

The hints were the central guide for the solve path:

HintInterpretation
look at the titleThe challenge name rotated suggests a rotation or shift operation
consider each byte separatelyThe transformation likely applies independently to every byte, not to text strings or file blocks

Step 2 — Initial Triage

The original file did not identify as a standard executable. A hex dump of the first bytes showed no familiar magic value.

file chall
xxd -g 1 -l 16 chall
ObservationValue
Original leading bytes9c 62 69 63 1f 1e 1e 1d 1d 1d 1d 1d 1d 1d 1d 1d
Expected ELF magic7f 45 4c 46
Difference per first four bytes0x1d

The difference between the observed bytes and the ELF magic bytes is consistent: each byte appears to have been increased by 0x1d modulo 256. The inverse operation is to subtract 0x1d from every byte.


Step 3 — Recovering the Hidden ELF

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

data = Path("chall").read_bytes()
decoded = bytes((b - 0x1d) & 0xff for b in data)
Path("chall_unrot").write_bytes(decoded)

After decoding, the first bytes become a valid ELF header:

IndexEncoded ByteSubtract 0x1dDecoded Meaning
00x9c0x7fELF magic byte 1
10x620x45ASCII E
20x690x4cASCII L
30x630x46ASCII F
file chall_unrot
readelf -h chall_unrot
Recovered PropertyValue
FormatELF 64-bit LSB PIE executable
Architecturex86-64
LinkingStatically linked
Entry Point0x58c8

Step 4 — Binary and Payload Analysis

Executing the recovered binary inside a disposable workspace drops a file named script.sh.

chmod +x chall_unrot
./chall_unrot
ls -la
cat script.sh

The script is intentionally noisy and uses shell expansion tricks to obscure a simple pipeline. The important portion is a base64-encoded gzip blob:

#!/bin/bash
${*,} ${@//nj2p#@$\!/^Bis\X} e\val "$(
  ...
  'p'r\i"n"tf
  'H4sIAEDAzmkC/0tNzshXUPLJz8/OzEtXSMsvUkhUSMtJTLdXUlBWSHEvyEpxjzKPzAo0THSzzPY18jL
  0y7Es8XMJNfY19rJ0Tre1BQCG
  qZA9QQAAAA=='
  ... b""'a'''s"e"6"${@^^}"4 -d ...
  ... \gu...n$'\172''i'p -c ...
)"

Cleaning up the obfuscation yields the underlying operation:

printf '<base64 gzip payload>' | base64 -d | gzip -c

Step 5 — Final Flag Extraction

The embedded gzip payload decompresses to a shell command containing another base64 string in a comment:

echo "Looking for a flag?" # dGpjdGZ7YjQ1aF9kM2J1Nl9tNDU3M3J9Cg==

The final base64 value decodes directly to the flag:

echo 'dGpjdGZ7YjQ1aF9kM2J1Nl9tNDU3M3J9Cg==' | base64 -d

Flag

tjctf{b45h_d3bu6_m4573r}

Complete Solve Script

#!/usr/bin/env python3
import base64
import gzip
import os
import re
import subprocess
from pathlib import Path

INPUT     = Path("chall")
RECOVERED = Path("chall_unrot")

# 1. Reverse the per-byte shift.
data = INPUT.read_bytes()
RECOVERED.write_bytes(bytes((b - 0x1d) & 0xff for b in data))
os.chmod(RECOVERED, 0o755)

# 2. Execute the recovered binary to produce script.sh.
subprocess.run([f"./{RECOVERED.name}"], check=True)

# 3. Extract the base64 gzip blob from the generated script.
script   = Path("script.sh").read_text(errors="replace")
gzip_b64 = re.search(r"H4sI[A-Za-z0-9+/=]+", script).group(0)

payload = gzip.decompress(base64.b64decode(gzip_b64)).decode()
print(payload)

# 4. Decode the final base64 string from the payload comment.
flag_b64 = re.search(r"#\s*([A-Za-z0-9+/=]+)", payload).group(1)
flag     = base64.b64decode(flag_b64).decode().strip()
print(flag)

Key Takeaways

LessonWhy It Mattered
Use file signatures as anchorsELF magic 7f 45 4c 46 is an excellent target when reversing unknown byte transformations
Take hints literally”consider each byte separately” strongly suggested a byte-wise operation instead of a block cipher
Reduce obfuscation to data flowThe shell script looked complex, but its useful behavior was only base64 decode → gzip decompress
Keep execution controlledRecovered binaries should be run in disposable CTF workspaces to avoid unintended side effects