Static Client
Static Client
Platform: cryptohack | Category: Crypto | Type: Challenge | Difficulty: Medium | OS: NA | Author: XESXOR | Date: 2026-06-22 | Status: Solved Techniques: generator_substitution, shared_secret_replay, static_exponent_oracle
Summary
Task: interactive DH (socket.cryptohack.org 13373) where Bob keeps a STATIC private exponent b; you first eavesdrop Alice<->Bob (p, g, A_alice, B, encrypted flag) then get to send Bob your own parameters. Solution: send {p, g=A_alice, A=dummy}; Bob computes and returns B' = g^b = A_alice^b mod p, which is exactly the Alice<->Bob shared secret, so derive key=sha1(str(B'))[:16] and AES-CBC decrypt the eavesdropped flag with NO discrete log.
Static Client — cryptohack (Diffie-Hellman)
Description
"You've just finished eavesdropping on a conversation between Alice and Bob. Now you have a chance to talk to Bob. What are you going to say?" Connect at
socket.cryptohack.org 13373.
Two phases over the socket:
- Eavesdrop: intercept Alice<->Bob — the prime
p, generatorg, Alice's publicA_alice = g^a, Bob's publicB, and the AES-CBC encrypted flag (key =sha1(str(shared)).digest()[:16]). - Talk to Bob: Bob now accepts parameters
{p, g, A}from us and replies with his public value computed using his static private exponentb.
Recon / Analysis
The genuine Alice<->Bob shared secret is:
shared = A_alice^b mod p = g^(a*b) mod p
Bob reuses the same static b when he talks to us, and he computes his reply as B' = g^b mod p using the generator g we supply. So if we set our generator equal to Alice's public value, g := A_alice, Bob computes:
B' = g^b mod p = A_alice^b mod p = shared
That is exactly the Alice<->Bob shared secret — handed to us directly. No discrete log, no factoring.
Solution
- Eavesdrop and record
p,A_alice,iv,encrypted_flag. - Send Bob
{"p": hex(p), "g": hex(A_alice), "A": hex(dummy)}. - Read
B' = A_alice^b mod p— the shared secret. - Derive the key and AES-CBC decrypt the eavesdropped flag.
python read-only • copy allowedimport json from hashlib import sha1 from Crypto.Cipher import AES from Crypto.Util.Padding import unpad from dhclient import connect, recv_until, recv_some, sendline # ---- phase 1: eavesdrop p, g, A_alice, B, iv, encrypted_flag from the banner ---- # (parsed from the intercepted Alice<->Bob transcript) # ---- phase 2: make Bob hand us A_alice^b ---- s = connect(13373) recv_until(s, b'parameters:') sendline(s, json.dumps({"p": hex(p), "g": hex(A_alice), "A": hex(1)})) resp = recv_some(s) Bp = int(json.loads(resp.splitlines()[-1])["B"], 16) # B' = A_alice^b = shared key = sha1(str(Bp).encode()).digest()[:16] pt = AES.new(key, AES.MODE_CBC, bytes.fromhex(iv)).decrypt(bytes.fromhex(encrypted_flag)) print(unpad(pt, 16).decode())
crypto{REDACTED}
Takeaways
- Static (non-ephemeral) DH keys turn the peer into an oracle. Whatever base you send, Bob raises it to his fixed secret
band gives you the result. - Set the generator to the other party's public value (
g := A_alice); Bob then returnsA_alice^b, which is preciselyg^(a*b)— the shared secret you wanted to decrypt — with no discrete log. - The flag name says it: not ephemeral enough. The fix is fresh per-session exponents (ephemeral DH).
- Recognize it by: an eavesdropped exchange plus a follow-up where the server reuses its key and lets you choose
g/A.