247CTF - Not My Modulus

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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.py
Ether / IP / TCP 172.17.0.3:39618 > 172.17.0.2:pcsync_https S
Ether / IP / TCP 172.17.0.2:pcsync_https > 172.17.0.3:39618 SA
Ether / IP / TCP 172.17.0.3:39618 > 172.17.0.2:pcsync_https A
Ether / IP / TCP 172.17.0.3:39618 > 172.17.0.2:pcsync_https PA / Raw
Ether / IP / TCP 172.17.0.2:pcsync_https > 172.17.0.3:39618 A
Ether / IP / TCP 172.17.0.2:pcsync_https > 172.17.0.3:39618 PA / Raw
Ether / IP / TCP 172.17.0.3:39618 > 172.17.0.2:pcsync_https A
Ether / IP / TCP 172.17.0.3:39618 > 172.17.0.2:pcsync_https PA / Raw
Ether / IP / TCP 172.17.0.2:pcsync_https > 172.17.0.3:39618 PA / Raw
Ether / IP / TCP 172.17.0.3:39618 > 172.17.0.2:pcsync_https PA / Raw
Ether / IP / TCP 172.17.0.2:pcsync_https > 172.17.0.3:39618 PA / Raw
Ether / IP / TCP 172.17.0.3:39618 > 172.17.0.2:pcsync_https PA / Raw
Ether / IP / TCP 172.17.0.2:pcsync_https > 172.17.0.3:39618 FA
Ether / IP / TCP 172.17.0.3:39618 > 172.17.0.2:pcsync_https FA
Ether / IP / TCP 172.17.0.2:pcsync_https > 172.17.0.3:39618 A
None

In 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#!/usr/bin/env python3
import glob
import os

from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey, RSAPublicKey
from scapy import dadict
from scapy.all import *
from scapy.all import Raw, load_layer, rdpcap
from scapy.layers.inet import IP, TCP
from scapy.layers.tls.all import TLS
from scapy.layers.tls.cert import Cert
from scapy.layers.tls.handshake import TLSCertificate

PCAP_FILE = "/home/kita/Downloads/encrypted.pcap"
KEYS = "/home/kita/Downloads/keys/"

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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[-] 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]]
24375337507354185617683779511918376432395633773833258410072584644021662441320894249037929602005866714516839751200216248045601121213022308135749564
91609032233051031378930201187873763500714123864260833067479968749121983638063505159497973513991377893076048230462912746389410409597739323192173371
69352194568205311849666985699263435298267316799837036996611272681669761306553432625513191541491177379341952990725045599588244319236967795618294845
60357990470602203287915925979746025998473724658331937707347236978200912995652850373393105186043638902549716830185403626256394732792288094019164336
124309631409843886613660844833927
[*] Searching for matching private key in /home/kita/Downloads/keys/ ...
[!] BINGO! Matching key found: /home/kita/Downloads/keys/518dfdb269ef17a932a893a63630644c.key
/home/kita/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{3693df4d4c007662b7131e7b94c9e9c3}