← Back to Writeups
CUSTOMEasyReversing

MiddleOut

XESXOR9/10/20263 min read
#reversing#custom#easy#constraint_inversion#custom_hash_reimplementation#static_analysis#xor_validation#windows

MiddleOut

Platform: spbctf | Category: Reversing | Type: Challenge | Difficulty: Easy | OS: Windows | Author: XESXOR | Date: 2026-06-24 | Status: Solved Techniques: constraint_inversion, custom_hash_reimplementation, static_analysis, xor_validation

Summary

Task: 32-bit PE crackme that validates a username (scanf %s) and integer password (scanf %d); flag is admin's password. Solution: static analysis in radare2 shows validation reduces to password = hash(username) ^ 0xFF; reimplement the custom multiplicative-XOR hash for 'admin' to recover REDACTED.

MiddleOut — spbctf

Description

Flag is the password for admin user.

A 32-bit Windows console binary (MiddleOut.exe, PE32, Intel 80386, ~9.7 KB) prompts for a username (scanf %s) and a password (scanf %d, signed integer). On valid input it prints You are welcome! Now you can use this app., otherwise Go out of here!. The goal is to recover the password that is accepted for the username admin — that password is the flag.

Analysis

Static analysis with radare2 reveals two key routines. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

main @ 0x401100

  1. Constant checksum loop: esi = 0xFF; for ecx in 0..0x5b: eax = ecx + 0x76; esi ^= eax. This is independent of any input. XOR-folding a fixed arithmetic sequence cancels out, and the result is exactly 0xFF.
  2. eax = hash(username) — calls the custom hash at fcn.00401080.
  3. esi ^= password — XOR the running value (0xFF) with the user-entered integer.
  4. cmp eax, esi — success iff hash(username) == 0xFF ^ password.

This collapses the whole check into a single algebraic constraint: ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

password = hash(username) ^ 0xFF

Username hash @ 0x401080

  • Computes strlen(username).
  • If strlen(username) > 6 → returns 0 (the cmp edx, 6; jbe branch sends lengths <= 6 to the hashing code; longer strings fall through to xor eax,eax; ret).
  • Otherwise (len <= 6):
    • seed h = 0x124B
    • mult = 1
    • for each character c (loaded with movsx, i.e. signed char):
      • h ^= (signed char)c * mult
      • mult = mult*16 + mult = mult*17 (assembled as shl edx,4; add edx,eax)
    • returns h ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

The ×17 multiplicative mixing of each successive character is the "Middle-Out" theme (a nod to Silicon Valley's "middle-out compression").

Solution

admin has length 5 (<= 6), so it is hashed. Tracing the loop:

charcodemultcontributionrunning h
seed0x124b
a9710x610x122a
d100170x6a40x148e
m1092890x7b0d0x6f83
i10549130x7df190x7b09a
n110835210x8c2fee0x8b9f74
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

So hash("admin") = 0x8b9f74, and:

password = 0x8b9f74 ^ 0xFF = 0x8b9f8b = REDACTED

Python solver

python
#!/usr/bin/env python3
def uname_hash(s):
    if len(s) > 6:
        return 0
    h = 0x124b
    mult = 1
    for ch in s:
        c = ord(ch)
        if c >= 128:
            c -= 256          # movsx = signed char
        h ^= (c * mult) & 0xFFFFFFFF
        h &= 0xFFFFFFFF
        mult = (mult * 17) & 0xFFFFFFFF  # shl 4 + add = *17
    return h

h = uname_hash("admin")       # 0x8b9f74
password = h ^ 0xff           # 0x8b9f8b
print(password)               # REDACTED

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

Entering username admin and password REDACTED yields You are welcome! Now you can use this app. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍