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