Java的Jar包是一个很方便的功能,特别是对于拥有大量依赖的程序,只需要将所有内容打包成一个jar包,即可分发给用户直接使用。
Python也是支持类似的功能的。我们可以尝试创建一个
__main__.py
文件:
print("hello world")
然后将其用zip打包,并且直接用python执行:
$ zip demo.zip ./*
adding: __main__.py (stored 0%)
$ python3 demo.zip
hello world
成功输出“hello world”。这说明Python同样支持使用一个包的方式来执行一个程序。
Python文档: https://docs.python.org/3/library/zipimport.html 中描述了zipimport模块:
This module adds the ability to import Python modules (
*.py
,
*.pyc
) and packages from ZIP-format archives. It is usually not needed to use the
zipimport
module explicitly; it is automatically used by the built-in
import
mechanism for
sys.path
items that are paths to ZIP archives.
zipimport支持Python从一个压缩包中导入模块,但是实际上我们平时不需要直接调用这个zipimport,因为Python默认的
import
语法是支持在压缩包里搜索模块的。
比如,我们拥有一个example.zip,其中包含模块
jwzthreading.py
,我们只需要将这个zip文件加入
sys.path
,在导入模块时就会在example.zip里搜索:
$ unzip -l example.zip
Archive: example.zip
Length Date Time Name
-------- ---- ---- ----
8467 11-26-02 22:30 jwzthreading.py
-------- -------
8467 1 file
$ python
>>> import sys
>>> sys.path.insert(0, 'example.zip') # Add .zip file to front of path
>>> import jwzthreading
>>> jwzthreading.__file__
'example.zip/jwzthreading.py'
Python执行zip文件也是类似原理,而我们提供的
__main__.py
只是一个执行入口。
那么,我们如何将一个Python工具打包成一个独立的zip包并使用呢?
我以常用的子域名发现工具 Sublist3r 为例,首先,拉取Sublist3r源码,并创建一个干净的Python虚拟环境:
$ git clone https://github.com/aboul3la/Sublist3r.git
Cloning into 'Sublist3r'...
remote: Enumerating objects: 6, done.
remote: Counting objects: 100% (6/6), done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 379 (delta 2), reused 6 (delta 2), pack-reused 373
Receiving objects: 100% (379/379), 1.12 MiB | 1.04 MiB/s, done.
Resolving deltas: 100% (210/210), done.
$ virtualenv env
Using base prefix '/usr'
New python executable in /tmp/www/env/bin/python3
Also creating executable in /tmp/www/env/bin/python
Installing setuptools, pip, wheel...
done.
再在这个虚拟环境里安装Sublist3r的一些第三方依赖:
$ source env/bin/activate
$ pip install -r Sublist3r/requirements.txt
Collecting argparse
Using cached argparse-1.4.0-py2.py3-none-any.whl (23 kB)
Collecting dnspython
Using cached dnspython-1.16.0-py2.py3-none-any.whl (188 kB)
Collecting requests
Using cached requests-2.23.0-py2.py3-none-any.whl (58 kB)
Collecting urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1
Using cached urllib3-1.25.9-py2.py3-none-any.whl (126 kB)
Collecting chardet<4,>=3.0.2
Using cached chardet-3.0.4-py2.py3-none-any.whl (133 kB)
Collecting idna<3,>=2.5
Using cached idna-2.9-py2.py3-none-any.whl (58 kB)
Collecting certifi>=2017.4.17
Using cached certifi-2020.4.5.1-py2.py3-none-any.whl (157 kB)
Installing collected packages: argparse, dnspython, urllib3, chardet, idna, certifi, requests
Successfully installed argparse-1.4.0 certifi-2020.4.5.1 chardet-3.0.4 dnspython-1.16.0 idna-2.9 requests-2.23.0 urllib3-1.25.9
此时,虚拟环境中包含了所有需要的依赖了。然后,我们将Sublist3r的源码拷贝到虚拟环境的
lib/python3.6/site-packages
目录下:
$ cp -r Sublist3r/subbrute Sublist3r/sublist3r.py env/lib/python3.6/site-packages
这时候,整体的环境就做好了,但是我们还需要一个入口文件,也就是
__main__.py
。我们可以直接将
env/lib/python3.6/site-packages/sublist3r.py
改名为
env/lib/python3.6/site-packages/__main__.py
,再打包即可:
$ cd env/lib/python3.6/site-packages
$ mv sublist3r.py __main__.py
$ zip -r sublister.zip ./
打包完成后大小有6MB,即可在任意有Python3的环境下直接运行了:
这个特性具体有哪些优势和用处呢?我觉得主要可能在下面这些场景用到:
Python命令行工具,比如这里的Sublist3r,使用zip的形式一键分发,任意环境下使用,不再需要安装依赖,更加方便
内网渗透或运维没有网络的场景下,无法使用pip,有些工具不方便安装,使用zip的形式也能摆脱这些烦恼
可能会有杀毒软件只针对了Python文本恶意文件进行检测,但没有考虑压缩包的形式,导致可以使用这种形式免杀
但实际使用中,这个特性的缺点也更加明显:
根据文档和实际测试发现,在import时只会搜索压缩包里的
*.py
和
*.pyc
文件,如果你的工具依赖了
*.so
和
*.pyd
等native模块,则会出现找不到模块的错误
很多软件没有考虑被放在压缩包里执行的情况(如sqlmap),在操作文件系统时可能会出现找不到文件的错误
更多具体的使用优缺点,大家可以自己体验体验,在留言里告诉我。