JustExperiment
JustExperiment
Platform: spbctf | Category: Reversing | Type: Challenge | Difficulty: Easy | OS: Windows | Author: XESXOR | Date: 2026-06-24 | Status: Solved Techniques: auto_vectorization_recognition, instruction_level_emulation, register_liveness_tracing
Summary
Task: 32-bit MSVC crackme that asks for an integer password and prints a flag on success. Solution: emulate the password-validation loop and the flag-computation helper at instruction level, noting the flag is the helper's return value (uninitialized ecx carried from main = password+1337), not the password itself.
JustExperiment — spbctf (forkbomb rev-kids)
Description
Pass the checks, and app will print the flag
A 32-bit PE32 console executable. It reads an integer password via scanf,
validates it against a value derived from a loop, and on success prints a flag.
The goal is to recover the printed flag value.
Analysis
file reports: PE32 executable, Intel 80386, MS Windows, MSVC-built. A leaked
PDB path confirms the toolchain:
C:\Users\dsnos\Desktop\...\CTF\binary05\Tasks\Release\Tasks.pdb
Notable strings:
This is secure app, you need integer password to use it!You are welcome! The flag is: %dGo out of here!
main() — password validation (0x00401110)
scanf("%d", &password) reads the integer. Then a loop computes a constant in
edi, seeded with edi = 0x111b25 (1121061):
edi = 0x111b25
for (esi = 0; esi < 0x63 (99); ) {
eax = esi;
ecx = esi + 7;
eax = eax * edi; // imul
esi++; // inc before idiv
edx = eax % ecx; // signed remainder (cdq; idiv ecx)
edi ^= edx;
}
The loop yields edi = 1121044. The check is:
ecx = password + 0x539 (1337)
cmp edi, ecx
So the required password is 1121044 - 1337 = 1119707.
The trap
A naive reading assumes the flag equals the password (1119707), because the
%d print follows the success branch. This is wrong. The value pushed to
printf is eax = call fcn.00401080, the return of a helper — not the
password. Always verify the operand order before concluding.
fcn.00401080 — flag computation
This is a compiler auto-vectorized loop with two equivalent paths selected by a
runtime guard (cmp dword [0x403364], 2; jl 0x4010fd). Both compute the same
result. Critically, ecx is not initialized inside the function — it
carries over from main, where after cmp edi, ecx we have
ecx = password + 1337 = 1121044. The intervening pop edi; pop esi do not
clobber ecx.
Scalar path (0x4010fd):
eax = 0
ecx = incoming_ecx // = password+1337 = 1121044
do {
ecx ^= eax;
eax += 7;
} while (eax < 0x1ab0); // 6832
return ecx;
The SSE path broadcasts edx, computes (edx + {0,1,2,3}) * 7, XOR-accumulates
into two xmm registers over edx < 0x3d0 (976), then horizontally XOR-folds and
xor eax, ecx. It produces the identical value.
Both paths return REDACTED.
Solution
Emulate both loops at instruction level. The flag is the helper return value.
python read-only • copy allowed#!/usr/bin/env python3 import ctypes def u32(x): return x & 0xffffffff def s32(x): return ctypes.c_int32(x).value # --- password validation loop -> edi --- edi = 0x111b25 esi = 0 while True: eax = s32(esi) ecx = s32(esi + 7) eax = s32(eax * edi) # imul esi = s32(esi + 1) # inc before idiv q = int(eax / ecx) # truncate toward zero (signed idiv) edx = s32(eax - q * ecx) edi ^= edx edi = s32(edi) if not (esi < 0x63): break # edi == 1121044; required password = edi - 1337 = 1119707 incoming_ecx = edi # = password + 1337 at the call to 0x401080 # --- flag helper (scalar path) --- eax = 0 ecx = incoming_ecx while True: ecx = u32(ecx ^ eax) eax = s32(eax + 7) if not (eax < 0x1ab0): # 6832 break print("required password:", edi - 1337) # 1119707 print("flag:", s32(ecx)) # REDACTED
Output:
required password: 1119707
flag: REDACTED
Running the binary with password 1119707 prints You are welcome! The flag is: REDACTED.