Rotations
We are given an unstripped x86–64 Linux ELF named rotator. Inspecting the symbols and disassembling check_flag exposes the validation flow immediately:
nm -n dist/rotator
objdump -d -M intel --disassemble=check_flag dist/rotatorThe binary defines several descriptive functions:
- xor_stage (0x401186): XORs each byte
- rol8 (0x4011cd): 8-bit left rotation
- rol8_stage (0x401202): applies rol8 to the whole buffer
- transform (0x40125f): calls xor_stage then rol8_stage
- check_flag (0x401298): enforces length and compares with expected
- expected (0x402010): 20-byte target buffer
In check_flag:
4012a8: cmp QWORD PTR [rbp-0x30], 0x14
4012db: call 40125f <transform>
4012e4: mov edx, 0x14
4012e9: lea rcx, [rip+0xd20] # 402010 <expected>
4012f6: call 401070 <memcmp@plt>The flag must be exactly 20 (0x14) bytes. Inside transform, xor_stage XORs each byte with 0x37, and rol8_stage rotates each byte left by 3 bits.
Get sftp2368’s stories in your inbox
Join Medium for free to get updates from this writer.
The forward transformation for each byte p is:
e = ((p ^ 0x37) << 3 | (p ^ 0x37) >> 5) & 0xFFSince both operations are reversible byte-by-byte, we just invert them in reverse order — rotate right by 3 bits, then XOR with 0x37:
p = (((e >> 3) | (e << 5)) & 0xFF) ^ 0x37Dumping the 20 target bytes starting at 0x402010:
a3 2a 72 3a 1a f2 7a 62 2a 20 0a 43 30 22 43 92 18 22 72 52Inverting them in Python:
expected = bytes.fromhex("a3 2a 72 3a 1a f2 7a 62 2a 20 0a 43 30 22 43 92 18 22 72 52")
flag = bytes((((b >> 3) | (b << 5)) & 0xFF) ^ 0x37 for b in expected)
print(flag.decode())Output:
Cryptix{r3v_1s...}Passing the flag to the binary confirmed the solve:
printf '%s\n' 'Cryptix{r3v_1s...}' | ./dist/rotator
Enter the flag: Correct!Rotations


