zip 文件暴破

2007年年初,美国德克萨斯州的布朗斯维尔市的程序员 Albert Castillo 协助警方调查 John Craig Zimmerman 涉嫌拥有和制造儿童色情物品的案件,而 Castillo 就是使用字典式攻击技术^[字典式攻击是一种破解密码的方法。是指在破解密码或密钥时,逐一偿试用户自定义词典中的单词或短语的攻击方式。——字典式攻击技术]解密了嫌疑人电脑中的zip压缩文件,从而得到儿童色情图片等证据,得以结束案件。本文十里就简单介绍一下使用 python3 进行字典式破解加密zip文件的方法。

20181022154020132354092.png

准备工作

加密码的zip文件

首先得准备一个加密码的 zip 文件,以 macOS 为例,可以使用 zip 命令进行压缩并加密码,比如要将 demo.md 压缩为 demo.zip,可以使用命令:

$ zip -e demo.zip demo.md
Enter password:
Verify password:
updating: demo.md (stored 0%)

输入密码和确认密码,即可完成加密码的压缩,我这里使用密码是 4 位的,为 6556,不自己做压缩包的话,可以使用压缩的 zip 压缩包:demo.zip

了解 zipfile 库

本文使用 zipfile 库操作压缩文件,这是一个内建的 python 库,不需要手动安装,这一小节在 ipython 环境下简单了解一下使用方法。命令行中输入 ipython 回车即可进入 python 交互环境:

导入 zipfile 库:

In [1]: import zipfile

打开压缩包

In [2]: zFile = zipfile.ZipFile('demo.zip')

解压压缩包

In [3]: zFile.extractall()
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
<ipython-input-3-24da26377a5e> in <module>()
----> 1 zFile.extractall()
...
RuntimeError: File <ZipInfo filename='demo.md' filemode='-rw-r--r--' file_size=17 compress_size=29> is encrypted, password required for extraction

可以看到提示,因为我们测试用的 demo.zip 是我们加了密码的,所以需要密码。extractall 方法有一个参数 pwd 指明密码,用正确密码试一下:

In [4]: zFile.extractall(pwd='6556')
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-4-f057151f05c2> in <module>()
----> 1 zFile.extractall(pwd='6556')
...
TypeError: pwd: expected bytes, got str

抛出了一个 TypeError 类型错误的异常,看提示是需要密码是 bytes,那进行转码^[Zipfile Method doesn’t works]试一下:

In [5]: zFile.extractall(pwd='6556'.encode('utf8'))

没有了异常或错误提示,说明解压成功了!我们再试一个错误密码:

In [6]: zFile.extractall(pwd='5555'.encode('utf8'))
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
<ipython-input-6-260b36a163cd> in <module>()
----> 1 zFile.extractall(pwd='5555'.encode('utf8'))

RuntimeError: Bad password for file <ZipInfo filename='demo.md' filemode='-rw-r--r--' file_size=17 compress_size=29>

可以看到如果密码错误会提示密码不对,所以可以使用try-except调用 extractall 方法验证密码是否正确,形如:

import zipfile

zFile = zipfile.ZipFile('demo.zip')
try:
    zFile.extractall(pwd=key.encode('utf8'))
    print(f'find password -> {key}')
except:
    print('bad password')

检查是否加密

检查压缩包是否加密这也是必要的,这里使用 infolist 方法可以查看加密文件信息^[How to check if a zip file is encrypted using python’s standard library zipfile?],从而知道是否加密,如下:

In [7]: for i in zFile.infolist():
    ...:     if i.flag_bits & 0x01:
    ...:         print('is encrypted')
    ...:
is encrypted

密码字典

这里使用 python3 生成密码字典 一文中实现的 keykey 工具生成数字组成的4位密码字典,使用keykey工具在当前目录生成密码字典文件 keys.dict

$ python3 keykey 4 4 -c '0123456789' -o 'keys.dict'
[+] 已生成 4 ~ 4 位密码字典 [+]
[+] 已保存密码字典到 /Users/5km/Documents/workspace/python/tools-with-script/crackzip/keys.dict [+]

最终生成密码字典文件 keys.dict 中,每行一个密码:

2018102315402664999086.png

实现

新建 crackzip 文件,添加以下内容,指明脚本解释器和导入 zipfile 库:

#!/usr/bin/env python3
import zipfile
import argparse

为文件 `` 添加运行权限:

$ chmod +x crackzip

为了使程序模块化,需要对一些操作进行封装。

破解操作

破解操作封装如下:

def crackzipfile(zipFile, password):
    """尝试破解 zip 文件
    Args:
        zipFile: 待解压 zip 实例
        password: 密码
    Return:
        如果破解解压成功返回 True,如果出现异常返回 False,说明密码错误
    """
    try:
        zipFile.extractall(pwd=password.encode('utf8'))
        return password
    except:
        return

如果密码正确就会返回密码,反之返回 None

判断是否加密

使用 检查是否加密 小节中的方法,封装为 is_encrypted方法如下:

def is_encrypted(zipFile):
    """检查 zip 文件是否加密
    Args:
        zipFile: 待解压 zip 实例
    """
    for info in zipFile.infolist():
        if info.flag_bits & 0x01:
            return True
        else:
            return False

获取命令参数

使用 argparse 库解析命令参数,这里也进行封装:

def getargs():
    """ 获取命令行参数
    Return:
        返回命令行参数解析结果
    """
    parser = argparse.ArgumentParser(description='暴力破解 zip 文件的密码')
    parser.add_argument('zfile', help='指明要破解的 zip 文件,可以是多个')
    parser.add_argument('dict', help='指定使用的密码词典')
    return parser.parse_args()

配置了两个占位参数,调用命令时,必须指定 zip 文件和密码字典文件。

主函数

整个破解流程封装到 main 方法:

def main():
    args = getargs()
    with zipfile.ZipFile(args.zfile) as zFile:
        if not is_encrypted(zFile):
            print(f'{args.zfile} 未加密!')
            exit(0)
        with open(args.dict, 'r') as f:
            for line in f.readlines():
                key = line.strip('\n')
                password = crackzipfile(zFile, key)
                if password:
                    print(f'[+] 密码是 {password} [+]')
                    exit(0)

首先获取命令参数,因为要用到 zip 文件和密码字典文件的路径,打开 zip 文件,然后判断是否是加密的,如果不是加密的就打印提示信息,并结束程序;如果是加密的,就读取密码字典中的密码尝试解压,如果找到正确密码,打印信息并结束程序。

直接在执行 main 即可运行程序:

if __name__ == '__main__':
    main()

完整代码参考:tools-with-script/crackzip/crackzip

测试

目录下有一个加密的 zip 文件 demo.zip 和一个没有加密的 zip 文件 demo1.zip,测试如下:

$ ./crackzip -h
usage: crackzip [-h] zfile dict

暴力破解 zip 文件的密码

positional arguments:
  zfile       指明要破解的 zip 文件,可以是多个
  dict        指定使用的密码词典

optional arguments:
  -h, --help  show this help message and exit
$ ./crackzip demo.zip keys.dict
[+] 密码是 6556 [+]
$ ./crackzip demo1.zip keys.dict
demo1.zip 未加密!

总结

通过使用 zipfile 库和 argparse 库简单实现了破解 zip 文件密码的工具,也简单了解了字典式破解密码的基本原理,这种破解原理适用大部分的密码破解工作,但有时也必须考虑所需时间的合理性。