← Back to Writeups
CUSTOMMediumCrypto

Static Client

XESXOR9/2/20263 min read
#crypto#custom#medium#generator_substitution#shared_secret_replay#static_exponent_oracle

Static Client

Platform: CUSTOM | Category: Crypto | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: generator_substitution, shared_secret_replay, static_exponent_oracle

Summary

Walkthrough of the CUSTOM Crypto Static Client. 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 paramet

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:

  1. Eavesdrop: intercept Alice<->Bob — the prime p, generator g, Alice's public A_alice = g^a, Bob's public B, and the AES-CBC encrypted flag (key = sha1(str(shared)).digest()[:16]).
  2. Talk to Bob: Bob now accepts parameters {p, g, A} from us and replies with his public value computed using his static private exponent b. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

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

  1. Eavesdrop and record p, A_alice, iv, encrypted_flag.
  2. Send Bob {"p": hex(p), "g": hex(A_alice), "A": hex(dummy)}.
  3. Read B' = A_alice^b mod p — the shared secret.
  4. Derive the key and AES-CBC decrypt the eavesdropped flag. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍
python
import 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 b and gives you the result.
  • Set the generator to the other party's public value (g := A_alice); Bob then returns A_alice^b, which is precisely g^(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. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Key Takeaways / Lessons

  • Mastering generator_substitution, shared_secret_replay, static_exponent_oracle was key to this challenge.
  • Always check for common misconfigurations and verify assumptions.

Crafted by XESXOR Research • 2026-06-22 • CUSTOM • Static Client • Difficulty: Medium