← Back to Writeups
CUSTOMEasyReversing

shmalidator

XESXOR9/10/20262 min read
#reversing#custom#easy#little_endian_dword_reconstruc#orphaned_function_recovery#static_disassembly

shmalidator

Platform: rev_kids20 | Category: Reversing | Type: Challenge | Difficulty: Easy | OS: NA | Author: XESXOR | Date: 2026-06-15 | Status: Solved Techniques: little_endian_dword_reconstruction, orphaned_function_recovery, static_disassembly

Summary

Task: stripped x86-64 ELF key validator whose three call instructions had corrupted rel32 offsets (point at garbage addresses), causing a SEGFAULT. Solution: locate the orphaned validator function bodies still present in .text, read the little-endian dword cmp constants at input+0/+4/+8, and concatenate them into the key REDACTED.

shmalidator — rev_kids20

Description

When I wrote this task, rain dropped into my code and changed my three called functions. Now it's all wet and does not work :( I tried 2^2^2^billion of keys but it only Segfaulted. What is the correct key?

A 64-bit ELF shmalidator.elf (x86-64, dynamically linked, stripped) reads a key via fgets (0x40 bytes), validates it, and prints Valid or Invalid. Running it as intended just SEGFAULTs. The goal is to recover the correct key. Flag is submitted via POST flag on rev-kids20.forkbomb.ru. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Analysis

main is at 0x4006ad. It does:

  1. fgets(buf, 0x40, stdin)
  2. Three sequential call instructions, each passing buf and AND-ing the boolean results together.
  3. If all three return true → prints Valid, otherwise Invalid.

The catch: the three call instructions point at garbage addresses — 0x2fda2584, 0x32526487, 0x11f3f5b3. These are not real functions; the relative call offsets (the rel32 bytes) were corrupted ("rain dropped in"), so the program jumps into the void and segfaults. The challenge narrative literally describes corrupted call instructions, not corrupted logic. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Key insight: the intended validator function bodies are still present in .text — only the call offsets were clobbered. Disassembling the .text region between the CRT stubs and main reveals three small orphaned functions that were the intended targets:

func1 @ 0x40062d:  cmp dword [input+0], 0x59353433   ; LE -> "345Y"
func2 @ 0x400650:  cmp dword [input+4], 0x4b433450   ; LE -> "P4CK"
func3 @ 0x400677:  cmp dword [input+8], 0x30473141   ; LE -> "A1G0"

Each function returns 1 if its 4-byte slice of the input matches its constant, 0 otherwise. Since main ANDs all three, the key must satisfy all three constraints on consecutive offsets +0 / +4 / +8. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Solution

Each cmp compares a little-endian dword. Decode each 32-bit constant back to its ASCII bytes (low byte first) and concatenate in offset order:

  • 0x59353433 → bytes 33 34 35 59 → "345Y"
  • 0x4b433450 → bytes 50 34 43 4b → "P4CK"
  • 0x30473141 → bytes 41 31 47 30 → "A1G0"
python
#!/usr/bin/env python3
import struct
key = b''.join(struct.pack('<I', v) for v in (0x59353433, 0x4b433450, 0x30473141))
print(key.decode())   # REDACTED

The 12-character key REDACTED satisfies all three checks. The server accepted both the bare form and the flag{...} wrapped form. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍