Scapy - examples / usage
A customer asked me at some point if we could evaluate how the CoPP DDOS automated filters on a Juniper MX are triggered and how fast they respond to different types of packets. As such I needed to craft custom traffic. I was not very good at coming up with it for Ostinato or Spirent so I used Scapy to craft the packet. Afterward I took the packet hexdump and input it into Spirent / Ostinato as what the streams from there should generate (that hexdump has indeed all data needed, source/dst addresses included which meant of course that it needs to match what Spirent thinks it has on the interface facing the Juniper device).
Examples of generated packets:
- OSPFv3 IPSEC Encrypted Packets
- BGP Open Packet
- BGP IPv6 Open Packet
- IPSEC ESP Packet
- BGP Update Packet
- ICMP Echo Request
- BFD echo
Scapy did not really work as initially expected. I needed a specific version of one of its dependencies or else it would fail upon installing. /usr/local/Cellar/python/2.7.12/bin/pip2.7 install pyx==0.12.1
My examples also show 1-2-3 ways of doing the same thing (you will see that I did not respect the same pattern/way of generating packets all the time).
OSPFv3 IPSEC encrypted packet
$ sudo scapy
>>> load_contrib("ospf")
>>> sa = SecurityAssociation(ESP, spi=0x00000100, crypt_algo='AES-CBC',crypt_key='sixteenbytes key')
>>> p6 = IPv6(src='fe80::2', dst='FF02::5')
>>> p6 = p6/OSPFv3_Hdr(src='11.11.11.2',area='0.0.0.0')
>>> p6 = p6/OSPFv3_Hello(router='11.11.11.2',neighbors='11.11.11.1')
>>> p6 = p6/OSPFv3_Hello(router='11.11.11.2',neighbors='11.11.11.1')
>>> p6 /= Raw('testdata')
>>> p6 = IPv6(str(p6))
>>> e6 = sa.encrypt(p6)
>>> send(e6,count=1)
or copying the hexdump of a packet from Wireshark, then insert it into a Spirent or Ostinato stream (not defining any other stream properties as this hexdump has all the data). Alternatively, you might need to change the SRC Mac / DST Mac address in the hex output (quite easy to spot the hex representation of the actual mac address which is known by the user).:
BGP Open Packet
m scapy.all import *
>>> pkt = Ether(dst="00:10:94:00:00:05",src="00:26:88:5f:7a:95")/IP(src="11.11.11.2",dst="11.11.11.1")/TCP(sport=1025,dport=179)/BGPOpen(AS=65199,hold_time=90,bgp_id="11.11.11.2")
>>> pkt.show()
###[ Ethernet ]###
dst = 00:10:94:00:00:05
src = 00:26:88:5f:7a:95
type = 0x800
###[ IP ]###
version = 4
ihl = None
tos = 0x0
len = None
id = 1
flags =
frag = 0
ttl = 64
proto = tcp
chksum = None
src = 11.11.11.2
dst = 11.11.11.1
\options \
###[ TCP ]###
sport = blackjack
dport = bgp
seq = 0
ack = 0
dataofs = None
reserved = 0
flags = S
window = 8192
chksum = None
urgptr = 0
options = {}
###[ BGP Open Header ]###
version = 4
AS = 65199
hold_time = 90
bgp_id = 11.11.11.2
opt_parm_len= None
\opt_parm \
>>> wrpcap('/tmp/bgpopen.pcap',pkt)
and then with wireshark
OR
>>> linehexdump(pkt)
00 10 94 00 00 05 00 26 88 5F 7A 95 08 00 45 00 00 32 00 01 00 00 40 06 4E AD 0B 0B 0B 02 0B 0B 0B 01 04 01 00 B3 00 00 00 00 00 00 00 00 50 02 20 00 43 F7 00 00 04 FE AF 00 5A 0B 0B 0B 02 00 .......&._z...E..2....@.N.....................P. .C.......Z.....
(eliminate spaces, take just the hex, copy paste in spirent -> create new frame -> custom)
BGP IPv6 Open Packet
>>> pkt = Ether(dst="00:10:94:00:00:05",src="00:26:88:5f:7a:95")/IPv6(src="2001:db8::2",dst="2001:db8::1")/TCP(sport=1025,dport=179)/BGPOpen(AS=65199,hold_time=90,bgp_id="11.11.11.2")
>>> linehexdump(pkt)
00 10 94 00 00 05 00 26 88 5F 7A 95 86 DD 60 00 00 00 00 1E 06 40 20 01 0D B8 00 00 00 00 00 00 00 00 00 00 00 02 20 01 0D B8 00 00 00 00 00 00 00 00 00 00 00 01 04 01 00 B3 00 00 00 00 00 00 00 00 50 02 20 00 14 9B 00 00 04 FE AF 00 5A 0B 0B 0B 02 00 .......&._z...`......@ ............... ...........................P. .........Z.....
put in a file, /tmp/t
# cat /tmp/p | tr -d " "
0010940000050026885F7A9586DD60000000001E064020010DB800000000000000000000000220010DB8000000000000000000000001040100B3000000000000000050022000149B000004FEAF005A0B0B0B0200
IPSEC ESP Packet
a = SecurityAssociation(ESP, spi=0xdeadbeef, crypt_algo='AES-CBC',crypt_key='sixteenbytes key')
>>> print sa
<scapy.layers.ipsec.SecurityAssociation object at 0x10638d510>
>>> p = IP(src='1.1.1.1', dst='2.2.2.2')
>>> p /= TCP(sport=45012, dport=80)
>>> p /= Raw('testdata')
>>> print p
>>> p = IP(str(p))
>>> e = sa.encrypt(p)
>>> send(e)
>>> send(e,count=20000)
This last example also sends (like a flood) such packets out. In real-life though a laptop or normal compute would not scale at generating traffic/flooding for security purpose testing a network router with enough capacity.
IPSEC ESP Packet
load_contrib("ospf")
sa = SecurityAssociation(ESP, spi=0x00000100, crypt_algo='AES-CBC',crypt_key='sixteenbytes key')
p6 = IPv6(src='fe80::3ac9:86ff:fe43:a72a', dst='FF02::5')
p6 = p6/OSPFv3_Hdr(src='127.0.0.15',area='0.0.0.0')
p6 = p6/OSPFv3_Hello(router='127.0.0.15',neighbors='127.0.0.10')
p6 /= Raw('testdata')
p6 = IPv6(str(p6))
e6 = sa.encrypt(p6)
send(e6,count=20000)
BGP Update Packet
a) /usr/local/lib/python2.7/site-packages/scapy/config.py
add bgp here
load_layers = ["l2", "inet", "dhcp", "dns", "dot11", "gprs", "tls",
"hsrp", "inet6", "ir", "isakmp", "l2tp", "mgcp",
"mobileip", "netbios", "netflow", "ntp", "ppp",
"radius", "rip", "rtp", "skinny", "smb", "snmp",
"tftp", "x509", "bluetooth", "dhcp6", "llmnr",
"sctp", "vrrp", "ipsec", "lltd", "vxlan", "bgp"]
b) copy it from contrib into the layers folder
mtanasescu-mbp:2.7 mtanasescu$ cp .//lib/python/site-packages/scapy/contrib/bgp.py .//lib/python/site-packages/scapy/layers/
/Users/mtanasescu/Library/Python/2.7/lib/python/site-packages/scapy/contrib/bgp.py
c) >>> from scapy.all import send, IP, ICMP, UDP, Raw, TCP
pkt = IP()/TCP()/BGPOpen()
>>> p = IP(src='10.10.10.2', dst='10.10.10.1')
>>> p /= TCP(sport=49152,dport=179)
// how to assign values - if multiple layers then IP()/Ether( ... value)..
// e=Ether(dst='02:35:b9:f4:00:01')
// i1=IP(dst='10.0.0.0',flags='MF')
// i2=IP(dst='10.0.0.0',frag=60,proto=17)
// OR
>>> pkt.dport="bgp"
WORKING PART
>>> pkt.src="10.10.10.2"
>>> pkt.dst="10.10.10.1"
>>> pkt.sport=900
>>> pkt.dport=179
>>> pkt.AS=65199
>>> pkt.hold_time=90
>>> pkt.bgp_id="10.10.10.2"
>>> pkt.show()
###[ IP ]###
version= 4
ihl= None
tos= 0x0
len= None
id= 1
flags=
frag= 0
ttl= 64
proto= tcp
chksum= None
src= 10.10.10.2
dst= 10.10.10.1
\options\
###[ TCP ]###
sport= omginitialrefs
dport= bgp
seq= 0
ack= 0
dataofs= None
reserved= 0
flags= S
window= 8192
chksum= None
urgptr= 0
options= {}
###[ BGP Open Header ]###
version= 4
AS= 65199
hold_time= 90
bgp_id= 10.10.10.2
opt_parm_len= None
\opt_parm\
#LAYER-2 ..but if it is not resolved or something..it goes to nirvana
>>> sendp(pkt,iface="en3",loop=True,inter=0.001)
#LAYER-3 uses already layer-2 on the interface configured ..=> we need proper IPs and config from the OS, this is why interface cannot be specified
>>> send(pkt,loop=True,inter=0.001)
ICMP Echo Request
cmp = ICMP()
# set the destinaion IP to the target
ip.dst = ip_target
# create 2 casade loops for sending the icmp packet
for i_type in range(0,256):
for i_code in range(0,256):
icmp.type = i_type
icmp.code = i_code
print i_type,i_code
send(ip/icmp)
type8, code 0 for echo request
OR
#! /usr/bin/env python
from scapy.all import send, IP, ICMP
>>> send(IP(src="10.10.10.2",dst="10.10.10.1")/ICMP()/"Hello World",loop=True,inter=0.00000001)
SEND vs SENDP
In scapy, the send() function will send packets at layer 3. That is to say it will handle routing and layer 2 for you. >» send(IP(dst=“1.2.3.4”)/ICMP())
The sendp() function will work at layer 2. It’s up to you to choose the right interface and the right link layer protocol. >» sendp(Ether()/IP(dst=“1.2.3.4”,ttl=(1,4)), iface=“eth1”)
BFD echo
(or sort of)
>>> from scapy.all import send, IP, ICMP, UDP, Raw
>>> p = IP(src='1.1.1.1', dst='2.2.2.2')
>>> p /= UDP(sport=49152, dport=3785)
>>> p /= Raw('000000000a0a0a0100000239')
>>> print p
>>> p = IP(str(p))
>>> send(p,count=20000)
Read PCAP with Scapy
>» a=rdpcap("/spare/captures/isakmp.cap")
Display packet made with Scapy
#!/usr/bin/python
from scapy.all import *
e=Ether(dst='02:35:b9:f4:00:01')
i1=IP(dst='10.0.0.0',flags='MF')
i2=IP(dst='10.0.0.0',frag=60,proto=17)
i3=IP(dst='10.0.0.0')
u=UDP(sport=1024,dport=1024)
p1=e/i1/u/pl
p1.show()