zipfile.BadZipFile:提取受密码保护的.zip和.zip时,提取时损坏了CRC-32错误

问题描述

抱歉,长时间的停顿……看来您已经陷入了困境。

  • 处理受密码保护的 .zip 文件
  • 尝试使用文件中的密码进行暴力破解( ciobaneste
  • 正确的密码在(上一步)文件中,但是尽管如此,某些文件仍未正确提取

1.调查

情况很复杂(我想说,这与 _ CVE_距离很远),很多原因都可以归咎于这种行为。

zipv1.zip / zipv2.zip 不匹配开始。仔细观察,似乎 。如果对于 zipv1来说 很容易发现( Congrats.txt 是唯一的文件),那么对于 zipv2来说“ ZIP Contents / Black- Large.png”的 为 。 它是可重复使用的任何文件,以及更多: 。

因此,事情开始变得更加清晰:

  • 由于密码文件中存在 dominique ,因此正在解压缩文件内容(不知道到那时会发生什么)
  • 在稍后的点,所述 的.zip 的1日条目被截断为 0 字节

查看尝试使用错误密码提取文件时引发的异常,共有3种类型(其中最后2种可以组合在一起):

  1. 其他:

我创建了自己的存档文件。为了保持一致性,从现在开始我将使用它,但是所有内容也将适用于任何其他文件。

  • 内容:
    • DummyFile0.zip10 个字节)-包含: 0123456789
    • DummyFile1.zip10 个字节)-包含: 0000000000
    • DummyFile2.zip10 字节)-包含: AAAAAAAAAA
  • 归档的3个文件与 总指挥官9.21a )内部 压缩 打包机,密码以保护它 多米尼克zip2.0 加密)。生成的存档(命名为 arc0.zip (但名称不相关))长 392 个字节

code.py

#!/usr/bin/env python3

import sys
import os
import zipfile


def main():
    arc_name = sys.argv[1] if len(sys.argv) > 1 else "./arc0.zip"
    pwds = [
        #b"dominique",
        #b"dickhead",
        b"coco",
    ]
    pwds = [item.strip() for item in open("orig/John The Ripper.txt.orig", "rb").readlines()]
    print("Unpacking (password protected: dominique) {:s},"
          " using a list of predefined passwords ...".format(arc_name))
    if not os.path.isfile(arc_name):
        raise SystemExit("Archive file must exist!\nExiting.")
    faulty_pwds = list()
    good_pwds = list()
    with zipfile.ZipFile(arc_name, "r") as zip_file:
        print("Zip names: {:}\n".format(zip_file.namelist()))
        for idx, pwd in enumerate(pwds):
            try:
                zip_file.extractall("Extracted", pwd=pwd)
            except:
                exc_cls, exc_inst, exc_tb = sys.exc_info()
                if exc_cls != RuntimeError:
                    print("Exception caught when using password ({:d}): [{:}] ".format(idx, pwd))
                    print("    {:}: {:}".format(exc_cls, exc_inst))
                    faulty_pwds.append(pwd)
            else:
                print("Success using password ({:d}): [{:}] ".format(idx, pwd))
                good_pwds.append(pwd)
    print("\nFaulty passwords: {:}\nGood passwords: {:}".format(faulty_pwds, good_pwds))


if __name__ == "__main__":
    print("Python {:s} on {:s}\n".format(sys.version, sys.platform))
    main()

[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q054532010]>

“e:\Work\Dev\VEnvs\py_064_03.06.08_test0\Scripts\python.exe” code.py arc0.zip Python 3.6.8 (tags/v3.6.8:3c6b436a57, Dec 24 2018, 00:16:47) [MSC v.1916 64 bit (AMD64)] on win32

Unpacking (password protected: dominique) arc0.zip, using a list of

predefined passwords … Zip names: [‘DummyFile0.txt’, ‘DummyFile1.txt’, ‘DummyFile2.txt’]

Exception caught when using password (1189): [b'mariah']
    <class 'zlib.error'>: Error -3 while decompressing data: invalid

code lengths set Exception caught when using password (1446): [b’zebra’]: Error -3 while decompressing data: invalid block type Exception caught when using password (1477): [b‘1977’]: Error -3 while decompressing data: invalid block type Success using password (1967): [b’dominique’] Exception caught when using password (2122): [b’hank’]: Error -3 while decompressing data: invalid code lengths set Exception caught when using password (2694): [b’solomon’]: Error -3 while decompressing data: invalid distance code Exception caught when using password (2768): [b’target’]: Error -3 while decompressing data: invalid block type Exception caught when using password (2816): [b’trish’]: Error -3 while decompressing data: invalid code lengths set Exception caught when using password (2989): [b’coco’]: Error -3 while decompressing data: invalid stored block lengths

Faulty passwords: [b'mariah', b'zebra', b'1977', b'hank', b'solomon',

b’target’, b’trish’, b’coco’] Good passwords: [b’dominique’]

查看 ZipFile.extractall 代码,它尝试提取所有成员。1号引发了一个异常,因此它开始变得更清楚为什么会如此行事。但是,当尝试使用2个错误的密码提取项目时,为什么会有行为上的差异? 从对2种引发的异常类型的追溯中可以看出,答案就在 ZipFile.open的末尾

经过更多调查,结果是由于

2. zip 加密弱点决定的冲突

根据[UT.CS]:dmitri-report-f15-16.pdf- ZIP文件中基于密码的加密(最后一个 是我的意思):

最初的加密方案通常称为PKZIP密码,由Roger Schaffely设计[1]。在[5]中,Biham和Kocher证明该密码很弱,并证明了需要13字节明文的攻击。已经开发了进一步的攻击,其中一些攻击根本不需要用户提供纯文本[6]。PKZIP密码本质上是流密码,即通过生成伪随机密钥流并将其与明文进行XOR运算来加密输入。密码的内部状态由三个32位字组成: , 和 。这些被初始化为 , 和 , 分别。该算法的核心步骤涉及使用输入的单个字节更新三个键。

在加密档案中的文件之前,首先将12个随机字节添加到其压缩内容之前,然后对生成的字节流进行加密。解密时,前12个字节需要丢弃。根据规范,这样做是为了使对数据的纯文本攻击无效。该规范还指出,在12个前置字节中,。这可以在解密文件的其余部分之前完成。

其他参考:

该算法的弱点:由于 进行 ,对于 错误密码,将有 。

该算法会丢弃大多数错误的密码,但有一些并非如此。

返回:当尝试使用密码提取文件时:

  • 如果在文件密码的最后一个字节上计算出的“哈希”与文件 CRC 的高位字节不同,则抛出异常
  • ,如果它们相等:
    • 打开一个新的文件流以进行写入(如果已经存在,则清空文件)
    • 尝试进行减压:
    • 对于错误的密码(已通过上述检查),解压缩将失败(但文件已被清空)

从上面的输出中可以看到,对于我的( .zip )文件,有 _ _ 密码将其弄乱了。注意:

  • 对于每个存档文件,结果都不同
  • 所述 的文件名和内容是相关的(至少对于1 ST之一)。更改其中任何一个将产生不同的结果(对于“相同”存档文件)

这是一个基于我的 .zip 文件中数据的测试:

>>> import zipfile
>>>
>>> zd_coco = zipfile._ZipDecrypter(b"coco")
>>> zd_dominique = zipfile._ZipDecrypter(b"dominique")
>>> zd_other = zipfile._ZipDecrypter(b"other")
>>> cipher = b'\xd1\x86y ^\xd77gRzZ\xee'  # Member (1st) file cipher: 12

bytes starting from archive offset 44 >>> >>> crc = 2793719750 # Member (1st) file CRC - archive bytes: 14 - 17 >>> hex(crc) ‘0xa684c7c6’ >>> for zd in (zd_coco, zd_dominique, zd_other): … print(zd, [hex(zd(c)) for c in cipher]) … [‘0x1f’, ‘0x58’, ‘0x89’, ‘0x29’, ‘0x89’, ‘0xe’, ‘0x32’, ‘0xe7’, ‘0x2’, ‘0x31’, ‘0x70’, ‘0xa6’] [‘0xa8’, ‘0x3f’, ‘0xa2’, ‘0x56’, ‘0x4c’, ‘0x37’, ‘0xbb’, ‘0x60’, ‘0xd3’, ‘0x5e’, ‘0x84’, ‘0xa6’] [‘0xeb’, ‘0x64’, ‘0x36’, ‘0xa3’, ‘0xca’, ‘0x46’, ‘0x17’, ‘0x1a’, ‘0xfb’, ‘0x6d’, ‘0x6c’, ‘0x4e’] >>> # As seen, the last element of the first 2 arrays (coco and dominique) is 0xA6 (166), which is the same as the first byte of the CRC

我使用其他拆包引擎(使用默认参数)进行了一些测试:

  1. WinRar :对于错误的密码,文件不会被修改,但是对于错误的密码,文件将被截断(与此处相同)
  2. 7-Zip :询问用户是否覆盖文件,并且无论是否进行解压缩都将其覆盖
  3. Total Commander 的内部( zip )解 压缩 器:与 _ 相同 _

3.结论

  • 我将其视为 zipfile 错误。指定这样一个错误(错误)的密码不应覆盖现有文件(如果有)。或者至少,行为应该是一致的(对于所有错误的密码)
  • 快速浏览未发现 Python上的 任何错误 __
  • 我看不到简单的解决方法,例如:
    • 压缩 算法不能提高(以更好地检查密码是否OK)
    • 我想到了几个修复程序,但它们可能会对性能产生负面影响,或者在某些(角落)情况下可能会导致性能下降

我已经提交了 (仅在 安全修复 模式下 , )。不知道它的结果是什么(在其他分支中),但是无论如何,它不会很快可用(在接下来的几个月里)。

或者,您可以下载补丁,然后在本地应用更改。检查[[SO]:从鼠标右键单击PyCharm Community Edition中的上下文菜单运行/调试Django应用程序的UnitTests( 部分),介绍如何在 Win 上应用修补程序(基本上,每行以 号开头的行都进入,而每行以 号开头的行都熄灭)。我正在使用 Cygwin顺便说一句 。 您可以将 zipfile.pyPython 的目录复制到您的项目(或某些“个人”)目录,然后对该文件进行修补, __安装原始。

解决方法

我正在尝试提取受密码保护的.zip文件,其中包含一个.txt文件(Congrats.txt这种情况可以)。现在其中Congrats.txt包含文本,因此其大小不为0kb。将其放在zipv1.zip带有密码的.zip中(出于线程的考虑,让其命名为.zip
),并为此设置密码dominique。该密码将存储在另一个.txt中的其他单词和名称中(file.txt为方便起见,我们将其命名)。

现在,如果我通过执行以下代码python Program.py -z zipv1.zip -f file.txt(假设所有这些文件都与处于同一文件夹中Program.py)运行,则我的程序将显示dominiquezipv1.zip其中的其他单词/密码的正确密码,file.txt并提取,zipv1.zipCongrats.txt为空,大小为0kb

现在我的代码如下:

import argparse
import multiprocessing
import zipfile

parser = argparse.ArgumentParser(description="Unzips a password protected .zip",usage="Program.py -z zip.zip -f file.txt")
# Creates -z arg
parser.add_argument("-z","--zip",metavar="",required=True,help="Location and the name of the .zip file.")
# Creates -f arg
parser.add_argument("-f","--file",help="Location and the name of file.txt.")
args = parser.parse_args()


def extract_zip(zip_filename,password):
    try:
        zip_file = zipfile.ZipFile(zip_filename)
        zip_file.extractall(pwd=password)
        print(f"[+] Password for the .zip: {password.decode('utf-8')} \n")
    except:
        # If a password fails,it moves to the next password without notifying the user. If all passwords fail,it will print nothing in the command prompt.
        pass


def main(zip,file):
    if (zip == None) | (file == None):
        # If the args are not used,it displays how to use them to the user.
        print(parser.usage)
        exit(0)
    # Opens the word list/password list/dictionary in "read binary" mode.
    txt_file = open(file,"rb")
    # Allows 8 instances of Python to be ran simultaneously.
    with multiprocessing.Pool(8) as pool:
        # "starmap" expands the tuples as 2 separate arguments to fit "extract_zip"
        pool.starmap(extract_zip,[(zip,line.strip()) for line in txt_file])


if __name__ == '__main__':
    main(args.zip,args.file)

但是,如果我zipv2.zip使用zipv1.zip与唯一不同的方法进行另一次zip()处理,则Congrats.txt是在文件夹中并排压缩了文件夹的情况下,Congrats.txt我确实获得了与zip相同的结果,zipv1.zip但是这次是Congrats.txt沿着其所在的文件夹提取的,并且Congrats.txt完好无损;其中的文字及其大小是完整的。

因此,为了解决此问题,我尝试阅读zipfile的文档,发现如果密码与.zip不匹配,则会抛出RuntimeError。所以我确实except:将代码更改为except RuntimeError:并尝试解压缩时遇到此错误zipv1.zip

(venv) C:\Users\USER\Documents\Jetbrains\PyCharm\Program>Program.py -z zipv1.zip -f file.txt
[+] Password for the .zip: dominique

multiprocessing.pool.RemoteTraceback:
"""
Traceback (most recent call last):
  File "C:\Users\USER\AppData\Local\Programs\Python\Python37\lib\multiprocessing\pool.py",line 121,in worker
result = (True,func(*args,**kwds))
  File "C:\Users\USER\AppData\Local\Programs\Python\Python37\lib\multiprocessing\pool.py",line 47,in starmapstar
return list(itertools.starmap(args[0],args[1]))
  File "C:\Users\USER\Documents\Jetbrains\PyCharm\Program\Program.py",line 16,in extract_zip
zip_file.extractall(pwd=password)
  File "C:\Users\USER\AppData\Local\Programs\Python\Python37\lib\zipfile.py",line 1594,in extractall
self._extract_member(zipinfo,path,pwd)
  File "C:\Users\USER\AppData\Local\Programs\Python\Python37\lib\zipfile.py",line 1649,in _extract_member
shutil.copyfileobj(source,target)
  File "C:\Users\USER\AppData\Local\Programs\Python\Python37\lib\shutil.py",line 79,in copyfileobj
buf = fsrc.read(length)
  File "C:\Users\USER\AppData\Local\Programs\Python\Python37\lib\zipfile.py",line 876,in read
data = self._read1(n)
  File "C:\Users\USER\AppData\Local\Programs\Python\Python37\lib\zipfile.py",line 966,in _read1
self._update_crc(data)
  File "C:\Users\USER\AppData\Local\Programs\Python\Python37\lib\zipfile.py",line 894,in _update_crc
raise BadZipFile("Bad CRC-32 for file %r" % self.name)
zipfile.BadZipFile: Bad CRC-32 for file 'Congrats.txt'
"""

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "C:\Users\USER\Documents\Jetbrains\PyCharm\Program\Program.py",line 38,in <module>
main(args.zip,args.file)
  File "C:\Users\USER\Documents\Jetbrains\PyCharm\Program\Program.py",line 33,in main
pool.starmap(extract_zip,line.strip()) for line in txt_file])
  File "C:\Users\USER\AppData\Local\Programs\Python\Python37\lib\multiprocessing\pool.py",line 276,in starmap
return self._map_async(func,iterable,starmapstar,chunksize).get()
  File "C:\Users\USER\AppData\Local\Programs\Python\Python37\lib\multiprocessing\pool.py",line 657,in get
raise self._value
zipfile.BadZipFile: Bad CRC-32 for file 'Congrats.txt'

尽管发生了相同的结果。密码被发现file.txtzipv1.zip提取,但是Congrats.txt是空的,0KB大小。因此,我再次运行了程序,但是zipv2.zip这次并得到了以下结果:

(venv) C:\Users\USER\Documents\Jetbrains\PyCharm\Program>Program.py -z zipv2.zip -f file.txt
[+] Password for the .zip: dominique

multiprocessing.pool.RemoteTraceback:
"""
Traceback (most recent call last):
  File "C:\Users\USER\AppData\Local\Programs\Python\Python37\lib\multiprocessing\pool.py",in get
raise self._value
zipfile.BadZipFile: Bad CRC-32 for file 'Congrats.txt'

同样,结果相同;该文件夹已成功Congrats.txt提取,并且其中的文本也已提取,并且其大小保持不变。

我确实看过了这个类似的线程以及这个线程,但是它们没有帮助。我还检查了zipfile的文档,但是对于该问题没有帮助。

编辑

现在with zipfile.ZipFile(zip_filename,'r') as zip_file:由于某种未知和奇怪的原因而实施;该程序可以读取/处理较小的单词列表/密码列表/字典,但如果其较大(?),则无法读取/处理。

我的意思是说.txt文件存在于zipv1.zip;中。以Congrats.txt文字命名You have cracked the .zip!。还存在相同的.txtzipv2.zip文件,但是这次将其放置在一个名为ZIP Contents然后被压缩/密码保护的文件夹中。密码是dominique两个邮编的。

请注意,每个.zip都是使用7zip中的Deflate压缩方法和ZipCrypto加密生成的。

现在,密码是Line 35(35/52线)John The Ripper Jr.txt,并Line 1968John The Ripper.txt(三千一百零六分之一千九百六十八线)。

现在,如果python Program.py -z zipv1 -f "John The Ripper Jr.txt"您使用CMD(或您选择的IDE)进行操作;它将创建一个名为的文件夹,Extracted并放置Congrats.txt我们之前设置的句子。同样适用,zipv2Congrats.txt将位于ZIP Contents文件夹内的Extracted文件夹中。在这种情况下,解压缩.zip文件没有问题。

但是,如果你尝试同样的事情John The Ripper.txt,即python Program.py -z zipv1 -f "John The Ripper.txt"在你的CMD(或您选择的IDE),它会创建一个Extracted文件夹,无论是拉链的; 就像,John The Ripper Jr.txt但是由于某种未知的原因,这一次对他们俩来说Congrats.txt都是 的。

我的代码和所有必要的文件如下:

import argparse
import multiprocessing
import zipfile

parser = argparse.ArgumentParser(description="Unzips a password protected .zip by performing a brute-force attack.",help="Location and the name of the word list/password list/dictionary.")
args = parser.parse_args()


def extract_zip(zip_filename,password):
    try:
        with zipfile.ZipFile(zip_filename,'r') as zip_file:
            zip_file.extractall('Extracted',pwd=password)
            print(f"[+] Password for the .zip: {password.decode('utf-8')} \n")
    except:
        # If a password fails,line.strip()) for line in txt_file])


if __name__ == '__main__':
    # Program.py - z zipname.zip -f filename.txt
    main(args.zip,args.file)

程序

zipv1.zip

zipv2.zip

约翰开膛手Jr.txt

约翰开膛手.txt

约翰开膛手v2.txt

我不确定为什么会这样,并且无法在任何地方找到该问题的答案。据我所知,它是完全未知的,我找不到调试或解决此问题的方法。

无论是否有不同的单词/密码列表,这种情况都会继续发生。尝试Congrats.txt从不同的单词列表/密码列表/字典生成具有相同但密码不同的更多.zip
。相同的方法;.txt使用了较大和较小的版本,并且获得了与上述相同的结果。

但是 我确实发现,如果我切入前2k个单词John The Ripper.txt并创建一个新的.txt,说John The Ripper v2.txt;
.zipExtracted文件成功解压缩,出现文件夹并Congrats.txt在其中显示文本。因此,我认为它与密码输入后的行有关。因此在这种情况下Line 1968; 之后脚本不会停止的地方Line 1968?我不知道为什么会这样。我想这不是解决方案,而是迈向解决方案的一步…

编辑2

因此,我尝试使用“池终止”代码:

import argparse
import multiprocessing
import zipfile

parser = argparse.ArgumentParser(description="Unzips a password protected .zip by performing a brute-force attack using",password,queue):
    try:
        with zipfile.ZipFile(zip_filename,"r") as zip_file:
            zip_file.extractall('Extracted',pwd=password)
            print(f"[+] Password for the .zip: {password.decode('utf-8')} \n")
            queue.put("Done")  # Signal success
    except:
        # If a password fails,file):
    if (zip == None) | (file == None):
        print(parser.usage)  # If the args are not used,it displays how to use them to the user.
        exit(0)
    # Opens the word list/password list/dictionary in "read binary" mode.
    txt_file = open(file,"rb")

    # Create a Queue
    manager = multiprocessing.Manager()
    queue = manager.Queue()

    with multiprocessing.Pool(8) as pool:  # Allows 8 instances of Python to be ran simultaneously.
        pool.starmap_async(extract_zip,line.strip(),queue) for line in txt_file])  # "starmap" expands the tuples as 2 separate arguments to fit "extract_zip"
        pool.close()
        queue.get(True)  # Wait for a process to signal success
        pool.terminate()  # Terminate the pool
        pool.join()


if __name__ == '__main__':
    main(args.zip,args.file)  # Program.py -z zip.zip -f file.txt.

现在,如果我使用此拉链,则两个拉链都将成功提取,就像以前的实例一样。 但是 这次zipv1.zip
Congrats.txt完好无损;里面有消息。但是关于zipv2.zip它仍然是空的,不能说相同的事情。

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...