| Adversarial Mind |
AI_n0t_th@t_gud
is that it ? |
2026-04-02 05:38 |
| Simple Password Check |
## Challenge
A Windows x64 PE binary prompts for a password and either prints `Access granted!` + a flag, or `Access denied!`.
---
## Step 1 — Understand the input pipeline
Disassembling `main()` reveals three stages after reading the password string from `stdin`:
1. **Expansion** — each character is passed to a function that splits it into its 8 bits (MSB first), stored as individual `0`/`1` bytes. A 16-character password therefore fills a 128-byte buffer with the password's raw bit representation.
2. **Verification** — the 128-byte buffer is passed to a large boolean circuit.
3. **Flag decode** — on success, 47 hardcoded encrypted bytes are XOR-decoded with the original password string (cycling) and printed.
---
## Step 2 — Reverse the boolean circuit
The verification function (`0x1400102f0`) contains:
- **128 permutation** loads — each buffer bit is copied to a specific stack slot.
- **71 NOT gates** — a sub-function that returns `1` if its input is `0`, else `0`.
- **127 AND gates** arranged as a binary tree — returns `1` only if both inputs are non-zero.
The tree terminates in a single root AND gate; its output is the pass/fail result.
---
## Step 3 — Solve with Z3
Parsing the disassembly, every buffer bit is constrained as either:
- **must be 0** (feeds a NOT gate — needs to be `0` to produce `1` into the AND tree)
- **must be 1** (feeds an AND gate directly — needs to be non-zero)
Feeding these 128 boolean constraints into **Z3** yields a unique solution — a 128-bit string:
```
00100100 00100101 01011001 01100001
01100011 01101011 01101001 01101110
01100111 00110001 00110010 00110011
00110100 00111001 00110000 00110001
```
---
## Step 4 — Recover the password
Grouping into 8-bit bytes (MSB first) and converting to ASCII:
| Bits | Hex | Char |
|------|-----|------|
| 00100100 | 0x24 | `$` |
| 00100101 | 0x25 | `%` |
| 01011001 | 0x59 | `Y` |
| 01100001 | 0x61 | `a` |
| 01100011 | 0x63 | `c` |
| 01101011 | 0x6b | `k` |
| 01101001 | 0x69 | `i` |
| 01101110 | 0x6e | `n` |
| 01100111 | 0x67 | `g` |
| 00110001 | 0x31 | `1` |
| 00110010 | 0x32 | `2` |
| 00110011 | 0x33 | `3` |
| 00110100 | 0x34 | `4` |
| 00111001 | 0x39 | `9` |
| 00110000 | 0x30 | `0` |
| 00110001 | 0x31 | `1` |
**Password: `$%Yacking1234901`**
---
## Step 5 — Decrypt the flag
The 47 encrypted bytes are XOR'd with the password (16 chars, cycling):
```python
flag = bytes(enc[i] ^ pw[i % 16] for i in range(47))
```
**Flag: `dzctf(SoftwareBasedHardwareAbstraction_is_cool)`**
---
## Verification
```
$ echo '$%Yacking1234901' | wine crackme_goodlucklol.exe
Welcome to the Secure Login System
Password: Access granted!
dzctf(SoftwareBasedHardwareAbstraction_is_cool)
```
---
## Key insight
The crackme's name is the hint. It implements a **software-emulated hardware circuit** — a boolean gate tree operating on the binary representation of the password. Standard symbolic execution on printable strings would fail; the correct approach is to extract and solve the gate constraints directly. |
2026-03-30 00:02 |