← Back to Writeups
CUSTOMMediumReversing

Я работаю в криптовалюте (I work in cryptocurrency)

XESXOR9/2/20264 min read
#reversing#custom#medium#custom-encryption-algorithm-re#xor-+-rol-+-block-swap-+-modif#function-size-to-bitmap-conver#image-transformation-(flip-+-r

Я работаю в криптовалюте (I work in cryptocurrency)

Platform: hackerlab | Category: Reversing | Type: Challenge | Difficulty: Medium | OS: NA | Author: XESXOR | Date: 2025-12-25 | Status: Solved Techniques: Custom encryption algorithm reverse engineering, XOR + ROL + block swap + modifier decryption, Function size to bitmap conversion, Image transformation (flip + rotate)

Summary

Two files provided: - crypter — encryption binary - FLAG.crypt — encrypted file

Я работаю в криптовалюте — HackerLab

Description

Two files provided:

  • crypter — encryption binary
  • FLAG.crypt — encrypted file

Analysis

Step 1: Reverse Engineering the Encryptor

Analysis of the crypter binary revealed:

  1. Encryption key found in strings:
Th1s_1s_N0t_Th3_K3y_U_R_L00k1ng_F0r
  1. Encryption algorithm (4 stages):
    • XOR each byte with SECRET_KEY[i % key_len]
    • ROL (rotate left) each byte by (SECRET_KEY[i % key_len] % 8) bits
    • Swap 16-byte blocks in each 32-byte chunk
    • Add modifier (file_size & 0xFF) to each byte ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Step 2: Decryption

To decrypt, perform the operations in reverse order:

python
#!/usr/bin/env python3
"""
Decryption script for the crypter binary.

Encryption algorithm (from reverse engineering):
1. XOR each byte with SECRET_KEY[i % key_len]
2. ROL (rotate left) each byte by (SECRET_KEY[i % key_len] % 8) bits
3. Swap 16-byte blocks (first 16 with second 16) for each 32-byte chunk
4. Add modifier byte (file_size & 0xFF) to each byte

Decryption is the reverse:
1. Subtract modifier byte from each byte
2. Swap 16-byte blocks back
3. ROR (rotate right) each byte
4. XOR with SECRET_KEY
"""
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

SECRET_KEY = b"Th1s_1s_N0t_Th3_K3y_U_R_L00k1ng_F0r"

def ror(byte, n):
    """Rotate right by n bits (8-bit)"""
    n = n & 7
    return ((byte >> n) | (byte << (8 - n))) & 0xFF

def proprietary_block_swap(data, offset):
    """Swap first 16 bytes with second 16 bytes in a 32-byte block"""
    if offset + 32 > len(data):
        return
    temp = data[offset:offset+16]
    data[offset:offset+16] = data[offset+16:offset+32]
    data[offset+16:offset+32] = temp

def decrypt(encrypted_data):
    """Decrypt data encrypted by the crypter binary"""
    data = bytearray(encrypted_data)
    file_size = len(data)
    key_len = len(SECRET_KEY)
    
    # The modifier is file_size & 0xFF (low byte of file size)
    modifier = file_size & 0xFF
    
    # Step 4 reverse: Subtract modifier from each byte
    for i in range(len(data)):
        data[i] = (data[i] - modifier) & 0xFF
    
    # Step 3 reverse: Swap 16-byte blocks back (same operation, it's symmetric)
    num_blocks = file_size >> 5  # file_size / 32
    for i in range(num_blocks):
        proprietary_block_swap(data, i * 32)
    
    # Step 2 reverse: ROR each byte (reverse of ROL)
    for i in range(len(data)):
        key_byte = SECRET_KEY[i % key_len]
        shift = key_byte % 8
        data[i] = ror(data[i], shift)
    
    # Step 1 reverse: XOR with SECRET_KEY
    for i in range(len(data)):
        data[i] ^= SECRET_KEY[i % key_len]
    
    return bytes(data)
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

with open("FLAG.crypt", "rb") as f:
    encrypted = f.read()

decrypted = decrypt(encrypted)

with open("FLAG.decrypted", "wb") as f:
    f.write(decrypted)

The decrypted file turned out to be a 32-bit ELF executable.

Step 3: Analyzing the Decrypted Binary

The decrypted ELF contains 7300 functions with names like e_X_Y:

  • X from 0 to 99 (100 values)
  • Y from 0 to 72 (73 values)
  • Forms a 100×73 matrix

Each function has a specific size. Key observation: function sizes encode a bitmap image. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Step 4: Visualizing the Flag

Extracting function sizes and building the image:

python
#!/usr/bin/env python3
from PIL import Image
import subprocess
import re

# Extract function sizes using nm
result = subprocess.run(['nm', '-S', 'FLAG.decrypted'], capture_output=True, text=True)

# Parse function sizes
functions = {}
for line in result.stdout.split('\n'):
    match = re.search(r'([0-9a-f]+)\s+([0-9a-f]+)\s+\w+\s+e_(\d+)_(\d+)', line)
    if match:
        size = int(match.group(2), 16)
        x = int(match.group(3))
        y = int(match.group(4))
        functions[(x, y)] = size
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# Create image
width, height = 100, 73
threshold = 70

img = Image.new('RGB', (width, height), 'white')
pixels = img.load()

for (x, y), size in functions.items():
    if size <= threshold:
        pixels[x, y] = (0, 0, 0)  # Black
    else:
        pixels[x, y] = (255, 255, 255)  # White

# Apply transformations
img = img.transpose(Image.FLIP_LEFT_RIGHT)  # Horizontal flip
img = img.rotate(90, expand=True)  # 90 degrees counter-clockwise

img.save('flag_image.png')

Transformations for readability:

  1. Horizontal flip (mirror)
  2. 90° counter-clockwise rotation ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

After transformations, the flag becomes readable.

Solution

CODEBY{REDACTED}

Failed Attempts

Reading the flag from the image was difficult due to leetspeak. Tried:

  1. CODEBY{yoREDACTEDand_found_me_easily} — incorrect
  2. CODEBY{REDACTEDand_found_me_easily} — incorrect
  3. CODEBY{0bfusc4t10n_1s_n0t_pr0t3ct10n} — incorrect (attempt to guess by meaning)
  4. CODEBY{yoREDACTEDn00000L9y} — incorrect (misread the ending)
  5. Various leetspeak substitution variants (0/o, 3/e, 1/i, 4/a)

Key mistake: initially only did horizontal flip without 90° rotation. After counter-clockwise rotation, the text became readable. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Difficult characters:

  • Y0u — capital Y, then zero, then lowercase u
  • decrYpTED — mixed case
  • @nD — @ symbol instead of a
  • f0unD — zero instead of o
  • h00000ray — five zeros (not letter O)

Task Features

  1. Multi-stage: first cryptography, then steganography
  2. Visual steganography: data encoded in function sizes
  3. Image transformations: flip + rotate required for readability
  4. Threshold selection: correct threshold (70) is critical for visualization
  5. Leetspeak: flag uses mixed case and character substitutions ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍