整体重构项目,优化代码,增加命令行统一操作

This commit is contained in:
xaoyo 2023-10-14 21:48:35 +08:00
parent 2d44da3960
commit a63884a08b
19 changed files with 837 additions and 332 deletions

View File

@ -1,34 +0,0 @@
---
name: Bug report
about: 帮助定位问题所在
title: ''
labels: ''
assignees: ''
---
**问题描述**
请在此处提供对问题的详细描述。
**复现步骤**
请提供重现问题所需的步骤。(执行的命令)
1. 步骤 1
2. 步骤 2
3. 步骤 3
**预期行为**
请清楚地描述您预期的行为。
**实际行为**
请描述实际的行为和问题出现的地方。
**环境信息**
- 操作系统版本:
- python版本
- 微信版本:
**其他信息**
请提供任何与问题相关的其他信息(文字,截图等)。

253
README.md
View File

@ -1,8 +1,13 @@
# <center>PyWxDump</center>
* 更新日志(发现[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/)
呗,谢谢啦~</big></strong>
## 二、使用方法
**目录结构**
### 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 <!--进程ID-->
[+] version: *.*.*.* <!--微信版本-->
[+] key: ******************************************d <!--数据库密钥-->
[+] name: ***** <!--昵称-->
[+] account: ******** <!--账号-->
[+] mobile: ****** <!--手机号-->
[+] mail: ***** <!--邮箱在微信3.7版本以上无效-->
========================================
[+] 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. 等等...............
## 、免责声明(非常重要!!!!!!!)
## 、免责声明(非常重要!!!!!!!)
本项目仅允许在授权情况下对数据库进行备份,严禁用于非法目的,否则自行承担所有相关责任。使用该工具则代表默认同意该条款;

View File

@ -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 *

8
app/analyse/__init__.py Normal file
View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

137
app/decrypted/decrypt.py Normal file
View File

@ -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]}"')

View File

@ -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)

View File

@ -319,7 +319,6 @@
63488320,
63486792,
0,
63488256,
56006136
63488256
]
}

View File

@ -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

75
app/wx_info/get_wx_db.py Normal file
View File

@ -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}")

View File

@ -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)

View File

@ -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!")

219
doc/python1.0_README.md Normal file
View File

@ -0,0 +1,219 @@
# <center>PyWxDump</center>
* 更新日志(发现[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语言版本同时添加了一些新的功能。
<strong><big>
超级想要star走过路过帮忙点个[![Star](https://img.shields.io/github/stars/xaoyaoo/PyWxDump.svg?style=social&label=Star)](https://github.com/xaoyaoo/PyWxDump/)
呗,谢谢啦~</big></strong>
## 二、使用方法
### 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 <!--进程ID-->
[+] version: *.*.*.* <!--微信版本-->
[+] key: ******************************************d <!--数据库密钥-->
[+] name: ***** <!--昵称-->
[+] account: ******** <!--账号-->
[+] mobile: ****** <!--手机号-->
[+] mail: ***** <!--邮箱在微信3.7版本以上无效-->
========================================
[+] 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. 等等...............
## 六、免责声明(非常重要!!!!!!!)
本项目仅允许在授权情况下对数据库进行备份,严禁用于非法目的,否则自行承担所有相关责任。使用该工具则代表默认同意该条款;
请勿利用本项目的相关技术从事非法测试,如因此产生的一切不良后果与项目作者无关。

250
main.py Normal file
View File

@ -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)