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 @@
-
-
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
@@ -69,7 +80,7 @@
"RunOnceActivity.OpenProjectViewOnStart": "true",
"RunOnceActivity.ShowReadmeOnStart": "true",
"last_opened_file_path": "D:/Program Files/Python310/Scripts/pyuic5.exe",
- "settings.editor.selected.configurable": "reference.settings.ide.settings.new.ui"
+ "settings.editor.selected.configurable": "preferences.pluginManager"
}
}
@@ -77,28 +88,7 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -107,11 +97,53 @@
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -141,7 +173,7 @@
-
+
@@ -153,7 +185,7 @@
-
+
@@ -162,7 +194,7 @@
-
+
@@ -170,14 +202,14 @@
-
+
-
+
-
+
@@ -185,11 +217,11 @@
-
-
-
+
-
+
+
+
@@ -205,13 +237,6 @@
1672848140146
-
- 1672911758283
-
-
-
- 1672911758283
-
1673412476080
@@ -548,7 +573,14 @@
1699702527136
-
+
+ 1699766149377
+
+
+
+ 1699766149377
+
+
@@ -584,7 +616,6 @@
-
@@ -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
-
+ file://$PROJECT_DIR$/app/decrypt/decrypt.py
+ 103
+
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-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