structurize
structurize
Platform: rev_kids20 | Category: Reversing | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2026-06-15 | Status: Solved Techniques: in_place_data_mutation_tracking, md5_flag_computation, static_analysis, struct_of_pointers_recovery
Summary
Task: 64-bit ELF crackme reads a name and password into a malloc'd struct of two char* and validates both with strcmp. Solution: name must equal .rodata SeCrEtUsEr; password must equal the .data global sOmEeL33TpAss AFTER an in-place toupper loop turns it into SOMEEL33TPASS; the flag is md5(SOMEEL33TPASS).
structurize — rev_kids20
Description
RE4_structurize / "structurize" Pass all checks, and app will tell you how to get the flag.
We are given a 64-bit ELF structurize.elf (x86-64, non-PIE base 0x400000, not stripped). The program prompts:
Enter your name:
Enter your password:
On passing both checks it prints md5(password) is a flag. The flag is the MD5 hex digest of the password that satisfies the checks, submitted via POST flag to rev-kids20.forkbomb.ru.
Analysis
The program is small and built from three relevant functions.
read_user()
Allocates a struct of two char* fields and fills them with fgets:
field0= name (fgets, up to0x80bytes), trailing newline stripped.field1= password (fgets, up to0x80bytes), trailing newline stripped.
So the struct is simply two heap pointers built by malloc — recognizing this struct-of-pointers layout is the first reading step.
main() — the in-place mutation (the crux)
Before calling check, main runs a loop that calls toupper() over every character of the global string at 0x601070, writing the result back in place. The .data global originally contains:
0x601070: "sOmEeL33TpAss\0"
After the loop it becomes:
0x601070: "SOMEEL33TPASS"
This is the trap: the value check later compares against is the uppercased version, not the raw .data bytes.
check(struct*)
c read-only • copy allowedif (strcmp(name, "SeCrEtUsEr") != 0) { puts("Nope!"); return; } // 0x40099c if (strcmp(password, global@0x601070) != 0) { puts("Nope!"); return; } puts("md5(password) is a flag"); // 0x4009ad
namemust equal the.rodataconstant at0x40099c=SeCrEtUsEr.passwordmust equal the now-uppercased global =SOMEEL33TPASS.
Relevant constants:
| Address | Section | Value |
|---|---|---|
0x40099c | .rodata | SeCrEtUsEr |
0x4009a7 | .rodata | Nope! |
0x4009ad | .rodata | md5(password) is a flag |
0x601070 | .data | sOmEeL33TpAss (→ SOMEEL33TPASS after toupper) |
Solution
- Name =
SeCrEtUsEr(exact.rodataconstant). - Password =
SOMEEL33TPASS— the.dataglobal after the in-placetouppertransform. Using the raw mixed-casesOmEeL33TpAssfails thestrcmp. - Flag = MD5 of the password you actually type, i.e.
md5("SOMEEL33TPASS").
python read-only • copy allowed#!/usr/bin/env python3 import hashlib name = "SeCrEtUsEr" password = "SOMEEL33TPASS" # .data global AFTER in-place toupper print("name :", name) print("password:", password) print("flag :", hashlib.md5(password.encode()).hexdigest()) # -> REDACTED
Verification by running the binary (the success branch prints the hint string):
bash read-only • copy allowedprintf 'SeCrEtUsEr\nSOMEEL33TPASS\n' | \ docker run --rm -i --platform linux/amd64 -v "$PWD:/w" -w /w ubuntu:22.04 \ ./structurize.elf # ... -> "md5(password) is a flag"
Trap note: md5("sOmEeL33TpAss") = df89e7cd604cd74621ae1d219a4e2681 was not accepted. Only the post-toupper password SOMEEL33TPASS gives the correct flag.