Description
In the heart of Tech City, every system is guarded by layers of cryptography. One network in particular — branded as PostECDSA — protects its gates not with passwords or handshakes, but with a strict ECDSA verification test. No signals to capture, no packets to sniff: you stand face-to-face with a server that demands your signature as proof of worth. To enter this city of circuits and glass, you must outsmart the code that was meant to be unbreakable.
File Provided: challenge.py
Solution
#!/usr/bin/env python3
from pwn import remote
import re, json, sys
from hashlib import sha256
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from ecdsa.ecdsa import generator_256
HOST = "2695f26587cd5f19.chal.ctf.ae"
PORT = 443
SNI = HOST
def modinv(a, m):
return pow(a, -1, m)
def recover_d_from_sig(sig_json):
msg = sig_json['msg']
r = int(sig_json['r'])
s = int(sig_json['s'])
# secp256k1 order (generator_256.order())
q = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
h = int(sha256(msg.encode()).hexdigest(), 16)
a = h >> 128 # h // 2**128
two128 = 1 << 128
rhs = (h - (s * a * two128)) % q
denom = (s - r) % q
d = (rhs * modinv(denom, q)) % q
return d
def decrypt_flag(enc_hex, d):
key = sha256(str(d).encode()).digest()[:16]
aes = AES.new(key, AES.MODE_ECB)
ct = bytes.fromhex(enc_hex)
pt = unpad(aes.decrypt(ct), 16)
return pt
def extract_sig_json(text):
# Try to find the printed "sig = '...json...'" or a raw JSON object
# 1) look for sig = '...'
m = re.search(r"sig\s*=\s*'(\{.*?\})'", text, flags=re.S)
if m:
try:
return json.loads(m.group(1))
except Exception:
pass
# 2) find the first {...} that parses as JSON
start = text.find('{')
if start != -1:
stack = []
for i in range(start, len(text)):
if text[i] == '{':
stack.append('{')
elif text[i] == '}':
stack.pop()
if not stack:
candidate = text[start:i+1]
try:
return json.loads(candidate)
except Exception:
break
return None
def extract_enc_flag(text):
# Prefer the explicit printed variable "enc_flag = '...'"
m = re.search(r"enc_flag\s*=\s*'([0-9a-fA-F]+)'", text)
if m:
return m.group(1)
# fallback: find a hex string of reasonable length (>=32 hex chars)
m2 = re.search(r"\b([0-9a-fA-F]{32,})\b", text)
if m2:
return m2.group(1)
return None
def main():
print(f"Connecting to {HOST}:{PORT} (ssl, sni={SNI}) ...")
io = remote(host=HOST, port=PORT, ssl=True, sni=SNI, timeout=8)
try:
data = io.recvall(timeout=4)
except Exception:
try:
data = io.recv(4096, timeout=4)
except Exception as e:
print("Receive failed:", e)
data = b''
try:
io.close()
except:
pass
text = data.decode(errors='ignore')
print("--- remote output ---")
print(text.strip())
print("--- end remote output ---\n")
sig = extract_sig_json(text)
if sig is None:
print("Could not extract signature JSON.")
sys.exit(1)
print("Signature JSON:", sig)
enc_hex = extract_enc_flag(text)
if enc_hex is None:
print("Could not extract enc_flag hex.")
sys.exit(1)
print("Encrypted flag (hex):", enc_hex)
try:
d = recover_d_from_sig(sig)
print("Recovered d =", d)
except Exception as e:
print("Failed to recover d:", e)
sys.exit(1)
try:
flag = decrypt_flag(enc_hex, d)
print("FLAG:", flag.decode())
except Exception as e:
print("Decryption/unpad failed:", e)
# Show raw decrypted bytes for debugging
try:
key = sha256(str(d).encode()).digest()[:16]
from Crypto.Cipher import AES
aes = AES.new(key, AES.MODE_ECB)
ct = bytes.fromhex(enc_hex)
raw = aes.decrypt(ct)
print("Raw decrypted bytes (hex):", raw.hex())
print("Raw decrypted bytes (repr):", raw)
except Exception as e2:
print("Also failed to show raw decrypted bytes:", e2)
if __name__ == "__main__":
main()flag: flag{dfcebeec1a283a6a}