diff --git a/Program/get_base_addr.py b/Program/get_base_addr.py
new file mode 100644
index 0000000..ca906db
--- /dev/null
+++ b/Program/get_base_addr.py
@@ -0,0 +1,280 @@
+# -*- coding: utf-8 -*-#
+# -------------------------------------------------------------------------------
+# Name: get_base_addr.py
+# Description:
+# Author: xaoyaoo
+# Date: 2023/08/22
+# -------------------------------------------------------------------------------
+import argparse
+import ctypes
+import json
+import re
+
+import psutil
+import win32api
+
+
+def hex2dec(hex):
+ return int(hex, 16)
+
+
+def dec2hex(dec):
+ return hex(dec)
+
+
+def hex_add(hex1, hex2, base1=16, base2=16):
+ """
+ 两个任意进制数相加
+ :param hex1:
+ :param hex2:
+ :return:
+ """
+ return hex(int(hex1, base1) + int(hex2, base2))
+
+
+def hex_sub(hex1, hex2, base1=16, base2=16):
+ """
+ 两个任意进制数相减
+ :param hex1:
+ :param hex2:
+ :return:
+ """
+ return hex(int(hex1, base1) - int(hex2, base2))
+
+
+def get_pid(keyword):
+ """
+ 获取进程id
+ :param keyword: 关键字
+ :return:
+ """
+ pids = {}
+ for proc in psutil.process_iter():
+ if keyword in proc.name():
+ pids[proc.pid] = proc
+ return pids
+
+
+class BaseAddr:
+ def __init__(self, pid, proc_module_name="WeChatWin.dll"):
+ self.pid = pid
+ self.module_name = proc_module_name
+ self.proc = psutil.Process(self.pid)
+ self.version = self.get_app_version(self.proc.exe())
+ self.base_address = 0
+ self.end_address = 0
+ self.batch = 0
+
+ self.key_start_addr = 0
+ self.key_end_addr = 0
+
+ self.mobile_addr = []
+ self.name_addr = []
+ self.account_addr = []
+ # self.key_addr = []
+
+ self.get_base_addr()
+
+ def get_app_version(self, executable_path):
+ info = win32api.GetFileVersionInfo(executable_path, "\\")
+ version = info['FileVersionMS'] >> 16, info['FileVersionMS'] & 0xFFFF, \
+ info['FileVersionLS'] >> 16, info['FileVersionLS'] & 0xFFFF
+ version_str = ".".join(map(str, version))
+
+ return version_str
+
+ def get_base_addr(self):
+ """
+ 获取模块基址
+ :param pid: 进程id
+ :param module_name: 模块名
+ :return:
+ """
+ base_address = 0
+ end_address = 0
+ batch = 0
+ n = 0
+ for module in self.proc.memory_maps(grouped=False):
+ if self.module_name in module.path:
+ if n == 0:
+ base_address = int(module.addr, 16)
+ batch = module.rss
+ n += 1
+ end_address = int(module.addr, 16) + module.rss
+
+ self.base_address = base_address
+ self.end_address = end_address
+ self.batch = batch
+ # self.batch = end_address - base_address
+
+ def find_all(self, c, string):
+ """
+ 查找字符串中所有子串的位置
+ :param c: 子串 b'123'
+ :param string: 字符串 b'123456789123'
+ :return:
+ """
+ return [m.start() for m in re.finditer(re.escape(c), string)]
+
+ # 搜索内存地址范围内的值
+ def search_memory_value(self, mobile, name, account):
+ mobile = mobile.encode("utf-8")
+ name = name.encode("utf-8")
+ account = account.encode("utf-8")
+
+ Handle = ctypes.windll.kernel32.OpenProcess(0x1F0FFF, False, self.pid)
+
+ mobile_addr = []
+ name_addr = []
+ account_addr = []
+
+ array = ctypes.create_string_buffer(self.batch)
+ for i in range(self.base_address, self.end_address, self.batch):
+ if ctypes.windll.kernel32.ReadProcessMemory(Handle, ctypes.c_void_p(i), array, self.batch, None) == 0:
+ continue
+
+ hex_string = array.raw # 读取到的内存数据
+
+ if mobile in hex_string:
+ mobile_addr = mobile_addr + [m.start() + i for m in re.finditer(re.escape(mobile), hex_string)]
+ if name in hex_string:
+ name_addr = name_addr + [m.start() + i for m in re.finditer(re.escape(name), hex_string)]
+ if account in hex_string:
+ account_addr = account_addr + [m.start() + i for m in re.finditer(re.escape(account), hex_string)]
+
+ self.mobile_addr = mobile_addr
+ self.name_addr = name_addr
+ self.account_addr = account_addr
+ return mobile_addr, name_addr, account_addr
+
+ def get_key_addr(self, key):
+ """
+ 获取key的地址
+ :param key:
+ :return:
+ """
+ key = bytes.fromhex(key)
+
+ module_start_addr = 34199871460642
+ module_end_addr = 0
+ for module in self.proc.memory_maps(grouped=False):
+ if "WeChat" in module.path:
+ start_addr = int(module.addr, 16)
+ end_addr = start_addr + module.rss
+
+ if module_start_addr > start_addr:
+ module_start_addr = start_addr
+ if module_end_addr < end_addr:
+ module_end_addr = end_addr
+
+ Handle = ctypes.windll.kernel32.OpenProcess(0x1F0FFF, False, self.pid)
+ array = ctypes.create_string_buffer(self.batch)
+
+ for i in range(module_start_addr, module_end_addr, self.batch):
+ if ctypes.windll.kernel32.ReadProcessMemory(Handle, ctypes.c_void_p(i), array, self.batch, None) == 0:
+ continue
+
+ hex_string = array.raw # 读取到的内存数据
+ if key in hex_string:
+ self.key_addr_tmp = i + hex_string.find(key)
+ break
+
+ array_key = []
+ for i in range(8):
+ byte_value = (self.key_addr_tmp >> (i * 8)) & 0xFF
+ hex_string = format(byte_value, '02x')
+ byte_obj = bytes.fromhex(hex_string)
+ array_key.append(byte_obj)
+ # 合并数组
+ array_key = b''.join(array_key)
+
+ array = ctypes.create_string_buffer(self.batch)
+ for i in range(self.base_address, self.end_address, self.batch):
+ if ctypes.windll.kernel32.ReadProcessMemory(Handle, ctypes.c_void_p(i), array, self.batch, None) == 0:
+ continue
+
+ hex_string = array.raw # 读取到的内存数据
+ if array_key in hex_string:
+ self.key_addr = i + hex_string.find(array_key)
+ return self.key_addr
+
+ def calculate_offset(self, addr):
+ """
+ 计算偏移量
+ :param addr:
+ :return:
+ """
+ offset = addr - self.base_address
+ return offset
+
+ def get_offset(self):
+ """
+ 计算偏移量
+ :param addr:
+ :return:
+ """
+ mobile_offset = 0
+ name_offset = 0
+ account_offset = 0
+ key_offset = 0
+ if len(self.mobile_addr) >= 1:
+ mobile_offset = self.calculate_offset(self.mobile_addr[0])
+ if len(self.name_addr) >= 1:
+ name_offset = self.calculate_offset(self.name_addr[0])
+ if len(self.account_addr) >= 1:
+ account_offset = self.calculate_offset(self.account_addr[1])
+
+ key_offset = self.calculate_offset(self.key_addr)
+
+ self.key_offset = key_offset
+ self.mobile_offset = mobile_offset
+ self.name_offset = name_offset
+ self.account_offset = account_offset
+ return name_offset, account_offset, mobile_offset, 0, key_offset
+
+
+def run(mobile, name, account, key):
+ proc_name = "WeChat.exe"
+ proc_module_name = "WeChatWin.dll"
+
+ pids = get_pid(proc_name)
+ for pid, proc in pids.items():
+ ba = BaseAddr(pid, proc_module_name)
+ ba.search_memory_value(mobile, name, account)
+ ba.get_key_addr(key)
+ name_offset, account_offset, mobile_offset, _, key_offset = ba.get_offset()
+ rdata = {ba.version: [name_offset, account_offset, mobile_offset, 0, key_offset]}
+ return rdata
+
+
+if __name__ == '__main__':
+ # 创建命令行参数解析器
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--mobile", type=str, help="手机号")
+ parser.add_argument("--name", type=str, help="微信昵称")
+ parser.add_argument("--account", type=str, help="微信账号")
+ parser.add_argument("--key", type=str, help="密钥")
+
+ # 解析命令行参数
+ args = parser.parse_args()
+
+ # 检查是否缺少必要参数,并抛出错误
+ if not args.mobile or not args.name or not args.account or not args.key:
+ raise ValueError("缺少必要的命令行参数!请提供手机号、微信昵称、微信账号和密钥。")
+
+ # 从命令行参数获取值
+ mobile = args.mobile
+ name = args.name
+ account = args.account
+ key = args.key
+
+ # 调用 run 函数,并传入参数
+ rdata = run(mobile, name, account, key)
+ print(rdata)
+
+ # 添加到version_list.json
+ with open("version_list.json", "r", encoding="utf-8") as f:
+ data = json.load(f)
+ data.update(rdata)
+ with open("version_list.json", "w", encoding="utf-8") as f:
+ json.dump(data, f, ensure_ascii=False, indent=4)
diff --git a/Program/get_wx_info.py b/Program/get_wx_info.py
index ec1786e..4b06be7 100644
--- a/Program/get_wx_info.py
+++ b/Program/get_wx_info.py
@@ -8,7 +8,7 @@
import binascii
import json
import ctypes
-
+import win32api
import psutil
@@ -40,7 +40,6 @@ def get_account(pid, base_address, n_size=100):
null_index = i
break
text = ctypes.string_at(ctypes.byref(array), null_index).decode('utf-8', errors='ignore')
-
return text
@@ -85,6 +84,7 @@ def get_hex(h_process, lp_base_address):
num = 32
array2 = (ctypes.c_ubyte * num)()
+
lp_base_address2 = (
(int(binascii.hexlify(array[7]), 16) << 56) +
(int(binascii.hexlify(array[6]), 16) << 48) +
@@ -95,17 +95,15 @@ def get_hex(h_process, lp_base_address):
(int(binascii.hexlify(array[1]), 16) << 8) +
int(binascii.hexlify(array[0]), 16)
)
-
if ctypes.windll.kernel32.ReadProcessMemory(h_process, ctypes.c_void_p(lp_base_address2), ctypes.byref(array2), num,
0) == 0:
return ""
-
hex_string = binascii.hexlify(bytes(array2))
return hex_string.decode('utf-8')
def get_file_version(file_path):
- import win32api
+
info = win32api.GetFileVersionInfo(file_path, "\\")
ms = info['FileVersionMS']
ls = info['FileVersionLS']
@@ -113,6 +111,9 @@ def get_file_version(file_path):
# version = parse(file_version)
return file_version
+# def get_wx_id(h_process, lp_base_address):
+
+
def read_info(version_list):
support_list = None
@@ -121,7 +122,7 @@ def read_info(version_list):
rd = []
for process in psutil.process_iter(['name', 'exe', 'pid', 'cmdline']):
- if process.info['name'] == 'WeChat.exe':
+ if process.name() == 'WeChat.exe':
tmp_rd = {}
wechat_process = process
tmp_rd['pid'] = wechat_process.pid
@@ -186,11 +187,12 @@ def read_info(version_list):
return "[-] WeChat No Run"
return rd
+
if __name__ == "__main__":
- version_list = json.load(open("../version_list.json", "r", encoding="utf-8"))
+ version_list = json.load(open("version_list.json", "r", encoding="utf-8"))
rd = read_info(version_list)
+ # print(rd)
for i in rd:
for k, v in i.items():
print(f"[+] {k}: {v}")
-
print("=====================================")
diff --git a/Program/version_list.json b/Program/version_list.json
new file mode 100644
index 0000000..d030352
--- /dev/null
+++ b/Program/version_list.json
@@ -0,0 +1,303 @@
+{
+ "3.2.1.154": [
+ 328121948,
+ 328122328,
+ 328123056,
+ 328121976,
+ 328123020
+ ],
+ "3.3.0.115": [
+ 31323364,
+ 31323744,
+ 31324472,
+ 31323392,
+ 31324436
+ ],
+ "3.3.0.84": [
+ 31315212,
+ 31315592,
+ 31316320,
+ 31315240,
+ 31316284
+ ],
+ "3.3.0.93": [
+ 31323364,
+ 31323744,
+ 31324472,
+ 31323392,
+ 31324436
+ ],
+ "3.3.5.34": [
+ 30603028,
+ 30603408,
+ 30604120,
+ 30603056,
+ 30604100
+ ],
+ "3.3.5.42": [
+ 30603012,
+ 30603392,
+ 30604120,
+ 30603040,
+ 30604084
+ ],
+ "3.3.5.46": [
+ 30578372,
+ 30578752,
+ 30579480,
+ 30578400,
+ 30579444
+ ],
+ "3.4.0.37": [
+ 31608116,
+ 31608496,
+ 31609224,
+ 31608144,
+ 31609188
+ ],
+ "3.4.0.38": [
+ 31604044,
+ 31604424,
+ 31605152,
+ 31604072,
+ 31605116
+ ],
+ "3.4.0.50": [
+ 31688500,
+ 31688880,
+ 31689608,
+ 31688528,
+ 31689572
+ ],
+ "3.4.0.54": [
+ 31700852,
+ 31701248,
+ 31700920,
+ 31700880,
+ 31701924
+ ],
+ "3.4.5.27": [
+ 32133788,
+ 32134168,
+ 32134896,
+ 32133816,
+ 32134860
+ ],
+ "3.4.5.45": [
+ 32147012,
+ 32147392,
+ 32147064,
+ 32147040,
+ 32148084
+ ],
+ "3.5.0.20": [
+ 35494484,
+ 35494864,
+ 35494536,
+ 35494512,
+ 35495556
+ ],
+ "3.5.0.29": [
+ 35507980,
+ 35508360,
+ 35508032,
+ 35508008,
+ 35509052
+ ],
+ "3.5.0.33": [
+ 35512140,
+ 35512520,
+ 35512192,
+ 35512168,
+ 35513212
+ ],
+ "3.5.0.39": [
+ 35516236,
+ 35516616,
+ 35516288,
+ 35516264,
+ 35517308
+ ],
+ "3.5.0.42": [
+ 35512140,
+ 35512520,
+ 35512192,
+ 35512168,
+ 35513212
+ ],
+ "3.5.0.44": [
+ 35510836,
+ 35511216,
+ 35510896,
+ 35510864,
+ 35511908
+ ],
+ "3.5.0.46": [
+ 35506740,
+ 35507120,
+ 35506800,
+ 35506768,
+ 35507812
+ ],
+ "3.6.0.18": [
+ 35842996,
+ 35843376,
+ 35843048,
+ 35843024,
+ 35844068
+ ],
+ "3.6.5.7": [
+ 35864356,
+ 35864736,
+ 35864408,
+ 35864384,
+ 35865428
+ ],
+ "3.6.5.16": [
+ 35909428,
+ 35909808,
+ 35909480,
+ 35909456,
+ 35910500
+ ],
+ "3.7.0.26": [
+ 37105908,
+ 37106288,
+ 37105960,
+ 37105936,
+ 37106980
+ ],
+ "3.7.0.29": [
+ 37105908,
+ 37106288,
+ 37105960,
+ 37105936,
+ 37106980
+ ],
+ "3.7.0.30": [
+ 37118196,
+ 37118576,
+ 37118248,
+ 37118224,
+ 37119268
+ ],
+ "3.7.5.11": [
+ 37883280,
+ 37884088,
+ 37883136,
+ 37883008,
+ 37884052
+ ],
+ "3.7.5.23": [
+ 37895736,
+ 37896544,
+ 37895592,
+ 37883008,
+ 37896508
+ ],
+ "3.7.5.27": [
+ 37895736,
+ 37896544,
+ 37895592,
+ 37895464,
+ 37896508
+ ],
+ "3.7.5.31": [
+ 37903928,
+ 37904736,
+ 37903784,
+ 37903656,
+ 37904700
+ ],
+ "3.7.6.24": [
+ 38978840,
+ 38979648,
+ 38978696,
+ 38978604,
+ 38979612
+ ],
+ "3.7.6.29": [
+ 38986376,
+ 38987184,
+ 38986232,
+ 38986104,
+ 38987148
+ ],
+ "3.7.6.44": [
+ 39016520,
+ 39017328,
+ 39016376,
+ 38986104,
+ 39017292
+ ],
+ "3.8.0.31": [
+ 46064088,
+ 46064912,
+ 46063944,
+ 38986104,
+ 46064876
+ ],
+ "3.8.0.33": [
+ 46059992,
+ 46060816,
+ 46059848,
+ 38986104,
+ 46060780
+ ],
+ "3.8.0.41": [
+ 46064024,
+ 46064848,
+ 46063880,
+ 38986104,
+ 46064812
+ ],
+ "3.8.1.26": [
+ 46409448,
+ 46410272,
+ 46409304,
+ 38986104,
+ 46410236
+ ],
+ "3.9.0.28": [
+ 48418376,
+ 48419280,
+ 48418232,
+ 38986104,
+ 48419244
+ ],
+ "3.9.2.23": [
+ 50320784,
+ 50321712,
+ 50320640,
+ 38986104,
+ 50321676
+ ],
+ "3.9.2.26": [
+ 50329040,
+ 50329968,
+ 50328896,
+ 38986104,
+ 50329932
+ ],
+ "3.9.5.91": [
+ 61654904,
+ 61654680,
+ 61654712,
+ 38986104,
+ 61656176
+ ],
+ "3.9.6.19": [
+ 61997688,
+ 61997464,
+ 61997496,
+ 38986104,
+ 61998960
+ ],
+ "3.9.6.33": [
+ 62030600,
+ 62031936,
+ 62030408,
+ 0,
+ 62031872
+ ]
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index 2bf256f..2a82eeb 100644
--- a/README.md
+++ b/README.md
@@ -1,50 +1,176 @@
-## SharpWxDump
-如何获取指定版本基址:https://github.com/AdminTest0/SharpWxDump/blob/master/CE%E8%8E%B7%E5%8F%96%E5%9F%BA%E5%9D%80.md
+#
SharpWxDump - Python
-## 特别说明
-该分支是SharpWxDump的python语言版本。
-同时添加了一些新的功能。
+## 一、项目介绍
-**使用方法**
+本项目可以获取微信基本信息,以及key,通过key可以解密微信数据库,获取聊天记录,好友信息,群信息等。
+
+该分支是[SharpWxDump](https://github.com/AdminTest0/SharpWxDump)的经过重构python语言版本,同时添加了一些新的功能。
+
+## 二、使用方法
+
+### 1. 安装依赖
+
+```shell script
+pip install -r requirements.txt
```
+
+**说明**:
+
+1. requirements.txt中的包可能不全,如果运行报错,请自行安装缺少的包
+2. 如果运行报错,请检查python版本,本项目使用的是python3.10
+3. 安装pycryptodome时可能会报错,可以使用下面的命令安装,自行搜索解决方案(该包为解密的核心包)
+
+### 2. 获取微信基本信息
+
+获取微信的信息,获取到几个,取决于现在登录的几个微信。
+
+**2.1 shell获取微信基本信息**
+
+```shell script
cd Program
-python3 Program.py
-
-# 也可以import 调用
-import Program
-Program.read_info(version)
+python get_wx_info.py
```
-**支持功能**
+结果
+
+```shell script
+[+] pid: 2365
+[+] version: *.*.*.*
+[+] key: ******************************************d
+[+] name: *****
+[+] account: ********
+[+] mobile: ******
+[+] mail: *****
+========================================
+[+] pid: 2365
+[+] version: *.*.*.*
+[+] key: ******************************************d
+[+] name: *****
+[+] account: ********
+[+] mobile: ******
+[+] mail: *****
+========================================
+...
+```
+
+**2.2 import 调用**
+
+```python
+import json
+from Program.get_wx_info import read_info
+
+version_list = json.load(open("version_list.json", "r", encoding="utf-8"))
+data = read_info(version_list)
+print(data)
+```
+
+结果:
+
+```list
+[
+ {
+ 'pid': 5632,
+ 'version': '*.*.*.*',
+ 'key': '***************************************',
+ 'name': '******',
+ 'account': '******',
+ 'mobile': '135********',
+ 'mail': '********'
+ },
+ {
+ 'pid': 5632,
+ 'version': '*.*.*.*',
+ 'key': '***************************************',
+ 'name': '******',
+ 'account': '******',
+ 'mobile': '135********',
+ 'mail': '********'
+ },
+ ...
+]
+```
+
+**说明**: 每个字段具体含义,参看上一条shell获取微信基本信息
+
+### 3. 获取偏移地址
+
+* 该方法一般不需要,只有当[version_list.json](./Program/version_list.json)没有对应的微信版本时,可以通过该方法获取偏移地址
+* 如果需要请参考下面的方法获取
+
+**3.1 通过python脚本获取**
+
+```shell
+python get_base_addr.py --mobile 152******** --name ****** --account ****** --key **********************************************
+```
+
+参数说明:
+
+ mobile = "152********" # 手机号
+ name = "******" # 微信昵称
+ account = "******" # 微信账号
+ # 以上信息可以通过微信客户端获取
+
+ key = '**********************************************' # key
+ # 需要降低版本使用get_wx_info.py获取key,也可以通过CheatEngine等工具获取
+ # 最好是保存之前同微信使用过的key,非常方便
+
+**3.2 通过CheatEngine等工具获取**
+
+具体请查看:[CE获取基址.md](./CE%E8%8E%B7%E5%8F%96%E5%9F%BA%E5%9D%80.md)
+
+* 该方法获取到的偏移地址需要手动添加到[version_list.json](./Program/version_list.json)中
+
+**3.3 最简单获取方法**
+
+最简单的方法当然是运行
+
+```shell
+git clone https://github.com/xaoyaoo/SharpWxDump.git
+```
+
+重新拉取一份新的啦~
+
+* ps: 该方法不一定能获取到最新的版本
+* 如果需要最新的版本,可以通过上面的方法获取
+* 你也可以提交Issues,分分钟给你更新
+
+## 三、获取解密数据库
+
+* [decrypt.py](./decrypted/decrypt.py) : 数据库解密脚本
+* [get_wx_decrypted_db.py](./decrypted/get_wx_decrypted_db.py) :直接读取当前登录微信的数据库,解密后保存到当前目录下的decrypted文件夹中
+
+
+
+* 解密后可拖入数据库工具查找敏感信息
+* 还有一份数据的说明文档,但是我累了,不想写了
+
+**方法**
+
+```shell
+```shell
+# 累了。。。不想写了,自己看代码吧
+```
+
+## 四、支持功能
+
1. 支持微信多开场景,获取多用户信息等
2. 微信需要登录状态才能获取数据库密钥
-3. 没有动态获取功能,已将偏移地址写入version_list.josn内,会不定期更新,**如有需要的版本请提交Issues**
-
-
**版本差异**
+
1. 版本 < 3.7.0.30 只运行不登录能获取个人信息,登录后可以获取数据库密钥
2. 版本 > 3.7.0.30 只运行不登录不能获取个人信息,登录后都能获取
**利用场景**
+
1. 钓鱼攻击(通过钓鱼控到的机器通常都是登录状态)
2. 渗透到运维机器(有些运维机器会日常登录自己的微信)
3. 某些工作需要取证(数据库需要拷贝到本地)
4. 自行备份(日常备份自己留存)
5. 等等...............
-**数据库解密**
+## 五、免责声明(非常重要!!!!!!!)
-解密后可拖入数据库工具查找敏感信息
-
-
-
-**参考地址**
-
-数据库解密脚本:https://mp.weixin.qq.com/s/4DbXOS5jDjJzM2PN0Mp2JA
-
-
-## 免责声明
本项目仅允许在授权情况下对数据库进行备份,严禁用于非法目的,否则自行承担所有相关责任。使用该工具则代表默认同意该条款;
请勿利用本项目的相关技术从事非法测试,如因此产生的一切不良后果与项目作者无关。
diff --git a/decrypted/decrypt.py b/decrypted/decrypt.py
new file mode 100644
index 0000000..223d616
--- /dev/null
+++ b/decrypted/decrypt.py
@@ -0,0 +1,50 @@
+import hmac
+import hashlib
+
+from Cryptodome.Cipher import AES
+
+SQLITE_FILE_HEADER = "SQLite format 3\x00" # SQLite文件头
+
+KEY_SIZE = 32
+DEFAULT_PAGESIZE = 4096
+DEFAULT_ITER = 64000
+
+
+# 通过密钥解密数据库
+def decrypt(key, filePath, decryptedPath):
+ password = bytes.fromhex(key.replace(" ", ""))
+ with open(filePath, "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]:
+ print("Decryption Success")
+ else:
+ print("Password Error")
+ return False
+
+ newblist = [blist[i:i + DEFAULT_PAGESIZE] for i in range(DEFAULT_PAGESIZE, len(blist), DEFAULT_PAGESIZE)]
+
+ with open(decryptedPath, "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
+
diff --git a/decrypted/decrypt1.py b/decrypted/decrypt1.py
deleted file mode 100644
index fd599ad..0000000
--- a/decrypted/decrypt1.py
+++ /dev/null
@@ -1,301 +0,0 @@
-import glob
-import os
-import hmac
-import hashlib
-import re
-import shutil
-import sqlite3
-import subprocess
-import winreg
-
-from Cryptodome.Cipher import AES
-
-SQLITE_FILE_HEADER = "SQLite format 3\x00"
-IV_SIZE = 16
-HMAC_SHA1_SIZE = 20
-KEY_SIZE = 32
-DEFAULT_PAGESIZE = 4096
-DEFAULT_ITER = 64000
-
-
-# 通过密钥解密数据库
-def decrypt(key, filePath, decryptedPath):
- password = bytes.fromhex(key.replace(" ", ""))
- with open(filePath, "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]:
- print("Decryption Success")
- else:
- print("Password Error")
-
- newblist = [blist[i:i + DEFAULT_PAGESIZE] for i in range(DEFAULT_PAGESIZE, len(blist), DEFAULT_PAGESIZE)]
-
- with open(decryptedPath, "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:])
-
-
-# 通过外部程序获取微信数据库的key
-def get_wx_key():
- """
- 执行 GoWxDump.exe -wxinfo 获取微信数据库的key
- :return:
- """
- # 获取当前文件路径的上一级目录
- current_path = os.path.dirname(os.path.abspath(__file__))
- current_path = os.path.abspath(os.path.join(current_path, "../.."))
- # 获取GoWxDump.exe的路径
- gowxdump_path = os.path.join(current_path, "Release", "GoWxDump.exe")
- # 判断GoWxDump.exe是否存在
- if not os.path.exists(gowxdump_path):
- print("GoWxDump.exe not found")
- return
- command = gowxdump_path + " -wxinfo"
- output = subprocess.check_output(command, shell=True, encoding='latin-1')
-
- wx_key = output.split("WeChat Key:")[-1].strip()
- return wx_key
-
-
-# 获取微信数据根目录
-def get_wechat_dir():
- """
- 读取注册表获取微信消息目录
- :return:
- """
- try:
- # 打开注册表的微信路径:HKEY_CURRENT_USER\Software\Tencent\WeChat\FileSavePath
- key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Tencent\WeChat", 0, winreg.KEY_READ)
- # 获取key的值
- value, _ = winreg.QueryValueEx(key, "FileSavePath")
- # 关闭注册表项
- winreg.CloseKey(key)
- w_dir = value
- except Exception as e:
- print("读取注册表错误:", str(e))
- return str(e)
-
- # 如果 w_dir 为 "MyDocument:"
- if w_dir == "MyDocument:":
- # 获取 %USERPROFILE%/Documents 目录
- profile = os.path.expanduser("~")
- # 获取微信消息目录
- msg_dir = os.path.join(profile, "Documents", "WeChat Files")
- else:
- # 获取微信消息目录
- msg_dir = os.path.join(w_dir, "WeChat Files")
- # 判断目录是否存在
- if not os.path.exists(msg_dir):
- raise FileNotFoundError("目录不存在")
- return msg_dir
-
-
-# 获取微信消息目录下的所有用户目录
-def get_wechat_user_dir(wechat_root):
- """
- // 获取微信消息目录下的所有用户目录,排除All Users目录和Applet目录,返回一个map,key用户id,value用户目录
- :param wechat_root: 微信消息目录
- :return:
- """
- user_dirs = {}
- # 获取微信消息目录下的所有用户目录
- files = os.listdir(wechat_root)
- for file_name in files:
- # 排除All Users目录和Applet目录
- if file_name == "All Users" or file_name == "Applet" or file_name == "WMPF":
- continue
- user_dirs[file_name] = os.path.join(wechat_root, file_name)
- return user_dirs
-
-
-# copy msg.db到tmp目录,并创建decrypted目录
-def copy_msg_db(data_dir):
- # 判断目录是否存在
- if not os.path.exists(data_dir):
- raise FileNotFoundError("目录不存在")
-
- # 判断运行目录是否存在tmp目录,如果不存在则创建
- tmp_dir = os.path.join(os.getcwd(), "tmp")
- if not os.path.exists(tmp_dir):
- os.mkdir(tmp_dir)
-
- # 正则匹配,将所有MSG数字.db文件拷贝到tmp目录,不扫描子目录
- for root, dirs, files in os.walk(data_dir):
- for file_name in files:
- if re.match(r".*MSG.*\.db", file_name):
- src_path = os.path.join(root, file_name)
- dst_path = os.path.join(tmp_dir, file_name)
- shutil.copyfile(src_path, dst_path)
-
- if "MicroMsg.db" in files:
- src_path = os.path.join(root, "MicroMsg.db")
- dst_path = os.path.join(tmp_dir, "MicroMsg.db")
- shutil.copyfile(src_path, dst_path)
-
- # 如果不存在decrypted目录则创建
- decrypted_dir = os.path.join(os.getcwd(), "")
- if not os.path.exists(decrypted_dir):
- os.mkdir(decrypted_dir)
- return tmp_dir, decrypted_dir
-
-
-# 合并相同名称的数据库
-def merge_db(db_path):
- dbs_paths = {}
- for root, dirs, files in os.walk(db_path):
- for file_name in files:
- if "db-shm" in file_name or "db-wal" in file_name:
- continue
- if "FTSMSG" in file_name:
- src_path = os.path.join(root, file_name)
- dbs_paths["FTSMSG_all.db"] = dbs_paths.get("FTSMSG_all.db", [])
- dbs_paths["FTSMSG_all.db"].append(src_path)
- elif "MediaMSG" in file_name:
- src_path = os.path.join(root, file_name)
- dbs_paths["MediaMSG_all.db"] = dbs_paths.get("MediaMSG_all.db", [])
- dbs_paths["MediaMSG_all.db"].append(src_path)
- elif "MSG" in file_name:
- src_path = os.path.join(root, file_name)
- dbs_paths["MSG_all.db"] = dbs_paths.get("MSG_all.db", [])
- dbs_paths["MSG_all.db"].append(src_path)
-
- for db_name, db_files in dbs_paths.items():
- if db_name != "MSG_all.db":
- continue
-
- save_path = os.path.join(db_path, db_name)
- merged_conn = sqlite3.connect(save_path)
- merged_cursor = merged_conn.cursor()
-
- for db_file in db_files:
- c0 = merged_cursor.execute("select tbl_name from sqlite_master where type='table'")
- r0 = c0.fetchall()
- r0 = [row[0] for row in r0]
-
- conn = sqlite3.connect(db_file)
- cursor = conn.cursor()
- c = cursor.execute("select tbl_name,sql from sqlite_master where type='table'")
- tbls = []
- for row in c:
- if row[0] == "sqlite_sequence":
- continue
- if "mmTokenizer" in row[1]:
- continue
- tbls.append(row[0])
- if row[0] in r0:
- continue
- try:
- merged_cursor.execute(row[1])
- except Exception as e:
- print(e)
- print(db_file)
- print(row[1])
- print(r0)
- raise e
- merged_conn.commit()
- for row in tbls:
- c1 = cursor.execute("select * from " + row)
- for r in c1:
- columns = conn.execute("PRAGMA table_info(" + row + ")").fetchall()
- if len(columns) > 1:
- columns = [column[1] for column in columns[1:]]
- values = r[1:]
- # query = "INSERT INTO " + row + " (" + ",".join(columns) + ") VALUES (" + ",".join(
- # ["?" for _ in range(len(values))]) + ")"
- else:
- columns = [columns[0][1]]
- values = [r[0]]
- query_1 = "select * from " + row + " where " + columns[0] + "=?"
- c2 = merged_cursor.execute(query_1, values)
- if len(c2.fetchall()) > 0:
- continue
- query = "INSERT INTO " + row + " (" + ",".join(columns) + ") VALUES (" + ",".join(
- ["?" for _ in range(len(values))]) + ")"
-
- try:
- merged_cursor.execute(query, values)
- except Exception as e:
- print()
- print("error")
- print(e)
- print(db_file)
- print(query, values)
- print(len(values))
- raise e
- merged_conn.commit()
-
- conn.close()
- print(db_file)
-
- merged_conn.close()
- # merge_databases(save_path, db_file)
-
-
-def merge_databases(db1, db2):
- con3 = sqlite3.connect(db1)
-
- con3.execute("ATTACH DATABASE '" + db2 + "' as dba")
-
- con3.execute("BEGIN")
- for row in con3.execute("SELECT * FROM dba.sqlite_master WHERE type='table'"):
- # 此处的ignore就是为了忽略重复ID导致的异常
- combine = "INSERT OR IGNORE INTO " + row[1] + " SELECT * FROM dba." + row[1]
- print(combine)
- con3.execute(combine)
- con3.commit()
- con3.execute("detach database dba")
-
-
-if __name__ == '__main__':
- # 获取微信数据库的key
- wx_key = get_wx_key()
-
- # 获取微信消息目录
- wechat_msg_dir = get_wechat_dir()
- user_msg_dirs = get_wechat_user_dir(wechat_msg_dir)
- if len(user_msg_dirs) == 1:
- data_dir = list(user_msg_dirs.values())[0]
- else:
- for i, user_dir in enumerate(user_msg_dirs):
- print(i, user_dir)
- index = int(input("请选择要导出的用户:"))
- data_dir = list(user_msg_dirs.values())[index]
-
- print("复制微信的msg数据文件...")
- # 复制微信的msg数据文件
- tmp_dir, decrypted_dir = copy_msg_db(os.path.join(data_dir, "Msg"))
-
- print("解密数据库...")
- # 解密数据库
- for file_name in os.listdir(tmp_dir):
- if re.match(r".*\.db$", file_name):
- src_path = os.path.join(tmp_dir, file_name)
- dst_path = os.path.join(decrypted_dir, file_name)
- decrypt(wx_key, src_path, dst_path)
-
- # 删除临时目录
- shutil.rmtree(tmp_dir)
-
- # decrypted_dir = os.path.join(os.getcwd(), "decrypted")
- print("合并数据库...")
- # 合并数据库
- merge_db(decrypted_dir)
diff --git a/decrypted/get_wx_decrypted_db.py b/decrypted/get_wx_decrypted_db.py
new file mode 100644
index 0000000..93c4e53
--- /dev/null
+++ b/decrypted/get_wx_decrypted_db.py
@@ -0,0 +1,293 @@
+# -*- coding: utf-8 -*-#
+# -------------------------------------------------------------------------------
+# Name: get_wx_decrypted_db.py
+# Description:
+# Author: xaoyaoo
+# Date: 2023/08/25
+# -------------------------------------------------------------------------------
+import os
+import re
+import shutil
+import sqlite3
+import winreg
+
+from .decrypt import decrypt
+
+
+# 开始获取微信数据库
+def get_wechat_db():
+ try:
+ key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Tencent\WeChat", 0, winreg.KEY_READ)
+ value, _ = winreg.QueryValueEx(key, "FileSavePath")
+ winreg.CloseKey(key)
+ w_dir = value
+ except Exception as e:
+ try:
+ w_dir = "MyDocument:"
+ except Exception as e:
+ print("读取注册表错误:", str(e))
+ return str(e)
+
+ if w_dir == "MyDocument:":
+ profile = os.path.expanduser("~")
+ msg_dir = os.path.join(profile, "Documents", "WeChat Files")
+ else:
+ msg_dir = os.path.join(w_dir, "WeChat Files")
+ if not os.path.exists(msg_dir):
+ return FileNotFoundError("目录不存在")
+ user_dirs = {} # wx用户目录
+ files = os.listdir(msg_dir)
+ for file_name in files:
+ if file_name == "All Users" or file_name == "Applet" or file_name == "WMPF":
+ continue
+ user_dirs[file_name] = os.path.join(msg_dir, file_name)
+
+ # 获取数据库路径
+ for user, user_dir in user_dirs.items():
+ Media_p = []
+ Micro_p = []
+ FTS_p = []
+ Sns_p = []
+ Msg = []
+ Emotion_p = []
+ for root, dirs, files in os.walk(user_dir):
+ for file_name in files:
+ if re.match(r".*MediaMSG.*\.db$", file_name):
+ src_path = os.path.join(root, file_name)
+ Media_p.append(src_path)
+ elif re.match(r".*MicroMsg.*\.db$", file_name):
+ src_path = os.path.join(root, file_name)
+ Micro_p.append(src_path)
+ elif re.match(r".*FTSMSG.*\.db$", file_name):
+ src_path = os.path.join(root, file_name)
+ FTS_p.append(src_path)
+ elif re.match(r".*MSG.*\.db$", file_name):
+ src_path = os.path.join(root, file_name)
+ Msg.append(src_path)
+ elif re.match(r".*Sns.*\.db$", file_name):
+ src_path = os.path.join(root, file_name)
+ Sns_p.append(src_path)
+ elif re.match(r".*Emotion.*\.db$", file_name):
+ src_path = os.path.join(root, file_name)
+ Emotion_p.append(src_path)
+ Media_p.sort()
+ Msg.sort()
+ Micro_p.sort()
+ # FTS_p.sort()
+ user_dirs[user] = {"MicroMsg": Micro_p, "Msg": Msg, "MediaMSG": Media_p, "Sns": Sns_p, "Emotion": Emotion_p}
+ return user_dirs
+
+
+# 解密所有数据库 paths(文件) 到 decrypted_path(目录)
+def all_decrypt(keys, paths, decrypted_path):
+ decrypted_paths = []
+
+ for key in keys:
+ for path in paths:
+
+ name = os.path.basename(path) # 文件名
+ dtp = os.path.join(decrypted_path, name) # 解密后的路径
+ if not decrypt(key, path, dtp):
+ break
+ decrypted_paths.append(dtp)
+ else: # for循环正常结束,没有break
+ break # 跳出while循环
+ else:
+ return False # while循环正常结束,没有break 解密失败
+ return decrypted_paths
+
+
+def merge_copy_msg_db(db_path, save_path):
+ if isinstance(db_path, list) and len(db_path) == 1:
+ db_path = db_path[0]
+ if not os.path.exists(db_path):
+ raise FileNotFoundError("目录不存在")
+ shutil.move(db_path, save_path)
+
+
+# 合并相同名称的数据库
+def merge_msg_db(db_path: list, save_path: str, CreateTime: int = 0): # CreateTime: 从这个时间开始的消息 10位时间戳
+
+ merged_conn = sqlite3.connect(save_path)
+ merged_cursor = merged_conn.cursor()
+
+ for db_file in db_path:
+ c_tabels = merged_cursor.execute(
+ "select tbl_name from sqlite_master where type='table' and tbl_name!='sqlite_sequence'")
+ tabels_all = c_tabels.fetchall() # 所有表名
+ tabels_all = [row[0] for row in tabels_all]
+
+ conn = sqlite3.connect(db_file)
+ cursor = conn.cursor()
+
+ # 创建表
+ if len(tabels_all) < 4:
+ cursor.execute(
+ "select tbl_name,sql from sqlite_master where type='table' and tbl_name!='sqlite_sequence'")
+ c_part = cursor.fetchall()
+
+ for tbl_name, sql in c_part:
+ if tbl_name in tabels_all:
+ continue
+ try:
+ merged_cursor.execute(sql)
+ tabels_all.append(tbl_name)
+ except Exception as e:
+ print(f"error: {db_file}\n{tbl_name}\n{sql}\n{e}\n**********")
+ raise e
+ merged_conn.commit()
+
+ # 写入数据
+ for tbl_name in tabels_all:
+ if tbl_name == "MSG":
+ MsgSvrIDs = merged_cursor.execute(
+ f"select MsgSvrID from MSG where CreateTime>{CreateTime} and MsgSvrID!=0").fetchall()
+
+ cursor.execute(f"PRAGMA table_info({tbl_name})")
+ columns = cursor.fetchall()
+ columns = [column[1] for column in columns[1:]]
+
+ ex_sql = f"select {','.join(columns)} from {tbl_name} where CreateTime>{CreateTime} and MsgSvrID not in ({','.join([str(MsgSvrID[0]) for MsgSvrID in MsgSvrIDs])})"
+ cursor.execute(ex_sql)
+
+ insert_sql = f"INSERT INTO {tbl_name} ({','.join(columns)}) VALUES ({','.join(['?' for _ in range(len(columns))])})"
+ try:
+ merged_cursor.executemany(insert_sql, cursor.fetchall())
+ except Exception as e:
+ print(
+ f"error: {db_file}\n{tbl_name}\n{insert_sql}\n{cursor.fetchall()}\n{len(cursor.fetchall())}\n{e}\n**********")
+ raise e
+ merged_conn.commit()
+ else:
+ ex_sql = f"select * from {tbl_name}"
+ cursor.execute(ex_sql)
+
+ for r in cursor.fetchall():
+ cursor.execute(f"PRAGMA table_info({tbl_name})")
+ columns = cursor.fetchall()
+ if len(columns) > 1:
+ columns = [column[1] for column in columns[1:]]
+ values = r[1:]
+ else:
+ columns = [columns[0][1]]
+ values = [r[0]]
+
+ query_1 = "select * from " + tbl_name + " where " + columns[0] + "=?" # 查询语句 用于判断是否存在
+ c2 = merged_cursor.execute(query_1, values)
+ if len(c2.fetchall()) > 0: # 已存在
+ continue
+ query = "INSERT INTO " + tbl_name + " (" + ",".join(columns) + ") VALUES (" + ",".join(
+ ["?" for _ in range(len(values))]) + ")"
+
+ try:
+ merged_cursor.execute(query, values)
+ except Exception as e:
+ print(f"error: {db_file}\n{tbl_name}\n{query}\n{values}\n{len(values)}\n{e}\n**********")
+ raise e
+ merged_conn.commit()
+
+ conn.close()
+ sql = '''delete from MSG where localId in (SELECT localId from MSG
+ where MsgSvrID != 0 and MsgSvrID in (select MsgSvrID from MSG
+ where MsgSvrID != 0 GROUP BY MsgSvrID HAVING COUNT(*) > 1)
+ and localId not in (select min(localId) from MSG
+ where MsgSvrID != 0 GROUP BY MsgSvrID HAVING COUNT(*) > 1))'''
+ c = merged_cursor.execute(sql)
+ merged_conn.commit()
+ merged_conn.close()
+ return save_path
+
+
+def merge_media_msg_db(db_path: list, save_path: str):
+ merged_conn = sqlite3.connect(save_path)
+ merged_cursor = merged_conn.cursor()
+
+ for db_file in db_path:
+
+ s = "select tbl_name,sql from sqlite_master where type='table' and tbl_name!='sqlite_sequence'"
+ have_tables = merged_cursor.execute(s).fetchall()
+ have_tables = [row[0] for row in have_tables]
+
+ conn_part = sqlite3.connect(db_file)
+ cursor = conn_part.cursor()
+
+ if len(have_tables) < 1:
+ cursor.execute(s)
+ table_part = cursor.fetchall()
+ tblname, sql = table_part[0]
+
+ sql = "CREATE TABLE Media(localId INTEGER PRIMARY KEY AUTOINCREMENT,Key TEXT,Reserved0 INT,Buf BLOB,Reserved1 INT,Reserved2 TEXT)"
+ try:
+ merged_cursor.execute(sql)
+ have_tables.append(tblname)
+ except Exception as e:
+ print(f"error: {db_file}\n{tblname}\n{sql}\n{e}\n**********")
+ raise e
+ merged_conn.commit()
+
+ for tblname in have_tables:
+ s = "select Reserved0 from " + tblname
+ merged_cursor.execute(s)
+ r0 = merged_cursor.fetchall()
+
+ ex_sql = f"select * from {tblname} where Reserved0 not in ({','.join([str(r[0]) for r in r0])})"
+ cursor.execute(ex_sql)
+ data = cursor.fetchall()
+
+ insert_sql = f"INSERT INTO {tblname} (Key,Reserved0,Buf,Reserved1,Reserved2) VALUES ({','.join(['?' for _ in range(5)])})"
+ try:
+ merged_cursor.executemany(insert_sql, data)
+ except Exception as e:
+ print(f"error: {db_file}\n{tblname}\n{insert_sql}\n{data}\n{len(data)}\n{e}\n**********")
+ raise e
+ merged_conn.commit()
+ conn_part.close()
+
+ merged_conn.close()
+ return save_path
+
+
+def main(keys: list = None):
+ decrypted_ROOT = os.path.join(os.getcwd(), "decrypted")
+
+
+ user_dirs = get_wechat_db()
+ for user, db_path in user_dirs.items(): # 遍历用户
+ MicroMsgPaths = db_path["MicroMsg"]
+ MsgPaths = db_path["Msg"]
+ MediaMSGPaths = db_path["MediaMSG"]
+ # FTSMSGPaths = db_path["FTSMSG"]
+ SnsPaths = db_path["Sns"]
+ EmotionPaths = db_path["Emotion"]
+
+ decrypted_path_tmp = os.path.join(decrypted_ROOT, user, "tmp") # 解密后的目录
+ if not os.path.exists(decrypted_path_tmp):
+ os.makedirs(decrypted_path_tmp)
+
+ MicroMsgDecryptPaths = all_decrypt(keys, MicroMsgPaths, decrypted_path_tmp)
+ MsgDecryptPaths = all_decrypt(keys, MsgPaths, decrypted_path_tmp)
+ MediaMSGDecryptPaths = all_decrypt(keys, MediaMSGPaths, decrypted_path_tmp)
+ SnsDecryptPaths = all_decrypt(keys, SnsPaths, decrypted_path_tmp)
+ EmotionDecryptPaths = all_decrypt(keys, EmotionPaths, decrypted_path_tmp)
+
+ # 合并数据库
+ decrypted_path = os.path.join(decrypted_ROOT, user) # 解密后的目录
+
+ MicroMsgDbPath = os.path.join(decrypted_path, "MicroMsg.db")
+ MsgDbPath = os.path.join(decrypted_path, "MSG_all.db")
+ MediaMSGDbPath = os.path.join(decrypted_path, "MediaMSG_all.db")
+ SnsDbPath = os.path.join(decrypted_path, "Sns_all.db")
+ EmmotionDbPath = os.path.join(decrypted_path, "Emotion_all.db")
+
+ merge_copy_msg_db(MicroMsgDecryptPaths, MicroMsgDbPath)
+ merge_msg_db(MsgDecryptPaths, MsgDbPath, 0)
+ merge_media_msg_db(MediaMSGDecryptPaths, MediaMSGDbPath)
+ merge_copy_msg_db(SnsDecryptPaths, SnsDbPath)
+ merge_copy_msg_db(EmotionDecryptPaths, EmmotionDbPath)
+
+ shutil.rmtree(decrypted_path_tmp) # 删除临时文件
+
+
+
+if __name__ == '__main__':
+ main()
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..1d9b053
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,3 @@
+psutil
+pycryptodome
+pywin32
diff --git a/version_list.json b/version_list.json
deleted file mode 100644
index 7d9584b..0000000
--- a/version_list.json
+++ /dev/null
@@ -1,303 +0,0 @@
-{
- "3.2.1.154": [
- 328121948,
- 328122328,
- 328123056,
- 328121976,
- 328123020
- ],
- "3.3.0.115": [
- 31323364,
- 31323744,
- 31324472,
- 31323392,
- 31324436
- ],
- "3.3.0.84": [
- 31315212,
- 31315592,
- 31316320,
- 31315240,
- 31316284
- ],
- "3.3.0.93": [
- 31323364,
- 31323744,
- 31324472,
- 31323392,
- 31324436
- ],
- "3.3.5.34": [
- 30603028,
- 30603408,
- 30604120,
- 30603056,
- 30604100
- ],
- "3.3.5.42": [
- 30603012,
- 30603392,
- 30604120,
- 30603040,
- 30604084
- ],
- "3.3.5.46": [
- 30578372,
- 30578752,
- 30579480,
- 30578400,
- 30579444
- ],
- "3.4.0.37": [
- 31608116,
- 31608496,
- 31609224,
- 31608144,
- 31609188
- ],
- "3.4.0.38": [
- 31604044,
- 31604424,
- 31605152,
- 31604072,
- 31605116
- ],
- "3.4.0.50": [
- 31688500,
- 31688880,
- 31689608,
- 31688528,
- 31689572
- ],
- "3.4.0.54": [
- 31700852,
- 31701248,
- 31700920,
- 31700880,
- 31701924
- ],
- "3.4.5.27": [
- 32133788,
- 32134168,
- 32134896,
- 32133816,
- 32134860
- ],
- "3.4.5.45": [
- 32147012,
- 32147392,
- 32147064,
- 32147040,
- 32148084
- ],
- "3.5.0.20": [
- 35494484,
- 35494864,
- 35494536,
- 35494512,
- 35495556
- ],
- "3.5.0.29": [
- 35507980,
- 35508360,
- 35508032,
- 35508008,
- 35509052
- ],
- "3.5.0.33": [
- 35512140,
- 35512520,
- 35512192,
- 35512168,
- 35513212
- ],
- "3.5.0.39": [
- 35516236,
- 35516616,
- 35516288,
- 35516264,
- 35517308
- ],
- "3.5.0.42": [
- 35512140,
- 35512520,
- 35512192,
- 35512168,
- 35513212
- ],
- "3.5.0.44": [
- 35510836,
- 35511216,
- 35510896,
- 35510864,
- 35511908
- ],
- "3.5.0.46": [
- 35506740,
- 35507120,
- 35506800,
- 35506768,
- 35507812
- ],
- "3.6.0.18": [
- 35842996,
- 35843376,
- 35843048,
- 35843024,
- 35844068
- ],
- "3.6.5.7": [
- 35864356,
- 35864736,
- 35864408,
- 35864384,
- 35865428
- ],
- "3.6.5.16": [
- 35909428,
- 35909808,
- 35909480,
- 35909456,
- 35910500
- ],
- "3.7.0.26": [
- 37105908,
- 37106288,
- 37105960,
- 37105936,
- 37106980
- ],
- "3.7.0.29": [
- 37105908,
- 37106288,
- 37105960,
- 37105936,
- 37106980
- ],
- "3.7.0.30": [
- 37118196,
- 37118576,
- 37118248,
- 37118224,
- 37119268
- ],
- "3.7.5.11": [
- 37883280,
- 37884088,
- 37883136,
- 37883008,
- 37884052
- ],
- "3.7.5.23": [
- 37895736,
- 37896544,
- 37895592,
- 37883008,
- 37896508
- ],
- "3.7.5.27": [
- 37895736,
- 37896544,
- 37895592,
- 37895464,
- 37896508
- ],
- "3.7.5.31": [
- 37903928,
- 37904736,
- 37903784,
- 37903656,
- 37904700
- ],
- "3.7.6.24": [
- 38978840,
- 38979648,
- 38978696,
- 38978604,
- 38979612
- ],
- "3.7.6.29": [
- 38986376,
- 38987184,
- 38986232,
- 38986104,
- 38987148
- ],
- "3.7.6.44": [
- 39016520,
- 39017328,
- 39016376,
- 38986104,
- 39017292
- ],
- "3.8.0.31": [
- 46064088,
- 46064912,
- 46063944,
- 38986104,
- 46064876
- ],
- "3.8.0.33": [
- 46059992,
- 46060816,
- 46059848,
- 38986104,
- 46060780
- ],
- "3.8.0.41": [
- 46064024,
- 46064848,
- 46063880,
- 38986104,
- 46064812
- ],
- "3.8.1.26": [
- 46409448,
- 46410272,
- 46409304,
- 38986104,
- 46410236
- ],
- "3.9.0.28": [
- 48418376,
- 48419280,
- 48418232,
- 38986104,
- 48419244
- ],
- "3.9.2.23": [
- 50320784,
- 50321712,
- 50320640,
- 38986104,
- 50321676
- ],
- "3.9.2.26": [
- 50329040,
- 50329968,
- 50328896,
- 38986104,
- 50329932
- ],
- "3.9.5.91": [
- 61654904,
- 61654680,
- 61654712,
- 38986104,
- 61656176
- ],
- "3.9.6.19": [
- 61997688,
- 61997464,
- 61997496,
- 38986104,
- 61998960
- ],
- "3.9.6.33": [
- 62030600,
- 62031936,
- 62030408,
- 38986104,
- 62031872
- ]
-}
\ No newline at end of file