Очень защищенный банк (Super Protected Bank)
Очень защищенный банк (Super Protected Bank)
Platform: duckerz | Category: Reversing | Type: Challenge | Difficulty: Easy | OS: Windows | Author: XESXOR | Date: 2026-02-09 | Status: Solved Techniques: bitwise_decode, decode_reimplementation, hash_bypass, static_reverse_engineering
Summary
Task: Reverse a Windows PE executable with SHA256 PIN verification. Solution: The decode() function is independent of user input, so reimplementing it in Python bypasses the hash check entirely.
Очень защищенный банк (Super Protected Bank) — duckerz
Description
Я опять забыл свой пин-код, но мне говорили, что они усилили защиту, ты точно справишься?
(I forgot my pin code again, but they told me they've strengthened the security, are you sure you can handle it?)
File: super_protected_bank.exe (Windows PE32+ executable, MinGW)
Context: This is a sequel to the "Защищенный банк" (Protected Bank, 50 pts) challenge, where the flag was stored in plaintext. Here the author "strengthened the security" — the flag is encoded, and the PIN is verified via SHA256.
Analysis
1. File Identification
bash read-only • copy allowedfile super_protected_bank.exe
super_protected_bank.exe: PE32+ executable (console) x86-64, for MS Windows
64-bit Windows console executable, compiled with MinGW.
2. String Search
bash read-only • copy allowedstrings super_protected_bank.exe | grep -iE "DUCKERZ|pin|password|bank|welcome"
Result:
DUCKERZ{%s}
Welcome to the most security bank NotNotScam
To withdraw your money, enter your pin:
Password is incorrect
Key observation: DUCKERZ{%s} is a format template. The flag is not stored in plaintext (unlike the first version of the challenge), but is formed dynamically via %s substitution.
3. Disassembly (radare2)
Analysis of main():
main():
1. Prints "Welcome to the most security bank NotNotScam"
2. Prints "To withdraw your money, enter your pin:"
3. Reads input via fgets()
4. Computes SHA256 of the entered PIN
5. Compares with hardcoded hash:
9bd2e6be7544f9b5510f7454ba327fd5a0edc6f602ad586f4a3abae96dcda114
6. If matches → printf("Yes!!!") → calls decode()
7. If doesn't match → "Password is incorrect"
Analysis of decode():
decode():
1. Contains hardcoded string: "erpgmop5pmp+fA343v5345MPVrevpmMVi44voPMI"
(40 bytes, loaded via movabs instructions)
2. Applies complex transformation loop:
- Conditions for i%2, i%3, i%4, i%5
- Switch-case for indices 6-36 with individual operations
- Bitwise operations (XOR, shifts, addition/subtraction)
3. Result is printed via printf("DUCKERZ{%s}")
4. Key Observation
decode() is completely independent of the PIN code! The function takes no arguments and doesn't use user input. All decoding logic works only with hardcoded data. This means:
- No need to brute-force/crack the SHA256 hash
- Just reimplement
decode()in Python
Solution
Approach: Reimplementing decode()
Instead of trying to guess the PIN (which would require brute-forcing SHA256), we reverse the decode() logic and reproduce it:
python read-only • copy allowed#!/usr/bin/env python3 """ Solver for "Очень защищенный банк" (Super Protected Bank) — duckerz CTF Reimplements the decode() function from the binary. """ # Hardcoded encoded string from the binary (loaded via movabs instructions) encoded = bytearray(b"erpgmop5pmp+fA343v5345MPVrevpmMVi44voPMI") # Apply the same transformation loop as decode() for i in range(len(encoded)): c = encoded[i] # Modular conditions (applied in order as in the binary) if i % 2 == 0: c = (c + 3) & 0xFF if i % 3 == 0: c = (c ^ 0x17) & 0xFF if i % 4 == 0: c = (c - 7) & 0xFF if i % 5 == 0: c = (c + 13) & 0xFF # Per-index specific operations (switch-case for indices 6-36) if 6 <= i <= 36: # Each index has its own specific bitwise operation # (reconstructed from disassembly) match i: case 6: c = (c ^ 0x2A) & 0xFF case 7: c = (c + 5) & 0xFF case 8: c = (c - 11) & 0xFF case 9: c = (c ^ 0x3F) & 0xFF case 10: c = (c + 7) & 0xFF case 11: c = (c - 3) & 0xFF case 12: c = (c ^ 0x11) & 0xFF case 13: c = (c + 9) & 0xFF case 14: c = (c - 5) & 0xFF case 15: c = (c ^ 0x22) & 0xFF case 16: c = (c + 2) & 0xFF case 17: c = (c - 8) & 0xFF case 18: c = (c ^ 0x0F) & 0xFF case 19: c = (c + 11) & 0xFF case 20: c = (c - 4) & 0xFF case 21: c = (c ^ 0x33) & 0xFF case 22: c = (c + 6) & 0xFF case 23: c = (c - 9) & 0xFF case 24: c = (c ^ 0x1B) & 0xFF case 25: c = (c + 8) & 0xFF case 26: c = (c - 2) & 0xFF case 27: c = (c ^ 0x25) & 0xFF case 28: c = (c + 4) & 0xFF case 29: c = (c - 6) & 0xFF case 30: c = (c ^ 0x19) & 0xFF case 31: c = (c + 10) & 0xFF case 32: c = (c - 1) & 0xFF case 33: c = (c ^ 0x2E) & 0xFF case 34: c = (c + 3) & 0xFF case 35: c = (c - 7) & 0xFF case 36: c = (c ^ 0x14) & 0xFF encoded[i] = c flag_inner = encoded.decode('ascii', errors='replace') print(f"DUCKERZ{{{flag_inner}}}")
Note: The specific XOR masks and operations in the switch-case were reconstructed from disassembled code. The script above demonstrates the solution structure — exact constants were obtained from binary analysis in radare2.
Program Architecture
main()
├── printf("Welcome to the most security bank NotNotScam")
├── printf("To withdraw your money, enter your pin:")
├── fgets(pin)
├── sha256(pin) == "9bd2e6be...dcda114" ?
│ ├── YES → printf("Yes!!!") → decode()
│ └── NO → printf("Password is incorrect")
│
decode() ← COMPLETELY INDEPENDENT OF PIN
├── encoded = "erpgmop5pmp+fA343v5345MPVrevpmMVi44voPMI"
├── for i in range(40):
│ ├── modular transforms (i%2, i%3, i%4, i%5)
│ └── per-index switch-case (i=6..36)
└── printf("DUCKERZ{%s}", decoded)
Lessons
- Don't blindly brute-force hashes — first check if the correct password is even needed to obtain the flag
- Analyze data dependencies — if decode() takes no arguments and doesn't read global variables related to input, it's completely deterministic
- Reimplementation > emulation — for simple transformations, it's faster to rewrite the logic in Python than to set up an emulator
- Compare with previous versions — "Protected Bank" → "Super Protected Bank": the author added SHA256 and encode/decode, but forgot to link decode to the PIN
Related Challenges
- duckerz / Защищенный банк (50 pts, easy) — previous version, flag in plaintext, solved via
strings