Warmup (Task 49)
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 read-only • copy allowed$ 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 read-only • copy allowed$ 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 read-only • copy allowed$ objdump -d warmup > warmup_disasm.txt
Analyzing the transform_string function:
asm read-only • copy allowed0000000000401116 <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 read-only • copy allowedrol 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 read-only • copy allowed$ 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
- Algorithm:
ROL(b, 5)— cyclic left shift by 5 bits - 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 read-only • copy allowed#!/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
| Operation | Description | Inverse Operation |
|---|---|---|
| ROL n | Left shift by n | ROR n |
| ROR n | Right shift by n | ROL n |
| |
In our case ROL b, 5 is equivalent to ROR b, 3 (because 8 - 5 = 3).
Correctness Verification
python read-only • copy allowed# 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:
- ROL/ROR instruction found in disassembler — clear sign of bit-level encryption
- Transform/encrypt function with bitwise operations detected —
<<,>>,|,& - Data of characteristic length in .rodata (~30 bytes for flag in
flag{...}format) - Simple ELF file without protections — static analysis is possible
- Small number of unique bitwise operations — simple algorithm
Tools
| Tool | Purpose |
|---|---|
strings | Search for readable strings in binary |
objdump | Disassembly and section analysis |
xxd | Hexadecimal dump |
python3 | Decryption and automation |
| |
Additional Materials
Binary Inspection
bash read-only • copy allowed# 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 read-only • copy allowed#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 read-only • copy alloweddef 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:
- Static analysis is powerful enough — disassembler shows all the code
- Bit rotation — common obfuscation technique, easily reversible
- Data in .rodata — typical location for storing encrypted constants
- Inverse operation — key to solution: ROL ↔ ROR
Difficulty: Easy — the task is ideal for warming up and learning basic reverse engineering techniques.