diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 55ddfbf..aad5cd3 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -4,14 +4,25 @@ - @@ -609,7 +640,8 @@ - @@ -618,7 +650,7 @@ file://$PROJECT_DIR$/app/DataBase/output.py - 413 + 412 @@ -627,9 +659,9 @@ - file://$PROJECT_DIR$/app/Ui/mainview.py - 10 - diff --git a/app/DataBase/__init__.py b/app/DataBase/__init__.py index 171ff5a..94ae7f2 100644 --- a/app/DataBase/__init__.py +++ b/app/DataBase/__init__.py @@ -7,7 +7,7 @@ @Version : Python3.10 @comment : ··· """ -from . import data -from . import output +# from . import data +# from . import output __all__ = ["data", 'output'] diff --git a/app/DataBase/database.py b/app/DataBase/database.py deleted file mode 100644 index 8b13789..0000000 --- a/app/DataBase/database.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/app/DataBase/micro_msg.py b/app/DataBase/micro_msg.py new file mode 100644 index 0000000..972a258 --- /dev/null +++ b/app/DataBase/micro_msg.py @@ -0,0 +1,23 @@ +import sqlite3 +from pprint import pprint + +DB = sqlite3.connect("./de_MicroMsg.db", check_same_thread=False) +# '''创建游标''' +cursor = DB.cursor() + + +def get_contact(): + sql = '''select UserName,Alias,Type,Remark,NickName,PYInitial,RemarkPYInitial,ContactHeadImgUrl.smallHeadImgUrl,ContactHeadImgUrl.bigHeadImgUrl + from Contact inner join ContactHeadImgUrl on Contact.UserName = ContactHeadImgUrl.usrName + where Type=3 and Alias is not null + order by PYInitial + ''' + cursor.execute(sql) + result = cursor.fetchall() + pprint(result) + print(len(result)) + return result + + +if __name__ == '__main__': + get_contact() diff --git a/app/Ui/__init__.py b/app/Ui/__init__.py index 730bbfa..380c558 100644 --- a/app/Ui/__init__.py +++ b/app/Ui/__init__.py @@ -13,5 +13,6 @@ from app.Ui import mainview # 文件__init__.py # from login import login from app.Ui.decrypt import decrypt +from app.Ui.pc_decrypt import pc_decrypt -__all__ = ["decrypt", 'mainview', 'chat'] +__all__ = ["decrypt", 'mainview', 'chat', 'pc_decrypt'] diff --git a/app/Ui/pc_decrypt/__init__.py b/app/Ui/pc_decrypt/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/Ui/pc_decrypt/decryptUi.py b/app/Ui/pc_decrypt/decryptUi.py new file mode 100644 index 0000000..545cf46 --- /dev/null +++ b/app/Ui/pc_decrypt/decryptUi.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'decryptUi.ui' +# +# Created by: PyQt5 UI code generator 5.15.7 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dialog") + Dialog.resize(400, 300) + font = QtGui.QFont() + font.setFamily("微软雅黑") + Dialog.setFont(font) + self.label_3 = QtWidgets.QLabel(Dialog) + self.label_3.setGeometry(QtCore.QRect(140, 0, 221, 51)) + font = QtGui.QFont() + font.setFamily("一纸情书") + font.setPointSize(20) + self.label_3.setFont(font) + self.label_3.setObjectName("label_3") + self.progressBar = QtWidgets.QProgressBar(Dialog) + self.progressBar.setGeometry(QtCore.QRect(90, 260, 271, 23)) + self.progressBar.setProperty("value", 50) + self.progressBar.setObjectName("progressBar") + self.btn_getinfo = QtWidgets.QPushButton(Dialog) + self.btn_getinfo.setGeometry(QtCore.QRect(290, 60, 91, 41)) + self.btn_getinfo.setObjectName("btn_getinfo") + self.btn_db_dir = QtWidgets.QPushButton(Dialog) + self.btn_db_dir.setGeometry(QtCore.QRect(20, 210, 91, 41)) + self.btn_db_dir.setObjectName("btn_db_dir") + self.label_db_dir = QtWidgets.QLabel(Dialog) + self.label_db_dir.setGeometry(QtCore.QRect(120, 220, 251, 21)) + self.label_db_dir.setText("") + self.label_db_dir.setObjectName("label_db_dir") + self.label_ready = QtWidgets.QLabel(Dialog) + self.label_ready.setGeometry(QtCore.QRect(40, 260, 41, 21)) + self.label_ready.setObjectName("label_ready") + self.widget = QtWidgets.QWidget(Dialog) + self.widget.setGeometry(QtCore.QRect(20, 50, 221, 151)) + self.widget.setObjectName("widget") + self.gridLayout = QtWidgets.QGridLayout(self.widget) + self.gridLayout.setContentsMargins(0, 0, 0, 0) + self.gridLayout.setObjectName("gridLayout") + self.label_wxid = QtWidgets.QLabel(self.widget) + self.label_wxid.setText("") + self.label_wxid.setObjectName("label_wxid") + self.gridLayout.addWidget(self.label_wxid, 4, 1, 1, 1) + self.label = QtWidgets.QLabel(self.widget) + self.label.setObjectName("label") + self.gridLayout.addWidget(self.label, 0, 0, 1, 1) + self.label_2 = QtWidgets.QLabel(self.widget) + self.label_2.setObjectName("label_2") + self.gridLayout.addWidget(self.label_2, 2, 0, 1, 1) + self.label_5 = QtWidgets.QLabel(self.widget) + self.label_5.setObjectName("label_5") + self.gridLayout.addWidget(self.label_5, 3, 0, 1, 1) + self.label_4 = QtWidgets.QLabel(self.widget) + self.label_4.setObjectName("label_4") + self.gridLayout.addWidget(self.label_4, 4, 0, 1, 1) + self.label_key = QtWidgets.QLabel(self.widget) + self.label_key.setText("") + self.label_key.setObjectName("label_key") + self.gridLayout.addWidget(self.label_key, 5, 1, 1, 1) + self.label_phone = QtWidgets.QLabel(self.widget) + self.label_phone.setText("") + self.label_phone.setObjectName("label_phone") + self.gridLayout.addWidget(self.label_phone, 2, 1, 1, 1) + self.label_6 = QtWidgets.QLabel(self.widget) + self.label_6.setObjectName("label_6") + self.gridLayout.addWidget(self.label_6, 5, 0, 1, 1) + self.label_pid = QtWidgets.QLabel(self.widget) + self.label_pid.setText("") + self.label_pid.setObjectName("label_pid") + self.gridLayout.addWidget(self.label_pid, 0, 1, 1, 1) + self.label_name = QtWidgets.QLabel(self.widget) + self.label_name.setText("") + self.label_name.setObjectName("label_name") + self.gridLayout.addWidget(self.label_name, 3, 1, 1, 1) + self.label_7 = QtWidgets.QLabel(self.widget) + self.label_7.setObjectName("label_7") + self.gridLayout.addWidget(self.label_7, 1, 0, 1, 1) + self.label_version = QtWidgets.QLabel(self.widget) + self.label_version.setText("") + self.label_version.setObjectName("label_version") + self.gridLayout.addWidget(self.label_version, 1, 1, 1, 1) + self.gridLayout.setColumnMinimumWidth(0, 1) + self.gridLayout.setColumnMinimumWidth(1, 2) + self.gridLayout.setRowMinimumHeight(5, 10) + self.gridLayout.setColumnStretch(0, 1) + self.gridLayout.setColumnStretch(1, 3) + self.gridLayout.setRowStretch(5, 10) + self.pushButton_3 = QtWidgets.QPushButton(Dialog) + self.pushButton_3.setGeometry(QtCore.QRect(290, 130, 91, 41)) + self.pushButton_3.setObjectName("pushButton_3") + + self.retranslateUi(Dialog) + QtCore.QMetaObject.connectSlotsByName(Dialog) + + def retranslateUi(self, Dialog): + _translate = QtCore.QCoreApplication.translate + Dialog.setWindowTitle(_translate("Dialog", "Dialog")) + self.label_3.setText(_translate("Dialog", "解密数据库")) + self.btn_getinfo.setText(_translate("Dialog", "获取信息")) + self.btn_db_dir.setText(_translate("Dialog", "设置微信路径")) + self.label_ready.setText(_translate("Dialog", "未就绪")) + self.label.setText(_translate("Dialog", "PID")) + self.label_2.setText(_translate("Dialog", "手机号")) + self.label_5.setText(_translate("Dialog", "微信昵称")) + self.label_4.setText(_translate("Dialog", "wxid")) + self.label_6.setText(_translate("Dialog", "密钥")) + self.label_7.setText(_translate("Dialog", "版本")) + self.pushButton_3.setText(_translate("Dialog", "开始解密")) diff --git a/app/Ui/pc_decrypt/decryptUi.ui b/app/Ui/pc_decrypt/decryptUi.ui new file mode 100644 index 0000000..a1096c4 --- /dev/null +++ b/app/Ui/pc_decrypt/decryptUi.ui @@ -0,0 +1,218 @@ + + + Dialog + + + + 0 + 0 + 400 + 300 + + + + + 微软雅黑 + + + + Dialog + + + + + 140 + 0 + 221 + 51 + + + + + 一纸情书 + 20 + + + + 解密数据库 + + + + + + 90 + 260 + 271 + 23 + + + + 50 + + + + + + 290 + 60 + 91 + 41 + + + + 获取信息 + + + + + + 20 + 210 + 91 + 41 + + + + 设置微信路径 + + + + + + 120 + 220 + 251 + 21 + + + + + + + + + + 40 + 260 + 41 + 21 + + + + 未就绪 + + + + + + 20 + 50 + 221 + 151 + + + + + + + + + + + + + + PID + + + + + + + 手机号 + + + + + + + 微信昵称 + + + + + + + wxid + + + + + + + + + + + + + + + + + + + + + 密钥 + + + + + + + + + + + + + + + + + + + + + 版本 + + + + + + + + + + + + + + + + 290 + 130 + 91 + 41 + + + + 开始解密 + + + + + + diff --git a/app/Ui/pc_decrypt/pc_decrypt.py b/app/Ui/pc_decrypt/pc_decrypt.py new file mode 100644 index 0000000..6cda791 --- /dev/null +++ b/app/Ui/pc_decrypt/pc_decrypt.py @@ -0,0 +1,152 @@ +import os.path +import time + +from PyQt5 import QtWidgets +from PyQt5.QtCore import * +from PyQt5.QtGui import * +from PyQt5.QtWidgets import * + +from app.decrypt import get_wx_info, decrypt +from . import decryptUi + + +class DecryptControl(QWidget, decryptUi.Ui_Dialog): + DecryptSignal = pyqtSignal(str) + registerSignal = pyqtSignal(str) + + def __init__(self, parent=None): + super(DecryptControl, self).__init__(parent) + self.setupUi(self) + self.setWindowTitle('解密') + self.setWindowIcon(QIcon('./app/data/icons/logo.svg')) + self.pushButton_3.clicked.connect(self.decrypt) + self.btn_getinfo.clicked.connect(self.get_info) + self.btn_db_dir.clicked.connect(self.select_db_dir) + self.info = {} + self.ready = False + self.wx_dir = None + + def get_info(self): + try: + result = get_wx_info.get_info() + # print(result) + if result: + self.ready = True + self.info = result[0] + self.label_key.setText(self.info['key']) + self.label_wxid.setText(self.info['wxid']) + self.label_name.setText(self.info['name']) + self.label_phone.setText(self.info['mobile']) + self.label_pid.setText(str(self.info['pid'])) + self.label_version.setText(self.info['version']) + if self.wx_dir and os.path.exists(os.path.join(self.wx_dir, self.info['wxid'])): + self.label_ready.setText('已就绪') + except Exception as e: + print(e) + QMessageBox.critical(self, "错误", "请登录微信") + + def select_db_dir(self): + directory = QtWidgets.QFileDialog.getExistingDirectory( + self, "选取微信安装目录——能看到All Users文件夹", + "C:/") # 起始路径 + if directory: + self.label_db_dir.setText(directory) + self.wx_dir = directory + if self.ready: + self.label_ready.setText('已就绪') + + def decrypt(self): + if not self.ready: + QMessageBox.critical(self, "错误", "请先获取密钥") + return + + if self.ready: + if not os.path.exists(os.path.join(self.wx_dir, self.info['wxid'])): + QMessageBox.critical(self, "错误", "文件夹选择错误\n一般以WeChat Files结尾") + return + # self.thread1 = MyThread() + # self.thread1.signal.connect(self.progressBar_view) + # self.thread1.start() + db_dir = os.path.join(self.wx_dir, self.info['wxid'], 'Msg') + self.thread2 = DecryptThread(db_dir, self.info['key']) + self.thread2.maxNumSignal.connect(self.setProgressBarMaxNum) + self.thread2.signal.connect(self.progressBar_view) + self.thread2.okSignal.connect(self.btnExitClicked) + self.thread2.start() + + def btnEnterClicked(self): + # print("enter clicked") + # 中间可以添加处理逻辑 + # QMessageBox.about(self, "解密成功", "数据库文件存储在app/DataBase/Msg文件夹下") + self.DecryptSignal.emit('ok') + self.close() + + def setProgressBarMaxNum(self, max_val): + self.progressBar.setRange(0, max_val) + + def progressBar_view(self, value): + """ + 进度条显示 + :param value: 进度0-100 + :return: None + """ + self.progressBar.setProperty('value', value) + # self.btnExitClicked() + # data.init_database() + + def btnExitClicked(self): + # print("Exit clicked") + self.DecryptSignal.emit('ok') + self.close() + + +class DecryptThread(QThread): + signal = pyqtSignal(str) + maxNumSignal = pyqtSignal(int) + okSignal = pyqtSignal(str) + + def __init__(self, db_path, key): + super(DecryptThread, self).__init__() + self.db_path = db_path + self.key = key + self.textBrowser = None + + def __del__(self): + pass + + def run(self): + # data.decrypt(self.db_path, self.key) + output_dir = 'app/DataBase/Msg' + if not os.path.exists(output_dir): + os.mkdir(output_dir) + tasks = [] + if os.path.exists(self.db_path): + for root, dirs, files in os.walk(self.db_path): + for file in files: + if '.db' == file[-3:]: + inpath = os.path.join(root, file) + # print(inpath) + output_path = os.path.join(output_dir, file) + tasks.append([self.key, inpath, output_path]) + self.maxNumSignal.emit(len(tasks)) + for i, task in enumerate(tasks): + decrypt.decrypt(*task) + self.signal.emit(str(i + 1)) + # print(self.db_path) + self.okSignal.emit('ok') + # self.signal.emit('100') + + +class MyThread(QThread): + signal = pyqtSignal(str) + + def __init__(self): + super(MyThread, self).__init__() + + def __del__(self): + pass + + def run(self): + for i in range(100): + self.signal.emit(str(i)) + time.sleep(0.1) diff --git a/app/config.py b/app/config.py index 4505531..87ce4e1 100644 --- a/app/config.py +++ b/app/config.py @@ -1,2 +1,2 @@ -version = '0.1.1' +version = '0.2.0' contact = '474379264' diff --git a/app/decrypt/decrypt.py b/app/decrypt/decrypt.py new file mode 100644 index 0000000..dad9e6e --- /dev/null +++ b/app/decrypt/decrypt.py @@ -0,0 +1,125 @@ +import hashlib +import hmac +import os +from typing import Union, List + +from Cryptodome.Cipher import AES + +# from Crypto.Cipher import AES # 如果上面的导入失败,可以尝试使用这个 + +SQLITE_FILE_HEADER = "SQLite format 3\x00" # SQLite文件头 + +KEY_SIZE = 32 +DEFAULT_PAGESIZE = 4096 +DEFAULT_ITER = 64000 + + +# 通过密钥解密数据库 +def decrypt(key: str, db_path, out_path): + if not os.path.exists(db_path): + return f"[-] db_path:'{db_path}' File not found!" + if not os.path.exists(os.path.dirname(out_path)): + return f"[-] out_path:'{out_path}' File not found!" + if len(key) != 64: + return f"[-] key:'{key}' Error!" + password = bytes.fromhex(key.strip()) + with open(db_path, "rb") as file: + blist = file.read() + + salt = blist[:16] + byteKey = hashlib.pbkdf2_hmac("sha1", password, salt, DEFAULT_ITER, KEY_SIZE) + first = blist[16:DEFAULT_PAGESIZE] + + mac_salt = bytes([(salt[i] ^ 58) for i in range(16)]) + mac_key = hashlib.pbkdf2_hmac("sha1", byteKey, mac_salt, 2, KEY_SIZE) + hash_mac = hmac.new(mac_key, first[:-32], hashlib.sha1) + hash_mac.update(b'\x01\x00\x00\x00') + + if hash_mac.digest() != first[-32:-12]: + return f"[-] Password Error! (key:'{key}'; db_path:'{db_path}'; out_path:'{out_path}' )" + + newblist = [blist[i:i + DEFAULT_PAGESIZE] for i in range(DEFAULT_PAGESIZE, len(blist), DEFAULT_PAGESIZE)] + + with open(out_path, "wb") as deFile: + deFile.write(SQLITE_FILE_HEADER.encode()) + t = AES.new(byteKey, AES.MODE_CBC, first[-48:-32]) + decrypted = t.decrypt(first[:-48]) + deFile.write(decrypted) + deFile.write(first[-48:]) + + for i in newblist: + t = AES.new(byteKey, AES.MODE_CBC, i[-48:-32]) + decrypted = t.decrypt(i[:-48]) + deFile.write(decrypted) + deFile.write(i[-48:]) + return [True, db_path, out_path, key] + + +def batch_decrypt(key: str, db_path: Union[str, List[str]], out_path: str): + if not isinstance(key, str) or not isinstance(out_path, str) or not os.path.exists(out_path) or len(key) != 64: + return f"[-] (key:'{key}' or out_path:'{out_path}') Error!" + + process_list = [] + + if isinstance(db_path, str): + if not os.path.exists(db_path): + return f"[-] db_path:'{db_path}' not found!" + + if os.path.isfile(db_path): + inpath = db_path + outpath = os.path.join(out_path, 'de_' + os.path.basename(db_path)) + process_list.append([key, inpath, outpath]) + + elif os.path.isdir(db_path): + for root, dirs, files in os.walk(db_path): + for file in files: + inpath = os.path.join(root, file) + rel = os.path.relpath(root, db_path) + outpath = os.path.join(out_path, rel, 'de_' + file) + + if not os.path.exists(os.path.dirname(outpath)): + os.makedirs(os.path.dirname(outpath)) + process_list.append([key, inpath, outpath]) + else: + return f"[-] db_path:'{db_path}' Error " + elif isinstance(db_path, list): + rt_path = os.path.commonprefix(db_path) + if not os.path.exists(rt_path): + rt_path = os.path.dirname(rt_path) + + for inpath in db_path: + if not os.path.exists(inpath): + return f"[-] db_path:'{db_path}' not found!" + + inpath = os.path.normpath(inpath) + rel = os.path.relpath(os.path.dirname(inpath), rt_path) + outpath = os.path.join(out_path, rel, 'de_' + os.path.basename(inpath)) + if not os.path.exists(os.path.dirname(outpath)): + os.makedirs(os.path.dirname(outpath)) + process_list.append([key, inpath, outpath]) + else: + return f"[-] db_path:'{db_path}' Error " + + result = [] + for i in process_list: + result.append(decrypt(*i)) # 解密 + + # 删除空文件夹 + for root, dirs, files in os.walk(out_path, topdown=False): + for dir in dirs: + if not os.listdir(os.path.join(root, dir)): + os.rmdir(os.path.join(root, dir)) + return result + + +if __name__ == '__main__': + # 调用 decrypt 函数,并传入参数 + key = "2aafab10af7940328bb92ac9d2a8ab5fc07a685646b14f2e9ae6948a7060c0fc" + db_path = "D:\Project\Python\PyWxDump\pywxdump\decrypted" + out_path = "test" + result = batch_decrypt(key, db_path, out_path) + for i in result: + if isinstance(i, str): + print(i) + else: + print(f'[+] "{i[1]}" -> "{i[2]}"') diff --git a/app/decrypt/get_wx_info.py b/app/decrypt/get_wx_info.py new file mode 100644 index 0000000..c942c14 --- /dev/null +++ b/app/decrypt/get_wx_info.py @@ -0,0 +1,141 @@ +# -*- coding: utf-8 -*-# +# ------------------------------------------------------------------------------- +# Name: getwxinfo.py +# Description: +# Author: xaoyaoo +# Date: 2023/08/21 +# ------------------------------------------------------------------------------- +import argparse +import ctypes +import json + +import psutil +from win32com.client import Dispatch + +ReadProcessMemory = ctypes.windll.kernel32.ReadProcessMemory +void_p = ctypes.c_void_p + + +# 读取内存中的字符串(非key部分) +def get_info_without_key(h_process, address, n_size=64): + array = ctypes.create_string_buffer(n_size) + if ReadProcessMemory(h_process, void_p(address), array, n_size, 0) == 0: return "None" + array = bytes(array).split(b"\x00")[0] if b"\x00" in array else bytes(array) + text = array.decode('utf-8', errors='ignore') + return text.strip() if text.strip() != "" else "None" + + +def get_info_wxid(h_process, address, n_size=32, address_len=8): + array = ctypes.create_string_buffer(address_len) + if ReadProcessMemory(h_process, void_p(address), array, address_len, 0) == 0: return "None" + address = int.from_bytes(array, byteorder='little') # 逆序转换为int地址(key地址) + wxid = get_info_without_key(h_process, address, n_size) + if not wxid.startswith("wxid_"): wxid = "None" + return wxid + + +# 读取内存中的key +def get_key(h_process, address, address_len=8): + array = ctypes.create_string_buffer(address_len) + if ReadProcessMemory(h_process, void_p(address), array, address_len, 0) == 0: return "None" + address = int.from_bytes(array, byteorder='little') # 逆序转换为int地址(key地址) + key = ctypes.create_string_buffer(32) + if ReadProcessMemory(h_process, void_p(address), key, 32, 0) == 0: return "None" + key_string = bytes(key).hex() + return key_string + + +# 读取微信信息(account,mobile,name,mail,wxid,key) +def read_info(version_list): + wechat_process = [] + result = [] + + for process in psutil.process_iter(['name', 'exe', 'pid', 'cmdline']): + if process.name() == 'WeChat.exe': + wechat_process.append(process) + + if len(wechat_process) == 0: + return "[-] WeChat No Run" + + for process in wechat_process: + tmp_rd = {} + + tmp_rd['pid'] = process.pid + tmp_rd['version'] = Dispatch("Scripting.FileSystemObject").GetFileVersion(process.exe()) + + bias_list = version_list.get(tmp_rd['version'], None) + if not isinstance(bias_list, list): + return f"[-] WeChat Current Version {tmp_rd['version']} Is Not Supported" + + wechat_base_address = 0 + for module in process.memory_maps(grouped=False): + if module.path and 'WeChatWin.dll' in module.path: + wechat_base_address = int(module.addr, 16) + break + if wechat_base_address == 0: + return f"[-] WeChat WeChatWin.dll Not Found" + + Handle = ctypes.windll.kernel32.OpenProcess(0x1F0FFF, False, process.pid) + + name_baseaddr = wechat_base_address + bias_list[0] + account__baseaddr = wechat_base_address + bias_list[1] + mobile_baseaddr = wechat_base_address + bias_list[2] + mail_baseaddr = wechat_base_address + bias_list[3] + key_baseaddr = wechat_base_address + bias_list[4] + wxid_baseaddr = wechat_base_address + bias_list[5] + + addrLen = 4 if tmp_rd['version'] in ["3.9.2.23", "3.9.2.26"] else 8 + + tmp_rd['account'] = get_info_without_key(Handle, account__baseaddr, 32) if bias_list[1] != 0 else "None" + tmp_rd['mobile'] = get_info_without_key(Handle, mobile_baseaddr, 64) if bias_list[2] != 0 else "None" + tmp_rd['name'] = get_info_without_key(Handle, name_baseaddr, 64) if bias_list[0] != 0 else "None" + tmp_rd['mail'] = get_info_without_key(Handle, mail_baseaddr, 64) if bias_list[3] != 0 else "None" + tmp_rd['wxid'] = get_info_wxid(Handle, wxid_baseaddr, 24, addrLen) if bias_list[5] != 0 else "None" + tmp_rd['key'] = get_key(Handle, key_baseaddr, addrLen) if bias_list[4] != 0 else "None" + result.append(tmp_rd) + + return result + + +def get_info(): + VERSION_LIST_PATH = "app/decrypt/version_list.json" + + with open(VERSION_LIST_PATH, "r", encoding="utf-8") as f: + VERSION_LIST = json.load(f) + + result = read_info(VERSION_LIST) # 读取微信信息 + return result + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--vlfile", type=str, help="手机号", required=False) + parser.add_argument("--vldict", type=str, help="微信昵称", required=False) + + args = parser.parse_args() + + # 读取微信各版本偏移 + if args.vlfile: + VERSION_LIST_PATH = args.vlfile + with open(VERSION_LIST_PATH, "r", encoding="utf-8") as f: + VERSION_LIST = json.load(f) + if args.vldict: + VERSION_LIST = json.loads(args.vldict) + + if not args.vlfile and not args.vldict: + VERSION_LIST_PATH = "./version_list.json" + + with open(VERSION_LIST_PATH, "r", encoding="utf-8") as f: + VERSION_LIST = json.load(f) + + result = read_info(VERSION_LIST) # 读取微信信息 + + print("=" * 32) + if isinstance(result, str): # 输出报错 + print(result) + else: # 输出结果 + for i, rlt in enumerate(result): + for k, v in rlt.items(): + print(f"[+] {k:>7}: {v}") + print(end="-" * 32 + "\n" if i != len(result) - 1 else "") + print("=" * 32) diff --git a/app/decrypt/version_list.json b/app/decrypt/version_list.json new file mode 100644 index 0000000..b525952 --- /dev/null +++ b/app/decrypt/version_list.json @@ -0,0 +1,386 @@ +{ + "3.2.1.154": [ + 328121948, + 328122328, + 328123056, + 328121976, + 328123020, + 0 + ], + "3.3.0.115": [ + 31323364, + 31323744, + 31324472, + 31323392, + 31324436, + 0 + ], + "3.3.0.84": [ + 31315212, + 31315592, + 31316320, + 31315240, + 31316284, + 0 + ], + "3.3.0.93": [ + 31323364, + 31323744, + 31324472, + 31323392, + 31324436, + 0 + ], + "3.3.5.34": [ + 30603028, + 30603408, + 30604120, + 30603056, + 30604100, + 0 + ], + "3.3.5.42": [ + 30603012, + 30603392, + 30604120, + 30603040, + 30604084, + 0 + ], + "3.3.5.46": [ + 30578372, + 30578752, + 30579480, + 30578400, + 30579444, + 0 + ], + "3.4.0.37": [ + 31608116, + 31608496, + 31609224, + 31608144, + 31609188, + 0 + ], + "3.4.0.38": [ + 31604044, + 31604424, + 31605152, + 31604072, + 31605116, + 0 + ], + "3.4.0.50": [ + 31688500, + 31688880, + 31689608, + 31688528, + 31689572, + 0 + ], + "3.4.0.54": [ + 31700852, + 31701248, + 31700920, + 31700880, + 31701924, + 0 + ], + "3.4.5.27": [ + 32133788, + 32134168, + 32134896, + 32133816, + 32134860, + 0 + ], + "3.4.5.45": [ + 32147012, + 32147392, + 32147064, + 32147040, + 32148084, + 0 + ], + "3.5.0.20": [ + 35494484, + 35494864, + 35494536, + 35494512, + 35495556, + 0 + ], + "3.5.0.29": [ + 35507980, + 35508360, + 35508032, + 35508008, + 35509052, + 0 + ], + "3.5.0.33": [ + 35512140, + 35512520, + 35512192, + 35512168, + 35513212, + 0 + ], + "3.5.0.39": [ + 35516236, + 35516616, + 35516288, + 35516264, + 35517308, + 0 + ], + "3.5.0.42": [ + 35512140, + 35512520, + 35512192, + 35512168, + 35513212, + 0 + ], + "3.5.0.44": [ + 35510836, + 35511216, + 35510896, + 35510864, + 35511908, + 0 + ], + "3.5.0.46": [ + 35506740, + 35507120, + 35506800, + 35506768, + 35507812, + 0 + ], + "3.6.0.18": [ + 35842996, + 35843376, + 35843048, + 35843024, + 35844068, + 0 + ], + "3.6.5.7": [ + 35864356, + 35864736, + 35864408, + 35864384, + 35865428, + 0 + ], + "3.6.5.16": [ + 35909428, + 35909808, + 35909480, + 35909456, + 35910500, + 0 + ], + "3.7.0.26": [ + 37105908, + 37106288, + 37105960, + 37105936, + 37106980, + 0 + ], + "3.7.0.29": [ + 37105908, + 37106288, + 37105960, + 37105936, + 37106980, + 0 + ], + "3.7.0.30": [ + 37118196, + 37118576, + 37118248, + 37118224, + 37119268, + 0 + ], + "3.7.5.11": [ + 37883280, + 37884088, + 37883136, + 37883008, + 37884052, + 0 + ], + "3.7.5.23": [ + 37895736, + 37896544, + 37895592, + 37883008, + 37896508, + 0 + ], + "3.7.5.27": [ + 37895736, + 37896544, + 37895592, + 37895464, + 37896508, + 0 + ], + "3.7.5.31": [ + 37903928, + 37904736, + 37903784, + 37903656, + 37904700, + 0 + ], + "3.7.6.24": [ + 38978840, + 38979648, + 38978696, + 38978604, + 38979612, + 0 + ], + "3.7.6.29": [ + 38986376, + 38987184, + 38986232, + 38986104, + 38987148, + 0 + ], + "3.7.6.44": [ + 39016520, + 39017328, + 39016376, + 38986104, + 39017292, + 0 + ], + "3.8.0.31": [ + 46064088, + 46064912, + 46063944, + 38986104, + 46064876, + 0 + ], + "3.8.0.33": [ + 46059992, + 46060816, + 46059848, + 38986104, + 46060780, + 0 + ], + "3.8.0.41": [ + 46064024, + 46064848, + 46063880, + 38986104, + 46064812, + 0 + ], + "3.8.1.26": [ + 46409448, + 46410272, + 46409304, + 38986104, + 46410236, + 0 + ], + "3.9.0.28": [ + 48418376, + 48419280, + 48418232, + 38986104, + 48419244, + 0 + ], + "3.9.2.23": [ + 50320784, + 50321712, + 50320640, + 38986104, + 50321676, + 50592864 + ], + "3.9.2.26": [ + 50329040, + 50329968, + 50328896, + 38986104, + 50329932, + 0 + ], + "3.9.5.81": [ + 61650872, + 61652208, + 61650680, + 0, + 61652144, + 0 + ], + "3.9.5.91": [ + 61654904, + 61656240, + 61654712, + 38986104, + 61656176, + 61677112 + ], + "3.9.6.19": [ + 61997688, + 61997464, + 61997496, + 38986104, + 61998960, + 0 + ], + "3.9.6.33": [ + 62030600, + 62031936, + 62030408, + 0, + 62031872, + 0 + ], + "3.9.7.15": [ + 63482696, + 63484032, + 63482504, + 0, + 63483968, + 0 + ], + "3.9.7.25": [ + 63482760, + 63484096, + 63482568, + 0, + 63484032, + 0 + ], + "3.9.7.29": [ + 63486984, + 63488320, + 63486792, + 0, + 63488256, + 63488352 + ], + "3.9.8.15": [ + 64996632, + 64997968, + 64996440, + 0, + 64997904, + 65011632 + ] +} \ No newline at end of file diff --git a/decrypt_window.py b/decrypt_window.py new file mode 100644 index 0000000..2918b42 --- /dev/null +++ b/decrypt_window.py @@ -0,0 +1,38 @@ +import ctypes +import sys + +from PyQt5.QtGui import QIcon +from PyQt5.QtWidgets import * + +from app.Ui import pc_decrypt + +ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("WeChatReport") + + +class ViewController(QWidget): + def __init__(self): + super().__init__() + self.setWindowTitle('解密') + self.setWindowIcon(QIcon('./app/data/icons/logo.svg')) + self.viewMainWIn = None + self.viewDecrypt = None + + def loadPCDecryptView(self): + """ + 登录界面 + :return: + """ + self.viewDecrypt = pc_decrypt.DecryptControl() + self.viewDecrypt.DecryptSignal.connect(self.show_success) + self.viewDecrypt.show() + + def show_success(self): + QMessageBox.about(self, "解密成功", "数据库文件存储在\napp/DataBase/Msg\n文件夹下") + + +if __name__ == '__main__': + app = QApplication(sys.argv) + view = ViewController() + view.loadPCDecryptView() + # view.show_success() + sys.exit(app.exec_()) diff --git a/doc/images/dirs.png b/doc/images/dirs.png new file mode 100644 index 0000000..a4af2d2 Binary files /dev/null and b/doc/images/dirs.png differ diff --git a/doc/images/pc_decrypt_info.png b/doc/images/pc_decrypt_info.png new file mode 100644 index 0000000..f3597ff Binary files /dev/null and b/doc/images/pc_decrypt_info.png differ diff --git a/doc/images/setting.png b/doc/images/setting.png new file mode 100644 index 0000000..799511a Binary files /dev/null and b/doc/images/setting.png differ diff --git a/main.py b/main.py index 126e4e0..a847d37 100644 --- a/main.py +++ b/main.py @@ -5,7 +5,7 @@ import time from PyQt5.QtWidgets import * import app.DataBase.data as DB -from app.Ui import decrypt, mainview +from app.Ui import decrypt, mainview, pc_decrypt ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("WeChatReport") @@ -25,19 +25,17 @@ class ViewController: else: self.viewDecrypt = decrypt.DecryptControl() # 需要将view login设为成员变量 self.viewDecrypt.DecryptSignal.connect(self.loadMainWinView) - self.viewDecrypt.registerSignal.connect(self.loadRegisterView) self.viewDecrypt.show() self.viewDecrypt.db_exist() - def loadRegisterView(self): + def loadPCDecryptView(self): """ - 注册界面 + 登录界面 :return: """ - pass - # self.viewDecrypt = register.registerControl() # 需要将view login设为成员变量 - # self.viewDecrypt.DecryptSignal.connect(self.loadDecryptView) - # self.viewDecrypt.show() + self.viewDecrypt = pc_decrypt.DecryptControl() + self.viewDecrypt.DecryptSignal.connect(self.loadMainWinView) + self.viewDecrypt.show() def loadMainWinView(self, username=None): """ @@ -61,6 +59,7 @@ class ViewController: if __name__ == '__main__': app = QApplication(sys.argv) view = ViewController() - view.loadDecryptView() # 进入登录界面,如果view login不是成员变量,则离开作用域后失效。 + view.loadPCDecryptView() + # view.loadDecryptView() # 进入登录界面,如果view login不是成员变量,则离开作用域后失效。 # view.loadMainWinView('102') sys.exit(app.exec_()) diff --git a/readme.md b/readme.md index 3069bc7..415e635 100644 --- a/readme.md +++ b/readme.md @@ -34,6 +34,8 @@ ## 效果 +
+ image-20230520235113261 ![image-20230520235220104](doc/images/image-20230520235220104.png) @@ -50,7 +52,44 @@ ![image-20230520235431091](doc/images/image-20230520235431091.png) -## 使用 +
+ +# 使用 + +## 解密PC版微信数据库 + +
+ +### 1. 安装 + +```shell +git clone https://github.com/LC044/WeChatMsg +cd WeChatMsg +pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple +``` + +### 2. 使用 + +1. 登录微信 +2. 运行程序 + ```shell + python decrypt_window.py + ``` +3. 点击获取信息 + + ![](./doc/images/pc_decrypt_info.png) +4. 设置微信安装路径 + 可以到微信->设置->文件管理查看 + ![](./doc/images/setting.png) + 点击**设置微信路径**按钮,选择该文件夹路径 +5. 获取到密钥和微信路径之后点击开始解密 +6. 解密后的数据库文件保存在./app/DataBase/Msg路径下 + +
+ +### 使用模拟器 + +
1. 根据[教程](https://blog.csdn.net/m0_59452630/article/details/124222235?spm=1001.2014.3001.5501)获得两个文件 - auth_info_key_prefs.xml——解析数据库密码 @@ -98,10 +137,16 @@ python main.py ![image-20230520235113261](doc/images/image-20230520235113261.png) +
+ ## 项目还有很多bug,希望大家能够及时反馈 项目地址:https://github.com/LC044/WeChatMsg +# 致谢 + +* PC微信解密工具:[https://github.com/xaoyaoo/PyWxDump](https://github.com/xaoyaoo/PyWxDump) + --- > 说明:该项目仅可用于交流学习,禁止任何非法用途,创作者不承担任何责任🙄 diff --git a/requirements.txt b/requirements.txt index acd23d2..638c6f7 100644 Binary files a/requirements.txt and b/requirements.txt differ