Python Scapy 和 Pyinstaller

Python Scapy and Pyinstaller

我正在尝试使用 pyinstaller 从我用于测试的脚本创建简单的可执行文件,这样我就不必在我正在测试的服务器上安装所有东西。

#! /usr/bin/env python

from scapy.all import *

sourceport=int(raw_input('Soruce port:'))
destinationport=int(raw_input('Destination port:'))
destinationip=raw_input('Destination IP:')
maxttl=int(raw_input('MAX TTL:'))

for i in range(1,maxttl):
     udptrace = IP(dst=destinationip,ttl=i)/UDP(dport=destinationport,sport=sourceport,len=500)
     received=sr1(udptrace,verbose=0,timeout=2)
     try:
         print received.summary()
     except AttributeError:
         print "** TIMEOUT **"

然后我制作可执行文件:

pyinstaller -F udp.py

然而,当我 运行 它时,出现以下错误:

Soruce port:500
Destination port:500
Destination IP:4.4.4.4
MAX TTL:3
Traceback (most recent call last):
  File "<string>", line 16, in <module>
NameError: name 'IP' is not defined
user@:~/2/dist$

我花了一些时间研究但没有找到任何答案。

问题

首先,我们需要查明确切的问题。

PyInstaller 手册 specifies 即:

Some Python scripts import modules in ways that PyInstaller cannot detect: for example, by using the __import__() function with variable data.

检查 Scapy's source code 表明这正是各种网络层的导入方式:

  1. scapy/layers/all.py:

    def _import_star(m):
        mod = __import__(m, globals(), locals())
        for k,v in mod.__dict__.iteritems():
            globals()[k] = v
    
    for _l in conf.load_layers:
        log_loading.debug("Loading layer %s" % _l)
        try:
            _import_star(_l)
        except Exception,e:
        log.warning("can't import layer %s: %s" % (_l,e))
    

    请注意 conf.load_layers 中的每个模块都会调用 __import__

  2. scapy/config.py:

    class Conf(ConfClass):
        """This object contains the configuration of scapy."""
         load_layers = ["l2", "inet", "dhcp", "dns", "dot11", "gprs", "hsrp", "inet6", "ir", "isakmp", "l2tp",
                       "mgcp", "mobileip", "netbios", "netflow", "ntp", "ppp", "radius", "rip", "rtp",
                       "sebek", "skinny", "smb", "snmp", "tftp", "x509", "bluetooth", "dhcp6", "llmnr", "sctp", "vrrp",
                       "ipsec" ]
    

    请注意 Conf.load_layers 包含 "inet"

  3. 文件 scapy/layers/inet.py 定义了 IP class,在随附的示例中未成功导入。

解决方案

现在,我们已经找到了根本原因,让我们看看可以做些什么。

PyInstaller 手册suggests 此类导入问题的一些解决方法:

  • You can give additional files on the PyInstaller command line.
  • You can give additional import paths on the command line.
  • You can edit the myscript.spec file that PyInstaller writes the first time you run it for your script. In the spec file you can tell PyInstaller about code and data files that are unique to your script.
  • You can write "hook" files that inform PyInstaller of hidden imports. If you "hook" imports for a package that other users might also use, you can contribute your hook file to PyInstaller.

一些谷歌搜索显示适当的 "hook" 已经添加到默认的 PyInstaller 发行版中,在此 commit 中,它引入了文件 PyInstaller/hooks/hook-scapy.layers.all.py.

PyInstaller 手册 indicates 这样的内置挂钩应该 运行 自动:

In summary, a "hook" file tells PyInstaller about hidden imports called by a particular module. The name of the hook file is hook-<module>.py where "<module>" is the name of a script or imported module that will be found by Analysis. You should browse through the existing hooks in the hooks folder of the PyInstaller distribution folder, if only to see the names of the many supported imports.

For example hook-cPickle.py is a hook file telling about hidden imports used by the module cPickle. When your script has import cPickle the Analysis will note it and check for a hook file hook-cPickle.py.

底线

因此,请确认您运行正在使用最新版本的 PyInstaller。如果您无法升级到最新版本,或者它不包含文件 PyInstaller/hooks/hook-scapy.layers.all.py,则使用 following content:

创建它
#-----------------------------------------------------------------------------
# Copyright (c) 2013, PyInstaller Development Team.
#
# Distributed under the terms of the GNU General Public License with exception
# for distributing bootloader.
#
# The full license is in the file COPYING.txt, distributed with this software.
#-----------------------------------------------------------------------------

from PyInstaller.hooks.hookutils import collect_submodules

# The layers to load can be configured using scapy's conf.load_layers.
#  from scapy.config import conf; print(conf.load_layers)
# I decided not to use this, but to include all layer modules. The
# reason is: When building the package, load_layers may not include
# all the layer modules the program will use later.

hiddenimports = collect_submodules('scapy.layers')