使用john,破解简单的6位纯数字密码 + 身份证后6位密码
git clone https://github.com/magnumripper/JohnTheRipper.git
cd JohnTheRipper/src
sudo apt-get update
./configure(如果报错,跳过openssl: ./configure --without-openssl)
make -s clean && make -sj4
JohnTheRipper/run/zip2john test.zip > test.hash
最简单版本
JohnTheRipper/run/john test.hash
破解6位纯数字
JohnTheRipper/run/john --mask=?d?d?d?d?d?d test.hash
破解6位纯小写字母(注意,问号后不是竖线,是L的小写字母)
JohnTheRipper/run/john --mask=?l?l?l?l?l?l test.hash
破解5位纯数字 + 1位小写字母(比如身份证后6位)
JohnTheRipper/run/john --mask=?d?d?d?d?d?l test.hash
首先,用Python生成密码(这里只生成了6位纯数数字 + 身份证后6位),然后写入文件
import itertools
import pandas as pd
def create_password_txt():
# 纯6位数字
df_password = pd.Series(range(0, 1000000)).astype(str).str.rjust(6, fillchar='0')
# 身份证后六位
birthday = [str(i).rjust(2, '0') for i in range(1, 32)] # 生日:01、02、03......30、31
other = '0123456789'
check = 'xX' # 身份证最后一位,校验位(为什么不包含数字,因为上面的6位纯数字密码集已经包含了)
df = pd.DataFrame(list(itertools.product(birthday, other, other, other, check)))
df_birthday = df[0].str.cat([df[1], df[2], df[3], df[4]], sep='')
# 合并
df_all = df_password.append(df_birthday)
# 验证
print(df_all.str.len().value_counts())
# 如果没错的话,数量应该是 1062000
print(len(df_all.unique()))
# 写入文件 ==> auto_job_config.cfg ==> password_file_path
with open('password.txt', 'w') as file:
file.write('\n'.join(df_all))
print('ok')
生成的密码文件 password.txt 如下:
--wordlist 参数指定密码文件,只会尝试密码文件中的密码 ,如果知道密码位数和类型,使用这种请会提升破解效率。
JohnTheRipper/run/john --wordlist=password.txt test.hash
测试案例:
--log-stderr 参数,忽略日志输出
JohnTheRipper/run/john --log-stderr test.hash
--session 参数,如果你想同时破解多个文件,需要指定不同进程名称,要不然会报错。
如果机器性能足够好,只要进程名称不同,就可以并行解析很多个zip文件。
# 进程1
JohnTheRipper/run/john --session=name1 test1.hash
# 进程2
JohnTheRipper/run/john --session=name2 test2.hash
tail -f JohnTheRipper/run/john.log
当重复执行同一个hash文件的时候,只有第一次会显示密码,因为有缓存机制。
可以用shell重定向清空缓存文件,即可每次都显示密码,但是每次也都会重新耗时破解密码,看使用需求。
true > JohnTheRipper/run/john.pot
或
> JohnTheRipper/run/john.pot
或
: > JohnTheRipper/run/john.pot
或
cp /dev/null JohnTheRipper/run/john.pot
或者用Python实现
with open('JohnTheRipper/run/john.pot', 'w') as f:
pass
首先写一个连串的命令模板,路径用参数替换。
decrypt_zip_command = ' ; '.join([
# 生成hash文件
"/Users/mk/JohnTheRipper/run/zip2john {zip_path} > {hash_path}",
# 解密,日志不写入文件,指定会话ID可以一次执行多个,不重复即可,指定密码文件
"/Users/mk/JohnTheRipper/run/john --no-log --session={random_str} --wordlist={password_path} {hash_path}",
# 清空缓存密码
"true > /Users/mk/JohnTheRipper/run/john.pot",
# 删除当前hash文件
"rm {hash_path}",
])
def exec_command(self, cmd: str, in_put: str = None, timeout=120) -> bool and str:
t1 = time.time()
self.logger.info(f'执行命令: "{cmd}"\n')
try:
output_str = subprocess.check_output(cmd, input=in_put, stderr=subprocess.STDOUT, shell=True, timeout=timeout).decode('utf-8').strip()
except subprocess.CalledProcessError as e:
error_output = e.output.decode('utf-8')
self.logger.error(f'执行命令失败: "{error_output}"\n')
return False, ''
else:
return True, output_str
finally:
if 'run/john' in cmd:
use_total = time.time() - t1
self.logger.info(f'破解密码用时: {use_total:.2f}s')
password_result_str 是 运行命令返回的所有字符串
# 清洗密码
match_object = re.search(r"\n(?P[\dxX]{6})\s+\(.*?\)", password_result_str)
if match_object:
password = match_object.group('password').strip()
print(f'解析密码成功: "{password}"')
不止自动化破解zip文件,还可以破解PDF文件
多线程处理
自动去除PDF文件密码
全部功能代码都在下文:
全文代码:
# -*- coding: utf-8 -*-
# @Author : mk
# @Time : 2022/07/22 09:50
import os
import re
import time
import zipfile
import threading
import subprocess
import pdfplumber
from PyPDF2 import PdfFileReader, PdfFileWriter
from pdfminer.pdfdocument import PDFPasswordIncorrect
from parse_base import Base
class TestThread(threading.Thread):
def __init__(self, target, args=()):
super(TestThread, self).__init__()
self.func = target
self.args = args
self.result = None
def run(self):
try:
self.result = self.func(*self.args)
except:
self.result = []
def get_result(self):
try:
return self.result
except Exception:
return []
class Zip(Base):
def __init__(self):
super().__init__()
# 爆破pdf命令
self.decrypt_pdf_command = r"pdfcrack -q -c 0123456789 -n 6 -m 6 -f {pdf_path}"
# 解压zip命令,指定密码,指定解压路径,强制覆盖文件
self.unzip_with_7z_command = '7z x -p{zip_password} -aoa {zip_path} -o{output_path}'
# 爆破zip命令
if self.IS_DEV == 'test':
self.decrypt_zip_command = ' ; '.join([
"/Users/mk/mk/学习/JohnTheRipper/run/zip2john {zip_path} > {hash_path}", # 生成hash文件
"/Users/mk/mk/学习/JohnTheRipper/run/john --no-log --session={random_str} --wordlist={password_path} {hash_path}", # 解密,日志不写入文件,指定会话ID可以一次执行多个,不重复即可,指定密码文件
"true > /Users/mk/mk/学习/JohnTheRipper/run/john.pot", # 清空缓存密码
"rm {hash_path}", # 删除当前hash文件
])
else:
self.decrypt_zip_command = ' ; '.join([
"/home/souche/JohnTheRipper/run/zip2john {zip_path} > {hash_path}", # 生成hash文件
"/home/souche/JohnTheRipper/run/john --no-log --session={random_str} --wordlist={password_path} {hash_path}", # 解密
"true > /home/souche/JohnTheRipper/run/john.pot", # 清空缓存密码
"rm {hash_path}", # 删除当前hash文件,防止占内存
])
# 判断是否图片类型
def is_image_type(self, pdf_file_path: str, pw='') -> bool:
with pdfplumber.open(pdf_file_path, password=pw) as pdf:
# 首页文本
first_page_text = pdf.pages[0].extract_words()
# 获取不到文字,应该就是图片类型了
if not first_page_text:
self.logger.error('PDF可能是图片类型,已经中止后续操作')
return True
else:
return False
# 去除PDF加密
def decrypt_and_move_file(self, old_pdf_path: str, password: str, new_pdf_path: str) -> None:
try:
pdf_reader = PdfFileReader(old_pdf_path)
pdf_reader.decrypt(password) # 解密
pdf_writer = PdfFileWriter()
for page in range(pdf_reader.getNumPages()):
pdf_writer.addPage(pdf_reader.getPage(page))
with open(new_pdf_path, "wb") as new_pdf:
pdf_writer.write(new_pdf) # 未加密的pdf
except:
self.logger.error('解除PDF加密失败')
else:
self.logger.info('解除PDF加密成功')
# 执行shell命令,破解密码,返回密码
def exec_command(self, cmd: str, in_put: str = None, timeout=120) -> bool and str:
t1 = time.time()
self.logger.info(f'执行命令: "{cmd}"\n')
try:
output_str = subprocess.check_output(cmd, input=in_put, stderr=subprocess.STDOUT, shell=True, timeout=timeout).decode('utf-8').strip()
except subprocess.CalledProcessError as e:
error_output = e.output.decode('utf-8')
self.logger.error(f'执行命令失败: "{error_output}"\n')
return False, ''
else:
return True, output_str
finally:
if 'run/john' in cmd:
use_total = time.time() - t1
self.logger.info(f'破解密码用时: {use_total:.2f}s')
# zip 解密 ==> PDF
def zip_to_pdf(self, zip_file_name: str) -> list:
self.logger.info(f'【{zip_file_name}】开始解析密码')
is_success = False
# 随机字符串
random_str = self.get_random_str()
# hash文件
hash_path = os.path.join(self.hash_file_path_root, f'{random_str}.hash')
# 密码文件
password_path = self.password_file_path
# 输入文件
zip_file_path = os.path.join(self.zip_file_path_root, zip_file_name)
# 输出文件夹
pdf_file_path_folder = os.path.join(self.pdf_file_path_root, zip_file_name.replace('.zip', '')) # 注意,这是文件夹,不是文件
if not os.path.exists(zip_file_path):
self.logger.error(f'zip文件不存在: {zip_file_path}')
return []
# 先判断是否有加密
with zipfile.ZipFile(zip_file_path) as zfp:
# 遍历,查看每个文件是否加密,有一个加密都算加密
is_encrypted = any([i.flag_bits & 0x1 for i in zfp.infolist()])
# is_encrypted = zfp.infolist()[0].flag_bits & 0x1
# 如果加密
if is_encrypted:
# 爆破命令
decrypt_zip_command = self.decrypt_zip_command.format(zip_path=zip_file_path,
hash_path=hash_path,
random_str=random_str,
password_path=password_path,
)
shell_success, password_result_str = self.exec_command(decrypt_zip_command)
# 如果执行成功
if shell_success:
# 清洗密码
match_object = re.search(r"\n(?P[\dxX]{6})\s+\(.*?\)", password_result_str)
if match_object:
password = match_object.group('password').strip()
self.logger.info(f'【{zip_file_name}】解析密码成功: "{password}"')
# 解压文件夹
unzip_with_7z_command = self.unzip_with_7z_command.format(zip_password=password,
zip_path=zip_file_path,
output_path=pdf_file_path_folder,
)
shell_success, unzip_result_str = self.exec_command(unzip_with_7z_command)
if shell_success and 'Everything is Ok' in unzip_result_str:
self.logger.info(f'【{zip_file_name}】解压成功 ==>【{pdf_file_path_folder}】')
is_success = True
else:
self.logger.error(f'【{zip_file_name}】解密失败,shell返回信息: {password_result_str}')
else:
# 如果没加密,就直接解压(几乎不可能。。。)
zfp.extractall(pdf_file_path_folder) # 解压到
is_success = True
self.logger.info(f'【{zip_file_name}】无密码')
if is_success:
# 返回绝对路径
pdf_file_abs_path_list = []
mode = str(zip_file_name)[0].upper()
for pdf_file_name in os.listdir(pdf_file_path_folder):
# 添加前缀,区分BC端
old_pdf_file_abs_path = os.path.join(pdf_file_path_folder, pdf_file_name)
new_pdf_file_abs_path = os.path.join(pdf_file_path_folder, f'{mode}_{pdf_file_name}')
# 文件重命名
os.rename(old_pdf_file_abs_path, new_pdf_file_abs_path)
if pdf_file_name.endswith('.pdf'):
pdf_file_abs_path_list.append(new_pdf_file_abs_path)
else:
self.logger.error(f'非PDF文件:{new_pdf_file_abs_path}')
return pdf_file_abs_path_list
else:
return []
# PDF 解密 ==> PDF
def pdf_to_pdf(self, pdf_file_name: str) -> list:
is_success = False
self.logger.info(f'【{pdf_file_name}】开始解析密码')
# 输入
old_pdf_file_path = os.path.join(self.zip_file_path_root, pdf_file_name)
# 输出
new_pdf_file_path = os.path.join(self.pdf_file_path_root, pdf_file_name)
if not os.path.exists(old_pdf_file_path):
self.logger.error(f'pdf文件不存在: {old_pdf_file_path}')
return []
# 判断是否加密
try:
with pdfplumber.open(old_pdf_file_path):
pass
# 如果加密
except PDFPasswordIncorrect:
# 传入文件路径,开始解密
shell_success, password_result_str = self.exec_command(self.decrypt_pdf_command.format(pdf_path=old_pdf_file_path))
# 如果执行命令成功
if shell_success:
# 清洗密码
match_object = re.search(r"found user-password: '(?P\d+)'", password_result_str)
# 如果解析出密码
if match_object:
password = match_object.group('password').strip()
self.logger.info(f'【{pdf_file_name}】解密成功: "{password}"')
# 判断是否图片类型PDF
pdf_is_image = self.is_image_type(old_pdf_file_path, password)
# 如果是图片,不做后续动作
if pdf_is_image:
is_success = False
# 如果不是图片类型
else:
# 解除密码,并且移动文件
self.decrypt_and_move_file(old_pdf_file_path, password, new_pdf_file_path)
is_success = True
else:
self.logger.error(f'【{pdf_file_name}】解密失败,shell返回信息: {password_result_str}')
# 如果无加密
else:
self.logger.info(f'【{pdf_file_name}】无密码')
# 是否图片
pdf_is_image = self.is_image_type(old_pdf_file_path)
if pdf_is_image:
is_success = False
else:
# 没密码,且不是图片,直接移动
os.rename(old_pdf_file_path, new_pdf_file_path)
is_success = True
if is_success:
return [new_pdf_file_path]
else:
return []
# 多线程处理文件
def to_pdf(self, file_name_list: list) -> list:
pdf_abs_path_list = []
if file_name_list and isinstance(file_name_list, list):
job_list = list()
# 遍历zip/PDF文件名称,非绝对路径
for file_name in file_name_list:
# 邮件附件是压缩包
if file_name.endswith('.zip'):
job_list.append(TestThread(target=self.zip_to_pdf, args=(file_name,)))
# 邮件附件是PDF文件
elif file_name.endswith('.pdf'):
job_list.append(TestThread(target=self.pdf_to_pdf, args=(file_name,)))
else:
self.logger.error(f'未知文件类型:{file_name}')
# 多线程处理
if job_list:
for job in job_list:
job.start()
for job in job_list:
job.join()
pdf_abs_path_list.extend(job.get_result())
# 返回pdf文件绝对路径
return pdf_abs_path_list