Scapy

Z WiKi AIRdump.CZ

Přejít na: navigace, hledání

Scapy je interaktivní nástroj pro manipulaci paketů, generátor paketůr, síťový scanner, analytický nástroj, paketový sniffer. Tímto nástrojem je možné nahradit řadu nástrojů jako hping, nmap, arpspoof, arp-sk, arping, tcpdump, tethereal, p0f a další. Scapy používá python interpreter jako základ pro příkazy. To znamená, že lze přímo používat jazyk python (assign variables, loops, definované funkce atd). If you give a file as parameter when you run scapy, your session (variables, functions, intances, ...) will be saved when you leave the interpretor, and restored the next time you launch scapy.

Scapy is not user proof yet. But it is almost reliable. Some more things need to be done to support more platforms.

The idea is simple. Those kind of tools do two things : sending packets and receiving answers. That's what scapy does : you define a set of packets, it sends them, receives answers, matches requests with answers and returns a list of packet couples (request, answer) and a list of unmatched packets. This has the big advantage over tools like nmap or hping that an answer is not reduced to (open/closed/filtered), but is the whole packet.


On top of this can be build more high level functions, for example one that does traceroutes and give as a result only the start TTL of the request and the source IP of the answer. One that pings a whole network and gives the list of machines answering. One that does a portscan and returns a LaTeX report.

Demo Interaktivní session

If you are new to python and you really don't understand a word because of that, or if you want to learn this language, take an hour to read the very good tutorial from Guido Van Rossum zde. After that, you'll know python :) (really!)

First, we introduce the Net class, which implicitely defines a set of IP addresses. Note that this class does not need to be used to give set of addresses as parameters, it will be automatically used. We also see that sessions work :)

# ./scapy.py -s mysession
New session [mysession]
Welcome to Scapy (0.9beta)
>>> Net("192.168.1.0/24")
<Net 192.168.1.0/24>
>>> target=Net("www.target.com")
>>> targetnet=Net("www.target.com/30")
>>> [ip for ip in targetnet]
['173.29.39.100', '173.29.39.101', '173.29.39.102', '173.29.39.103']
>>> ^D

# ./scapy.py -s mysession
Using session [mysession]
Welcome to Scapy (0.9beta)
>>> target
<Net www.target.com>

The configuration is hold into a variable named conf, that is saved with the session.

>>> conf
L2listen   = <class scapy.L2ListenSocket at 0x83a77dc>
L2socket   = <class scapy.L2Socket at 0x83aabbc>
L3socket   = <class scapy.L3PacketSocket at 0x83aa8cc>
checkIPID  = 1
checkIPsrc = 1
debug_match = 0
except_filter = ''
histfile   = '/home/pbi/.scapy_history'
iface      = 'eth0'
nmap_base  = '/usr/share/nmap/nmap-os-fingerprints'
p0f_base   = '/etc/p0f.fp'
padding    = 1
promisc    = 'not implemented'
queso_base = '/etc/queso.conf'
route      = Network         Netmask         Gateway         Iface
127.0.0.0       255.0.0.0       0.0.0.0         lo
192.168.8.0     255.255.255.0   0.0.0.0         eth0
0.0.0.0         0.0.0.0         192.168.8.1     eth0

session    = ''
sniff_promisc = 0
stealth    = 'not implemented'
verb       = 2

>>> conf.verb=1

Now, let's manipulate some packets. Here you can see layers that are supported for the moment. It's really easy to add one.

>>> ls()
Dot11Elt   : 802.11 Information Element
Dot11      : 802.11
SNAP       : SNAP
IPerror    : IP in ICMP
BOOTP      : BOOTP
PrismHeader : abstract packet
Ether      : Ethernet
TCP        : TCP
Dot11ProbeResp : 802.11 Probe Response
TCPerror   : TCP in ICMP
Dot11AssoResp : 802.11 Association Response
Packet     : abstract packet
UDPerror   : UDP in ICMP
ISAKMP     : ISAKMP
Dot11ProbeReq : 802.11 Probe Request
NTP        : NTP
Dot11Beacon : 802.11 Beacon
DNSRR      : DNS Resource Record
STP        : Spanning Tree Protocol
ARP        : ARP
UDP        : UDP
Dot11ReassoResp : 802.11 Reassociation Response
Dot11ReassoReq : 802.11 Reassociation Request
Dot1Q      : 802.1Q
ICMPerror  : ICMP in ICMP
Raw        : Raw
IKETransform : IKE Transform
IKE_SA     : IKE SA
ISAKMP_payload : ISAKMP payload
LLPPP      : PPP Link Layer
IP         : IP
LLC        : LLC
Dot11Deauth : 802.11 Deauthentication
Dot11AssoReq : 802.11 Association Request
ICMP       : ICMP
Dot3       : 802.3
EAPOL      : EAPOL
Dot11Disas : 802.11 Disassociation
Padding    : Padding
DNS        : DNS
Dot11Auth  : 802.11 Authentication
Dot11ATIM  : 802.11 ATIM
DNSQR      : DNS Question Record
EAP        : EAP
IKE_proposal : IKE proposal
>>> IP()
<IP |''>
>>> a=IP(dst="172.16.1.40")
>>> a
<IP dst=172.16.1.40 |''>

>>> a.dst
'172.16.1.40'
>>> a.ttl
64

A layer has default values for every field, so that you don't have to fill them all. If you give a value to the field, it will overload the default value. If you delete the field, the default value will be back. Moreover, fields with default values are not displayed.

>>> a.ttl=32
>>> a
<IP dst=172.16.1.40 ttl=32 |''>
>>> del(a.ttl)

>>> a
<IP dst=172.16.1.40 |''>
>>> a.ttl
64

Fields can be made human readable. For example IP and TCP flags : (note the rfc3514 compliance for IP).

>>> t=TCP()
>>> t.flags="SA"
>>> t.flags     
18
>>> t

<TCP flags=SA |>
>>> t.flags=23
>>> t
<TCP flags=FSRA |>
>>> 
>>> i=IP(flags="DF+MF")
>>> i.flags
3
>>> i
<IP flags=MF+DF |>

>>> i.flags=6
>>> i
<IP flags=DF+evil |>

Some default values are not constant values. For example, the source IP of a packet will default to the IP of the interface that should be used to send a packet to the given destination, according to the local routing tables.

>>> a.dst
'172.16.1.40'
>>> a.src
'172.16.1.24'
>>> del(a.dst)

>>> a.dst
'127.0.0.1'
>>> a.src
'127.0.0.1'
>>> a.dst="192.168.11.10"
>>> a.src
'192.168.11.1'
>>> a.dst=target
>>> a.src
'172.16.1.24'
>>> a.src="1.2.3.4"
>>> a
<IP src=1.2.3.4 dst=<Net www.target.com> |''>

Here, you can guess that my routing table looks like :

$ route -n 
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
172.16.0.0      0.0.0.0         255.255.0.0     U     0      0        0 eth0
192.168.11.0    0.0.0.0         255.255.255.0   U     0      0        0 eth1
0.0.0.0         172.16.1.1      0.0.0.0         UG    0      0        0 eth0

We will see later that scapy has its own routing table.

The / operator has been used as a composition operator between two layers. When doing so, the lower layer can have one or more of its defaults fields overloaded according to the upper layer. (You still can give the value you want). A string can be used as a raw layer.

>>> IP()
<IP |''>

>>> IP()/TCP()
<IP proto=6 |<TCP |''>>
>>> Ether()/IP()/TCP()
<Ether type=0x800 |<IP proto=6 |<TCP |''>>>
>>> IP()/TCP()/"GET /index.html HTTP/1.0\n\n"
<IP proto=6 |<TCP |<Raw load='GET /index.html HTTP/1.0\n\n' |''>>>

>>> Ether()/IP()/IP()/IP()/UDP()
<Ether type=0x800 |<IP proto=0 |<IP proto=0 |<IP proto=17 |<UDP |''>>>>>
>>> IP(proto=55)/TCP()
<IP proto=55 |<TCP |''>>

Each packet can be build or dissected (note: in python _ (underscode) is the latest result) :

>>> str(IP())
'E\x00\x00\x14\x00\x01\x00\x00@\x00|\xe7\x7f\x00\x00\x01\x7f\x00\x00\x01'
>>> IP(_)
<IP frag=0 src=127.0.0.1 proto=0 tos=0x0 dst=127.0.0.1 chksum=0x7ce7 len=20 version=4 flags=0 ihl=5 ttl=64 id=1 |''>
>>> a=Ether()/IP(dst=target)/TCP()/"GET /index.html HTTP/1.0 \n\n"
>>> b=str(a)
>>> b      
'\x00\x90\x7f\x1e \xc8\x00\x03G\x88\x1d/\x08\x00E\x00\x00C\x00\x01\x00\x00@\x06
 \xcd7\xac\x10\x01\x18\xad\x1d\'e\x00P\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x0
 2\x00\x00/\xf9\x00\x00GET /index.html HTTP/1.0 \n\n'
>>> c=Ether(b)
>>> c

<Ether src=00:03:47:88:1d:2f dst=00:90:7f:1e:26:c8 type=0x800 |<IP frag=0 
 src=172.16.1.24 proto=6 tos=0x0 dst=173.29.39.101 chksum=0xcd37 len=67 options='' version=4
 flags=0 ihl=5 ttl=64 id=1 |<TCP reserved=0 seq=0L ack=0L dataofs=5 dport=80 window=0
 flags=0x2 chksum=0x2ff9 urgptr=0 sport=80 options='' | <Raw load='GET /index.html HTTP/1.0 \n\n' |''>>>>

We see that a dissected packet has all its fields filled. That's because I consider that each field has its value imposed by the original string. If this is too verbose, the method hide_defaults() will delete every field that has the same value as the default.

>>> c.hide_defaults()
>>> c
<Ether src=00:03:47:88:1d:2f dst=00:90:7f:1e:26:c8 type=0x800 |<IP src=172.16.1.24
proto=6 dst=173.29.39.101 chksum=0xcd37 len=67 ihl=5 |<TCP dataofs=5 chksum=0x2ff9 |
<Raw load='GET /index.html HTTP/1.0 \n\n' |''>>>>

For the moment, we have only generated one packet. Let see how to specify sets of packets as easily. Each field of the whole packet (ever layers) can be a set. This implicidely define a set of packets, generated using a kind of cartesian product between all the fields.

>>> a=IP(dst=targetnet)
>>> a
<IP dst=<Net www.target.com/30> |''>
>>> [p for p in a]
[<IP dst=173.29.39.100 |''>, <IP dst=173.29.39.101 |''>, <IP dst=173.29.39.102 |''>, <IP dst=173.29.39.103 |''>]

>>> b=IP(ttl=[1,2,(5,9)])
>>> b
<IP ttl=[1, 2, (5, 9)] |''>
>>> [p for p in b]
[<IP ttl=1 |''>, <IP ttl=2 |''>, <IP ttl=5 |''>, <IP ttl=6 |''>,
 <IP ttl=7 |''>, <IP ttl=8 |''>, <IP ttl=9 |''>]

>>> c=TCP(dport=[80,443])
>>> [p for p in a/c]
[<IP dst=173.29.39.100 proto=6 |<TCP dport=80 |''>>, <IP dst=173.29.39.100 proto=6 |<TCP dport=443 |''>>,
 <IP dst=173.29.39.101 proto=6 |<TCP dport=80 |''>>, <IP dst=173.29.39.101 proto=6 |<TCP dport=443 |''>>,
 <IP dst=173.29.39.102 proto=6 |<TCP dport=80 |''>>, <IP dst=173.29.39.102 proto=6 |<TCP dport=443 |''>>,
 <IP dst=173.29.39.103 proto=6 |<TCP dport=80 |''>>, <IP dst=173.29.39.103 proto=6 |<TCP dport=443 |''>>]

Some operations (like building the string from a packet) can't work on a set of packets. In these cases, if you forgot to unroll your set of packets, only the first element of the list you forgot to generate will be used to assemble the packet.


Now, let's try to do some fun things. The sr() function is for sending packets and receiving answers. The function returns a couple of packet and answers, and the unanswered packets. The function sr1() is a variant that only return one packet that answered the packet (or the packet set) sent. The packets must be layer 3 packets (IP, ARP, etc.). The function srp() do the same for layer 2 packets (Ethernet, 802.3, etc.).

>>> p=sr1(IP(dst="172.16.1.40")/ICMP()/"XXXXXXXXXXX")
Finished to send 1 packets.
*
Received 1 packets, got 1 answers, remaining 0 packets
>>> p

<IP frag=0 src=172.16.1.40 proto=1 tos=0x0 dst=172.16.1.24 chksum=0xd56c 
  len=39 options='' version=4 flags= ihl=5 ttl=255 id=35848 |<ICMP code=0
  type=echo-reply id=0x0 seq=0x0 chksum=0xee45 |<Raw load='XXXXXXXXXXX' |
  <Padding load='\x00\x00\x00\x00\x00\x00\x00' |>>>>
>>> p.display()
---[ IP ]---
version   = 4
ihl       = 5
tos       = 0x0
len       = 39
id        = 35848
flags     = 
frag      = 0
ttl       = 255
proto     = ICMP
chksum    = 0xd56c
src       = 172.16.1.40
dst       = 172.16.1.24
options   = ''
---[ ICMP ]---
   type      = echo-reply
   code      = 0
   chksum    = 0xee45
   id        = 0x0
   seq       = 0x0
---[ Raw ]---
      load      = 'XXXXXXXXXXX'
---[ Padding ]---
         load      = '\x00\x00\x00\x00\x00\x00\x00'

A DNS query (rd = recursion desired).

>>> sr1(IP(dst="172.16.1.40")/UDP()/DNS(rd=1,qd=DNSQR(qname="www.target.com")))
Finished to send 1 packets.
.*
Received 2 packets, got 1 answers, remaining 0 packets
<IP frag=0 src=172.16.1.40 proto=UDP tos=0x0 dst=172.16.1.24 chksum=0xdfb8 
 len=212 options='' version=4 flags=DF ihl=5 ttl=64 id=0 |<UDP dport=80
 sport=53 len=192 chksum=0x188 |<DNS aa=0 qr=1 an=<DNSRR rdata='173.29.33.99' 
 ttl=300L rrname='www.target.com.' type=A class=IN |> ns=<DNSRR 
 rdata='ns4.target.com.' ttl=345600L rrname='target.com.' type=NS class=IN 
 |<DNSRR rdata='ns1.target.com.' ttl=345600L rrname='target.com.' type=NS 
 class=IN |<DNSRR rdata='ns2.target.com.' ttl=345600L rrname='target.com.'
 type=NS class=IN |<DNSRR rdata='ns3.target.com.' ttl=345600L 
 rrname='target.com.' type=NS class=IN |>>>> nscount=4 qdcount=1 
 tc=0 ancount=1 rd=1 arcount=4 ar=<DNSRR rdata='173.29.32.10' ttl=326818L 
 rrname='ns1.target.com.' type=A class=IN |<DNSRR rdata='173.29.34.10' 
 ttl=326818L rrname='ns2.target.com.' type=A class=IN |<DNSRR 
 rdata='173.29.36.10' ttl=326818L rrname='ns3.target.com.' type=A class=IN
 |<DNSRR rdata='173.29.38.10' ttl=326818L rrname='ns4.target.com.' type=A
 class=IN |>>>> opcode=0 ra=1 z=0 rcode=0 id=0 qd=<DNSQR 
 qclass=IN qtype=A qname='www.target.com.' |> |>>>

>>> _.an
<DNSRR rdata='173.29.33.99' ttl=300L rrname='www.target.com.' type=A class=IN |>

The "send'n'receive" functions family is the heart of scapy. They return a couple of two lists. The first element is a list of couples (packet sent, answer), and the second element is the list of unanswered packets. These two elements are lists, but they are wrapped by an object to present them better, and to provide them with some methods that do most frequently needed actions.

>>> sr(IP(dst="192.168.8.1")/TCP(dport=[21,22,23]))
Received 6 packets, got 3 answers, remaining 0 packets
(<Results: UDP:0 ICMP:0 TCP:3 Other:0>, <Unanswered: UDP:0 ICMP:0 TCP:0 Other:0>)
>>> ans,unans=_
>>> ans.summary()
IP / TCP 192.168.8.14:20 > 192.168.8.1:21 S ==> Ether / IP / TCP 192.168.8.1:21 > 192.168.8.14:20 RA / Padding
IP / TCP 192.168.8.14:20 > 192.168.8.1:22 S ==> Ether / IP / TCP 192.168.8.1:22 > 192.168.8.14:20 RA / Padding
IP / TCP 192.168.8.14:20 > 192.168.8.1:23 S ==> Ether / IP / TCP 192.168.8.1:23 > 192.168.8.14:20 RA / Padding

If there is a limited rate of answers, you can specify a time interval to wait between two packets with the inter parameter. If some packets are lost or if specifying an interval is not enough, you can resend all the unanswered packets, either by calling the function again, directly with the unanswered list, or by specifying a retry parameter. If retry is 3, scapy will try to resend unanswered packets 3 times. If retry is -3, scapy will resend unanswered packets until no more answer is given for the same set of unanswered packets 3 times in a row. The timeout parameter specify the time to wait after the last packet has been sent.

>>> sr(IP(dst="172.20.29.5/30")/TCP(dport=[21,22,23]),inter=0.5,retry=-2,timeout=1)
Begin emission:
Finished to send 12 packets.
Begin emission:
Finished to send 9 packets.
Begin emission:
Finished to send 9 packets.

Received 100 packets, got 3 answers, remaining 9 packets
(<Results: UDP:0 ICMP:0 TCP:3 Other:0>, <Unanswered: UDP:0 ICMP:0 TCP:9 Other:0>)

A TCP traceroute.

>>> ans,unans=sr(IP(dst=target, ttl=(4,25),id=RandShort())/TCP(flags=0x2))
*****.******.*.***..*.**Finished to send 22 packets.
***......
Received 33 packets, got 21 answers, remaining 1 packets
>>> for snd,rcv in ans:
...     print snd.ttl, rcv.src, isinstance(rcv.payload, TCP)
... 
5 194.51.159.65 0
6 194.51.159.49 0
4 194.250.107.181 0
7 193.251.126.34 0
8 193.251.126.154 0
9 193.251.241.89 0
10 193.251.241.110 0
11 193.251.241.173 0
13 208.172.251.165 0
12 193.251.241.173 0
14 208.172.251.165 0
15 206.24.226.99 0
16 206.24.238.34 0
17 173.109.66.90 0
18 173.109.88.218 0
19 173.29.39.101 1
20 173.29.39.101 1
21 173.29.39.101 1
22 173.29.39.101 1
23 173.29.39.101 1
24 173.29.39.101 1

Note that the TCP traceroute and some other high-level functions are already coded :

>>> lsc()
sr               : Send and receive packets at layer 3
sr1              : Send packets at layer 3 and return only the first answer
srp              : Send and receive packets at layer 2
srp1             : Send and receive packets at layer 2 and return only the first answer
srloop           : Send a packet at layer 3 in loop and print the answer each time
srploop          : Send a packet at layer 2 in loop and print the answer each time
sniff            : Sniff packets
p0f              : Passive OS fingerprinting: which OS emitted this TCP SYN ?
arpcachepoison   : Poison target's cache with (your MAC,victim's IP) couple
send             : Send packets at layer 3
sendp            : Send packets at layer 2
traceroute       : Instant TCP traceroute
arping           : Send ARP who-has requests to determine which hosts are up
ls               : List  available layers, or infos on a given layer
lsc              : List user commands
queso            : Queso OS fingerprinting
nmap_fp          : nmap fingerprinting
report_ports     : portscan a target and output a LaTeX table
dyndns_add       : Send a DNS add message to a nameserver for "name" to have a new "rdata"
dyndns_del       : Send a DNS delete message to a nameserver for "name"

The process of sending packets and receiving is quite complicated. As I wanted to use the PF_PACKET interface to go through netfilter, I also needed to implement an ARP stack and ARP cache, and a LL stack. Well it seems to work, on ethernet and PPP interfaces, but I don't guarantee anything. Anyway, the fact I used a kind of super-socket for that mean that you can switch your IO layer very easily, and use PF_INET/SOCK_RAW, or use PF_PACKET at level 2 (giving the LL header (ethernet,...) and giving yourself mac addresses, ...). I've just added a super socket which use libdnet and libpcap, so that it should be portable :

>>> conf.L3socket=L3dnetSocket
>>> conf.L3listen=L3pcapListenSocket

We can easily capture some packets or even clone tcpdump or tethereal. If no interface is given, sniffing will happen on every interfaces.

>>> sniff(count=2) 

<Sniffed: UDP:0 ICMP:2 TCP:0 Other:0>
>>> _.summary()
Ether / IP / ICMP 192.168.8.14 echo-request 0 / Raw
Ether / IP / ICMP 80.67.173.5 echo-reply 0 / Raw
>>> _[:] 
[<Ether src=00:d0:b7:88:50:f2 dst=00:03:47:88:1d:2f type=0x800 |<IP frag=0
  src=172.16.1.40 proto=1 tos=0x0 dst=172.16.1.24 chksum=0x3974 len=84 options=''
  version=4 flags=0 ihl=5 ttl=255 id=10196 |<ICMP code=0 type=0 id=0xdc0f 
  seq=0x7138 chksum=0x25e5 |<Raw load='>r\x15f\x00\x07M\xf0\x08\t\n\x0b
  \x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e
  \x1f !"#$%&\'()*+,-./01234567' |''>>>>, 
 <Ether src=00:d0:b7:88:50:f2 dst=00:03:47:88:1d:2f type=0x800 |<IP frag=0 
  src=172.16.1.40 proto=1 tos=0x0 dst=172.16.1.24 chksum=0x3973 len=84 options=''i
  version=4 flags=0 ihl=5 ttl=255 id=10197 |<ICMP code=0 type=0 id=0xdc0f 
  seq=0x7238 chksum=0x1792 |<Raw load='>r\x15f\x00\x07[C\x08\t\n\x0b\x0c\r\x0e
  \x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !"#$%&\'
  ()*+,-./01234567' |''>>>>]

>>> sniff(iface="wifi0", prn=lambda x: x.summary())
802.11 Management 8 ff:ff:ff:ff:ff:ff / 802.11 Beacon / Info SSID / Info Rates / Info DSset / Info TIM / Info 133
802.11 Management 4 ff:ff:ff:ff:ff:ff / 802.11 Probe Request / Info SSID / Info Rates
802.11 Management 5 00:0a:41:ee:a5:50 / 802.11 Probe Response / Info SSID / Info Rates / Info DSset / Info 133
802.11 Management 4 ff:ff:ff:ff:ff:ff / 802.11 Probe Request / Info SSID / Info Rates
802.11 Management 4 ff:ff:ff:ff:ff:ff / 802.11 Probe Request / Info SSID / Info Rates
802.11 Management 8 ff:ff:ff:ff:ff:ff / 802.11 Beacon / Info SSID / Info Rates / Info DSset / Info TIM / Info 133
802.11 Management 11 00:07:50:d6:44:3f / 802.11 Authentication
802.11 Management 11 00:0a:41:ee:a5:50 / 802.11 Authentication
802.11 Management 0 00:07:50:d6:44:3f / 802.11 Association Request / Info SSID / Info Rates / Info 133 / Info 149
802.11 Management 1 00:0a:41:ee:a5:50 / 802.11 Association Response / Info Rates / Info 133 / Info 149
802.11 Management 8 ff:ff:ff:ff:ff:ff / 802.11 Beacon / Info SSID / Info Rates / Info DSset / Info TIM / Info 133
802.11 Management 8 ff:ff:ff:ff:ff:ff / 802.11 Beacon / Info SSID / Info Rates / Info DSset / Info TIM / Info 133
ARP who has 172.20.70.172 says 172.20.70.171 / Padding
ARP is at 00:0a:b7:4b:9c:dd says 172.20.70.172 / Padding
ICMP echo-request 0 / Raw
ICMP echo-reply 0 / Raw
>>> sniff(iface="lo", prn=lambda x: x.display())
---[ Ethernet ]---
dst       = 00:00:00:00:00:00
src       = 00:00:00:00:00:00
type      = 0x800
---[ IP ]---
   version   = 4
   ihl       = 5
   tos       = 0x0
   len       = 84
   id        = 0
   flags     = 2
   frag      = 0
   ttl       = 64
   proto     = 1
   chksum    = 0x3ca7
   src       = 127.0.0.1
   dst       = 127.0.0.1
   options   = ''
---[ ICMP ]---
      type      = echo-request
      code      = 0
      chksum    = 0x4f7c
      id        = 0xe10f
      seq       = 0x0
---[ Raw ]---
         load      = '>r\x15\xe0\x00\n\x88\x14\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11
\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !"#$%&\'()*+,-./01234567'
---[ Ethernet ]---
dst       = 00:00:00:00:00:00
src       = 00:00:00:00:00:00
type      = 0x800
---[ IP ]---
   version   = 4
   ihl       = 5
   tos       = 0x0
   len       = 84
   id        = 35452
   flags     = 0
   frag      = 0
   ttl       = 64
   proto     = 1
   chksum    = 0xf22a
   src       = 127.0.0.1
   dst       = 127.0.0.1
   options   = ''
---[ ICMP ]---
      type      = echo-reply
      code      = 0
      chksum    = 0x577c
      id        = 0xe10f
      seq       = 0x0
---[ Raw ]---
         load      = '>r\x15\xe0\x00\n\x88\x14\x08\t\n\x0b\x0c\r\x0e\x0f\x10
\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !"#$%&\'()*+,-./01234567'

We can sniff and do passive OS fingerprinting.

>>> p
<Ether src=00:40:33:96:7b:60 dst=00:10:4b:b3:7d:4e type=0x800 |<IP frag=0
 src=192.168.8.10 proto=6 tos=0x10 dst=192.168.8.1 chksum=0xb85e len=60
 options='' version=4 flags=2 ihl=5 ttl=64 id=61681 |<TCP reserved=0
 seq=2023566040L ack=0L dataofs=10 dport=80 window=5840 flags=SEC
 chksum=0x570c urgptr=0 sport=46511 options={'Timestamp': (342940201L, 0L),
 'MSS': 1460, 'NOP': (), 'SAckOK': '', 'WScale': 0} |''>>>

>>> p0f(p)
(1.0, ['Linux 2.4.2 - 2.4.14 (1)'])
>>> a=sniff(prn=prnp0f)
(1.0, ['Linux 2.4.2 - 2.4.14 (1)'])
(1.0, ['Linux 2.4.2 - 2.4.14 (1)'])
(0.875, ['Linux 2.4.2 - 2.4.14 (1)', 'Linux 2.4.10 (1)', 'Windows 98 (?)'])
(1.0, ['Windows 2000 (9)'])

The number before the OS guess is the accurracy of the guess.

Demo of both bpf filter and sprintf() method :

>>> a=sniff(filter="tcp and ( port 25 or port 110 )",
 prn=lambda x: x.sprintf("%IP.src%:%TCP.sport% -> %IP.dst%:%TCP.dport%  %2s,TCP.flags% : %TCP.payload%"))
192.168.8.10:47226 -> 213.228.0.14:110   S : 
213.228.0.14:110 -> 192.168.8.10:47226  SA : 
192.168.8.10:47226 -> 213.228.0.14:110   A : 
213.228.0.14:110 -> 192.168.8.10:47226  PA : +OK <13103.1048117923@pop2-1.free.fr>

192.168.8.10:47226 -> 213.228.0.14:110   A : 
192.168.8.10:47226 -> 213.228.0.14:110  PA : USER toto

213.228.0.14:110 -> 192.168.8.10:47226   A : 
213.228.0.14:110 -> 192.168.8.10:47226  PA : +OK 

192.168.8.10:47226 -> 213.228.0.14:110   A : 
192.168.8.10:47226 -> 213.228.0.14:110  PA : PASS tata

213.228.0.14:110 -> 192.168.8.10:47226  PA : -ERR authorization failed

192.168.8.10:47226 -> 213.228.0.14:110   A : 
213.228.0.14:110 -> 192.168.8.10:47226  FA : 
192.168.8.10:47226 -> 213.228.0.14:110  FA : 
213.228.0.14:110 -> 192.168.8.10:47226   A : 

Here is an example of a (h)ping-like functionnality : you always send the same set of packets to see if something change :

>>> srloop(IP(dst="www.target.com/30")/TCP())
RECV 1: Ether / IP / TCP 192.168.11.99:80 > 192.168.8.14:20 SA / Padding
fail 3: IP / TCP 192.168.8.14:20 > 192.168.11.96:80 S
        IP / TCP 192.168.8.14:20 > 192.168.11.98:80 S
        IP / TCP 192.168.8.14:20 > 192.168.11.97:80 S
RECV 1: Ether / IP / TCP 192.168.11.99:80 > 192.168.8.14:20 SA / Padding
fail 3: IP / TCP 192.168.8.14:20 > 192.168.11.96:80 S
        IP / TCP 192.168.8.14:20 > 192.168.11.98:80 S
        IP / TCP 192.168.8.14:20 > 192.168.11.97:80 S

Now we have a demonstration of the make_table() presentation function. It takes a list as parameter, and 3 functions. The first gives the value on the x axis from an element of the list, the second is about the y value and the third is the value that we want to see at coordinates (x,y). The result is a table. This function has 2 variants, make_lined_table() and

make_tex_table() to copy/paste into your LaTeX pentest report. Those functions are available as methods of a result object :

Here we can see a multi-parallel traceroute (scapy already has a multi TCP traceroute function. See later).

>>> ans,unans=sr(IP(dst="www.test.fr/30", ttl=(1,6))/TCP())
Received 49 packets, got 24 answers, remaining 0 packets
>>> ans.make_table( lambda x: x[0].dst, lambda x:x[0].ttl, lambda x:x[1].src)
  216.15.189.192  216.15.189.193  216.15.189.194  216.15.189.195  
1 192.168.8.1     192.168.8.1     192.168.8.1     192.168.8.1     
2 81.57.239.254   81.57.239.254   81.57.239.254   81.57.239.254   
3 213.228.4.254   213.228.4.254   213.228.4.254   213.228.4.254   
4 213.228.3.3     213.228.3.3     213.228.3.3     213.228.3.3     
5 193.251.254.1   193.251.251.69  193.251.254.1   193.251.251.69  
6 193.251.241.174 193.251.241.178 193.251.241.174 193.251.241.178 

Here is a more complex example to identify machines from their IPID field. We can see that 172.20.80.200:22 is answered by the same IP stack than 172.20.80.201 and that 172.20.80.197:25 is not answered by the sape IP stack than other ports on the same IP.

>>> ans,unans=sr(IP(dst="172.20.80.192/28")/TCP(dport=[20,21,22,25,53,80]))
Received 142 packets, got 25 answers, remaining 71 packets
>>> ans.make_table(lambda x: x[0].dst, lambda x:x[0].dport, lambda x:x[1].sprintf("%IP.id%"))
   172.20.80.196 172.20.80.197 172.20.80.198 172.20.80.200 172.20.80.201 
20 0             4203          7021          -             11562             
21 0             4204          7022          -             11563             
22 0             4205          7023          11561         11564             
25 0             0             7024          -             11565             
53 0             4207          7025          -             11566             
80 0             4028          7026          -             11567             

It can help identify network topologies very easily when playing with TTL, displaying received TTL, etc.

Now scapy has its own routing table, so that you can have your packets routed diffrently than the system.

>>> conf.route
Network         Netmask         Gateway         Iface
127.0.0.0       255.0.0.0       0.0.0.0         lo
192.168.8.0     255.255.255.0   0.0.0.0         eth0
0.0.0.0         0.0.0.0         192.168.8.1     eth0
>>> conf.route.delt(net="0.0.0.0/0",gw="192.168.8.1")
>>> conf.route.add(net="0.0.0.0/0",gw="192.168.8.254")
>>> conf.route.add(host="192.168.1.1",gw="192.168.8.1")
>>> conf.route
Network         Netmask         Gateway         Iface
127.0.0.0       255.0.0.0       0.0.0.0         lo
192.168.8.0     255.255.255.0   0.0.0.0         eth0
0.0.0.0         0.0.0.0         192.168.8.254   eth0
192.168.1.1     255.255.255.255 192.168.8.1     eth0

>>> conf.route.resync()
>>> conf.route
Network         Netmask         Gateway         Iface
127.0.0.0       255.0.0.0       0.0.0.0         lo
192.168.8.0     255.255.255.0   0.0.0.0         eth0
0.0.0.0         0.0.0.0         192.168.8.1     eth0

We can easily plot some harvested values using Gnuplot. For example, we can observe the IP ID patterns to know how many distinct IP stacks are used behind a load balancer :

>>> a,b=sr(IP(dst="www.target.com")/TCP(sport=[RandShort()]*1000))
>>> a.plot(lambda x:x[1].id)
<Gnuplot._Gnuplot.Gnuplot instance at 0xb7d6a74c>

ipid.png

Scapy also has a powerful TCP traceroute function. Unlike other traceroute programs that wait for each node to reply before going to the next, scapy sends all the packets at the same time. This has the disadvantage that it can't know when to stop (thus the maxttl parameter) but the great advantage that it took less than 3 seconds to get this multi-target traceroute result :

>>> traceroute(["yahoo.com","altavista.com","wisenut.com","copernic.com"],maxttl=20)
Received 80 packets, got 80 answers, remaining 0 packets
   193.45.10.88:80    216.109.118.79:80  64.241.242.243:80  66.94.229.254:80   
1  192.168.8.1        192.168.8.1        192.168.8.1        192.168.8.1        
2  82.243.5.254       82.243.5.254       82.243.5.254       82.243.5.254     
3  213.228.4.254      213.228.4.254      213.228.4.254      213.228.4.254      
4  212.27.50.46       212.27.50.46       212.27.50.46       212.27.50.46       
5  212.27.50.37       212.27.50.41       212.27.50.37       212.27.50.41       
6  212.27.50.34       212.27.50.34       213.228.3.234      193.251.251.69     
7  213.248.71.141     217.118.239.149    208.184.231.214    193.251.241.178    
8  213.248.65.81      217.118.224.44     64.125.31.129      193.251.242.98     
9  213.248.70.14      213.206.129.85     64.125.31.186      193.251.243.89     
10 193.45.10.88    SA 213.206.128.160    64.125.29.122      193.251.254.126    
11 193.45.10.88    SA 206.24.169.41      64.125.28.70       216.115.97.178     
12 193.45.10.88    SA 206.24.226.99      64.125.28.209      66.218.64.146      
13 193.45.10.88    SA 206.24.227.106     64.125.29.45       66.218.82.230      
14 193.45.10.88    SA 216.109.74.30      64.125.31.214      66.94.229.254   SA 
15 193.45.10.88    SA 216.109.120.149    64.124.229.109     66.94.229.254   SA 
16 193.45.10.88    SA 216.109.118.79  SA 64.241.242.243  SA 66.94.229.254   SA 
17 193.45.10.88    SA 216.109.118.79  SA 64.241.242.243  SA 66.94.229.254   SA 
18 193.45.10.88    SA 216.109.118.79  SA 64.241.242.243  SA 66.94.229.254   SA 
19 193.45.10.88    SA 216.109.118.79  SA 64.241.242.243  SA 66.94.229.254   SA 
20 193.45.10.88    SA 216.109.118.79  SA 64.241.242.243  SA 66.94.229.254   SA 
<Traceroute: UDP:0 TCP:28 ICMP:52 Other:0>

The last line is in fact a traceroute result object. It is a more specialised version (a subclass, in fact) of a classic result object. We can save it to consult the traceroute result again a bit later, or to deeply inspect one of the answers, for example to check padding.


>>> result=_
>>> result.display()
   193.45.10.88:80    216.109.118.79:80  64.241.242.243:80  66.94.229.254:80   
1  192.168.8.1        192.168.8.1        192.168.8.1        192.168.8.1        
2  82.251.4.254       82.251.4.254       82.251.4.254       82.251.4.254     
3  213.228.4.254      213.228.4.254      213.228.4.254      213.228.4.254      
[...]
>>> result.filter(lambda x:x[1].haslayer(Padding))
<filtered Traceroute: UDP:0 TCP:24 ICMP:8 Other:0>

Like any result object, traceroute objects can be added :

>>> r2=traceroute(["www.voila.com"],maxttl=20)
Received 19 packets, got 19 answers, remaining 1 packets
   195.101.94.25:80   
1  192.168.8.1        
2  82.251.4.254     
3  213.228.4.254      
4  212.27.50.169      
5  212.27.50.162      
6  193.252.161.97     
7  193.252.103.86     
8  193.252.103.77     
9  193.252.101.1      
10 193.252.227.245    
12 195.101.94.25   SA 
13 195.101.94.25   SA 
14 195.101.94.25   SA 
15 195.101.94.25   SA 
16 195.101.94.25   SA 
17 195.101.94.25   SA 
18 195.101.94.25   SA 
19 195.101.94.25   SA 
20 195.101.94.25   SA 
>>>
>>> r3=result+r2
>>> r3.display()
   195.101.94.25:80   212.23.37.13:80    216.109.118.72:80  64.241.242.243:80  66.94.229.254:80   
1  192.168.8.1        192.168.8.1        192.168.8.1        192.168.8.1        192.168.8.1        
2  82.251.4.254       82.251.4.254       82.251.4.254       82.251.4.254       82.251.4.254     
3  213.228.4.254      213.228.4.254      213.228.4.254      213.228.4.254      213.228.4.254      
4  212.27.50.169      212.27.50.169      212.27.50.46       -                  212.27.50.46       
5  212.27.50.162      212.27.50.162      212.27.50.37       212.27.50.41       212.27.50.37       
6  193.252.161.97     194.68.129.168     212.27.50.34       213.228.3.234      193.251.251.69     
7  193.252.103.86     212.23.42.33       217.118.239.185    208.184.231.214    193.251.241.178    
8  193.252.103.77     212.23.42.6        217.118.224.44     64.125.31.129      193.251.242.98     
9  193.252.101.1      212.23.37.13    SA 213.206.129.85     64.125.31.186      193.251.243.89     
10 193.252.227.245    212.23.37.13    SA 213.206.128.160    64.125.29.122      193.251.254.126    
11 -                  212.23.37.13    SA 206.24.169.41      64.125.28.70       216.115.97.178     
12 195.101.94.25   SA 212.23.37.13    SA 206.24.226.100     64.125.28.209      216.115.101.46     
13 195.101.94.25   SA 212.23.37.13    SA 206.24.238.166     64.125.29.45       66.218.82.234      
14 195.101.94.25   SA 212.23.37.13    SA 216.109.74.30      64.125.31.214      66.94.229.254   SA 
15 195.101.94.25   SA 212.23.37.13    SA 216.109.120.151    64.124.229.109     66.94.229.254   SA 
16 195.101.94.25   SA 212.23.37.13    SA 216.109.118.72  SA 64.241.242.243  SA 66.94.229.254   SA 
17 195.101.94.25   SA 212.23.37.13    SA 216.109.118.72  SA 64.241.242.243  SA 66.94.229.254   SA 
18 195.101.94.25   SA 212.23.37.13    SA 216.109.118.72  SA 64.241.242.243  SA 66.94.229.254   SA 
19 195.101.94.25   SA 212.23.37.13    SA 216.109.118.72  SA 64.241.242.243  SA 66.94.229.254   SA 
20 195.101.94.25   SA 212.23.37.13    SA 216.109.118.72  SA 64.241.242.243  SA 66.94.229.254   SA 

Traceroute result object also have a very neat feature : they can make a directed graph from all the routes they got, and cluster them by AS. You will need graphviz. By default, ImageMagick is used to display the graph.

>>> res = traceroute(["microsoft.com","cisco.com","yahoo.com","wanadoo.fr","pacsec.com"],dport=[80,443],maxttl=20,retry=-2)
Received 190 packets, got 190 answers, remaining 10 packets
   193.252.122.103:443 193.252.122.103:80 198.133.219.25:443 198.133.219.25:80  207.46...
1  192.168.8.1         192.168.8.1        192.168.8.1        192.168.8.1        192.16...
2  82.251.4.254        82.251.4.254       82.251.4.254       82.251.4.254       82.251...
3  213.228.4.254       213.228.4.254      213.228.4.254      213.228.4.254      213.22...
[...]
>>> res.graph()                          # piped to ImageMagick's display program. Image below.
>>> res.graph(type="ps",target="| lp")   # piped to postscript printer
>>> res.graph(target="> /tmp/graph.svg") # saved to file 

graph_traceroute.gif graph_traceroute.svg The same in SVG

Soon to come : examples for arping, scanning, arp cache poisoning, dns spoofing, etc. (they are present in the scapy_lsm2003.pdf)

Bugy

  • Link layer not well managed yet
  • Does not give the right source IP for routes that use interface aliases (/proc/net/route reports only master interface)
  • May miss packets under heavy load

Reference

Citováno z „http://wiki.airdump.cz/Scapy
Osobní nástroje