Python攻防-暴力破解ZIP加密文件的密码

Posted Tr0e

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python攻防-暴力破解ZIP加密文件的密码相关的知识,希望对你有一定的参考价值。

前言

本文继续记录学习下 Python 的有趣应用:借助 Python 脚本暴力破解 ZIP 加密文件的密码。虽然有相关的工具 ARCHPR 可实现 RAR、ZIP 等压缩加密文件的可视化暴力破解,但是主要是为了学习 Python 编程应用。

Python语法

既然本意是学习 Python 编程,那自然是要对本实战应用场景的编码过程遇到的相关语法知识进行学习。

在此先推荐一个 Python 语法的官方站点:Python官方中文文档,支持下载到本地。

自定义迭代器

迭代是 Python 最强大的功能之一,是访问集合元素的一种方式。迭代器是一个可以记住遍历的位置的对象,迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退,迭代器有两个基本的方法:iter()next()

1、迭代器对象可以使用常规 for 语句进行遍历:

#!/usr/bin/python3
list=[1,2,3,4]
it = iter(list)    # 创建迭代器对象
for x in it:
    print (x, end=" ")

执行以上程序,输出结果如下:
1 2 3 4

2、也可以使用 next() 函数:

#!/usr/bin/python3 
import sys         # 引入 sys 模块
 
list=[1,2,3,4]
it = iter(list)    # 创建迭代器对象 
while True:
    try:
        print (next(it))
    except StopIteration:
        sys.exit()
        
执行以上程序,输出结果如下:
1
2
3
4

3、Python 支持编写 class 来自定义迭代器,如何自定义一个迭代器:

  • 在自定义的类中添加了__iter__魔法方法可取得迭代器;
  • 在自定义的类中通过__next__魔法方法指出所有的数据。

来看看一个简单的自定义可迭代的类示例:

class TopTen(object):
    def __init__(self):
        self.num = 1
    def __iter__(self):
        return self
    def __next__(self):
        if self.num <= 10:
            val = self.num
            self.num += 1
            return val
        else:
            raise StopIteration

if __name__ == "__main__":
    values = TopTen()
    for v in values:
        print(v, end=' ')

代码运行效果:

Python多线程

线程是 CPU 分配资源的基本单位,但一个程序开始运行后这个程序就变成了一个进程,而一个进程相当于一个或者多个线程。当没有多线程编程时,一个进程也是一个主线程,但有多线程编程时,一个进程包含多个线程,包括主线程。使用线程可以实现程序的并发,Python 多线程快速入门可参见:python3 多线程编程

Python3 线程中常用的两个模块为:

  • (1)_thread;
  • (2)threading (推荐使用)

其中 thread 模块已被废弃,用户可以使用 threading 模块代替。所以在 Python3 中不能再使用 “thread” 模块,为了兼容性,Python3 将 thread 重命名为 “_thread”。

1、函数创建多线程

Python3 中提供了一个内置模块threading.Thread,可以很方便的创建多线程,threading.Thread()一般接收2个参数:

  • 1)线程函数名:要放置线程让其后台执行的函数,有用户自己定义,主要不要加();
  • 2)线程函数的参数:线程函数名所需的参数,以 tuple 元组的形式传入,如果不需要参数,可以不指定。

下面来看看一个简单的多线程示例:

import time
import threading

# 自定义线程函数
def my_threadfunc(name='python3'):
    for i in range(2):
        print("hello", name)
        time.sleep(1)


if __name__=="__main__":
    # 创建线程01,不指定参数
    thread_01 = threading.Thread(target=my_threadfunc)
    # 启动线程01
    thread_01.start()
    # 创建线程02,指定参数,注意逗号不要少,否则不是一个tuple
    thread_02 = threading.Thread(target=my_threadfunc, args=('Tr0e',))
    # 启动线程02
    thread_02.start()

代码运行效果:
2、类创建多线程

首先,自定义一个类,对这个自定义的类有两个要求:

  • 1)必须继承 threading.Thread 这个父类;
  • 2)必须重写 run() 这个方法:run() 方法相当于第一种方法中的线程函数,可以写自己需要的业务逻辑代码,在start()后将会调用。

来看看示例代码:

import time
from threading import Thread

class MyThread(Thread):
    def __init__(self, name='Python3'):
        super().__init__()
        self.name = name

    def run(self):
        for i in range(2):
            print("Hello", self.name)
            time.sleep(1)

3、 join() 方法

多线程中 join() 作用是调用 join() 的线程阻塞直到某一线程结束才继续执行。来看看示例代码:

import threading
import time


class mythread(threading.Thread):
    def run(self):
        self.i = 1
        print("子线程运行秒数:",'%d' % (self.i))
        self.i = self.i + 1
        time.sleep(1)  # 睡眠一秒
        print("子线程运行秒数:",'%d' % (self.i))
        time.sleep(1)
        print("子线程运行结束!")


if __name__ == '__main__':
    ta = mythread()  # 实例化线程
    ta.start()  # 开启ta线程
    ta.join()  # 主线程等待 ta线程结束才继续执行
    print("主线程结束!")

代码运行效果:
4、线程的同步——锁

当一个进程拥有多个线程之后,如果他们各做各的任务互没有关系还行,但既然属于同一个进程,他们之间总是具有一定关系的。比如多个线程都要对某个数据进行修改,则可能会出现不可预料的结果。为保证操作正确,就需要引入锁来进行线程间的同步。

Python3 中的 threading 模块提供了 RLock 锁(可重入锁):

  • 对于某一时间只能让一个线程操作的语句放到 RLock 的 acquire 方法 和 release 方法之间;
  • 即 acquire() 函数相当于给 RLock 锁 上锁,而 release() 函数相当于解锁。

来看看一个简单的演示案例:

import threading


class mythread(threading.Thread):
    def run(self):
        global x        # 声明一个全局变量
        lock.acquire()  # 上锁,acquire()和release()之间的语句一次只能有一个线程进入,其余线程在acquire()处等待
        x += 10
        print('%s:%d' % (self.name, x))
        lock.release()  # 解锁


x = 0
lock = threading.RLock()  # 创建 可重入锁


def main():
    l = []
    for i in range(5):
        l.append(mythread())  # 创建5个线程,并把他们放到一个列表中
    for i in l:
        i.start()  # 开启列表中的所有线程


if __name__ == '__main__':
    main()

代码运行效果:
5、多线程函数小结:

t = Thread(target=func)
# 启动子线程
t.start()
# 阻塞子线程,待子线程结束后,再往下执行
t.join()
# 判断线程是否在执行状态,在执行返回True,否则返回False
t.is_alive()
t.isAlive()
# 设置线程是否随主线程退出而退出,默认为False
t.daemon = True
t.daemon = False
# 设置线程名
t.name = "My-Thread"

Python脚本

下面将从单线程、多线程两种角度实现 ZIP 加密文件的密码爆破。

单线程数字爆破

先来生成一个用数字密码(“101”)加密的 ZIP 压缩文件 password.zip,压缩文件为图片 pasword.png(注意勾选 “ZIP 传统加密” 的选项,后面的代码不支持 WinRAR 新式的默认加密方式),如下图所示:

爆破密码的脚本也相对简单,直接上代码:

import zipfile
import os
import time
import sys

"""
获取zip文件
"""
def get_zipfile():
    os.chdir(r'D:\\Code\\Python\\MyTest\\Basic')
    files = os.listdir()
    for file in files:
        if file.endswith('.zip'):
            return file

"""
爆破zip文件
"""
def extract():
    file = get_zipfile()
    zfile = zipfile.ZipFile(file)  # 读取压缩文件
    start_time = time.time()
    for num in range(1, 99999):      # 设置爆破的数字密码区间
        try:
            zfile.extractall(path='.', pwd=str(num).encode('utf-8'))
            print('解压密码是:', str(num))
            end_time = time.time()
            print('单线程破解压缩包花了%s秒' % (end_time - start_time))
            sys.exit(0)  # 让程序在得到结果后,就停止运行,正常退出
        except Exception as e:
            print(e)
            #pass


if __name__ == "__main__":
    extract()

以上代码没什么需要特别解释的,简单补充两点:

  1. 需要注意的是在爆破过程需要使用异常处理机制避免密码错误时程序直接终止;
  2. 对于 zipfile 库的用法有疑问请参见官方文档:ZipFile数据压缩与存档

下面直接来看看 Pycharm 中运行脚本的效果:

单线程字符爆破

先来看看脚本:

import zipfile
import random
import time
import sys

class MyIterator():
    # 用于暴力破解的字符集合
    letters = 'abcdefghijklmnopqrstuvwxyz012345678'
    min_digits = 0
    max_digits = 0

    def __init__(self, min_digits, max_digits):
        # 下面的if-else是为了解决extract函数中,for循环中传递的密码长度可能前者的值大于后者,这一bug
        if min_digits < max_digits:
            self.min_digits = min_digits
            self.max_digits = max_digits
        else:
            self.min_digits = max_digits
            self.max_digits = min_digits

    # 迭代器访问定义,直接返回self实列对象
    def __iter__(self):
        return self
        
    # 通过不断地轮循,生成密码
    def __next__(self):
        rst = str()
        for item in range(0, random.randrange(self.min_digits, self.max_digits + 1)):
            # 从letters中随机取选取一个值,并把选取几次的结果拼接成一个字符,即一个密码
            rst += random.choice(MyIterator.letters)
        return rst


def extract():
    start_time = time.time()
    zfile = zipfile.ZipFile("password.zip")
    # 随机迭代出5~6位数的密码
    for password in MyIterator(5, 6):
        try:
            zfile.extractall(path=".", pwd=str(password).encode('utf-8'))
            print("the password is {}".format(password))
            now_time = time.time()
            print("spend time is {}".format(now_time - start_time))
            sys.exit(0)
        except Exception as e:
            print(e)
            pass


if __name__ == '__main__':
    extract()

将 password.png 重新压缩并将解压密码设置为 “ab12” 数字与字母组合的字符串,上述利用自定义迭代器生成的字符组合范围太广了,爆破起来可能跑到天荒地老……故演示此代码时我依据已知的密码对代码做了如下更改:

  1. 设置缩小字符范围:letters = 'abcd0123456789'
  2. 设置缩小遍历的字符串长度:for password in MyIterator(3, 4)

来看看脚本运行效果,还足足跑了 78 秒之久:

多线程字典爆破

直接上脚本:

import zipfile
import threading
import sys
import os


'''
多线程爆破完成
'''
def extractfile(zip_file, password):
    try:
        zip_file.extractall(path='.', pwd=str(password).encode("utf-8"))
        print('[+] 解压密码是:', password)
        sys.exit(0)
    except Exception as e:
        print("当前爆破的密码:%s 不正确!" % (password))
        pass


def main():
    os.chdir(r'D:\\Code\\Python\\MyTest\\Basic')
    password_file = 'pwd.txt'
    files = os.listdir()
    for file in files:  # 遍历当前路径下的所有文件
        if file.endswith('.zip'):  # 爆破zip文件
            zip_file = zipfile.ZipFile(file)
            pass_file = open(password_file)
            for line in pass_file.readlines():
                password = line.strip('\\n')
                t = threading.Thread(target=extractfile, args=(zip_file, password))
                t.start()


if __name__ == '__main__':
    main()

代码运行效果:

总结

个人感觉最后的多线程脚本实际上意义不大,仅供简单学习多线程使用……因为此程序中对每个密码的尝试都单开了一个线程、而尝试密码是否正常的逻辑函数 extractfile() 又十分简单,没有必要单开一个线程来浪费资源,除非说处理的逻辑函数 extractfile() 执行了十分耗时的操作(比如需要下载文件、或者说每次执行 extractfile() 函数都对一个单独的大型字典进行爆破等)。

以上是关于Python攻防-暴力破解ZIP加密文件的密码的主要内容,如果未能解决你的问题,请参考以下文章

手把手教你用Python突破加密 zip 文件的密码

手把手教你用Python突破加密 zip 文件的密码

Python攻防-暴力破解附近局域网WIFI密码

Python攻防-暴力破解附近局域网WIFI密码

mac用fcrackzip命令破解zip压缩文件密码

利用 Python 破解 ZIP 或 RAR 文件密码