From a63884a08bba0128986b07f1d7745d3d015d4897 Mon Sep 17 00:00:00 2001 From: xaoyo Date: Sat, 14 Oct 2023 21:48:35 +0800 Subject: [PATCH] =?UTF-8?q?=E6=95=B4=E4=BD=93=E9=87=8D=E6=9E=84=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=EF=BC=8C=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81=EF=BC=8C?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=91=BD=E4=BB=A4=E8=A1=8C=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/ISSUE_TEMPLATE/bug_report.md | 34 --- README.md | 253 +++++++----------- {decrypted => app}/__init__.py | 10 +- app/analyse/__init__.py | 8 + {parse_db => app/analyse}/parse.py | 0 {parse_db => app/bias_addr}/__init__.py | 7 +- .../bias_addr/get_bias_addr.py | 20 +- app/decrypted/__init__.py | 9 + app/decrypted/decrypt.py | 137 ++++++++++ .../decrypted}/get_wx_decrypted_db.py | 37 ++- {Program => app}/version_list.json | 3 +- {Program => app/wx_info}/__init__.py | 6 +- app/wx_info/get_wx_db.py | 75 ++++++ {Program => app/wx_info}/get_wx_info.py | 2 +- decrypted/decrypt.py | 99 ------- CE获取基址.md => doc/CE获取基址.md | 0 doc/python1.0_README.md | 219 +++++++++++++++ parse_db/README.md => doc/wx数据库简述.md | 0 main.py | 250 +++++++++++++++++ 19 files changed, 837 insertions(+), 332 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md rename {decrypted => app}/__init__.py (68%) create mode 100644 app/analyse/__init__.py rename {parse_db => app/analyse}/parse.py (100%) rename {parse_db => app/bias_addr}/__init__.py (80%) rename Program/get_base_addr.py => app/bias_addr/get_bias_addr.py (94%) create mode 100644 app/decrypted/__init__.py create mode 100644 app/decrypted/decrypt.py rename {decrypted => app/decrypted}/get_wx_decrypted_db.py (99%) rename {Program => app}/version_list.json (99%) rename {Program => app/wx_info}/__init__.py (80%) create mode 100644 app/wx_info/get_wx_db.py rename {Program => app/wx_info}/get_wx_info.py (97%) delete mode 100644 decrypted/decrypt.py rename CE获取基址.md => doc/CE获取基址.md (100%) create mode 100644 doc/python1.0_README.md rename parse_db/README.md => doc/wx数据库简述.md (100%) create mode 100644 main.py diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 67d9aea..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -name: Bug report -about: 帮助定位问题所在 -title: '' -labels: '' -assignees: '' - ---- - -**问题描述** -请在此处提供对问题的详细描述。 - -**复现步骤** -请提供重现问题所需的步骤。(执行的命令) - -1. 步骤 1 -2. 步骤 2 -3. 步骤 3 - -**预期行为** -请清楚地描述您预期的行为。 - -**实际行为** -请描述实际的行为和问题出现的地方。 - -**环境信息** -- 操作系统版本: -- python版本: -- 微信版本: - - - -**其他信息** -请提供任何与问题相关的其他信息(文字,截图等)。 diff --git a/README.md b/README.md index 9fafaa7..715dd64 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,13 @@ #
PyWxDump
-* 更新日志(发现[version_list.json](./Program/version_list.json) +[![Python](https://img.shields.io/badge/Python-3.10-blue.svg)](https://www.python.org/) +[![GitHub stars](https://img.shields.io/github/stars/xaoyaoo/PyWxDump.svg?style=social&label=Star)](https://github.com/xaoyaoo/PyWxDump) + +* 更新日志(发现[version_list.json](app/version_list.json) 缺失或错误,请提交[issues](https://github.com/xaoyaoo/PyWxDump/issues)): - * 2023.10.11 添加"3.9.5.81"版本的偏移地址[#10](https://github.com/xaoyaoo/PyWxDump/issues/10), 感谢@**[sv3nbeast](https://github.com/sv3nbeast)** + * 2023.10.14 整体重构项目,优化代码,增加命令行统一操作 + * 2023.10.11 添加"3.9.5.81"版本的偏移地址[#10](https://github.com/xaoyaoo/PyWxDump/issues/10), + 感谢@[sv3nbeast](https://github.com/sv3nbeast) * 2023.10.09 获取key基址偏移可以根据微信文件夹获取,不需要输入key * 2023.10.09 优化代码,删减没必要代码,重新修改获取基址代码,加快运行速度(需要安装新的库 pymem) * 2023.10.07 修改获取基址内存搜索方式,防止进入死循环 @@ -11,7 +16,7 @@ * 2023.09.28 增加了数据库部分解析 * 2023.09.15 增加了3.9.7.25版本的偏移地址 -## 一、项目介绍 +# 一、项目介绍 本项目可以获取微信基本信息,以及key,通过key可以解密微信数据库,获取聊天记录,好友信息,群信息等。 @@ -21,9 +26,34 @@ 超级想要star,走过路过,帮忙点个[![Star](https://img.shields.io/github/stars/xaoyaoo/PyWxDump.svg?style=social&label=Star)](https://github.com/xaoyaoo/PyWxDump/) 呗,谢谢啦~ -## 二、使用方法 +**目录结构** -### 1. 安装依赖 +``` +PyWxDump +├─ app # 项目代码,存放各个模块 +│ ├─ analyse # 解析数据库 +│ │ └─ parse.py # 解析数据库脚本,可以解析语音、图片、聊天记录等 +│ ├─ bias_addr # 获取偏移地址 +│ │ └─ get_bias_addr.py # 获取偏移地址脚本 +│ ├─ decrypted # 解密数据库 +│ │ ├─ decrypt.py # 解密数据库脚本 +│ │ └─ get_wx_decrypted_db.py # 直接读取当前登录微信的数据库,解密后保存到当前目录下的decrypted文件夹中 +│ ├─ wx_info # 获取微信基本信息 +│ │ ├─ get_wx_info.py # 获取微信基本信息脚本 +│ │ └─ get_wx_db.py # 获取本地所有的微信相关数据库 +│ └─ version_list.json # 微信版本列表 +├─ doc # 项目文档 +│ ├─ wx数据库简述.md # wx数据库简述 +│ └─ CE获取基址.md # CE获取基址 +├─ main.py # 命令行入口 +├─ README.md +└─ requirements.txt +``` + + +# 二、使用方法 + +## 1. 安装依赖 ```shell script pip install -r requirements.txt @@ -35,166 +65,87 @@ pip install -r requirements.txt 2. 如果运行报错,请检查python版本,本项目使用的是python3.10 3. 安装pycryptodome时可能会报错,可以使用下面的命令安装,自行搜索解决方案(该包为解密的核心包) -### 2. 获取微信基本信息 +## 2. 使用方法 -获取微信的信息,获取到几个,取决于现在登录的几个微信。 - -**2.1 shell获取微信基本信息** +### 2.1 命令行 ```shell script -cd Program -python get_wx_info.py +python main.py [模式] [参数] +# 运行模式(mode): +# bias_addr 获取微信基址偏移 +# wx_info 获取微信信息 +# wx_db 获取微信文件夹路径 +# decrypt 解密微信数据库 +# analyse 解析微信数据库(未完成) +# all 执行所有操作(除获取基址偏移、Analyse) ``` -结果 +*示例* + +以下是示例命令: ```shell script -[+] pid: 2365 -[+] version: *.*.*.* -[+] key: ******************************************d -[+] name: ***** -[+] account: ******** -[+] mobile: ****** -[+] mail: ***** -======================================== -[+] pid: 2365 -[+] version: *.*.*.* -[+] key: ******************************************d -[+] name: ***** -[+] account: ******** -[+] mobile: ****** -[+] mail: ***** -======================================== -... +python main.py bias_addr -h +#usage: main.py bias_addr [-h] --mobile MOBILE --name NAME --account ACCOUNT [--key KEY] [--db_path DB_PATH] [-vlp VLP] +#options: +# -h, --help show this help message and exit +# --mobile MOBILE 手机号 +# --name NAME 微信昵称 +# --account ACCOUNT 微信账号 +# --key KEY (与db_path二选一)密钥 +# --db_path DB_PATH (与key二选一)已登录账号的微信文件夹路径 +# -vlp VLP (可选)微信版本偏移文件路径 + +python main.py wx_info -h +#usage: main.py wx_info [-h] [-vlp VLP] +#options: +# -h, --help show this help message and exit +# -vlp VLP (可选)微信版本偏移文件路径 + +python main.py wx_db -h +#usage: main.py wx_db [-h] [-r REQUIRE_LIST] [-wf WF] +#options: +# -h, --help show this help message and exit +# -r REQUIRE_LIST, --require_list REQUIRE_LIST +# (可选)需要的数据库名称(eg: -r MediaMSG;MicroMsg;FTSMSG;MSG;Sns;Emotion ) +# -wf WF (可选)'WeChat Files'路径 + +python main.py decrypt -h +#usage: main.py decrypt [-h] -k KEY -i DB_PATH -o OUT_PATH +#options: +# -h, --help show this help message and exit +# -k KEY, --key KEY 密钥 +# -i DB_PATH, --db_path DB_PATH +# 数据库路径(目录or文件) +# -o OUT_PATH, --out_path OUT_PATH +# 输出路径(必须是目录),输出文件为 out_path/de_{original_name} + +python main.py analyse -h +#usage: main.py analyse [-h] [--arg ARG] +#options: +# -h, --help show this help message and exit +# --arg ARG 参数 + +python main.py all -h +#usage: main.py all [-h] +#options: +# -h, --help show this help message and exit ``` -**2.2 import 调用** +### 2.2 python API ```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) +from app import * +# 单独使用各模块,返回值一般为字典,参数参考命令行 ``` -结果: +【注】: -```list -[ - { - 'pid': 5632, - 'version': '*.*.*.*', - 'key': '***************************************', - 'name': '******', - 'account': '******', - 'mobile': '135********', - 'mail': '********' - }, - { - 'pid': 5632, - 'version': '*.*.*.*', - 'key': '***************************************', - 'name': '******', - 'account': '******', - 'mobile': '135********', - 'mail': '********' - }, - ... -] -``` +* 关于基址使用cheat engine获取,参考[CE获取基址.md](doc/CE获取基址.md) +* 关于数据库解析,参考[wx数据库简述.md](doc/wx数据库简述.md) +* 关于更多使用方法,以及各个模块的使用方法,参考前一版本的[python1.0_README.md](doc/python1.0_README.md) -**说明**: 每个字段具体含义,参看上一条shell获取微信基本信息 - -### 3. 获取偏移地址 - -* 该方法一般不需要,只有当[version_list.json](./Program/version_list.json)没有对应的微信版本时,可以通过该方法获取偏移地址 -* 如果需要请参考下面的方法获取 - -**3.1 通过python脚本获取** - -```shell -python get_base_addr.py --mobile 152***** --name **** --account *** --key ********** --db_path "****\WeChat Files\wxid_******" -``` - -参数说明: - - 以下参数必选 - mobile = "152********" # 手机号 - name = "******" # 微信昵称 - account = "******" # 微信账号 - # 以上信息可以通过微信客户端获取 - - 以下参数二选一(key获取偏移更快,db_path获取偏移很慢,本地测试需要10-60s) - key = '**********************************************' - # 需要降低版本使用get_wx_info.py获取key,也可以通过CheatEngine等工具获取 - # 最好是保存之前同微、同设备信使用过的key,非常方便 - db_path = "****\WeChat Files\wxid_******" - # 微信文件夹,通过微信客户端,设置-文件管理-微信文件的默认保存位置获取 - -return:{'3.9.7.29': [63486984, 63488320, 63486792, 0, 63488256, 56006136]} - - (十进制)按顺序代表:微信昵称、微信账号、微信手机号、微信邮箱(默认0)、微信KEY、版本信息 - -[注]:如果参数错误,得到的对应地址偏移为0,邮箱高版本失效,默认为0 - -**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/PyWxDump.git -``` - -重新拉取一份新的啦~ - -* ps: 该方法不一定能获取到最新的版本 -* 如果需要最新的版本,可以通过上面的方法获取 -* 你也可以提交Issues,分分钟给你更新 - -## 三、获取解密数据库 - -* [decrypt.py](./decrypted/decrypt.py) : 数据库解密脚本 -* [get_wx_decrypted_db.py](./decrypted/get_wx_decrypted_db.py) :直接读取当前登录微信的数据库,解密后保存到当前目录下的decrypted文件夹中 - -[注]:每台设备、每个微信账号对应一个key,切换设备或者微信账号,key都会变化 - -![image](https://user-images.githubusercontent.com/33925462/179410883-10deefb3-793d-4e15-8475-a74954fafe19.png) - -* 解密后可拖入数据库工具查找敏感信息 -* 还有一份数据的说明文档,但是我累了,不想写了 - -**方法** - -进入目录[decrypted](./decrypted) - -```shell -python decrypt.py --key ******** --db_path ./decrypted/decrypted.db --out_path ./decrypted/decrypted.db -``` - -[注]:--key为数据库密钥,--db_path为数据库路径,--out_path为解密后的数据库路径(解密后的路径目录必须存在) - -自动根据注册表读取本地微信聊天记录文件夹,解密后保存到当前目录下的decrypted文件夹中 - -```shell -python get_wx_decrypted_db.py --key ******** -``` - -## 四、解析数据库 - -* [parse.py](./parse_db/parse.py) : 数据库解析脚本,可以解析语音、图片、聊天记录等 -* 关于各个数据库的说明文档,请查看[parse_db](./parse_db)目录下的[README.md](./parse_db/README.md) - -未完待续... - -## 五、支持功能 +## 三、支持功能 1. 支持微信多开场景,获取多用户信息等 2. 微信需要登录状态才能获取数据库密钥 @@ -212,7 +163,7 @@ python get_wx_decrypted_db.py --key ******** 4. 自行备份(日常备份自己留存) 5. 等等............... -## 六、免责声明(非常重要!!!!!!!) +## 四、免责声明(非常重要!!!!!!!) 本项目仅允许在授权情况下对数据库进行备份,严禁用于非法目的,否则自行承担所有相关责任。使用该工具则代表默认同意该条款; diff --git a/decrypted/__init__.py b/app/__init__.py similarity index 68% rename from decrypted/__init__.py rename to app/__init__.py index 7e65b3e..fb76081 100644 --- a/decrypted/__init__.py +++ b/app/__init__.py @@ -3,9 +3,9 @@ # Name: __init__.py.py # Description: # Author: xaoyaoo -# Date: 2023/08/21 +# Date: 2023/10/14 # ------------------------------------------------------------------------------- - - -if __name__ == '__main__': - pass +from .bias_addr import * +from .wx_info import * +from .decrypted import * +from .analyse import * \ No newline at end of file diff --git a/app/analyse/__init__.py b/app/analyse/__init__.py new file mode 100644 index 0000000..7b74b1c --- /dev/null +++ b/app/analyse/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*-# +# ------------------------------------------------------------------------------- +# Name: __init__.py.py +# Description: +# Author: xaoyaoo +# Date: 2023/09/27 +# ------------------------------------------------------------------------------- +from .parse import read_img_dat, read_emoji, decompress_CompressContent, read_audio_buf, read_audio diff --git a/parse_db/parse.py b/app/analyse/parse.py similarity index 100% rename from parse_db/parse.py rename to app/analyse/parse.py diff --git a/parse_db/__init__.py b/app/bias_addr/__init__.py similarity index 80% rename from parse_db/__init__.py rename to app/bias_addr/__init__.py index a69538a..de2c860 100644 --- a/parse_db/__init__.py +++ b/app/bias_addr/__init__.py @@ -3,9 +3,6 @@ # Name: __init__.py.py # Description: # Author: xaoyaoo -# Date: 2023/09/27 +# Date: 2023/10/14 # ------------------------------------------------------------------------------- - - -if __name__ == '__main__': - pass +from .get_bias_addr import BiasAddr diff --git a/Program/get_base_addr.py b/app/bias_addr/get_bias_addr.py similarity index 94% rename from Program/get_base_addr.py rename to app/bias_addr/get_bias_addr.py index ee1b848..9a46409 100644 --- a/Program/get_base_addr.py +++ b/app/bias_addr/get_bias_addr.py @@ -42,7 +42,7 @@ def validate_key(key, salt, first, mac_salt): return False -class BaseAddr: +class BiasAddr: def __init__(self, account, mobile, name, key, db_path): self.account = account.encode("utf-8") self.mobile = mobile.encode("utf-8") @@ -201,22 +201,22 @@ class BaseAddr: mobile_bias = self.search_memory_value(self.mobile) name_bias = self.search_memory_value(self.name) account_bias = self.search_memory_value(self.account) - version_bias = self.search_memory_value(self.version.encode("utf-8")) + # version_bias = self.search_memory_value(self.version.encode("utf-8")) if self.key: key_bias = self.search_key(self.key) elif self.db_path: key_bias = self.get_key_bias(self.db_path, account_bias) else: key_bias = 0 - return {self.version: [name_bias, account_bias, mobile_bias, 0, key_bias, version_bias]} + return {self.version: [name_bias, account_bias, mobile_bias, 0, key_bias]} 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("--mobile", type=str, help="手机号", required=True) + parser.add_argument("--name", type=str, help="微信昵称", required=True) + parser.add_argument("--account", type=str, help="微信账号", required=True) parser.add_argument("--key", type=str, help="密钥") parser.add_argument("--db_path", type=str, help="微信文件夹(已经登录微信)路径") @@ -232,16 +232,16 @@ if __name__ == '__main__': mobile = args.mobile name = args.name account = args.account - key = None # args.key + key = args.key db_path = args.db_path # 调用 run 函数,并传入参数 - rdata = BaseAddr(account, mobile, name, key, db_path).run() + rdata = BiasAddr(account, mobile, name, key, db_path).run() print(rdata) # 添加到version_list.json - with open("version_list.json", "r", encoding="utf-8") as f: + 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: + with open("../version_list.json", "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=4) diff --git a/app/decrypted/__init__.py b/app/decrypted/__init__.py new file mode 100644 index 0000000..59542dc --- /dev/null +++ b/app/decrypted/__init__.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*-# +# ------------------------------------------------------------------------------- +# Name: __init__.py.py +# Description: +# Author: xaoyaoo +# Date: 2023/08/21 +# ------------------------------------------------------------------------------- +from .decrypt import batch_decrypt, decrypt +from .get_wx_decrypted_db import all_decrypt, merge_copy_msg_db, merge_msg_db, merge_media_msg_db diff --git a/app/decrypted/decrypt.py b/app/decrypted/decrypt.py new file mode 100644 index 0000000..c3e0e70 --- /dev/null +++ b/app/decrypted/decrypt.py @@ -0,0 +1,137 @@ +import argparse +import hmac +import hashlib +import os + +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!" + + 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: [str | list], 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__': + # 创建命令行参数解析器 + parser = argparse.ArgumentParser() + parser.add_argument("-k", "--key", type=str, help="密钥", required=True) + parser.add_argument("-i", "--db_path", type=str, help="数据库路径(目录or文件)", required=True) + parser.add_argument("-o", "--out_path", type=str, + help="输出路径(必须是目录),输出文件为 out_path/de_{original_name}", required=True) + + # 解析命令行参数 + args = parser.parse_args() + + # 从命令行参数获取值 + key = args.key + db_path = args.db_path + out_path = args.out_path + + # 调用 decrypt 函数,并传入参数 + 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/decrypted/get_wx_decrypted_db.py b/app/decrypted/get_wx_decrypted_db.py similarity index 99% rename from decrypted/get_wx_decrypted_db.py rename to app/decrypted/get_wx_decrypted_db.py index 082ea32..32953ca 100644 --- a/decrypted/get_wx_decrypted_db.py +++ b/app/decrypted/get_wx_decrypted_db.py @@ -248,12 +248,26 @@ def merge_media_msg_db(db_path: list, save_path: str): return save_path -def main(keys=None): +if __name__ == '__main__': + # 创建命令行参数解析器 + parser = argparse.ArgumentParser() + parser.add_argument("-k", "--key", help="解密密钥", nargs="+", required=True) + + # 解析命令行参数 + args = parser.parse_args() + + # 检查是否缺少必要参数,并抛出错误 + if not args.key: + raise ValueError("缺少必要的命令行参数!请提供密钥。") + + # 从命令行参数获取值 + keys = args.key + decrypted_ROOT = os.path.join(os.getcwd(), "decrypted") if keys is None: print("keys is None") - return False + exit(0) if isinstance(keys, str): keys = [keys] @@ -293,22 +307,3 @@ def main(keys=None): shutil.rmtree(decrypted_path_tmp) # 删除临时文件 print(f"解密完成:{user}, {decrypted_path}") - return True - - -if __name__ == '__main__': - # 创建命令行参数解析器 - parser = argparse.ArgumentParser() - parser.add_argument("-k", "--key", help="解密密钥", nargs="+", required=True) - - # 解析命令行参数 - args = parser.parse_args() - - # 检查是否缺少必要参数,并抛出错误 - if not args.key: - raise ValueError("缺少必要的命令行参数!请提供密钥。") - - # 从命令行参数获取值 - keys = args.key - - main(keys) diff --git a/Program/version_list.json b/app/version_list.json similarity index 99% rename from Program/version_list.json rename to app/version_list.json index 195ced8..9464729 100644 --- a/Program/version_list.json +++ b/app/version_list.json @@ -319,7 +319,6 @@ 63488320, 63486792, 0, - 63488256, - 56006136 + 63488256 ] } \ No newline at end of file diff --git a/Program/__init__.py b/app/wx_info/__init__.py similarity index 80% rename from Program/__init__.py rename to app/wx_info/__init__.py index 7e65b3e..2e9671c 100644 --- a/Program/__init__.py +++ b/app/wx_info/__init__.py @@ -5,7 +5,5 @@ # Author: xaoyaoo # Date: 2023/08/21 # ------------------------------------------------------------------------------- - - -if __name__ == '__main__': - pass +from .get_wx_info import read_info +from .get_wx_db import get_wechat_db \ No newline at end of file diff --git a/app/wx_info/get_wx_db.py b/app/wx_info/get_wx_db.py new file mode 100644 index 0000000..db33374 --- /dev/null +++ b/app/wx_info/get_wx_db.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*-# +# ------------------------------------------------------------------------------- +# Name: get_wx_db.py +# Description: +# Author: xaoyaoo +# Date: 2023/10/14 +# ------------------------------------------------------------------------------- +import os +import re +import winreg + + +def get_wechat_db(require_list: [list | str] = "all", msg_dir: str = None): + if not msg_dir: + 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: + w_dir = "MyDocument:" + + 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 "[-] 目录不存在" + + 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) + + if isinstance(require_list, str): + require_list = require_list.split(";") + + if "all" in require_list: + pattern = {"all": re.compile(r".*\.db$")} + elif isinstance(require_list, list): + pattern = {} + for require in require_list: + pattern[require] = re.compile(r".*%s.*\.db$" % require) + else: + return "[-] 参数错误" + + # 获取数据库路径 + for user, user_dir in user_dirs.items(): # 遍历用户目录 + user_dirs[user] = {n: [] for n in pattern.keys()} + for root, dirs, files in os.walk(user_dir): + for file_name in files: + for n, p in pattern.items(): + if p.match(file_name): + src_path = os.path.join(root, file_name) + user_dirs[user][n].append(src_path) + return user_dirs + + +if __name__ == '__main__': + require_list = ["MediaMSG", "MicroMsg", "FTSMSG", "MSG", "Sns", "Emotion"] + # require_list = "all" + user_dirs = get_wechat_db(require_list) + if isinstance(user_dirs, str): + print(user_dirs) + else: + for user, user_dir in user_dirs.items(): + print(f"[+] {user}") + for n, paths in user_dir.items(): + print(f" {n}:") + for path in paths: + print(f" {path}") diff --git a/Program/get_wx_info.py b/app/wx_info/get_wx_info.py similarity index 97% rename from Program/get_wx_info.py rename to app/wx_info/get_wx_info.py index f9f441d..ede8d7a 100644 --- a/Program/get_wx_info.py +++ b/app/wx_info/get_wx_info.py @@ -84,7 +84,7 @@ def read_info(version_list): 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")) result = read_info(version_list) # 读取微信信息 print("=" * 32) diff --git a/decrypted/decrypt.py b/decrypted/decrypt.py deleted file mode 100644 index fcd265c..0000000 --- a/decrypted/decrypt.py +++ /dev/null @@ -1,99 +0,0 @@ -import argparse -import hmac -import hashlib -import os - -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, db_path, out_path): - if not os.path.exists(db_path): - print("[-] db_path File not found!") - return False - if not os.path.exists(os.path.dirname(out_path)): - print("[-] out_path File Path not found!") - return False - - 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]: - 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(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 - - -def batch_decrypt(key, db_path, out_path): - if not os.path.exists(db_path): - print("[-] db_path File not found!") - return False - if not os.path.exists(os.path.dirname(out_path)): - print("[-] out_path File Path not found!") - return False - - if os.path.isfile(db_path) and not os.path.isdir(out_path): - return decrypt(key, db_path, out_path) - if os.path.isdir(db_path) and not os.path.isfile(out_path): - for root, dirs, files in os.walk(db_path): - for file in files: - decrypt(key, os.path.join(root, file), os.path.join(out_path, "decrypted" + file)) - return True - - -if __name__ == '__main__': - # 创建命令行参数解析器 - parser = argparse.ArgumentParser() - parser.add_argument("--key", type=str, help="密钥") - parser.add_argument("--db_path", type=str, help="加密数据库路径") - parser.add_argument("--out_path", type=str, help="解密后的数据库路径") - - # 解析命令行参数 - args = parser.parse_args() - - # 检查是否缺少必要参数,并抛出错误 - if not args.key or not args.db_path or not args.out_path: - raise ValueError("缺少必要的命令行参数!请提供密钥、加密数据库路径和解密后的数据库路径。") - - # 从命令行参数获取值 - key = args.key - db_path = args.db_path - out_path = args.out_path - - # 调用 decrypt 函数,并传入参数 - result = batch_decrypt(key, db_path, out_path) - print(f"{result} done!") diff --git a/CE获取基址.md b/doc/CE获取基址.md similarity index 100% rename from CE获取基址.md rename to doc/CE获取基址.md diff --git a/doc/python1.0_README.md b/doc/python1.0_README.md new file mode 100644 index 0000000..41d04b5 --- /dev/null +++ b/doc/python1.0_README.md @@ -0,0 +1,219 @@ +#
PyWxDump
+ +* 更新日志(发现[version_list.json](./Program/version_list.json) + 缺失或错误,请提交[issues](https://github.com/xaoyaoo/PyWxDump/issues)): + * 2023.10.11 添加"3.9.5.81"版本的偏移地址[#10](https://github.com/xaoyaoo/PyWxDump/issues/10), 感谢@**[sv3nbeast](https://github.com/sv3nbeast)** + * 2023.10.09 获取key基址偏移可以根据微信文件夹获取,不需要输入key + * 2023.10.09 优化代码,删减没必要代码,重新修改获取基址代码,加快运行速度(需要安装新的库 pymem) + * 2023.10.07 修改获取基址内存搜索方式,防止进入死循环 + * 2023.10.07 增加了3.9.7.29版本的偏移地址 + * 2023.10.06 增加命令行解密数据库 + * 2023.09.28 增加了数据库部分解析 + * 2023.09.15 增加了3.9.7.25版本的偏移地址 + +## 一、项目介绍 + +本项目可以获取微信基本信息,以及key,通过key可以解密微信数据库,获取聊天记录,好友信息,群信息等。 + +该分支是[SharpWxDump](https://github.com/AdminTest0/SharpWxDump)的经过重构python语言版本,同时添加了一些新的功能。 + + +超级想要star,走过路过,帮忙点个[![Star](https://img.shields.io/github/stars/xaoyaoo/PyWxDump.svg?style=social&label=Star)](https://github.com/xaoyaoo/PyWxDump/) +呗,谢谢啦~ + +## 二、使用方法 + +### 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 +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 ********** --db_path "****\WeChat Files\wxid_******" +``` + +参数说明: + + 以下参数必选 + mobile = "152********" # 手机号 + name = "******" # 微信昵称 + account = "******" # 微信账号 + # 以上信息可以通过微信客户端获取 + + 以下参数二选一(key获取偏移更快,db_path获取偏移很慢,本地测试需要10-60s) + key = '**********************************************' + # 需要降低版本使用get_wx_info.py获取key,也可以通过CheatEngine等工具获取 + # 最好是保存之前同微、同设备信使用过的key,非常方便 + db_path = "****\WeChat Files\wxid_******" + # 微信文件夹,通过微信客户端,设置-文件管理-微信文件的默认保存位置获取 + +return:{'3.9.7.29': [63486984, 63488320, 63486792, 0, 63488256, 56006136]} + + (十进制)按顺序代表:微信昵称、微信账号、微信手机号、微信邮箱(默认0)、微信KEY、版本信息 + +[注]:如果参数错误,得到的对应地址偏移为0,邮箱高版本失效,默认为0 + +**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/PyWxDump.git +``` + +重新拉取一份新的啦~ + +* ps: 该方法不一定能获取到最新的版本 +* 如果需要最新的版本,可以通过上面的方法获取 +* 你也可以提交Issues,分分钟给你更新 + +## 三、获取解密数据库 + +* [decrypt.py](./decrypted/decrypt.py) : 数据库解密脚本 +* [get_wx_decrypted_db.py](./decrypted/get_wx_decrypted_db.py) :直接读取当前登录微信的数据库,解密后保存到当前目录下的decrypted文件夹中 + +[注]:每台设备、每个微信账号对应一个key,切换设备或者微信账号,key都会变化 + +![image](https://user-images.githubusercontent.com/33925462/179410883-10deefb3-793d-4e15-8475-a74954fafe19.png) + +* 解密后可拖入数据库工具查找敏感信息 +* 还有一份数据的说明文档,但是我累了,不想写了 + +**方法** + +进入目录[decrypted](./decrypted) + +```shell +python decrypt.py --key ******** --db_path ./decrypted/decrypted.db --out_path ./decrypted/decrypted.db +``` + +[注]:--key为数据库密钥,--db_path为数据库路径,--out_path为解密后的数据库路径(解密后的路径目录必须存在) + +自动根据注册表读取本地微信聊天记录文件夹,解密后保存到当前目录下的decrypted文件夹中 + +```shell +python get_wx_decrypted_db.py --key ******** +``` + +## 四、解析数据库 + +* [parse.py](./parse_db/parse.py) : 数据库解析脚本,可以解析语音、图片、聊天记录等 +* 关于各个数据库的说明文档,请查看[parse_db](./parse_db)目录下的[README.md](./parse_db/README.md) + +未完待续... + +## 五、支持功能 + +1. 支持微信多开场景,获取多用户信息等 +2. 微信需要登录状态才能获取数据库密钥 + +**版本差异** + +1. 版本 < 3.7.0.30 只运行不登录能获取个人信息,登录后可以获取数据库密钥 +2. 版本 > 3.7.0.30 只运行不登录不能获取个人信息,登录后都能获取 + +**利用场景** + +1. 钓鱼攻击(通过钓鱼控到的机器通常都是登录状态) +2. 渗透到运维机器(有些运维机器会日常登录自己的微信) +3. 某些工作需要取证(数据库需要拷贝到本地) +4. 自行备份(日常备份自己留存) +5. 等等............... + +## 六、免责声明(非常重要!!!!!!!) + +本项目仅允许在授权情况下对数据库进行备份,严禁用于非法目的,否则自行承担所有相关责任。使用该工具则代表默认同意该条款; + +请勿利用本项目的相关技术从事非法测试,如因此产生的一切不良后果与项目作者无关。 \ No newline at end of file diff --git a/parse_db/README.md b/doc/wx数据库简述.md similarity index 100% rename from parse_db/README.md rename to doc/wx数据库简述.md diff --git a/main.py b/main.py new file mode 100644 index 0000000..f4082e3 --- /dev/null +++ b/main.py @@ -0,0 +1,250 @@ +# -*- coding: utf-8 -*-# +# ------------------------------------------------------------------------------- +# Name: main.py.py +# Description: +# Author: xaoyaoo +# Date: 2023/10/14 +# ------------------------------------------------------------------------------- +import json +import argparse +import os + +from app import * + + +class MainBiasAddr(): + def init_parses(self, parser): + # 添加 'bias_addr' 子命令解析器 + sb_bias_addr = parser.add_parser("bias_addr", help="获取微信基址偏移") + sb_bias_addr.add_argument("--mobile", type=str, help="手机号", required=True) + sb_bias_addr.add_argument("--name", type=str, help="微信昵称", required=True) + sb_bias_addr.add_argument("--account", type=str, help="微信账号", required=True) + sb_bias_addr.add_argument("--key", type=str, help="(与db_path二选一)密钥") + sb_bias_addr.add_argument("--db_path", type=str, help="(与key二选一)已登录账号的微信文件夹路径") + sb_bias_addr.add_argument("-vlp", type=str, help="(可选)微信版本偏移文件路径", + default="./app/version_list.json") + return sb_bias_addr + + def run(self, args): + # 判断是否至少输入一个参数 + if not args.key and not args.db_path: + sb_bias_addr.error("必须至少指定 --key 或 --db_path 参数中的一个") + + # 从命令行参数获取值 + mobile = args.mobile + name = args.name + account = args.account + key = args.key + db_path = args.db_path + version_list_path = args.vlp + # 调用 run 函数,并传入参数 + rdata = BiasAddr(account, mobile, name, key, db_path).run() + print(rdata) + + # 添加到version_list.json + version_list = json.load(open(version_list_path, "r", encoding="utf-8")) + version_list.update(rdata) + json.dump(version_list, open(version_list_path, "w", encoding="utf-8"), ensure_ascii=False, indent=4) + + return rdata + + +class MainWxInfo(): + def init_parses(self, parser): + # 添加 'wx_info' 子命令解析器 + sb_wx_info = parser.add_parser("wx_info", help="获取微信信息") + sb_wx_info.add_argument("-vlp", type=str, help="(可选)微信版本偏移文件路径", default="./app/version_list.json") + return sb_wx_info + + def run(self, args): + # 读取微信各版本偏移 + version_list_path = args.vlp + version_list = json.load(open(version_list_path, "r", encoding="utf-8")) + 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) + return result + + +class MainWxDbPath(): + def init_parses(self, parser): + # 添加 'wx_db_path' 子命令解析器 + sb_wx_db_path = parser.add_parser("wx_db", help="获取微信文件夹路径") + sb_wx_db_path.add_argument("-r", "--require_list", type=str, + help="(可选)需要的数据库名称(eg: -r MediaMSG;MicroMsg;FTSMSG;MSG;Sns;Emotion )", + default="all") + sb_wx_db_path.add_argument("-wf", type=str, help="(可选)'WeChat Files'路径", default=None) + return sb_wx_db_path + + def run(self, args): + # 从命令行参数获取值 + require_list = args.require_list + msg_dir = args.wf + + user_dirs = get_wechat_db(require_list, msg_dir) + + if isinstance(user_dirs, str): + print(user_dirs) + else: + for user, user_dir in user_dirs.items(): + print(f"[+] {user}") + for n, paths in user_dir.items(): + print(f" {n}:") + for path in paths[:2]: + print(f" {path}") + if len(paths) > 2: + print(f" ...") + print("-" * 32) + print(f"[+] 共 {len(user_dirs)} 个微信账号") + + return user_dirs + + +class MainDecrypt(): + def init_parses(self, parser): + # 添加 'decrypt' 子命令解析器 + sb_decrypt = parser.add_parser("decrypt", help="解密微信数据库") + sb_decrypt.add_argument("-k", "--key", type=str, help="密钥", required=True) + sb_decrypt.add_argument("-i", "--db_path", type=str, help="数据库路径(目录or文件)", required=True) + sb_decrypt.add_argument("-o", "--out_path", type=str, + help="输出路径(必须是目录),输出文件为 out_path/de_{original_name}", required=True) + return sb_decrypt + + def run(self, args): + # 从命令行参数获取值 + key = args.key + db_path = args.db_path + out_path = args.out_path + + # 调用 decrypt 函数,并传入参数 + result = batch_decrypt(key, db_path, out_path) + if isinstance(result, list): + for i in result: + if isinstance(i, str): + print(i) + else: + print(f'[+] "{i[1]}" -> "{os.path.relpath(i[2], out_path)}"') + else: + print(result) + + +class MainAnalyseWxDb(): + def init_parses(self, parser): + # 添加 'parse_wx_db' 子命令解析器 + sb_parse_wx_db = parser.add_parser("analyse", help="解析微信数据库(未完成)") + sb_parse_wx_db.add_argument("--arg", type=str, help="参数") + return sb_parse_wx_db + + def run(self, args): + print(f"解析微信数据库(未完成)") + + +class MainAll(): + def init_parses(self, parser): + # 添加 'all' 子命令解析器 + sb_all = parser.add_parser("all", help="执行所有操作(除获取基址偏移、Analyse)") + return sb_all + + def run(self, args): + # 获取微信信息 + args.vlp = "./app/version_list.json" + result_WxInfo = MainWxInfo().run(args) + keys = [i.get('key', "") for i in result_WxInfo] + + args.require_list = 'all' + args.wf = None + result_WxDbPath = MainWxDbPath().run(args) + wxdbpaths = [path for user_dir in result_WxDbPath.values() for paths in user_dir.values() for path in paths] + wxdblen = len(wxdbpaths) + print(f"[+] 共 {wxdblen} 个微信数据库(包含所有本地曾登录的微信)") + print("=" * 32) + + out_path = os.path.join(os.getcwd(), "decrypted") + print(f"[*] 解密后文件夹:{out_path} ") + print(f"[*] 解密中...(用时较久,耐心等待)") + if not os.path.exists(out_path): + os.makedirs(out_path) + + rd = {} + for key in keys: + rd[key] = batch_decrypt(key, wxdbpaths, out_path) + + result_Decrypt = [None] * wxdblen + for i in range(wxdblen): + for k, v in rd.items(): + if isinstance(v[i], list): + result_Decrypt[i] = v[i] + break + else: + result_Decrypt[i] = v[i] + + print("[+] 解密完成") + print("-" * 32) + + errors = [] + for i in range(wxdblen): + if isinstance(result_Decrypt[i], str): + errors.append(i) + else: + print( + f'[+] success "{os.path.relpath(result_Decrypt[i][1], os.path.commonprefix(wxdbpaths))}" -> "{os.path.relpath(result_Decrypt[i][2], os.getcwd())}"') + print("-" * 32) + print("[-] " + f"共 {len(errors)} 个文件解密失败;") + # print("; ".join([f'"{wxdbpaths[i]}"' for i in errors])) + print("=" * 32) + + +if __name__ == '__main__': + # 创建命令行参数解析器 + parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter) + + # 添加子命令解析器 + subparsers = parser.add_subparsers(dest="mode", help="""运行模式:""") + + # 添加 'bias_addr' 子命令解析器 + main_bias_addr = MainBiasAddr() + sb_bias_addr = main_bias_addr.init_parses(subparsers) + + # 添加 'wx_info' 子命令解析器 + main_wx_info = MainWxInfo() + sb_wx_info = main_wx_info.init_parses(subparsers) + + # 添加 'wx_db_path' 子命令解析器 + main_wx_db_path = MainWxDbPath() + sb_wx_db_path = main_wx_db_path.init_parses(subparsers) + + # 添加 'decrypt' 子命令解析器 + main_decrypt = MainDecrypt() + sb_decrypt = main_decrypt.init_parses(subparsers) + + # 添加 'parse_wx_db' 子命令解析器 + main_parse_wx_db = MainAnalyseWxDb() + sb_parse_wx_db = main_parse_wx_db.init_parses(subparsers) + + # 添加 'all' 子命令解析器 + main_all = MainAll() + sb_all = main_all.init_parses(subparsers) + + args = parser.parse_args() # 解析命令行参数 + + # 根据不同的 'mode' 参数,执行不同的操作 + if args.mode == "bias_addr": + main_bias_addr.run(args) + elif args.mode == "wx_info": + main_wx_info.run(args) + elif args.mode == "wx_db": + main_wx_db_path.run(args) + elif args.mode == "decrypt": + main_decrypt.run(args) + elif args.mode == "parse": + main_parse_wx_db.run(args) + elif args.mode == "all": + main_all.run(args)