Logon Zero
Logon Zero
Platform: CUSTOM | Category: Crypto | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: all_zero_token, cfb8_state_collapse, retry_until_zero_keystream, authentication_bypass
Summary
Walkthrough of the CUSTOM Crypto Logon Zero. Task: a CryptoHack AES-CFB8 'Domain Controller' that re-creates the ZeroLogon (REDACTED) cryptographic core; reset_password decrypts an attacker-controlled token in CFB-8 (attacker controls IV + ciphe
Description
Please authenticate to this Domain Controller to proceed
Symmetric Ciphers / AES challenge (80 pts). A networked "Domain Controller"
service over socket.cryptohack.org:13399 re-creates the cryptographic core of
ZeroLogon (REDACTED) using AES in CFB-8 mode with a secret random
key. The goal is to authenticate without knowing the random 20-byte password and
recover the flag.
Analysis
The server (13399.py) exposes a JSON protocol with three options:
authenticate— returns the flag iffpassword == self.password(initiallyurandom(20)).reset_connection— re-randomizes the AES key (CFB8(urandom(16))).reset_password— decrypts an attacker-supplied hextokenwith CFB-8, then:python read-only • copy allowedtoken = self.cipher.decrypt(token_ct) # token_ct >= 28 bytes new_password = token[:-4] self.password_length = bytes_to_long(token[-4:]) self.password = new_password[:self.password_length]
The CFB-8 implementation is the textbook self-feeding one, and crucially the
attacker controls both the IV and every ciphertext byte (the token is sent
as raw ciphertext, IV = ciphertext[:16]):
python read-only • copy alloweddef decrypt(self, ciphertext): IV = ciphertext[:16] ct = ciphertext[16:] cipher = AES.new(self.key, AES.MODE_ECB) pt = b'' state = IV for i in range(len(ct)): b = cipher.encrypt(state)[0] # keystream byte = E_K(state)[0] c = b ^ ct[i] # plaintext byte pt += bytes([c]) state = state[1:] + bytes([ct[i]]) # feed CIPHERTEXT byte into state return pt
The all-zero token (ZeroLogon analogue)
Submit a 28-byte all-zero token: a 16-byte zero IV followed by 12 zero
ciphertext bytes ("00" * 28). Trace the decrypt:
- Initial
state = IV = b"\x00" * 16. - Byte 0:
b = E_K(0)[0],pt[0] = b ^ ct[0] = E_K(0)[0] ^ 0 = E_K(0)[0]. Thenstate = state[1:] + bytes([ct[0]]) = (15 zero bytes) + 0x00, which is still all zeros. - Because the state never changes (we always feed the zero ciphertext byte back
in), every keystream byte is
E_K(0)[0]and every plaintext byte isE_K(0)[0].
So the entire 12-byte decrypted token is bytes([E_K(0)[0]]) * 12.
The 1/256 probability
With probability 1/256 over the random key, E_K(0)[0] == 0. When that
happens, the decrypted token is b"\x00" * 12, and:
python read-only • copy allowednew_password = token[:-4] = b"\x00" * 8 password_length = bytes_to_long(token[-4:]) = int(b"\x00\x00\x00\x00") = 0 password = new_password[:0] = b"" # EMPTY
The password becomes the empty string, so authenticate with password=""
succeeds and returns the flag.
This is exactly the ZeroLogon flaw: Netlogon used AES-CFB8 with a zero IV, and an all-zero ciphertext keeps the cipher state zero, so for ~1/256 of session keys the keystream collapses to zero and an attacker can bypass authentication simply by retrying.
Solution
Exploit loop:
reset_passwordwith the all-zero token.authenticatewithpassword="".- On success the response contains the flag. On failure (current key has
E_K(0)[0] != 0),reset_connectionto re-randomize the key and repeat.
Each iteration has ~1/256 success probability, so it lands within a few hundred iterations (we hit it around iteration ~500).
python read-only • copy allowed#!/usr/bin/env python3 """Logon Zero (port 13399) — ZeroLogon (CVE-2020-1472) on AES-CFB8.""" import socket, json, sys HOST, PORT = "socket.cryptohack.org", 13399 def main(): s = socket.create_connection((HOST, PORT), timeout=60) f = s.makefile("rwb") f.readline() # banner def send(obj): f.write((json.dumps(obj) + "\n").encode()); f.flush() return json.loads(f.readline().decode()) zero_token = "00" * 28 # 16-byte zero IV + 12 zero ciphertext bytes it = 0 while True: it += 1 send({"option": "reset_password", "token": zero_token}) r = send({"option": "authenticate", "password": ""}) msg = r.get("msg", "") if "crypto{" in msg: print(msg) return if it % 50 == 0: print(f"[*] iter {it}, still trying...", file=sys.stderr) send({"option": "reset_connection"}) # new random key if __name__ == "__main__": main()
Key Takeaways / Lessons
- Mastering all_zero_token, cfb8_state_collapse, retry_until_zero_keystream, authentication_bypass was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-06-22 • CUSTOM • Logon Zero • Difficulty: Medium