protocol_analysis
Challenge
Imported from local notes.md.
Solution
Original Notes
protocol_analysis
Challenge Summary
- Given:
starting_files/Protocol_Analysis_chals.pdf, which documents nine live protocol-analysis challenges backed byhttps://protocols.live. - Goal: recover every stage flag and store the full set in
flag.txt. - Constraints: each protocol instance is stateful and destroyed on invalid requests, so the solve path needs to be scripted and replayable.
Initial Recon / Triage
- Extracted the PDF with
pdftotext -layoutintoartifacts/manual.txtto get the exact protocol scripts and utility descriptions. - Probed
/model/{n},/alice,/bob, and the/util/*helpers to confirm live message semantics. - Confirmed several implementation quirks that matter for solving:
asym_encryptworks with the public key even though the PDF text is misleading.asym_signrequires the private key.asym_decryptworks for generated key pairs but not for entity public keys from the live service.- Alice and Bob validate message shape strictly, not just content.
Hypotheses & Approach
- Hypothesis 1: most stages could be solved by directly following the protocol transcript while substituting chosen names, keys, or ciphertexts where the script is weak.
- Hypothesis 2: the harder stages would need targeted debugging against the live API because the manual is not a complete description of the implementation.
- Approach: build one reusable Python client in
artifacts/solve_protocol_analysis.py, then add focused debug scripts for stages 6, 8, and 9 where the live behavior diverged from the first-pass interpretation.
Execution Steps (Reproducible)
Setup
Commands:
cd /root/dawg2026CTF/protocol_analysis
pdftotext -layout starting_files/Protocol_Analysis_chals.pdf artifacts/manual.txt
python3 artifacts/solve_protocol_analysis.py
Results:
artifacts/manual.txtcontains the nine protocol descriptions in plain text.artifacts/solve_protocol_analysis.pynow recovers all nine flags in one run.
Stage 1
- Relay Alice's plaintext request directly to Bob and read the returned flag.
Stage 2
- Bob only checks the claimed sender name in the request, so send the request directly to Bob with
n:charliein Alice's place.
Stage 3
- Bob has no initiator on the other side, so send the expected request directly to Bob and collect the response.
Stage 4
- Ask Alice for the symmetric key and nonce, relay the request to Bob, then decrypt Bob's ciphertext with
/util/sym_decrypt.
Stage 5
- Generate an attacker key pair, swap Alice's advertised public key for the attacker public key, forward the message to Bob, and decrypt Bob's response with the attacker private key.
Stage 6
- Impersonate
mallorywith a valid attacker key/cert to get Alice to encrypt her initial message to the attacker. - Re-encrypt that plaintext to Bob, forward Bob's reply back through Alice, and relay the recovered nonce back to Bob.
- The final ciphertext is decrypted with:
- key =
hash_text(n_a + n_b) - nonce = first 24 hex characters of that hash
- key =
- This derivation was confirmed in
artifacts/debug_stage6.pybefore being folded into the main solver.
Stage 7
- Generate a Mallory key pair and cert.
- Use Bob's returned
n_band Alice's originaln_ato signn:mallory|d:<n_b>|d:<n_a>with the Mallory private key. - Send the forged identity bundle to Alice, then relay Alice's final signature to Bob.
Stage 8
- Start both sides to obtain
alice_startandbob_start. - Send
alice_start|d:<chosen_nonce>to Bob to obtain Bob's nonce. - Reflect Bob's own opening tuple into Alice by sending
bob_start|d:<bob_nonce>to Alice. - Relay Alice's response directly back to Bob.
- The live implementation accepts the signature only in this form; using
alice_startin both legs causesInvalid sig.
Stage 9
- Generate a Mallory key pair and valid cert.
- Ask Bob for his doubly encrypted flag blob by sending Alice's initial message.
- Use Alice as a decryption/re-encryption oracle twice:
- wrap the Bob ciphertext as
d:<cipher>|n:mallory - encrypt that wrapper to Alice's public key
- send
k:<mallory_pub>|n:mallory|d:<mallory_cert>|d:<wrapped>|n:aliceto Alice - decrypt Alice's returned Mallory-layer ciphertext twice with the attacker key
- wrap the Bob ciphertext as
- The first unwrap exposes Bob's inner Alice-layer ciphertext; repeating the same oracle flow on that inner ciphertext yields the final plaintext flag.
- The trailing
|n:alicefield is required. Without it, Alice rejects the crafted message as structurally invalid.
Artifacts Produced
artifacts/manual.txt: extracted protocol manual.artifacts/solve_protocol_analysis.py: final end-to-end solver for stages 1 through 9.artifacts/debug_stage6.py: stage-6 key-derivation debugger used to verify the finalhash_text-based decrypt.artifacts/debug_stage8.py: stage-8 debugger used to confirm the working reflection path.artifacts/debug_stage9.py: stage-9 oracle debugger used to confirm the required message shape and two-round unwrap.
Flag
stage 1: DawgCTF{PR0T0C0LS_R_3ZPZ}
stage 2: DawgCTF{CH4NG3_0F_PL4N5}
stage 3: DawgCTF{N0_0N3_3LS3_H0M3}
stage 4: DawgCTF{N0T_S0_S3CR3T_K3Y}
stage 5: DawgCTF{C3RT1F13D_1NS3CUR3}
stage 6: DawgCTF{FORM3RLY_S3CUR3}
stage 7: DawgCTF{F33L1NG_1NS3CUR3}
stage 8: DawgCTF{4SK_4ND_U_SH4LL_R3C31V3}
stage 9: DawgCTF{ST4R3_1NTO_TH3_VO1D}