← Back to Writeups
CUSTOMEasyReversing

Warmup (Task 49)

XESXOR9/2/20266 min read
#reversing#custom#easy#bitwise_rotation#reverse_engineering#rodata_extraction#static_binary_analysis#linux

Warmup (Task 49)

Platform: duckerz | Category: Reversing | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2026-01-09 | Status: Solved Techniques: bitwise_rotation, reverse_engineering, rodata_extraction, static_binary_analysis

Summary

Task: 64-bit ELF binary with encrypted flag. Solution: Identified ROL 5 bit rotation in transform_string function, extracted encrypted data from .rodata section, applied inverse ROR 5 operation to decrypt the flag.

Warmup — duckerz CTF

Description

Name: Warmup (Task 49)
Category: Reverse Engineering
Difficulty: Easy
Points: 100
Flag format: DUCKERZ{...}
File: warmup (ELF 64-bit, x86-64)

"Разминаемся и идем дальше"

The task is a simple ELF binary that needs to be reversed to obtain the flag.


Binary Analysis

Initial Analysis

bash
$ file warmup
warmup: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped

$ ls -la warmup
-rwxr-xr-x 1 user user 16896 Jan  9 02:00 warmup

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Binary characteristics:

  • 64-bit ELF
  • Statically linked
  • Not stripped (symbols preserved)

String Search

Using strings to find interesting strings:

bash
$ strings warmup | head -50
...
WarmUp.c
encrypted
transform_string
...

Found:

  • Source file: WarmUp.c
  • Key strings: encrypted, transform_string

Static Analysis

Disassembly

Disassembling the binary to analyze the transform_string function:

bash
$ objdump -d warmup > warmup_disasm.txt

Analyzing the transform_string function: ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

asm
0000000000401116 <transform_string>:
  401116:       55                      push   rbp
  401117:       48 89 e5                mov    rbp,rsp
  401118:       48 89 7d e8             mov    QWORD PTR [rbp-0x18],rdi
  40111b:       c7 45 fc 00 00 00 00    mov    DWORD PTR [rbp-0x4],0x0
  401122:       eb 18                   jmp    40113c <transform_string+0x26>
  401124:       8b 45 fc                mov    eax,DWORD PTR [rbp-0x4]
  401127:       48 98                   cdqe
  401129:       48 8b 55 e8             mov    rdx,QWORD PTR [rbp-0x18]
  40112d:       0f b6 0c 02             movzx  ecx,BYTE PTR [rdx+rax*1]
  401131:       8b 45 fc                mov    eax,DWORD PTR [rbp-0x4]
  401134:       c1 c1 05                rol    cl,0x5
  401137:       88 0c 02                mov    BYTE PTR [rdx+rax*1],cl
  40113a:       83 45 fc 01             add    DWORD PTR [rbp-0x4],0x1
  40113e:       83 7d fc 1b             cmp    DWORD PTR [rbp-0x4],0x1b
  401142:       7e e0                   jle    401124 <transform_string+0xe>
  401144:       90                      nop
  401145:       5d                      pop    rbp
  401146:       c3                      ret

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Key Observation

At line 401134 we see the instruction:

asm
rol cl, 0x5

ROL (Rotate Left) — cyclic left shift by 5 positions for byte cl.

Since 0x5 = 5 and a byte has 8 bits, this is equivalent to a left shift by 3 bits (because 8 - 5 = 3).

Mathematical Explanation

Cyclic left shift by 5 bits for a byte:

ROL(b, 5) = (b << 5) | (b >> 3)  (truncated to 8 bits)

This can be expressed as a left shift by 3 bits with cyclic carry of high bits to low positions.


Extracting Encrypted Data

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Finding Encrypted Data in .rodata

bash
$ objdump -s -j .rodata warmup

Contents of section .rodata:
  2010 22aa1a5a 2a92d2db 990aa9cb fa818399 930ab94b 8172faa9 8163b399 23eb

Extracted bytes (at address 0x2010):

22 aa 1a 5a 2a 92 d2 db 99 0a a9 cb fa 81 83 99
93 0a b9 4b 81 72 fa a9 81 63 b3 99 23 eb

Length: 28 bytes (which matches the DUCKERZ{...} format).


Solution

Understanding the Encryption Algorithm

  1. Algorithm: ROL(b, 5) — cyclic left shift by 5 bits
  2. To decrypt, apply the inverse operation: ROR(b, 5) — cyclic right shift by 5 bits ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Or, considering that ROL(b, 5) = ROL(b, -3):

  • Encrypted: left shift by 3 bits
  • Decryption: right shift by 3 bits

Decryption Script

python
#!/usr/bin/env python3
"""
DUCKERZ CTF - Warmup (Task 49)
Reverse Engineering Challenge

Algorithm: cyclic left shift by 5 bits (ROL b, 5)
For decryption: cyclic right shift by 5 bits (ROR b, 5)
"""

def rotate_right(b: int, n: int) -> int:
    """
    Cyclic right shift of a byte by n bits.
    
    Args:
        b: Byte (0-255)
        n: Number of positions to shift right
    
    Returns:
        Result of cyclic right shift
    """
    return ((b >> n) | ((b & ((1 << n) - 1)) << (8 - n))) & 0xFF
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍


def decrypt_flag(encrypted_bytes: bytes) -> str:
    """
    Decrypts the flag by applying ROR 5 to each byte.
    
    Args:
        encrypted_bytes: Encrypted flag bytes
    
    Returns:
        Decrypted flag string
    """
    decrypted = bytes(rotate_right(b, 5) for b in encrypted_bytes)
    return decrypted.decode('ascii')


def main():
    # Encrypted data from .rodata section (address 0x2010)
    encrypted_flag = bytes([
        0x22, 0xaa, 0x1a, 0x5a, 0x2a, 0x92, 0xd2, 0xdb,
        0x99, 0x0a, 0xa9, 0xcb, 0xfa, 0x81, 0x83, 0x99,
        0x93, 0x0a, 0xb9, 0x4b, 0x81, 0x72, 0xfa, 0xa9,
        0x81, 0x63, 0xb3, 0x99, 0x23, 0xeb
    ])
    
    # Decrypt the flag
    flag = decrypt_flag(encrypted_flag)
    
    print(f"Encrypted bytes: {encrypted_flag.hex()}")
    print(f"Decrypted flag: {flag}")
    
    # Verify format
    assert flag.startswith("DUCKERZ{"), "Invalid flag format!"
    assert flag.endswith("}"), "Flag must end with }!"
    
    print("\n✅ Flag successfully obtained!")
    return flag
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍


if __name__ == "__main__":
    main()

Execution Result

$ python3 solve_warmup.py
Encrypted bytes: 22aa1a5a2a92d2db990aa9cbfa818399930ab94b8172faa98163b39923eb
Decrypted flag: DUCKERZ{REDACTED}

✅ Flag successfully obtained!

Technique Breakdown

What is ROL (Rotate Left)?

ROL (cyclic left shift) moves each bit to the left, and high bits wrap around to low positions.

Before: 10110100 (0xB4)
ROL 2:  11010010 (0xD2) - high 2 bits wrap to bottom

Inverse Operation

OperationDescriptionInverse Operation
ROL nLeft shift by nROR n
ROR nRight shift by nROL n
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

In our case ROL b, 5 is equivalent to ROR b, 3 (because 8 - 5 = 3).

Correctness Verification

python
# Verification: encrypt -> decrypt should return original byte
original = ord('A')  # 0x41 = 01000001

# Simulate encryption (ROL 5)
encrypted = ((original << 5) | (original >> 3)) & 0xFF

# Decryption (ROR 5)
decrypted = ((encrypted >> 5) | ((encrypted & 0x1F) << 3)) & 0xFF

assert decrypted == original  # OK!

Key Indicators for This Technique

This technique (bit rotation attack) should be applied when: ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

  1. ROL/ROR instruction found in disassembler — clear sign of bit-level encryption
  2. Transform/encrypt function with bitwise operations detected — <<, >>, |, &
  3. Data of characteristic length in .rodata (~30 bytes for flag in flag{...} format)
  4. Simple ELF file without protections — static analysis is possible
  5. Small number of unique bitwise operations — simple algorithm

Tools

ToolPurpose
stringsSearch for readable strings in binary
objdumpDisassembly and section analysis
xxdHexadecimal dump
python3Decryption and automation
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Additional Materials

Binary Inspection

bash
# Binary information
file warmup

# Check sections
readelf -S warmup

# Search strings with context
strings -t x warmup

# Full disassembly
objdump -d warmup > full_disasm.txt

Equivalent Implementations

In C:

c
#include <stdint.h>

uint8_t rotate_right(uint8_t b, int n) {
    return (b >> n) | ((b & ((1 << n) - 1)) << (8 - n));
}

In Python (alternative):

python
def rotate_right(b: int, n: int) -> int:
    n = n % 8  # normalization
    return ((b >> n) | ((b & ((1 << n) - 1)) << (8 - n))) & 0xFF

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍


Conclusions

The Warmup task is an excellent example of simple reverse engineering:

  1. Static analysis is powerful enough — disassembler shows all the code
  2. Bit rotation — common obfuscation technique, easily reversible
  3. Data in .rodata — typical location for storing encrypted constants
  4. Inverse operation — key to solution: ROL ↔ ROR

Difficulty: Easy — the task is ideal for warming up and learning basic reverse engineering techniques. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍