← Back to Writeups
CUSTOMEasyReversing

callmebaby

XESXOR9/10/20262 min read
#reversing#custom#easy#byte_deref_search#decoy_function_filtering#strcmp_check_reversal#xor_constant_recovery#linux

callmebaby

Platform: rev_kids20 | Category: Reversing | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2026-06-15 | Status: Solved Techniques: byte_deref_search, decoy_function_filtering, strcmp_check_reversal, xor_constant_recovery

Summary

Task: 64-bit ELF crackme with 1000 decoy C++ Validate functions chained via calls (function-flood anti-analysis), the 'call me maybe' theme. Solution: programmatically find the only function that dereferences an input byte; it strcmp's input against a 13-byte .rodata constant XOR 0x37, recovering the flag directly.

callmebaby — rev_kids20

Description

Just call me maybe

USAGE: %s <flag to check>

A 64-bit ELF callmebaby.elf (x86-64, dynamically linked, not stripped). It takes the flag as argv[1] and prints Valid or Invalid. The flag is then submitted to the platform via a POST flag parameter.

Goal: recover the flag accepted by the checker.

Analysis

The binary is a classic function-flood / decoy-chain anti-analysis crackme:

  • It defines 1000 functions named _Z18Validate<10 hexchars>Pc, i.e. the C++ mangled form of ValidateXXXXXXXXXX(char*).
  • main calls exactly one of them (Validate6c4b677a46) passing argv[1].
  • Each Validate function is noise: it makes several junk rand() calls, then calls the next Validate function and returns its boolean result. This builds a 1000-deep call chain — the "call me maybe" theme — where 999 functions do nothing meaningful. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Tracing the entire chain by hand is the trap. The shortcut: only one function actually touches the input. Every decoy ignores argv[1] entirely. So scan the full disassembly for the single function that dereferences an input byte and compares it — i.e. the one containing a movzx of [rax] together with a cmp/strcmp.

bash
objdump -d -M intel callmebaby.elf > callmebaby.asm
# find the only function with a byte deref + comparison of the input
grep -nE 'movzx|strcmp|cmp.*\[rax\]' callmebaby.asm

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

That points to Validate553575746b @ 0x416802 — the real checker.

The real checker (Validate553575746b @ 0x416802)

  1. Loads a 13-byte constant from .rodata at vaddr 0x427f44.
  2. Loops i = 0 .. 0xc (13 iterations), building a local buffer: local[i] = const[i] XOR 0x37.
  3. strcmp(input, local_buffer) → Valid (.rodata 0x427f53) if equal, else Invalid (0x427f59).

The 13 constant bytes at 0x427f44:

06 00 68 06 02 68 04 03 02 7e 7e 7e 4e

Solution

The flag is simply each constant byte XOR 0x37. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

python
#!/usr/bin/env python3
t = [0x06, 0x00, 0x68, 0x06, 0x02, 0x68, 0x04, 0x03, 0x02, 0x7e, 0x7e, 0x7e, 0x4e]
print(''.join(chr(b ^ 0x37) for b in t))  # REDACTED

Verified by running the binary directly:

bash
docker run --rm --platform linux/amd64 -v "$PWD:/w" -w /w ubuntu:22.04 \
    ./callmebaby.elf "REDACTED"
# => Valid

Both the bare value and the flag{...}-wrapped form were accepted by the server. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍