First Time: Scapy
Prerequisites
- Python 2.x
- scapy
- additional libs as needed, see references
- good starting point: Backtrack / Kali
Basics
- idea: stacking of network layers separated by “/”, e.g. “Ether(<options>)/IP(<options)/TCP(options)/”<payload>””
- concept: use reasonable defaults for all values and flags, but allow modification of anything
- why use: “Decode, Do Not Interpret” ([4]); you’d like to see the bits and their meaning, not the interpretation of the bits in a tool (perhpas mis-interpreting the situation and presenting wrong “answers”)
- start: “scapy”, exit: “exit()”
- list of layers / information about layer: “ls()”
- list of procedures / functions: “lsc()”
- works (i.e. sends and receives packets) on layer 2 (data link) and 3 (network)
- object information:
- only modified fields: “<layer>”, e.g. “IP” (or a variable pointing to an object)
- all fields: “<layer>.display()”
- complete packet: “ls(<layer>)”
>>> i=IP() >>> i <IP |> >>> i.display() ###[ IP ]### version= 4 ihl= None tos= 0x0 len= None id= 1 flags= frag= 0 ttl= 64 proto= ip chksum= 0x0 src= 127.0.0.1 dst= 127.0.0.1 options= '' >>> ls(i) version : BitField = 4 (4) ihl : BitField = None (None) tos : XByteField = 0 (0) len : ShortField = None (None) id : ShortField = 1 (1) flags : FlagsField = 0 (0) frag : BitField = 0 (0) ttl : ByteField = 64 (64) proto : ByteEnumField = 0 (0) chksum : XShortField = None (None) src : Emph = '127.0.0.1' (None) dst : Emph = '127.0.0.1' ('127.0.0.1') options : IPoptionsField = '' ('') >>> it=IP()/TCP() >>> ls(it) version : BitField = 4 (4) ihl : BitField = None (None) tos : XByteField = 0 (0) len : ShortField = None (None) id : ShortField = 1 (1) flags : FlagsField = 0 (0) frag : BitField = 0 (0) ttl : ByteField = 64 (64) proto : ByteEnumField = 6 (0) chksum : XShortField = None (None) src : Emph = '127.0.0.1' (None) dst : Emph = '127.0.0.1' ('127.0.0.1') options : IPoptionsField = '' ('') -- sport : ShortEnumField = 20 (20) dport : ShortEnumField = 80 (80) seq : IntField = 0 (0) ack : IntField = 0 (0) dataofs : BitField = None (None) reserved : BitField = 0 (0) flags : FlagsField = 2 (2) window : ShortField = 8192 (8192) chksum : XShortField = None (None) urgptr : ShortField = 0 (0) options : TCPOptionsField = {} ({})
- sending packets:
- layer 2 (select networking options yourself): “sendp()”, “srp()”, “srp1()”, “srploop()”
- layer 3 (let scapy do network magic): “send()”, “sr()”, “sr1()”, “srloop()”
- “sr*()” parameter: timeout (in seconds), retry (e.g. -2, see docs), inter (time in seconds to wait between sendings), iface, filter (BPF filter), multi
Advanced
- implicit packets: packets, with a field or fields containing a set of values, e.g. “IP(dst=”192.168.1.10-15″)/ICMP()”
- hide_defaults(): remove all values which are identical to defaults in output of dissected packet
>>> p=IP(dst="192.168.1.1")/TCP(dport=80) # the default dport in scapy is 80 for TCP >>> p <IP frag=0 proto=tcp dst=192.168.1.1 |<TCP dport=www |>> >>> p.hide_defaults() >>> p <IP frag=0 proto=tcp dst=192.168.1.1 |<TCP |>>
- show(): show packet values before assembling of packet (i.e. w/o checksum, length etc.)
- show2(): show all packet values as computed after assembling the packet
- sprintf(): fill a string with values of the packet
>>> p=IP(dst="192.168.1.1")/TCP(sport=4444,dport=666) >>> p.sprintf("My packet: %IP.src%:%TCP.sport% -> %IP.dst%:%TCP.dport%") 'My packet: 192.168.1.154:4444 -> 192.168.1.1:666'
- objects to hold packet lists: PacketList (for any packets), Dot11PacketList (special list for 802.11 packets), SndRcvList (couple of packets, e.g. from sniff()), TracerouteResult (for traceroute())
- filter a list: “<listname>[<filter>]”, e.g. “pkts[UDP]”
- conversations(): provide a graphical presentation of a conversation
- configuration: “conf”
- “conf.iface”: default output interface
- “hint_iface”: IP address; output interface will be choosen through a lookup into the routing table for this IP
Examples
DNS Query to local Router
- define i as IP(), u as UDP() and d as DNS(), and assign values as follows:
>>> i <IP dst=192.168.1.254 |> >>> u <UDP sport=11111 dport=domain |> >>> d <DNS id=11111 qr=0 opcode=QUERY qdcount=1 qd='\x03www\x05heise\x02de\x00\x00\x01\x00\x01' |>
- note: for DNS details see e.g. [5] (and references therein)
- send packet, and have a look at the answer (wrapped for better readability):
>>> packet = i/u/d >>> packet <IP frag=0 proto=udp dst=192.168.1.254 |<UDP sport=11111 dport=domain |<DNS id=11111 qr=0 opcode=QUERY qdcount=1 qd='\x03www\x05heise\x02de\x00\x00\x01\x00\x01' |>>> >>> res = sr1(packet) Begin emission: Finished to send 1 packets. * Received 1 packets, got 1 answers, remaining 0 packets >>> res <IP version=4L ihl=5L tos=0x0 len=74 id=0 flags=DF frag=0L ttl=64 proto=udp chksum=0xb5ed src=192.168.1.254 dst=192.168.1.103 options='' | <UDP sport=domain dport=11111 len=54 chksum=0xc831 | <DNS id=11111 qr=1L opcode=QUERY aa=0L tc=0L rd=0L ra=1L z=0L rcode=ok qdcount=1 ancount=1 nscount=0 arcount=0 qd=<DNSQR qname='www.heise.de.' qtype=A qclass=IN | > an=<DNSRR rrname='www.heise.de.' type=A rclass=IN ttl=2237 rdata='193.99.144.85' | > ns=None ar=None |>>>
TCP Scanner with Implicit Packets
>>> pkts=IP(dst=["192.168.1.100-110", "www.thierfreund.de"])/TCP(dport=[80,443,8080,8443],sport=44444) >>> ans,unans=sr(pkts, timeout=2) Begin emission: ...[...] ..Finished to send 48 packets. .* Received 86 packets, got 5 answers, remaining 43 packets >>> ans.nsummary() 0000 IP / TCP 192.168.1.154:44444 > 192.168.1.101:www S ==> IP / TCP 192.168.1.101:www > 192.168.1.154:44444 RA / Padding 0001 IP / TCP 192.168.1.154:44444 > 192.168.1.101:https S ==> IP / TCP 192.168.1.101:https > 192.168.1.154:44444 RA / Padding 0002 IP / TCP 192.168.1.154:44444 > 192.168.1.101:http_alt S ==> IP / TCP 192.168.1.101:http_alt > 192.168.1.154:44444 RA / Padding 0003 IP / TCP 192.168.1.154:44444 > 192.168.1.101:8443 S ==> IP / TCP 192.168.1.101:8443 > 192.168.1.154:44444 RA / Padding 0004 IP / TCP 192.168.1.154:44444 > 85.214.99.102:https S ==> IP / TCP 85.214.99.102:https > 192.168.1.154:44444 SA / Padding >>> unans.nsummary() 0000 IP / TCP 192.168.1.154:44444 > 192.168.1.109:https S 0001 IP / TCP 192.168.1.154:44444 > 192.168.1.100:www S 0002 IP / TCP 192.168.1.154:44444 > 192.168.1.106:https S [...]
Improved TCP Scanner
(based on example from [4])
>>> pkts=IP(dst=["192.168.1.100-110", "www.thierfreund.de"])/TCP(dport=[80,443,8080,8443],sport=44444) >>> ans,unans=sr(pkts,timeout=2) Begin emission: ..[..] ...*Finished to send 48 packets. ........[...] eceived 301 packets, got 5 answers, remaining 43 packets >>> ans.nsummary() 0000 IP / TCP 192.168.1.154:44444 > 192.168.1.101:www S ==> IP / TCP 192.168.1.101:www > 192.168.1.154:44444 RA / Padding 0001 IP / TCP 192.168.1.154:44444 > 192.168.1.101:https S ==> IP / TCP 192.168.1.101:https > 192.168.1.154:44444 RA / Padding 0002 IP / TCP 192.168.1.154:44444 > 192.168.1.101:http_alt S ==> IP / TCP 192.168.1.101:http_alt > 192.168.1.154:44444 RA / Padding 0003 IP / TCP 192.168.1.154:44444 > 192.168.1.101:8443 S ==> IP / TCP 192.168.1.101:8443 > 192.168.1.154:44444 RA / Padding 0004 IP / TCP 192.168.1.154:44444 > 85.214.99.102:https S ==> IP / TCP 85.214.99.102:https > 192.168.1.154:44444 SA / Padding >>> ans.make_lined_table(lambda (s,r): (s.dport, s.dst, r.sprintf("%IP.id% %IP.ttl% %TCP.flags% %TCP.seq%"))) --------------+-----------+-------------------+-----------+-----------+ | 80 | 443 | 8080 | 8443 | --------------+-----------+-------------------+-----------+-----------+ 85.214.99.102 | - | 0 54 SA 896441686 | - | - | 192.168.1.101 | 0 64 RA 0 | 0 64 RA 0 | 0 64 RA 0 | 0 64 RA 0 | --------------+-----------+-------------------+-----------+-----------+
Caveats
- scapy is great, but it’s sloooooooow 😉
References
- [1]: Scapy Homepage, Scapy Demo
- [2]: Rob klein Gunnewiek (2005) – “Packet Wizardry: Ruling the Network with Python” [TXT]
- [3]: Adam Maxwell (2013) – “The Very Unofficial Dummies Guide To Scapy” [PDF]
- [4]: various (2007) – “Security Power Tools” [Shop], pp. 136 – 183
- [5]: Gebhard Zocher (2013) – “IT Security Notebook” [Link]