scapy涉及了pf_packet套結字程式設計,路由以及面向對象設計等諸技術,它本身使用python編寫,熟悉python的家夥這下有福了,可以目睹這個優秀的軟體而無需下載下傳編譯源碼之類的了。
scapy中到處都是class,不過要說起來,使用class表示協定棧是再友善不過的了,協定棧本身實作的每一個層就是一個class,而每一個經過該層的資料包就是該class的一個對象,一個class擁有很多屬性,比如針對ip層來講,它有源位址,目的位址,ttl等屬性,同時它還有send,receive等方法,而每一個經過的資料包隻是将這一切具體化了而已,比如一個ip層的資料包可以是這樣子的:[src=1.2.3.4,dst=4.3.2.1,ttl=100,...],然後它可以使用send方法被發送給下層,或者說發送給對方的對等層,從這個意義上講,我們完全可以簡單的使用oo的技術寫出一個使用者态的協定棧,資料包的來源來自pcap抓取的包,而我們也可以自己使用上述的oo技術構造一個資料包,逐層封裝(一直到鍊路層)後使用packet技術發出去,如果實在不想使用packet技術,也可以搞一個很簡單的硬體,比如序列槽,轉接于雙絞線(此含義即一條線的一端為rj45口,插入以太網卡,另一端為序列槽,插入機器序列槽),這樣的話,從序列槽讀出的就是裸資料了,然後進入我們的使用者态使用oo實作的協定棧,解析之後傳給我們自己的應用或者再次通過此技術forward出去,注意,我們雖然無法将解析後的資料傳給别的應用,因為别的應用使用核心的協定棧,其socket是工作于核心的,然而我們可以代理别的應用,此技術就是本地代理。
簡單并且直接的說,scapy就是一個簡單的使用者态協定棧實作,雖然它還要使用核心的路由功能。我本身非常欣賞scapy這個東西的整體設計,而它的使用也是非常簡單,隻需先在Debian上apt-get install之,然後直接在指令行輸入scapy即可,會出現下列提示符:
Welcome to Scapy (0.9.17.1beta)
>>>
此時我們輸入一行資料回車之:
>>> a=IP(src="192.168.40.246",dst="193.168.40.34")/TCP(sport=1111,dport=2222)
這樣我們就等于構造好了一個ip資料包了,然後我們再輸入下列行回車之:
>>> sr(a)
資料包a即被發送出去了,這一切的操作就是這麼簡單。上述的IP(...)和TCP(...)中的IP和TCP實際上就是類,圓括号内的資料即可以構造出兩個對象,一個IP的,另一個TCP的,兩個對象堆列在一起即可形成一個ip資料包,其上是tcp資料,然後當我們調用sr的時候,資料被發送,sr實際上是一個全局的函數,不屬于任何類的,熟悉python的一定明白這一點。
def sr(x,filter=None, iface=None, *args,**kargs):
if not kargs.has_key("timeout"):
kargs["timeout"] = -1
s = conf.L3socket(filter=filter, iface=iface)
a,b,c=sndrcv(s,x,*args,**kargs)
s.close()
return a,b
核心之處在于構造s和發送s,s由L3socket構造,而其也是一個類對象,它的實作為:
class L3PacketSocket(SuperSocket):
def __init__(self, type = ETH_P_ALL, filter=None, promisc=None, iface=None): #構造方法
self.type = type
#下面将構造出一個packet協定族的socket,并且還是raw的,用于接收資料,使用packet實為自己解析之友善
self.ins = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(type))
self.ins.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 2**30)
...#下面将構造出一個packet協定族的socket,并且還是raw的,用于發送自己構造的資料
self.outs = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(type))
...
if iface is None: #得到所有機器上的網卡
self.iff = get_if_list()
...#設定網卡為promisc模式用于抓取任意資料包
def close(self):
def recv(self, x): #接收方法
pkt, sa_ll = self.ins.recvfrom(x) #首先通過作業系統的packet套結字的接收方法接收資料
pkt = cls(pkt)
...
return pkt
def send(self, x):
if hasattr(x,"dst"):
#根據構造ip包時的dst屬性值來決定往哪個網卡口發送這個自己構造好的資料包,這是通過路由得到的,在scapy初始化的時候,路由即已經被讀入了記憶體中,然後我們這裡隻是根據dst來查找該dst所對應的路由,最終将之解析(python的struct unpack操作)成iff-網卡,gw-網關等資料。
iff,a,gw = conf.route.route(x.dst)
sdto = (iff, self.type) #得到類似c語言的sockaddr_ll結構體
self.outs.bind(sdto) #将packet套結字bind到該網卡上(c語言中的sockaddr_ll結構體則是bind的參數類型)
sn = self.outs.getsockname()
self.outs.sendto(str(x), sdto) #最終通過作業系統的packet套結字發送出去
上面就是scapy中的核心套結字class,它封裝了套結字的幾乎所有行為,那麼可想而知套結字操作的資料則被封裝成了另一個核心的class,這就是Packet,然而該Packet作為抽象父類出現,任何特定協定的資料包都是一個它的子類,對于最簡單的以太幀來講,它是:
class Ether(Packet):
name = "Ethernet"
fields_desc = [ DestMACField("dst"),
SourceMACField("src"),
XShortField("type", 0x0000) ]
def hashret(self):
return struct.pack("H",self.type)+self.payload.hashret()
def answers(self, other):
if isinstance(other,Ether):
if self.type == other.type:
return self.payload.answers(other.payload)
return 0
def mysummary(self):
return "%s > %s (%04x)" % (self.src, self.dst, self.type)
sr函數的作用就是用一個SOCKET将一個PACKET發送出去,之是以使用大寫就是因為這都是scapy中的類對象,而不是内建的資料類型或者系統的資料類型,既然SOCKET和PACKET都是類,那麼也就是因為它們是類,是以很容易通過繼承擴充出很多不同種類的SOCKET和PACKET,比如鍊路層socket,網絡層socket這些不同層次的SOCKET(它們的send/recv方法實作當然不同),以及以太幀,ip資料報,tcp資料段這些不同層次的PACKET,總之,SOCKET定義了資料的發送接收流程和發送接收方式,而PACKET定義了資料包的格式,可以想象,将SOCKET和PACKET都定義成頂層的抽象超類,然後在不同的協定層定義不同的SOCKET和PACKET子類即可,注意每一個協定層的SOCKET子類和PACKET子類并不是一一對應的,對于每個層次來水SOCKET子類應該很少,而PACKET卻很多,簡單的說,該層支援那些具體協定,就有幾個PACKET子類。事實上,scapy就是這麼設計的。
上述的L3PacketSocket表示一個網絡層的socket,它的定義如下,以SuperSocket作為父類:
class L3PacketSocket(SuperSocket)
而上述的Ether表示一個鍊路層的packet,它的定義如下,以Packet作為父類:
class Ether(Packet)
有了上述的Socket和Packet,整個發送接收流程就可以運作了,接下來最後需要幾個全局的方法用于發送不同層次的資料包,這就是sr,srp等方法了,在scapy的終端,敲入下列指令可以看到幫助資訊:
>>> help('sr') #類似的還有srp(發送二層資料幀),sr1等等
Help on function sr in module __main__:
sr(x, filter=None, iface=None, nofilter=0, *args, **kargs)
Send and receive packets at layer 3
nofilter: put 1 to avoid use of bpf filters
retry: if positive, how many times to resend unanswered packets
if negative, how many times to retry when no more packets are answered
timeout: how much time to wait after the last packet has been sent
verbose: set verbosity level
multi: whether to accept multiple answers for the same stimulus
filter: provide a BPF filter
iface: listen answers only on the given interface
整個流程是:
1.scapy啟動,全局讀取路由資訊,供以後發送三層以及三層以上資料包時确定網卡出口時使用,要知道,建立一個af_packet類型的socket需要bind一個sockaddr_ll結構體,而此結構體需要指定一個确定的網卡資訊,比如eth0,eth1等:
struct sockaddr_ll sl;
int fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
sl.sll_family = PF_PACKET;
struct ifreq req;
strcpy(req.ifr_name, "eth0");
ioctl(fd, SIOCGIFINDEX, &req);
sl.sll_ifindex = req.ifr_ifindex;
sl.sll_protocol = htons(ETH_P_ALL);
bind(fd, (struct sockaddr *)&sl, sizeof(sl));
bind網卡的時候需要的eth?資訊就是通過scapy初始化時得到的全局路由得到的,到時候,會根據IP(...,dst="xxx",...)中的dst資訊來在全局路由中得到ethN資訊,進而可以将socket進行bind;
2.使用者輸入一個諸如a=IP(src="192.168.40.246",dst="192.168.40.34")/TCP(sport=1111,dport=2222)之類的建立一個對象,該對象擁有dst屬性:192.168.40.34;
3.使用者調用sr(a)發送資料包,此時初始化一個L3PacketSocket對象,并且調用L3PacketSocket對象的send函數,後者将原生socket bind到一個ethN上(由路由确定),然後調用原生socket将資料發出;
4.讀取傳回。
5.以上的1-4僅僅是三層資料的發送過程,sr函數隻能發送ip層以及ip攜帶的資料
本文轉自 dog250 51CTO部落格,原文連結:http://blog.51cto.com/dog250/1271136