Category: Cryptography
Flag: EH4X{Cav1aR_c1Gar3TT3s_Ch3bYsH3V_05091946}
Challenge Description
She keeps her Moët et Chandon in her pretty cabinet.
Nothing she offers is accidental. Nothing she withholds is without reason.
Recommended at the price, insatiable an appetite — wanna try?
Analysis
unzip -l "Handout.zip"Archive: Handout.zip... Handout/Killer-Queen/Freddie.txt Handout/Killer-Queen/Lyrics/ciggarettes.txt Handout/Killer-Queen/Lyrics/dynamite.txt Handout/Killer-Queen/Lyrics/fibonacci.txt Handout/Killer-Queen/Lyrics/laserbeam.txt Handout/Killer-Queen/Notes/antoinette.md Handout/Killer-Queen/Notes/biography.md Handout/Killer-Queen/Notes/curves.md Handout/Killer-Queen/Notes/sheet_music.mdThe handout immediately looked like a guided crypto puzzle, with the notes explicitly pointing to Chebyshev composition and a two-layer lock. That matters because it shifts the approach from “break one huge primitive” to “recover the intended pipeline and replay it cleanly.”
rg -n "Chebyshev|T_m\(T_n\(x\)\)|first door|second door|SHA-256|unwrap|two voices|index" "/home/LIGHT/Downloads/killerqueen_work/Handout/Killer-Queen".../Lyrics/fibonacci.txt:17: T_m(T_n(x)) = T_{m·n}(x).../Notes/sheet_music.md:58: ♩ The stream is locked behind two doors..../Notes/sheet_music.md:59: ♩ The first door opens with the index..../Notes/sheet_music.md:60: ♩ The second door is built in blocks.../Notes/sheet_music.md:63: ♩ SHA-256 derives the second key.../Notes/sheet_music.md:72:• The Queen speaks in two voices.../Notes/sheet_music.md:81:You'll need to unwrap what's inside — twice.The service behavior matched those hints exactly: one query gives two related voice values for the same index, and public iv/ciphertext are fixed for that session.

printf '0\n1\n2\n3\n4\n5\nexit\n' | nc 20.244.7.184 7331...p = 7073193951809819973664306187302601643156849222029017483853417297144476949947829139698672438579337709916095808633124126138796016767532929021864211602000001v = 5587994941705590424272649068295916108274072829805484356511387258719009236678476179597670504915988561703594735329013208151470008715612024345889541886986304iv = f9c60e2a62756a6ebcd59c589dfd61b6ciphertext = ad218e83bffe076fbceeddc17f540cd3089e3c4e6309a0e63c7f7cbed1c8b0f9fd2aced60c79a00de855acb4d5047bcd
[20 left] q> pretty_cabinet = 1 moet_chandon = 1
[19 left] q> pretty_cabinet = 5587994941705590424272649068295916108274072829805484356511387258719009236678476179597670504915988561703594735329013208151470008715612024345889541886986304 moet_chandon = 6224016679887966716101625712054632071019252852377882598120412242331086101200983099281554722925606107557863470191553080919315116795993075331627024609220227...From there the final solve path was to derive material from moet_chandon(q) in index order, decrypt the AES-CBC outer layer, unpad, then do the second unwrap as a blockwise XOR stream where each 16-byte block is SHA256(str(moet_chandon(i)))[:16]. I tried heavier DLP-centric routes first, but those dead ends were useful because they confirmed the challenge was more about composition and serialization correctness than defeating the largest subgroup factor.
python3.12 "/home/LIGHT/Downloads/killerqueen_work/Handout/Killer-Queen/solve_killerqueen.py"[+] p bits: 512[+] v: 11305618717155759150724013649828687503261898264977063258438514099154408236632078431105146562036594498971222989731073226625866287035766764084910955827909772[+] iv: 83f4896579e6e4a8f29272ae5feef4ff[+] ciphertext bytes: 48[+] plaintext: EH4X{Cav1aR_c1Gar3TT3s_Ch3bYsH3V_05091946}[+] FLAG: EH4X{Cav1aR_c1Gar3TT3s_Ch3bYsH3V_05091946}Running it against the real remote session verified the whole chain end-to-end and produced the real flag in plaintext.
Solution
import reimport socketfrom hashlib import sha256
from Crypto.Cipher import AES
HOST = "20.244.7.184"PORT = 7331
def must_match(pattern: str, data: str, flags: int = 0) -> re.Match[str]: match = re.search(pattern, data, flags) if match is None: raise ValueError(f"pattern not found: {pattern}") return match
def recv_until(sock: socket.socket, marker: bytes) -> bytes: data = b"" while marker not in data: chunk = sock.recv(4096) if not chunk: break data += chunk return data
def parse_public(blob: str): p = int(must_match(r"^\s*p\s*=\s*(\d+)\s*$", blob, re.M).group(1)) v = int(must_match(r"^\s*v\s*=\s*(\d+)\s*$", blob, re.M).group(1)) iv = bytes.fromhex(must_match(r"^\s*iv\s*=\s*([0-9a-f]+)\s*$", blob, re.M).group(1)) ciphertext = bytes.fromhex( must_match(r"^\s*ciphertext\s*=\s*([0-9a-f]+)\s*$", blob, re.M).group(1) ) return p, v, iv, ciphertext
def parse_oracle_reply(blob: str): pretty = int(must_match(r"pretty_cabinet\s*=\s*(\d+)", blob).group(1)) moet = int(must_match(r"moet_chandon\s*=\s*(\d+)", blob).group(1)) return pretty, moet
def pkcs7_unpad(data: bytes) -> bytes: pad = data[-1] if pad < 1 or pad > 16 or data[-pad:] != bytes([pad]) * pad: raise ValueError("invalid PKCS#7 padding") return data[:-pad]
def main() -> None: with socket.create_connection((HOST, PORT), timeout=10) as sock: sock.settimeout(5) banner = recv_until(sock, b"q> ").decode(errors="replace") p, v, iv, ciphertext = parse_public(banner)
print(f"[+] p bits: {p.bit_length()}") print(f"[+] v: {v}") print(f"[+] iv: {iv.hex()}") print(f"[+] ciphertext bytes: {len(ciphertext)}")
moet_values = {} for q in range(1, 21): sock.sendall(f"{q}\n".encode()) reply = recv_until(sock, b"q> ").decode(errors="replace") if "yawns" in reply or "Goodbye" in reply: break _, moet = parse_oracle_reply(reply) moet_values[q] = moet
outer_key = sha256(str(moet_values[1]).encode()).digest()[:16] inner = AES.new(outer_key, AES.MODE_CBC, iv).decrypt(ciphertext) inner = pkcs7_unpad(inner)
block_count = (len(inner) + 15) // 16 stream = b"".join( sha256(str(moet_values[i]).encode()).digest()[:16] for i in range(1, block_count + 1) )
plaintext = bytes(a ^ b for a, b in zip(inner, stream)).decode(errors="replace") flag_match = re.search(r"EH4X\{[^}]+\}", plaintext) if flag_match is None: raise RuntimeError(f"flag not found in plaintext: {plaintext!r}")
print(f"[+] plaintext: {plaintext}") print(f"[+] FLAG: {flag_match.group(0)}")
if __name__ == "__main__": main()python3.12 solve.py[+] p bits: 512[+] v: 11305618717155759150724013649828687503261898264977063258438514099154408236632078431105146562036594498971222989731073226625866287035766764084910955827909772[+] iv: 83f4896579e6e4a8f29272ae5feef4ff[+] ciphertext bytes: 48[+] plaintext: EH4X{Cav1aR_c1Gar3TT3s_Ch3bYsH3V_05091946}[+] FLAG: EH4X{Cav1aR_c1Gar3TT3s_Ch3bYsH3V_05091946}