如何用Python构建ICMP报文?基于Scapy实现ICMP/DNS隐式传文件
Hey there! Let's break this down step by step since you're working on a class assignment with Scapy—super cool project idea, by the way. I'll cover both building ICMP packets and walking you through the basics of covert file transfer using ICMP/DNS payloads.
First, make sure you've got Scapy installed:
pip install scapy
Scapy makes packet building ridiculously straightforward—you just stack layers. Here's a simple example to build and send an ICMP Echo Request (ping) packet:
from scapy.all import IP, ICMP, send # Build the packet: IP layer + ICMP layer ip_layer = IP(dst="192.168.1.1") # Replace with your target IP icmp_layer = ICMP() # Default is Echo Request (type 8, code 0) packet = ip_layer / icmp_layer # Optional: Add custom payload to the ICMP packet packet = packet / "This is my custom ICMP payload!" # Send the packet send(packet, verbose=0) # verbose=0 turns off extra output
To receive ICMP packets and inspect their content, use Scapy's sniff() function:
from scapy.all import sniff, ICMP def icmp_callback(packet): if packet.haslayer(ICMP): # Check if it's an Echo Reply (type 0) if packet[ICMP].type == 0: print(f"Received ICMP reply from {packet[IP].src}") print(f"Payload: {packet[ICMP].payload.load.decode('utf-8', errors='ignore')}") # Start sniffing ICMP packets (requires elevated privileges) sniff(prn=icmp_callback, filter="icmp", store=0)
Note: On Linux/macOS, run this with sudo; on Windows, open your terminal as Administrator—raw packet access needs elevated permissions.
Your project's core logic is solid: split a file into chunks, embed each chunk into a packet payload, send them over, then reassemble the file on the receiving end. Let's start with ICMP (it's simpler) then touch on DNS.
2.1 ICMP File Transfer: Sender Side
The sender needs to:
- Read the target file in small chunks
- Assign a sequence number to each chunk (so the receiver can reorder correctly)
- Pack the sequence number + chunk data into the ICMP payload
- Send each packet to the receiver's IP
- Send a "termination" packet to signal the end of transfer
Here's a minimal working example:
from scapy.all import IP, ICMP, send import struct def send_file_via_icmp(file_path, dst_ip, chunk_size=100): with open(file_path, "rb") as f: seq_num = 0 while True: chunk = f.read(chunk_size) if not chunk: break # End of file reached # Pack sequence number (4 bytes, big-endian) + chunk data payload = struct.pack(">I", seq_num) + chunk # Build and send the packet packet = IP(dst=dst_ip) / ICMP() / payload send(packet, verbose=0) seq_num += 1 # Send termination packet (seq_num = -1 encoded as 0xFFFFFFFF) termination_payload = struct.pack(">I", 0xFFFFFFFF) send(IP(dst=dst_ip) / ICMP() / termination_payload, verbose=0) # Usage: Replace with your file path and receiver IP send_file_via_icmp("secret_file.txt", "192.168.1.100")
2.2 ICMP File Transfer: Receiver Side
The receiver needs to:
- Sniff incoming ICMP packets
- Extract the sequence number and chunk data from each payload
- Store chunks in a dictionary keyed by sequence number
- Once the termination packet is received, sort chunks and write to file
Example code:
from scapy.all import sniff, ICMP, IP import struct received_chunks = {} def icmp_file_receiver(packet): global received_chunks # Listen for Echo Requests (we're using these to carry data) if packet.haslayer(ICMP) and packet[ICMP].type == 8: payload = packet[ICMP].payload.load # Unpack sequence number (first 4 bytes of payload) seq_num = struct.unpack(">I", payload[:4])[0] if seq_num == 0xFFFFFFFF: # Termination signal received—reassemble the file print("Transfer complete! Reassembling file...") with open("received_file.txt", "wb") as f: for seq in sorted(received_chunks.keys()): f.write(received_chunks[seq]) print("File saved as received_file.txt") return True # Stop sniffing after transfer finishes # Store the chunk data (skip the first 4 bytes which are the sequence number) received_chunks[seq_num] = payload[4:] print(f"Received chunk {seq_num}") # Start sniffing ICMP packets sniff(prn=icmp_file_receiver, filter="icmp", store=0, stop_filter=lambda p: icmp_file_receiver(p))
2.3 Quick Notes on DNS-Based Transfer
If you want to use DNS instead, the core idea is similar but uses DNS query/response payloads (like TXT records):
- Sender: Split the file into chunks, encode each chunk as a TXT record string, send DNS queries to a dummy domain (e.g.,
chunk-123.example.comwhere123is the sequence number) - Receiver: Sniff DNS packets, extract the TXT record data from incoming queries/responses, then reassemble the file
Here's a quick snippet to build a DNS query with a custom payload using Scapy:
from scapy.all import IP, UDP, DNS, DNSQR # Embed chunk data in the DNS query's QNAME chunk_data = "a1b2c3" dns_query = IP(dst="8.8.8.8") / UDP(dport=53) / DNS(rd=1, qd=DNSQR(qname=f"{chunk_data}.example.com")) send(dns_query, verbose=0)
Note: DNS has stricter payload limits (TXT records max 255 bytes per string, QNAME has length restrictions), so you'll need smaller chunks or split data across multiple records.
Key Tips for Your Assignment
- Test in a controlled environment: Firewalls often block ICMP or unsolicited DNS traffic—use a local network (e.g., two VMs or your own machine sending to itself)
- Add error checking: Include checksums for each chunk to detect corrupted data
- Handle retransmissions: Optional, but adding a way for the receiver to request missing chunks will make your project more robust
- Document your code: Since it's a class assignment, explain each part clearly so your instructor understands your logic
内容的提问来源于stack exchange,提问作者EnnaGrigor

