# Pragyan CTF 2025 - sECCuritymaxxing **Title:** sECCuritymaxxing **Description:** In a significant breakthrough in law enforcement, authorities have arrested the infamous crime boss, known for orchestrating a sprawling illegal business network that has plagued the city for years. As authorities began to sift through the extensive evidence collected during the raid, they quickly realized that the case against him would require more than just physical evidence. As sources say the government has made deals with an black hat hacker who is in jail, to mimic him and take down his network in turn leading him to freedom... Would you take the deal? `ncat --ssl seccmaxx.ctf.prgy.in 1337` **Files:** [server_obf.py](https://raw.githubusercontent.com/delta/PCTF25-Writeups/refs/heads/main/crypto/sECCuritymaxxing/includes/docker/server_obf.py) ## Solution: I began by inspecting the provided **server_obf.py** file. Although the code is mildly obfuscated and were with extraneous functions, a few interesting observations stood out to me: - **Elliptic Curve Parameters:** The service uses the [secp256k1](https://en.bitcoin.it/wiki/Secp256k1) curve with standard parameters. The base point "G" and the order "n" are clearly defined. Most of the cryptographic operations are built around elliptic curve arithmetic. - **Signature Generation:** When selecting option **1. Sign messages**, the server signs a user-supplied message using a scheme similar to [ECDSA](https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm). The signature is a tuple of values `(r, s)` computed as: - `r` is derived from the x-coordinate of an ephemeral point (computed as a scalar multiplication on the curve). - `s` is calculated using the formula: ``` s = ((hash(message) + r * d) * k⁻¹) mod n ``` where `d` is the server’s private key and `k` is the ephemeral nonce. - **Vulnerability – Nonce Reuse:** Due to poor randomness (and I guess intentional obfuscation), the service reuses ephemeral nonces. Basically, different messages sometimes produce signatures which share the same `r` value. This reuse of `k` is exploitable in ECDSA-like schemes. - **Exploiting Nonce Reuse:** When two signatures share the same `r`, say: ``` s₁ = (h₁ + r*d) / k and s₂ = (h₂ + r*d) / k (mod n) ``` where `h₁` and `h₂` are the integer representations of SHA1 hashes of two distinct messages, one can subtract these equations to cancel out the private key term: ``` s₁ - s₂ = (h₁ - h₂) / k (mod n) ``` This lets us solve for the ephemeral nonce: ``` k = (h₁ - h₂) / (s₁ - s₂) mod n ``` Once `k` is known, the private key `d` can be recovered from one of the signature equations: ``` d = (s₁ * k - h₁) / r mod n ``` - **Forging a Signature:** With the recovered private key `d`, it becomes straightforward to forge a valid signature for any message. In our case, we need to forge a signature for the message `"give_me_signature"`. By choosing a fresh random nonce `k'` and computing: ``` r_forged = (k' * G).x mod n s_forged = ((hash("give_me_signature") + r_forged*d) * (k')⁻¹) mod n ``` we obtain a forged signature that the server will accept. --- ### Exploit Script The provided solve script automates the following steps: 1. **Collect Signatures:** The script repeatedly selects option **1. Sign messages** and sends distinct messages (e.g., `"msg1"`, `"msg2"`, …) until it finds two signatures with the same `r` value. 2. **Extract Signature Data:** For each signature, it parses out `r` and `s` (both given in hexadecimal) and computes the SHA1 hash of the message (converted to an integer). 3. **Recover Nonce and Private Key:** Once a nonce reuse is detected, the script calculates: - The ephemeral nonce `k` using the difference of the two hashes and signatures. - The private key `d` from one of the signature equations. 4. **Forge a Signature:** With `d` in hand, the script forges a signature for the target message `"give_me_signature"` by performing a standard ECDSA signature computation. 5. **Submit the Forged Signature:** Finally, the script selects option **2. Submit signature** from the menu, submits the forged `(r, s)`, and the server (upon verifying the signature) reveals the flag. Below is the full attack script: ```python #!/usr/bin/env python3 from pwn import remote import re, hashlib, sys, time, random a = 0 b = 7 p = 2**256 - 2**32 - 2**9 - 2**8 - 2**7 - 2**6 - 2**4 - 1 n = 115792089237316195423570985008687907852837564279074904382605163141518161494337 G = ( 55066263022277343669578718895168534326250603453777594175500187360389116729240, 32670510020758816978083085130507043184471273380659243275938904335757337482424, ) def point_add(P, Q): if P is None: return Q if Q is None: return P x1, y1 = P x2, y2 = Q if x1 == x2 and (y1 + y2) % p == 0: return None if P == Q: m = (3 * x1 * x1) * pow(2 * y1, -1, p) % p else: m = (y2 - y1) * pow(x2 - x1, -1, p) % p x3 = (m * m - x1 - x2) % p y3 = (m * (x1 - x3) - y1) % p return (x3, y3) def scalar_mult(k, P): result = None addend = P while k: if k & 1: result = point_add(result, addend) addend = point_add(addend, addend) k //= 2 return result def sha1_int(message): return int(hashlib.sha1(message.encode()).hexdigest(), 16) HOST = "seccmaxx.ctf.prgy.in" PORT = 1337 r = remote(HOST, PORT, ssl=True) sigs = {} print("[*] Collecting signatures until we find a nonce reuse...") i = 0 reused = None while True: i += 1 r.recvuntil(b"> ") r.sendline(b"1") r.recvuntil(b"Message to sign > ") msg = f"msg{i}" r.sendline(msg.encode()) line = r.recvline().strip().decode() m = re.search(r"\('([^']+)', '([^']+)'\)", line) if not m: print("[!] Could not parse signature from:", line) continue r_hex, s_hex = m.group(1), m.group(2) r_val = int(r_hex, 16) s_val = int(s_hex, 16) h_val = sha1_int(msg) print(f"[*] Got signature for '{msg}': r = {hex(r_val)} s = {hex(s_val)}") if r_val in sigs: print("[*] Found reused nonce!") reused = (r_val, sigs[r_val], (msg, s_val, h_val)) break sigs[r_val] = (msg, s_val, h_val) time.sleep(0.1) if not reused: print("[!] Failed to find a reused nonce. Exiting.") sys.exit(1) r_common = reused[0] (msg1, s1, h1) = reused[1] (msg2, s2, h2) = reused[2] print(f"[*] Reused r: {hex(r_common)} for '{msg1}': s1 = {hex(s1)}, h1 = {h1} and '{msg2}': s2 = {hex(s2)}, h2 = {h2}") diff_s = (s1 - s2) % n inv_diff_s = pow(diff_s, -1, n) k_recovered = ((h1 - h2) * inv_diff_s) % n print(f"[*] Recovered k (nonce): {k_recovered}") d = ((s1 * k_recovered - h1) * pow(r_common, -1, n)) % n print(f"[*] Recovered private key d: {d}") target_msg = "give_me_signature" h_target = sha1_int(target_msg) k_prime = random.randrange(1, n) R_point = scalar_mult(k_prime, G) r_forged = R_point[0] % n inv_k_prime = pow(k_prime, -1, n) s_forged = ((h_target + r_forged * d) * inv_k_prime) % n print(f"[*] Forged signature for '{target_msg}': r = {r_forged} s = {s_forged}") r.recvuntil(b"> ") r.sendline(b"2") r.recvuntil(b"Enter the signature") r.recvuntil(b"Enter int value of r: ") r.sendline(str(r_forged).encode()) r.recvuntil(b"Enter int value of s: ") r.sendline(str(s_forged).encode()) result = r.recvall(timeout=5).decode() print("\n[*] Service output:") print(result) ``` --- ### Summary of the script - **Vulnerability:** The server’s signature scheme reuses the ephemeral nonce (`k`), resulting in repeated `r` values across signatures. - **Attack:** By collecting signatures until a reused `r` is found, we can compute the nonce `k` and then try to recover the private key `d`. - **Exploitation:** With the private key in hand, we can forge a valid signature for the message `"give_me_signature"` and submit it to retrieve the flag. ```bash $ python3 solve.py [+] Opening connection to seccmaxx.ctf.prgy.in on port 1337: Done [*] Collecting signatures until we find a nonce reuse... [*] Got signature for 'msg1': r = 0xb6bf284a2c515e8cce83971f4111038e3b398c568d54d0f238f277dc4582040 s = 0x7f8c544c85e91dcefd5e826e0be3b728c7d912db8b3e8d2ca7bd6d2b8addac62 [*] Got signature for 'msg2': r = 0xc049ecaf7d0b98bd40b31919fce224ebafefcaa2dd21efa5578ac7dbb52d3925 s = 0x9564782836908b3521176f9b7262f2686050ac1ed75729b5652427bb128dc3d4 [*] Got signature for 'msg3': r = 0xbf88904468bd5227c3a1667cbdd3894c64b0e7c74a97a0705995f7acb2279ab7 s = 0xc31727aa5b3bbc6c5e3a259b5aea7cd5108ae9096e50af6414deeff6dc48fda4 [*] Got signature for 'msg4': r = 0x72c431350b705d6852caa94177367809275b4171d3a02b6f80a6003bc2b51f1c s = 0xf278a9267705e92d5f7fe278ad4ed0b73fc588fd16498025fd375624dbf199d2 ...... [*] Got signature for 'msg199': r = 0x93fc8a4af22257d8d258273f93f68d43daa7ed793fd8b699a05a989f2f89d3c6 s = 0x3b36c66ed68ce7c48f4ccab182c9d6c199fba7f7386af068f6063ddec270542a [*] Got signature for 'msg200': r = 0x8c54b970323db3103c4762cb2f61f2f2594a5b09d4b9c9e2529b7440b57e51f6 s = 0x39d00f6896dd5df81bd8fb6575dfb6b6f22ea130bf4b81a227b0bd3c6044fab8 [*] Got signature for 'msg201': r = 0xb6bf284a2c515e8cce83971f4111038e3b398c568d54d0f238f277dc4582040 s = 0x4307c4ebdfea8089f82f5086238ce1ec5cf48f8f74578507d82b837f18411187 [*] Found reused nonce! [*] Reused r: 0xb6bf284a2c515e8cce83971f4111038e3b398c568d54d0f238f277dc4582040 for 'msg1': s1 = 0x7f8c544c85e91dcefd5e826e0be3b728c7d912db8b3e8d2ca7bd6d2b8addac62, h1 = 89863736152649285331718032504651948055260563451 and 'msg201': s2 = 0x4307c4ebdfea8089f82f5086238ce1ec5cf48f8f74578507d82b837f18411187, h2 = 458576312539331598060506760890071314144242112971 [*] Recovered k (nonce): 68070401583449206840824013261923089795898580509143137542138124357608615993704 [*] Recovered private key d: 42146383479319943870997042134658294935182027500939772452444709785299502716484 [*] Forged signature for 'give_me_signature': r = 24327815471911615621543785277781222981173928485380570732060800471717212076785 s = 86376521349794165337617789691036193852060122642239471912703158541854111174424 [+] Receiving all data: Done (125B) [*] Closed connection to seccmaxx.ctf.prgy.in port 1337 [*] Service output: p_ctf{I5it_tH3K3Y_0r_y0|_|r_pr!5ef0R_fr3340m} Welcome boss, what do you want me to do! 1. Sign messages 2.Submit signature > ``` And there's our flag: `p_ctf{I5it_tH3K3Y_0r_y0|_|r_pr!5ef0R_fr3340m}`