← Back to Writeups
CUSTOMEasyReversing

rotated

XESXOR9/10/20264 min read
#reversing#custom#easy#base64_gzip_decoding#bash_parameter_expansion_deobf#byte_frequency_analysis#caesar_byte_rotation#upx_unpacking

rotated

Platform: CUSTOM | Category: Reversing | Type: Challenge | Difficulty: Easy | OS: N/A | **Author: XESXOR Techniques: base64_gzip_decoding, bash_parameter_expansion_deobfuscation, byte_frequency_analysis, caesar_byte_rotation, upx_unpacking

Summary

Walkthrough of the CUSTOM Reversing rotated. 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 freq

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

bash
$ 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:

bash
#!/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 → printf
  • b""'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)

python
#!/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

bash
upx -d chall_decoded -o chall_unpacked
# 6480 -> 16152 bytes (40.12% ratio)

Step 3: Extract and decode the bash payload

bash
echo 'H4sIAEDAzmkC/0tNzshXUPLJz8/OzEtXSMsvUkhUSMtJTLdXUlBWSHEvyEpxjzKPzAo0THSzzPY18jL0y7Es8XMJNfY19rJ0Tre1BQCGqZA9QQAAAA==' | base64 -d | gunzip -c

Output:

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

Step 4: Decode the final base64 from the comment

bash
echo 'dGpjdGZ7YjQ1aF9kM2J1Nl9tNDU3M3J9Cg==' | base64 -d

Output: tjctf{REDACTED}

Full solve script

python
#!/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}")

Key Takeaways / Lessons

  • Mastering base64_gzip_decoding, bash_parameter_expansion_deobfuscation, byte_frequency_analysis, caesar_byte_rotation, upx_unpacking was key to this challenge.
  • Always check for common misconfigurations and verify assumptions.

Crafted by XESXOR Research • 2026-05-16 • CUSTOM • rotated • Difficulty: Easy