> ## Content Index
> Fetch the complete content index at: https://taiwanding.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# crackmes.one Very Easy Crackme Writeup: Reversing a Base-64 Hash Check
- URL: https://taiwanding.com/en/crackmes-one-very-easy-crackme-writeup/
- Published: 2025-09-22T07:40:36.000Z
- Updated: 2026-07-15T02:49:51.000Z
- Author: Kevin Chen
- Tags: #en, #en-ctf

Challenge link: [https://crackmes.one/crackme/67649fee60fa67152406bcac](https://crackmes.one/crackme/67649fee60fa67152406bcac?ref=taiwanding.com)

## 1) Start with main: locate the comparison logic

Over in `FUN_00401000` (which is essentially main) we see:

- Read the user's input: `gets_s(local_1f8, 500);`
- Feed the input into a computation function: `iVar1 = FUN_00401090();`
- Compare against a constant: `if (iVar1 == 0x54A337) ...`

`0x54A337` in decimal is **5,546,807**.  
**Goal**: find any string `s` such that `FUN_00401090(s) == 5,546,807`, and the program prints `Good cracker !`.

## 2) Follow `FUN_00401090`: the rule

Boiled down to its equivalent logic:

```c
int H(const unsigned char *s) {
    int v = 0;
    for (size_t i = 0; i < strlen((char*)s); i++) {
        v = (v * 64 + s[i]) % 10000000;  // 0x40 = 64
    }
    return v;
}

```

### What does "base 64" mean here?

Think of the whole string as a number written in a custom radix:  
decimal is "multiply each digit by 10, then add the next digit," hex is "multiply each digit by 16, then add the next digit,"  
**here it's "each step, multiply by 64 then add the ASCII value of the next character."**  
To keep the value from exploding, **every step** also takes the remainder `% 10,000,000`, keeping it in the range 0–9,999,999.

### In plain terms:

It's a **multiply-then-add all the way through** algorithm:  
start from 0, and for every character repeat "`current value * 64 + character's ASCII`, then `% 10,000,000`."

## 3) Worked example: why does `:Rew` pass?

Character ASCII values: `':'=58, 'R'=82, 'e'=101, 'w'=119`. Step by step:

1. `v=0 → 0*64+58 = 58`
2. `v=58 → 58*64+82 = 3794`
3. `v=3794 → 3794*64+101 = 242,917`
4. `v=242,917 → 242,917*64+119 = ... ≡ **5,546,807** (mod 10,000,000)`

Equal to the target, so entering `**:Rew**` prints `Good cracker !`.

## 4) Why is there more than one solution? How do you "assemble" an arbitrary one?

1. **First decide what the last character has to look like**  
Picture the final step of the check as: "that big pile of numbers so far ×64, then add the last character."  
Since the last step "first multiplies by 64, then adds the last character," **the last character of the answer** has to make the overall "residue (the remainder mod 64)" line up with the residue of the target value.
  - Do the math: `r = target T % 64`. Here `T=5,546,807`, so `r=55`.
  - A single byte can jump by +64, +128, +192 and still have the same residue.  
  So the last character `y` can only be: `{55, 55+64, 55+128, 55+192}` intersected with `[0..255]`.  
  → For this challenge the choices are `{55('7'), 119('w'), 183(0xB7), 247(0xF7)}`.  
  Intuition to remember: **the last character aligns the "mod-64 residue" with the target.**
2. **Undo the final step**  
Once you've picked `y`, you can "peel off" the last step:
  - First compute `T - y`, which is exactly "that big pile ×64."
  - Then divide by 64: `T1 = (T - y) / 64` (this divides evenly, precisely because we chose y to line up).  
  Now the original "whole string" problem shrinks to "just make the **prefix** satisfy `prefix's result ≡ T1 (mod 156,250)`," since `10,000,000 / 64 = 156,250`.
3. **Search for the prefix in a smaller world**  
Now all you need is a short prefix `P` such that  
`H(P) % 156,250 == T1`.  
Using printable ASCII (space through tilde, 32–126) and a small search over lengths 2–4, **you find one fast**.
4. **Concatenate prefix + last character, and that's your answer**
  - Choosing `y='w'(119)` gives `T1=86,667`, and you find `P=":Re"` → **`:Rew`**.
  - Choosing `y='7'(55)` gives `T1=86,668`, and you find `P=":Rf"` → **`:Rf7`** (the combo you confirmed works in practice).

### The one-liner takeaway

- **Align the last byte to the `%64` residue** (this fixes y).
- **Roll back the final step** (compute `T1=(T-y)/64`).
- **Search for a short prefix in the mod-156,250 space**, tack y back on, and you've got a working passphrase.  
Because this construction yields a huge number of combinations, the answer isn't unique; but the rule is fixed, so the same set of passing combinations gets accepted every time.

## 5) Verification and an auto-generation script

### Verify a given input

```python
def H(s, M=10_000_000):
    v = 0
    for ch in s.encode():
        v = (v * 64 + ch) % M
    return v

print(H(":Rew"))  # 5546807
print(H(":Rf7"))  # 5546807

```

### Auto-generate a "printable" solution

```python
# Generate a printable solution (ASCII 32..126) with H(s) == T
import itertools

M  = 10_000_000
B  = 64
T  = 5_546_807
PR = list(range(32, 127))  # printable ASCII
MOD_PREFIX = M // B        # 156250

def H_mod(bytes_seq, mod):
    v = 0
    for b in bytes_seq:
        v = (v * B + b) % mod
    return v

def gen_one(max_len_prefix=4):
    r = T % B
    last_candidates = [r + 64*k for k in range(4) if r + 64*k <= 255 and 32 <= r + 64*k <= 126]
    for y in last_candidates:
        T1 = (T - y) // B
        for L in range(1, max_len_prefix + 1):
            for tup in itertools.product(PR, repeat=L):
                if H_mod(tup, MOD_PREFIX) == T1:
                    s = bytes(tup + (y,)).decode('latin1')
                    return s
    return None

print("solution =", gen_one())  # might print ':Rew', ':Rf7', etc.

```

## 6) Wrap-up

- **The backbone**: input → `FUN_00401090` → compare against the constant `0x54A337`.
- **The rule in plain English**: each step is `×64 + character ASCII`, with `% 10,000,000` at every step.
- **Demo solutions**: `:Rew`, `:Rf7`, plus you can generate a whole bunch of answers.

## Lessons learned

Regarding section 4), here's a more intuitive way to think about it:

1. **Every step "wraps around"**  
It's not simply growing bigger and bigger to the end; **every step** squashes the number back into 0–9,999,999 (i.e. takes the remainder mod 10,000,000).
  - `v0 = 0`
  - Read char 1: `v1 = (v0*64 + ASCII1) % 10,000,000` \= `ASCII1`
  - Read char 2: `v2 = (v1*64 + ASCII2) % 10,000,000`
  - Read char 3: `v3 = (v2*64 + ASCII3) % 10,000,000`
  - Read char 4: `v4 = (v3*64 + ASCII4) % 10,000,000`
  - …and the final `vn` has to equal **5,546,807**.
2. **The last byte must align to `%64`**  
Because the final step is "everything before ×64 + the last byte," **the last byte's ASCII** must make  
`final result % 64 == last byte % 64`.  
In other words "last byte ≡ 5,546,807 (mod 64)"; here `5,546,807 % 64 = 55`,  
so the last byte can be any congruent value such as `55('7')`, `119('w')`, `183(0xB7)`, or `247(0xF7)`.