reversefreeeasy

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/
upx_unpackingbyte_frequency_analysiscaesar_byte_rotationbash_parameter_expansion_deobfuscationbase64_gzip_decoding

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- 0x1dExpected
0x9c0x7f\x7f
0x620x45E
0x690x4cL
0x630x46F

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\valeval
  • 'p'r\i"n"tfprintf
  • b""'a'''s"e"6"${@^^}"4base64
  • \gu...n$'\172''i'pgunzip (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