Credentials
Credentials
Platform: spbctf | Category: Reversing | Type: Challenge | Difficulty: Easy | OS: Windows | Author: XESXOR | Date: 2026-06-24 | Status: Solved Techniques: custom_string_hash_reversal, format_string_identification, multiplier_overflow_analysis, static_disassembly
Summary
Task: 32-bit MSVC console crackme that hashes the username and compares it to an integer password. Solution: reverse the custom hash function, notice the per-char multiplier overflows after 4 bytes so only the first 4 chars of 'admin' matter, and compute the hash 0x697c7f44 = REDACTED.
Credentials — spbctf
Description
Flag is the password for admin user
A small 32-bit Windows console crackme (Credentials.exe). It asks for a
username and a numeric password, runs a custom hash over the username, and
grants access only if the hash equals the entered number. The goal is to find
the password that authorizes the admin user — and that password value is
the flag.
Recon
$ file Credentials.exe
Credentials.exe: PE32 executable (console) Intel 80386, for MS Windows
A ~9.7 KB MSVC-built PE32 (Intel 80386, 32-bit). Interesting strings:
$ strings Credentials.exe
This is secure app, you need to be authorized
username:
password:
You are welcome! Now you can use this app.
Go out of here!
%s
%d
C:\Users\dsnos\Desktop\CTF\binary05\Tasks\Release\Tasks.pdb
The PDB path (...\binary05\Tasks\...\Tasks.pdb) confirms a small hand-written
crackme. The %s / %d format strings tell us the username is read as a
string and the password as a signed decimal integer.
Disassembly — main (0x401100)
The control flow of main:
-
Print the banner
"This is secure app, you need to be authorized"and the prompt"username: ", thenscanf("%s", &username)into a stack buffer at[ebp-0x104]. -
Print
"password: ", thenscanf("%d", &password)into[ebp-0x108](a signed 32-bit int). -
Call the custom hash routine at
0x401080with the username pointer (passed in a register, fastcall-style). -
Compare the returned hash in
eaxagainst the entered password: asm read-only • copy allowed0x401157: cmp eax, dword ptr [ebp-0x108] -
On equality push
0x402158→"You are welcome! Now you can use this app."; otherwise push0x402184→"Go out of here!".
So we need an integer password equal to hash(username).
Disassembly — the hash function (0x401080)
Reconstructed logic (input = username string, base pointer in ebx):
asm read-only • copy allowed; edi = accumulator, esi = multiplier mov edi, 0x111b25 ; accumulator seed mov esi, 1 ; multiplier = 1 ; reject usernames longer than 6 chars ... strlen(username) -> edx cmp edx, 0x6 jbe ... ; if len > 6 -> return 0 ; per-character loop loop: movsx eax, byte [edx+ebx] ; sign-extended char c imul eax, esi ; eax = c * multiplier shl esi, 8 ; multiplier <<= 8 (one byte higher each step) xor edi, eax ; accumulator ^= c * multiplier ... ret ; return edi
In C this is roughly:
c read-only • copy allowedunsigned int hash(const char *s) { if (strlen(s) > 6) return 0; // length gate unsigned int acc = 0x111b25; unsigned int mul = 1; for (; *s; ++s) { acc ^= (unsigned int)((signed char)*s * mul); mul <<= 8; } return acc; }
The key insight: the multiplier overflows
The multiplier esi is shifted left by 8 bits (one byte) every iteration:
| char index | multiplier (esi) |
|---|---|
| 0 | 0x00000001 |
| 1 | 0x00000100 |
| 2 | 0x00010000 |
| 3 | 0x01000000 |
| 4 | 0x00000000 ← overflow! |
| 5+ | 0x00000000 |
After the 4th character, 1 << 32 truncates to 0 in a 32-bit register, so
c * multiplier = 0 and xor edi, 0 is a no-op. Only the first 4
characters of the username contribute to the hash. The length-≤-6 gate just
ensures admin (5 chars) is accepted at all.
Deriving the flag
For username admin, only 'a', 'd', 'm', 'i' matter (the trailing
'n' lands on multiplier 0):
h = 0x111b25
^ ('a' * 0x00000001) ; 0x61
^ ('d' * 0x00000100) ; 0x6400
^ ('m' * 0x00010000) ; 0x6d0000
^ ('i' * 0x01000000) ; 0x69000000
= 0x697c7f44
= REDACTED (decimal)
Since the password is read with %d (signed), and 0x697c7f44 = REDACTED
is positive and fits in a signed 32-bit int, we simply enter the decimal value.
Python reimplementation used to derive and verify the answer:
python read-only • copy allowed#!/usr/bin/env python3 def h(s): edi = 0x111b25 esi = 1 for ch in s: c = ord(ch) if c >= 128: # movsx -> signed char c -= 256 edi ^= (c * esi) & 0xFFFFFFFF edi &= 0xFFFFFFFF esi = (esi << 8) & 0xFFFFFFFF return edi print(h("admin")) # REDACTED
$ python3 solve.py
REDACTED
Solution
Run the binary (on Windows / Wine), then:
username: admin
password: REDACTED
You are welcome! Now you can use this app.
The flag is the password value itself.