rotated
tjctf
Task: a 6480-byte file identified as 'data' — each byte was shifted by +0x1d (ROT-29), hiding a UPX-packed ELF with an obfuscated bash script containing a multi-layer encoded flag. Solution: byte frequency analysis to find the rotation offset, UPX unpacking, bash deobfuscation, then base64+gzip+base64 decoding to recover the flag.
$ ls tags/ techniques/
rotated — TJCTF 2026
Description
this file isn't making any sense to me. can you discover what it means?
Hint 1: "look at the title" Hint 2: "consider each byte separately"
English summary: a single binary file chall (6480 bytes) is provided. The file command identifies it only as "data" — not a recognized format. The goal is to reverse the obfuscation and find the flag.
Analysis
Initial recon
$ file chall chall: data $ wc -c chall 6480 chall
The file is not recognized as any known format. A hex dump of the first bytes shows no recognizable magic number.
Byte frequency analysis
The most common byte in the file is 0x1d, appearing 644 times out of 6480 bytes. In a typical ELF binary, the most common byte is 0x00 (null). This suggests each byte was shifted by adding 0x1d (decimal 29) modulo 256.
Verification against the expected ELF magic header \x7fELF:
| File byte | - 0x1d | Expected |
|---|---|---|
0x9c | 0x7f | \x7f ✓ |
0x62 | 0x45 | E ✓ |
0x69 | 0x4c | L ✓ |
0x63 | 0x46 | F ✓ |
The "rotation" in the title refers to a Caesar/ROT cipher applied to raw bytes: each byte had 0x1d (29) added to it modulo 256.
Decoded binary analysis
After subtracting 0x1d from every byte, the result is a valid ELF binary — but UPX-packed:
$ file chall_decoded
chall_decoded: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), statically linked, no section header
$ strings chall_decoded | grep UPX
$Info: This file is packed with the UPX executable packer http://upx.sf.net $
$Id: UPX 4.24 Copyright (C) 1996-2024 the UPX Team. All Rights Reserved. $
Embedded bash script
After UPX unpacking (6480 → 16152 bytes), strings reveals an embedded obfuscated bash script using heavy bash parameter expansion tricks:
#!/bin/bash ${*,} ${@//nj2p#@$\!/^Bis\X} e\val "$( ${*#+*=\`gr4#} ${*~~} 'p'r\i"n"tf 'H4sIAEDAzmkC/...' ${!*} | ${*%b:d\)} b""'a'''s"e"6"${@^^}"4 -d ${*} | ${*//\`FsXY^F} ${*#0ms7JMci} \gu${*//.Km\`1B/vmdvatBX}n$'\172''i'p -c ${*,,} )" ${*%TB.h}
All ${*,}, ${@//...}, ${*~~}, ${*#+*=...} etc. are bash parameter expansions that resolve to empty strings when there are no positional arguments. The obfuscated command names decode as:
e\val→eval'p'r\i"n"tf→printfb""'a'''s"e"6"${@^^}"4→base64\gu...n$'\172''i'p→gunzip(where$'\172'=z)
Clean command: eval "$(printf '<base64_string>' | base64 -d | gunzip -c)"
Solution
Step 1: Reverse the byte rotation (ROT-29)
#!/usr/bin/env python3 data = open('chall', 'rb').read() decoded = bytes([(b - 0x1d) & 0xFF for b in data]) open('chall_decoded', 'wb').write(decoded)
Step 2: UPX unpack
upx -d chall_decoded -o chall_unpacked # 6480 -> 16152 bytes (40.12% ratio)
Step 3: Extract and decode the bash payload
echo 'H4sIAEDAzmkC/0tNzshXUPLJz8/OzEtXSMsvUkhUSMtJTLdXUlBWSHEvyEpxjzKPzAo0THSzzPY18jL0y7Es8XMJNfY19rJ0Tre1BQCGqZA9QQAAAA==' | base64 -d | gunzip -c
Output:
echo "Looking for a flag?" # dGpjdGZ7YjQ1aF9kM2J1Nl9tNDU3M3J9Cg==
Step 4: Decode the final base64 from the comment
echo 'dGpjdGZ7YjQ1aF9kM2J1Nl9tNDU3M3J9Cg==' | base64 -d
Output: tjctf{b45h_d3bu6_m4573r}
Full solve script
#!/usr/bin/env python3 """TJCTF 2026 - rotated: ROT-29 byte shift → UPX → bash obfuscation → base64+gzip → base64 flag""" import base64 import gzip # Step 1: Reverse byte rotation data = open('chall', 'rb').read() decoded = bytes([(b - 0x1d) & 0xFF for b in data]) # Step 2: Extract base64 payload from strings (found in unpacked binary) b64_payload = 'H4sIAEDAzmkC/0tNzshXUPLJz8/OzEtXSMsvUkhUSMtJTLdXUlBWSHEvyEpxjzKPzAo0THSzzPY18jL0y7Es8XMJNfY19rJ0Tre1BQCGqZA9QQAAAA==' # Step 3: Decode base64 → gunzip bash_script = gzip.decompress(base64.b64decode(b64_payload)).decode() print(f"Bash script: {bash_script}") # Step 4: Extract and decode the hidden base64 from the comment hidden_b64 = bash_script.split('# ')[1].strip() flag = base64.b64decode(hidden_b64).decode().strip() print(f"Flag: {flag}")
$ cat /etc/motd
Liked this one?
Pro unlocks every writeup, every flag, and API access. $9/mo.
$ cat pricing.md$ grep --similar
Similar writeups
- [reverse][free]remoose— tjctf
- [reverse][Pro]Happy New Year— grodno_new_year_2026
- [reverse][Pro]Boss— duckerz
- [forensics][free]Obscure Crusher 1— tjctf
- [misc][Pro]Нулевой байт (Null Byte)— hackerlab