Given many private keys, identify which one corresponds to a TLS connection captured in a PCAP file.
Vulnerability
The TLS certificate in the handshake reveals the public key (modulus). By comparing the public key’s modulus with all available private keys, the correct one can be identified.
Solution
Step 1: Extract the Certificate from PCAP
Analyze the PCAP file:
tshark -r encrypted.pcap 1 0.000000 172.17.0.3 → 172.17.0.2 TCP 74 39618 → 8443 [SYN] Seq=0 Win=29200 Len=0 MSS=1460 SACK_PERM TSval=1540683717 TSecr=0 WS=128 2 0.000020 172.17.0.2 → 172.17.0.3 TCP 74 8443 → 39618 [SYN, ACK] Seq=0 Ack=1 Win=28960 Len=0 MSS=1460 SACK_PERM TSval=2839138450 TSecr=1540683717 WS=128 3 0.000053 172.17.0.3 → 172.17.0.2 TCP 66 39618 → 8443 [ACK] Seq=1 Ack=1 Win=29312 Len=0 TSval=1540683717 TSecr=2839138450 4 0.011354 172.17.0.3 → 172.17.0.2 TLSv1 186 Client Hello 5 0.011372 172.17.0.2 → 172.17.0.3 TCP 66 8443 → 39618 [ACK] Seq=1 Ack=121 Win=29056 Len=0 TSval=2839138461 TSecr=1540683728 6 0.011884 172.17.0.2 → 172.17.0.3 TLSv1.2 1046 Server Hello, Certificate, Server Hello Done 7 0.011928 172.17.0.3 → 172.17.0.2 TCP 66 39618 → 8443 [ACK] Seq=121 Ack=981 Win=31232 Len=0 TSval=1540683729 TSecr=2839138461 8 0.012436 172.17.0.3 → 172.17.0.2 TLSv1.2 384 Client Key Exchange, Change Cipher Spec, Encrypted Handshake Message 9 0.015465 172.17.0.2 → 172.17.0.3 TLSv1.2 117 Change Cipher Spec, Encrypted Handshake Message 10 0.015676 172.17.0.3 → 172.17.0.2 TLSv1.2 289 Application Data 11 0.016161 172.17.0.2 → 172.17.0.3 TLSv1.2 145 Application Data, Encrypted Alert 12 0.016381 172.17.0.3 → 172.17.0.2 TLSv1.2 97 Encrypted Alert 13 0.016847 172.17.0.2 → 172.17.0.3 TCP 66 8443 → 39618 [FIN, ACK] Seq=1111 Ack=693 Win=31104 Len=0 TSval=2839138466 TSecr=1540683733 14 0.017436 172.17.0.3 → 172.17.0.2 TCP 66 39618 → 8443 [FIN, ACK] Seq=693 Ack=1112 Win=31232 Len=0 TSval=1540683734 TSecr=2839138466 15 0.017446 172.17.0.2 → 172.17.0.3 TCP 66 8443 → 39618 [ACK] Seq=1112 Ack=694 Win=31104 Len=0 TSval=2839138467 TSecr=1540683734
❯ sudo python scrapy2.pyEther / IP / TCP 172.17.0.3:39618 > 172.17.0.2:pcsync_https SEther / IP / TCP 172.17.0.2:pcsync_https > 172.17.0.3:39618 SAEther / IP / TCP 172.17.0.3:39618 > 172.17.0.2:pcsync_https AEther / IP / TCP 172.17.0.3:39618 > 172.17.0.2:pcsync_https PA / RawEther / IP / TCP 172.17.0.2:pcsync_https > 172.17.0.3:39618 AEther / IP / TCP 172.17.0.2:pcsync_https > 172.17.0.3:39618 PA / RawEther / IP / TCP 172.17.0.3:39618 > 172.17.0.2:pcsync_https AEther / IP / TCP 172.17.0.3:39618 > 172.17.0.2:pcsync_https PA / RawEther / IP / TCP 172.17.0.2:pcsync_https > 172.17.0.3:39618 PA / RawEther / IP / TCP 172.17.0.3:39618 > 172.17.0.2:pcsync_https PA / RawEther / IP / TCP 172.17.0.2:pcsync_https > 172.17.0.3:39618 PA / RawEther / IP / TCP 172.17.0.3:39618 > 172.17.0.2:pcsync_https PA / RawEther / IP / TCP 172.17.0.2:pcsync_https > 172.17.0.3:39618 FAEther / IP / TCP 172.17.0.3:39618 > 172.17.0.2:pcsync_https FAEther / IP / TCP 172.17.0.2:pcsync_https > 172.17.0.3:39618 ANoneIn the TLSv1.2 handshake, the server sends its X.509 certificate containing the RSA public key. In RSA, the public and private keys share the same modulus (N).
Step 3: Match the Modulus Against Available Keys
#!/usr/bin/env python3import globimport os
from cryptography import x509from cryptography.hazmat.backends import default_backendfrom cryptography.hazmat.primitives import serializationfrom cryptography.hazmat.primitives.asymmetric import rsafrom cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey, RSAPublicKeyfrom scapy import dadictfrom scapy.all import *from scapy.all import Raw, load_layer, rdpcapfrom scapy.layers.inet import IP, TCPfrom scapy.layers.tls.all import TLSfrom scapy.layers.tls.cert import Certfrom scapy.layers.tls.handshake import TLSCertificate
PCAP_FILE = "~/Downloads/encrypted.pcap"KEYS = "~/Downloads/keys/"
71 collapsed lines
load_layer("tls")
def find_cert(pcap_path): packets = rdpcap(pcap_path) cert_list = []
for pkt in packets: if pkt.haslayer(Raw): raw = pkt[Raw].load try: tls_parsed = TLS(raw) tls_cert_layer = tls_parsed.getlayer(TLSCertificate) if tls_cert_layer: print("[-] Found TLS Certificate.") cert = tls_cert_layer.certs for _, x509_wrapper in cert: cert_list.append(x509_wrapper) print("[-] Extracted a TLS Certificate.") else: print("[-] Found TLS packet.") except Exception as e: print(f"[-] Error: {e}") continue return cert_list
def find_matching_private_key(target_modulus: int, keys_directory: str) -> str | None: print(f"[*] Searching for matching private key in {keys_directory} ...")
for key_file in glob(os.path.join(keys_directory, "*")): try: with open(key_file, "rb") as f: private_key = serialization.load_pem_private_key( f.read(), password=None, backend=default_backend() )
# 类型守卫 if isinstance(private_key, rsa.RSAPrivateKey): priv_modulus = private_key.private_numbers().public_numbers.n if priv_modulus == target_modulus: print(f"[!] BINGO! Matching key found: {key_file}") return key_file except Exception: pass # 静默忽略无法解析的非私钥文件
print("[-] No matching key found.") return None
def get_modulus(cert_list): for cert_obj in cert_list: assert isinstance(cert_obj, Cert) pubkey_bytes = cert_obj.pubKey.der
try: public_key = serialization.load_der_public_key(pubkey_bytes)
if not isinstance(public_key, rsa.RSAPublicKey): print("[-] Not an RSA public key.") continue
# 3. 提取 Modulus (N) return public_key.public_numbers().n
except ValueError as e: print(f"[-] Failed to parse public key: {e}") return None
if __name__ == "__main__": cert_list = find_cert(PCAP_FILE) print(cert_list) modulus = get_modulus(cert_list) print(modulus)
if modulus: key_path = find_matching_private_key(modulus, KEYS) print(key_path)[-] Found TLS packet.[-] Found TLS Certificate.[-] Extracted a TLS Certificate.[-] Found TLS packet.[-] Found TLS packet.[-] Found TLS packet.[-] Found TLS packet.[-] Found TLS packet.[[X.509 Cert. Subject:/C=US/O=247CTF/OU=net100/CN=127.0.0.1, Issuer:/C=US/O=247CTF/OU=net100/CN=127.0.0.1]]24375337507354185617683779511918376432395633773833258410072584644021662441320894249037929602005866714516839751200216248045601121213022308135749564916090322330510313789302011878737635007141238642608330674799687491219836380635051594979735139913778930760482304629127463894104095977393231921733716935219456820531184966698569926343529826731679983703699661127268166976130655343262551319154149117737934195299072504559958824431923696779561829484560357990470602203287915925979746025998473724658331937707347236978200912995652850373393105186043638902549716830185403626256394732792288094019164336124309631409843886613660844833927[*] Searching for matching private key in ~/Downloads/keys/ ...[!] BINGO! Matching key found: ~/Downloads/keys/518dfdb269ef17a932a893a63630644c.key~/Downloads/keys/518dfdb269ef17a932a893a63630644c.key
[Process exited 0]Step 4: Import Key into Wireshark
Open Wireshark → Preferences → RSA Keys → Import the matching key to decrypt the TLS traffic.
247CTF{[flag redacted]}