Scapy:send函数剖析(参数、返回值、应用)

        send函数是Scapy中发包的重要函数,使用Scapy的程序员免不了经常与它打交道。但是,我们真的了解它吗?思此有感,我特地翻阅了源码,并将自己的分析写成博客分享给大家。水平不高,如有疑惑请在评论区留言。

        文档中是这样介绍send函数的(怀疑机翻):

scapy.sendrecv.send(x, iface=None, **kargs)

在第三层发送数据包

参数

  • x -- 包裹

  • inter -- 两个数据包之间的时间(以秒为单位)(默认值为0)

  • loop -- send packet indefinitely (default 0) (bing翻译为“无限期发送数据包 默认值 0”)

  • count -- 要发送的数据包数(默认无=1)

  • verbose -- 详细模式(默认无=conf.详细) (指conf.verb)

  • realtime -- 在发送下一个包之前,请检查是否已发送了一个包

  • return_packets -- 返回发送的数据包

  • socket -- 要使用的套接字(默认为conf.L3socket(kargs))

  • iface -- 发送数据包的接口

  • monitor -- (不在linux上)以监视模式发送

返回: 没有 (可能指None)

(源码附后)

        绿色背景的是我的注释。

        不难看出,send函数的参数列表显式地由三个部分组成:x指要发送的包,iface指发送数据包的接口,还有一个接受不定参数的kargs。

文档中提到的参数和返回值,经研究后,结果如下:

参数解析:

x:

        类型为_PacketIterable,无默认值。

        指要发送的包,可传递包、迭代器或存有包的列表。不能传元组(原因不明)。send函数的底层使用for循环遍历x参数,按理来说可迭代对象均可传入。

 

inter:

        类型为int或float,默认为0。

        发包后的停顿时间,以秒为单位。注释上面标注为int型,实际传入float也可正常运行。这是因为它调用的是time模块中的sleep函数。

 

loop:

        类型为int,默认为0。

        发送数据包的数量,是发包while循环的条件。如果没有传递loop的值,同时也没有传递count的值,则loop值会被设为-1。如果传了count的值,则loop会被设为-count。每次循环后,loop值加1,加到0时循环停止。

 

count:

        类型为int,默认为None。

        发送包的数量。当count为None时,loop被设为-1,反之被设为-count。注意不要传入非None、int的值,否则报错。如果传入非负数,正常运行。传入小数,while循环因为loop始终不为0而不会停止。传入正数,因为send函数底层的做法是将loop赋为-count,在每次循环且loop小于0时执行loop += 1,所以loop始终不为0,故循环不会终止。

 

verbose:

        类型为int,默认值为conf.verb,即2。

        是否开启详细模式。该值为真时,打印发包过程的详细信息(其实也没多详细)。即每法送一个包,打印一个点,并在发包结束之后打印发送的总包数。

 

realtime:

        类型为bool,默认为False。

        是否在发送下一个包之前,检查是否已发送了一个包。

        这个参数有没有用我持怀疑态度。在捋完那段绕来绕去的源码后,我得出一个等式:

第一次发包时间-第一个包构造时的时间>第二次发包时间-第二个包构造时时间,只要满足这个不等式,send函数就会使用sleep函数阻塞进程第二个包构造时间-第一个包构造时间+第一次发包时间-第二次发包时间。这段代码是如何检查是否发送了一个包的,我不得而知。

 

return_packets:

        类型为bool,默认为False。

        是否返回发送的数据包。注意,返回的是一个PacketList对象,不是包。PacketList可以用访问下标的方法访问其中的元素。具体的包储存在res成员中。

 

socket:

        类型为SuperSocket, 默认值为conf.L3socket(kargs))。

        使用的套接字。当缺省时,send函数会自行处理。

 

iface:

        类型为str,默认值None。该参数用于指定网卡的名称,网卡的使用由send函数内部完成。如传入"eth0",send函数就会使用eth0网卡发包。当缺省时,send函数会自行选择合适的网卡。

 

montor:

        (用的Linux,没找到这个东西)

返回值解析:

        文档中提到的返回值为None。但经源码分析之后,send函数的返回值应有两个:None或PacketList。当return_packets参数为真时,返回PacketList类,反之返回None。

        None值不多做解释。

        PacketList是定义在plist模块中的一个类。它的一个父类_PacketList重写了__repr__(str类进行类型转换和print函数调用的方法)、__iter__(迭代开始时调用的方法)、__getitem__(索引操作使用的方法)、__add__(重载加法运算符)、__len__(len函数调用的方法)。由此可看出Scapy中对运算符、语句和内置函数的运用不少。

        故此可实现以下操作:

        1.使用print函数打印或调用str类转换send函数的返回值。

        output:

                <PacketList: TCP:0 UDP:0 ICMP:0 Other:1>

        2.像迭代range函数的返回值一样迭代send函数的返回值。

        3.使用索引调取储存在send函数返回值的数据包。这并不是对其中res成员的简单访问,它还涉及其他返回值,目前还没有研究。

        4.使用加法运算符在send函数的返回值之间运算。使用这个东西需要注意。如下:

        代码:

                a = b = send(IP(), return_packets=True)

                d = a + b

                print(d)

        output:

                . (这里有个点)

                Sent 1 packets.

                <PacketList+PacketList: TCP:0 UDP:0 ICMP:0 Other:2>

        如标红部分所示,多少个PacketList类相加,这里的算式就有多少个元素。故此,如果真的要将send函数的返回值相加,谨慎打印。

        5.使用len函数计算send函数返回值中的数据包数量。

        此外,使用PackekList的summary方法,可以按行打印其中储存的包的summary方法结果,即按行打印各个包的梗概。

        当然,返回的PacketList有点鸡肋,毕竟那是我自己发出去的包。但是,在发送随机包的时候,的确可能需要返回发送的包。

        据此,可以总结一些send函数的用法:

1.将count参数设为负值,由此死循环发送数据包,之后用Ctrl+C停止程序

from scapy.all import IP, TCP, send

packet = IP() / TCP()

send(packet, count=-1)

output:

                                                                                                                              
┌──(matriller㉿Hack)-[~/桌面/Practice]
└─$ sudo python demo.py
..................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................^C
Sent 850 packets.

        因为send函数中使用专门的变量来储存发包数,加上整个发包过程处于try-exept语句中,当按下Ctrl+C时,send函数捕获KeyboardInterrupt错误,之后根据传参决定是否展示信息,及传回None或PacketList对象。

        这可以用于事先不知道要发送多少个包的情况。尽管使用while循环可以获得同样的效果,但是调用send函数的花销很大,每次循环都调用一次send函数,那么程序性能会明显下降。

2.传入inter参数,使每次发包之后停顿一段时间

from scapy.all import IP, TCP, send
from time import time, sleep

packet = IP() / TCP()

t = time()
send(packet, count=5, inter=0.5)
print(time()-t)

t = time()
for i in range(5):
    send(packet)
    sleep(0.5)
print(time()-t)

output:

┌──(matriller㉿Hack)-[~/桌面/Practice]
└─$ sudo python demo.py
.....
Sent 5 packets.
2.5401618480682373
.
Sent 1 packets.
.
Sent 1 packets.
.
Sent 1 packets.
.
Sent 1 packets.
.
Sent 1 packets.
2.712531805038452

        这个方法很重要。调用send函数的花销很大,将停顿的工作交由send函数底层去实现可以提高程序性能。在上方的输出中,仅仅只是发送5个包,使用for循环停顿的比起传参停顿的已经有明显的迟钝。而在网络编程中,要发送的绝不仅5个包。

3.向verbose传入假值关闭send函数自身的输出

from scapy.all import IP, TCP, send
from time import time, sleep

packet = IP() / TCP()

send(packet, verbose=0)

output:

┌──(matriller㉿Hack)-[~/桌面/Practice]
└─$ sudo python demo.py

        没有了每发一个包都要打印的点和结束时的总数。有时我们在使用Scapy时不希望send函数打印消息到屏幕,那么这个参数会大有用处。

        由于send函数底层将verbose参数作为if语句的条件,向verbose传入下列值都可以得到相同的效果:(None不行)

0,0.0,'',b'',False,[],()

4.向x参数传入列表,提升发包速度

from scapy.all import IP, TCP, send
from time import time, sleep

packet = [IP() / TCP() for i in range(5)]
print(len(packet))
print(type(packet))
print(packet)

t = time()
send(packet)
print(time()-t)

t = time()
for i in range(5):
    send(IP() / TCP())
print(time()-t)

output:

┌──(matriller㉿Hack)-[~/桌面/Practice]
└─$ sudo python demo.py
5
<class 'list'>
[<IP  frag=0 proto=tcp |<TCP  |>>, <IP  frag=0 proto=tcp |<TCP  |>>, <IP  frag=0 proto=tcp |<TCP  |>>, <IP  frag=0 proto=tcp |<TCP  |>>, <IP  frag=0 proto=tcp |<TCP  |>>]
.....
Sent 5 packets.
0.03799152374267578
.
Sent 1 packets.
.
Sent 1 packets.
.
Sent 1 packets.
.
Sent 1 packets.
.
Sent 1 packets.
0.1679677963256836

        可以看到,向send函数传入列表以使其一次性发送,性能比使用for循环多次调用send函数性能要高的多。详情可以看我的另一篇文章:

Scapy:快速syn洪水攻击(syn flood)_Matriller的博客-CSDN博客利用scapy的特性提升进行syn洪水攻击(syn floor)的速度favicon32.icohttps://blog.csdn.net/m0_71713477/article/details/128556822?spm=1001.2014.3001.5501

源码部分:

def __gen_send(s,  # type: SuperSocket
               x,  # type: _PacketIterable
               inter=0,  # type: int
               loop=0,  # type: int
               count=None,  # type: Optional[int]
               verbose=None,  # type: Optional[int]
               realtime=False,  # type: bool
               return_packets=False,  # type: bool
               *args,  # type: Any
               **kargs  # type: Any
               ):
    # type: (...) -> Optional[PacketList]
    """
    An internal function used by send/sendp to actually send the packets,
    implement the send logic...

    It will take care of iterating through the different packets
    """
    if isinstance(x, str):
        x = conf.raw_layer(load=x)
    if not isinstance(x, Gen):
        x = SetGen(x)
    if verbose is None:
        verbose = conf.verb
    n = 0
    if count is not None:
        loop = -count
    elif not loop:
        loop = -1
    sent_packets = PacketList() if return_packets else None
    p = None
    try:
        while loop:
            dt0 = None
            for p in x:
                if realtime:
                    ct = time.time()
                    if dt0:
                        st = dt0 + float(p.time) - ct
                        if st > 0:
                            time.sleep(st)
                    else:
                        dt0 = ct - float(p.time)
                s.send(p)
                if sent_packets is not None:
                    sent_packets.append(p)
                n += 1
                if verbose:
                    os.write(1, b".")
                time.sleep(inter)
            if loop < 0:
                loop += 1
    except KeyboardInterrupt:
        pass
    finally:
        try:
            cast(Packet, x).sent_time = cast(Packet, p).sent_time
        except AttributeError:
            pass
    if verbose:
        print("\nSent %i packets." % n)
    return sent_packets


def _send(x,  # type: _PacketIterable
          _func,  # type: Callable[[NetworkInterface], Type[SuperSocket]]
          inter=0,  # type: int
          loop=0,  # type: int
          iface=None,  # type: Optional[_GlobInterfaceType]
          count=None,  # type: Optional[int]
          verbose=None,  # type: Optional[int]
          realtime=False,  # type: bool
          return_packets=False,  # type: bool
          socket=None,  # type: Optional[SuperSocket]
          **kargs  # type: Any
          ):
    # type: (...) -> Optional[PacketList]
    """Internal function used by send and sendp"""
    need_closing = socket is None
    iface = resolve_iface(iface or conf.iface)
    socket = socket or _func(iface)(iface=iface, **kargs)
    results = __gen_send(socket, x, inter=inter, loop=loop,
                         count=count, verbose=verbose,
                         realtime=realtime, return_packets=return_packets)
    if need_closing:
        socket.close()
    return results


@conf.commands.register
def send(x,  # type: _PacketIterable
         iface=None,  # type: Optional[_GlobInterfaceType]
         **kargs  # type: Any
         ):
    # type: (...) -> Optional[PacketList]
    """
    Send packets at layer 3

    :param x: the packets
    :param inter: time (in s) between two packets (default 0)
    :param loop: send packet indefinitely (default 0)
    :param count: number of packets to send (default None=1)
    :param verbose: verbose mode (default None=conf.verb)
    :param realtime: check that a packet was sent before sending the next one
    :param return_packets: return the sent packets
    :param socket: the socket to use (default is conf.L3socket(kargs))
    :param iface: the interface to send the packets on
    :param monitor: (not on linux) send in monitor mode
    :returns: None
    """
    iface = _interface_selection(iface, x)
    return _send(
        x,
        lambda iface: iface.l3socket(),
        iface=iface,
        **kargs
    )

参考文献:

浅析Python运算符重载_viclee108的博客-CSDN博客_python 运算符重载
scapy.sendrecv — Scapy 2.4.4. 文档

https://github.com/secdev/scapy/blob/master/scapy/sendrecv.py#L413-L44

 

 

 

 

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值