整体重构项目,优化代码,增加命令行统一操作
This commit is contained in:
parent
2d44da3960
commit
a63884a08b
34
.github/ISSUE_TEMPLATE/bug_report.md
vendored
34
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,34 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: 帮助定位问题所在
|
|
||||||
title: ''
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**问题描述**
|
|
||||||
请在此处提供对问题的详细描述。
|
|
||||||
|
|
||||||
**复现步骤**
|
|
||||||
请提供重现问题所需的步骤。(执行的命令)
|
|
||||||
|
|
||||||
1. 步骤 1
|
|
||||||
2. 步骤 2
|
|
||||||
3. 步骤 3
|
|
||||||
|
|
||||||
**预期行为**
|
|
||||||
请清楚地描述您预期的行为。
|
|
||||||
|
|
||||||
**实际行为**
|
|
||||||
请描述实际的行为和问题出现的地方。
|
|
||||||
|
|
||||||
**环境信息**
|
|
||||||
- 操作系统版本:
|
|
||||||
- python版本:
|
|
||||||
- 微信版本:
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**其他信息**
|
|
||||||
请提供任何与问题相关的其他信息(文字,截图等)。
|
|
253
README.md
253
README.md
@ -1,8 +1,13 @@
|
|||||||
# <center>PyWxDump</center>
|
# <center>PyWxDump</center>
|
||||||
|
|
||||||
* 更新日志(发现[version_list.json](./Program/version_list.json)
|
[](https://www.python.org/)
|
||||||
|
[](https://github.com/xaoyaoo/PyWxDump)
|
||||||
|
|
||||||
|
* 更新日志(发现[version_list.json](app/version_list.json)
|
||||||
缺失或错误,请提交[issues](https://github.com/xaoyaoo/PyWxDump/issues)):
|
缺失或错误,请提交[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 获取key基址偏移可以根据微信文件夹获取,不需要输入key
|
||||||
* 2023.10.09 优化代码,删减没必要代码,重新修改获取基址代码,加快运行速度(需要安装新的库 pymem)
|
* 2023.10.09 优化代码,删减没必要代码,重新修改获取基址代码,加快运行速度(需要安装新的库 pymem)
|
||||||
* 2023.10.07 修改获取基址内存搜索方式,防止进入死循环
|
* 2023.10.07 修改获取基址内存搜索方式,防止进入死循环
|
||||||
@ -11,7 +16,7 @@
|
|||||||
* 2023.09.28 增加了数据库部分解析
|
* 2023.09.28 增加了数据库部分解析
|
||||||
* 2023.09.15 增加了3.9.7.25版本的偏移地址
|
* 2023.09.15 增加了3.9.7.25版本的偏移地址
|
||||||
|
|
||||||
## 一、项目介绍
|
# 一、项目介绍
|
||||||
|
|
||||||
本项目可以获取微信基本信息,以及key,通过key可以解密微信数据库,获取聊天记录,好友信息,群信息等。
|
本项目可以获取微信基本信息,以及key,通过key可以解密微信数据库,获取聊天记录,好友信息,群信息等。
|
||||||
|
|
||||||
@ -21,9 +26,34 @@
|
|||||||
超级想要star,走过路过,帮忙点个[](https://github.com/xaoyaoo/PyWxDump/)
|
超级想要star,走过路过,帮忙点个[](https://github.com/xaoyaoo/PyWxDump/)
|
||||||
呗,谢谢啦~</big></strong>
|
呗,谢谢啦~</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
|
```shell script
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
@ -35,166 +65,87 @@ pip install -r requirements.txt
|
|||||||
2. 如果运行报错,请检查python版本,本项目使用的是python3.10
|
2. 如果运行报错,请检查python版本,本项目使用的是python3.10
|
||||||
3. 安装pycryptodome时可能会报错,可以使用下面的命令安装,自行搜索解决方案(该包为解密的核心包)
|
3. 安装pycryptodome时可能会报错,可以使用下面的命令安装,自行搜索解决方案(该包为解密的核心包)
|
||||||
|
|
||||||
### 2. 获取微信基本信息
|
## 2. 使用方法
|
||||||
|
|
||||||
获取微信的信息,获取到几个,取决于现在登录的几个微信。
|
### 2.1 命令行
|
||||||
|
|
||||||
**2.1 shell获取微信基本信息**
|
|
||||||
|
|
||||||
```shell script
|
```shell script
|
||||||
cd Program
|
python main.py [模式] [参数]
|
||||||
python get_wx_info.py
|
# 运行模式(mode):
|
||||||
|
# bias_addr 获取微信基址偏移
|
||||||
|
# wx_info 获取微信信息
|
||||||
|
# wx_db 获取微信文件夹路径
|
||||||
|
# decrypt 解密微信数据库
|
||||||
|
# analyse 解析微信数据库(未完成)
|
||||||
|
# all 执行所有操作(除获取基址偏移、Analyse)
|
||||||
```
|
```
|
||||||
|
|
||||||
结果
|
*示例*
|
||||||
|
|
||||||
|
以下是示例命令:
|
||||||
|
|
||||||
```shell script
|
```shell script
|
||||||
[+] pid: 2365 <!--进程ID-->
|
python main.py bias_addr -h
|
||||||
[+] version: *.*.*.* <!--微信版本-->
|
#usage: main.py bias_addr [-h] --mobile MOBILE --name NAME --account ACCOUNT [--key KEY] [--db_path DB_PATH] [-vlp VLP]
|
||||||
[+] key: ******************************************d <!--数据库密钥-->
|
#options:
|
||||||
[+] name: ***** <!--昵称-->
|
# -h, --help show this help message and exit
|
||||||
[+] account: ******** <!--账号-->
|
# --mobile MOBILE 手机号
|
||||||
[+] mobile: ****** <!--手机号-->
|
# --name NAME 微信昵称
|
||||||
[+] mail: ***** <!--邮箱:在微信3.7版本以上无效-->
|
# --account ACCOUNT 微信账号
|
||||||
========================================
|
# --key KEY (与db_path二选一)密钥
|
||||||
[+] pid: 2365
|
# --db_path DB_PATH (与key二选一)已登录账号的微信文件夹路径
|
||||||
[+] version: *.*.*.*
|
# -vlp VLP (可选)微信版本偏移文件路径
|
||||||
[+] key: ******************************************d
|
|
||||||
[+] name: *****
|
python main.py wx_info -h
|
||||||
[+] account: ********
|
#usage: main.py wx_info [-h] [-vlp VLP]
|
||||||
[+] mobile: ******
|
#options:
|
||||||
[+] mail: *****
|
# -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
|
```python
|
||||||
import json
|
from app import *
|
||||||
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
|
* 关于基址使用cheat engine获取,参考[CE获取基址.md](doc/CE获取基址.md)
|
||||||
[
|
* 关于数据库解析,参考[wx数据库简述.md](doc/wx数据库简述.md)
|
||||||
{
|
* 关于更多使用方法,以及各个模块的使用方法,参考前一版本的[python1.0_README.md](doc/python1.0_README.md)
|
||||||
'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都会变化
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
* 解密后可拖入数据库工具查找敏感信息
|
|
||||||
* 还有一份数据的说明文档,但是我累了,不想写了
|
|
||||||
|
|
||||||
**方法**
|
|
||||||
|
|
||||||
进入目录[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. 支持微信多开场景,获取多用户信息等
|
1. 支持微信多开场景,获取多用户信息等
|
||||||
2. 微信需要登录状态才能获取数据库密钥
|
2. 微信需要登录状态才能获取数据库密钥
|
||||||
@ -212,7 +163,7 @@ python get_wx_decrypted_db.py --key ********
|
|||||||
4. 自行备份(日常备份自己留存)
|
4. 自行备份(日常备份自己留存)
|
||||||
5. 等等...............
|
5. 等等...............
|
||||||
|
|
||||||
## 六、免责声明(非常重要!!!!!!!)
|
## 四、免责声明(非常重要!!!!!!!)
|
||||||
|
|
||||||
本项目仅允许在授权情况下对数据库进行备份,严禁用于非法目的,否则自行承担所有相关责任。使用该工具则代表默认同意该条款;
|
本项目仅允许在授权情况下对数据库进行备份,严禁用于非法目的,否则自行承担所有相关责任。使用该工具则代表默认同意该条款;
|
||||||
|
|
||||||
|
@ -3,9 +3,9 @@
|
|||||||
# Name: __init__.py.py
|
# Name: __init__.py.py
|
||||||
# Description:
|
# Description:
|
||||||
# Author: xaoyaoo
|
# Author: xaoyaoo
|
||||||
# Date: 2023/08/21
|
# Date: 2023/10/14
|
||||||
# -------------------------------------------------------------------------------
|
# -------------------------------------------------------------------------------
|
||||||
|
from .bias_addr import *
|
||||||
|
from .wx_info import *
|
||||||
if __name__ == '__main__':
|
from .decrypted import *
|
||||||
pass
|
from .analyse import *
|
8
app/analyse/__init__.py
Normal file
8
app/analyse/__init__.py
Normal 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
|
@ -3,9 +3,6 @@
|
|||||||
# Name: __init__.py.py
|
# Name: __init__.py.py
|
||||||
# Description:
|
# Description:
|
||||||
# Author: xaoyaoo
|
# Author: xaoyaoo
|
||||||
# Date: 2023/09/27
|
# Date: 2023/10/14
|
||||||
# -------------------------------------------------------------------------------
|
# -------------------------------------------------------------------------------
|
||||||
|
from .get_bias_addr import BiasAddr
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
pass
|
|
@ -42,7 +42,7 @@ def validate_key(key, salt, first, mac_salt):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class BaseAddr:
|
class BiasAddr:
|
||||||
def __init__(self, account, mobile, name, key, db_path):
|
def __init__(self, account, mobile, name, key, db_path):
|
||||||
self.account = account.encode("utf-8")
|
self.account = account.encode("utf-8")
|
||||||
self.mobile = mobile.encode("utf-8")
|
self.mobile = mobile.encode("utf-8")
|
||||||
@ -201,22 +201,22 @@ class BaseAddr:
|
|||||||
mobile_bias = self.search_memory_value(self.mobile)
|
mobile_bias = self.search_memory_value(self.mobile)
|
||||||
name_bias = self.search_memory_value(self.name)
|
name_bias = self.search_memory_value(self.name)
|
||||||
account_bias = self.search_memory_value(self.account)
|
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:
|
if self.key:
|
||||||
key_bias = self.search_key(self.key)
|
key_bias = self.search_key(self.key)
|
||||||
elif self.db_path:
|
elif self.db_path:
|
||||||
key_bias = self.get_key_bias(self.db_path, account_bias)
|
key_bias = self.get_key_bias(self.db_path, account_bias)
|
||||||
else:
|
else:
|
||||||
key_bias = 0
|
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__':
|
if __name__ == '__main__':
|
||||||
# 创建命令行参数解析器
|
# 创建命令行参数解析器
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument("--mobile", type=str, help="手机号")
|
parser.add_argument("--mobile", type=str, help="手机号", required=True)
|
||||||
parser.add_argument("--name", type=str, help="微信昵称")
|
parser.add_argument("--name", type=str, help="微信昵称", required=True)
|
||||||
parser.add_argument("--account", type=str, help="微信账号")
|
parser.add_argument("--account", type=str, help="微信账号", required=True)
|
||||||
parser.add_argument("--key", type=str, help="密钥")
|
parser.add_argument("--key", type=str, help="密钥")
|
||||||
parser.add_argument("--db_path", type=str, help="微信文件夹(已经登录微信)路径")
|
parser.add_argument("--db_path", type=str, help="微信文件夹(已经登录微信)路径")
|
||||||
|
|
||||||
@ -232,16 +232,16 @@ if __name__ == '__main__':
|
|||||||
mobile = args.mobile
|
mobile = args.mobile
|
||||||
name = args.name
|
name = args.name
|
||||||
account = args.account
|
account = args.account
|
||||||
key = None # args.key
|
key = args.key
|
||||||
db_path = args.db_path
|
db_path = args.db_path
|
||||||
|
|
||||||
# 调用 run 函数,并传入参数
|
# 调用 run 函数,并传入参数
|
||||||
rdata = BaseAddr(account, mobile, name, key, db_path).run()
|
rdata = BiasAddr(account, mobile, name, key, db_path).run()
|
||||||
print(rdata)
|
print(rdata)
|
||||||
|
|
||||||
# 添加到version_list.json
|
# 添加到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 = json.load(f)
|
||||||
data.update(rdata)
|
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)
|
json.dump(data, f, ensure_ascii=False, indent=4)
|
9
app/decrypted/__init__.py
Normal file
9
app/decrypted/__init__.py
Normal 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
137
app/decrypted/decrypt.py
Normal 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]}"')
|
@ -248,12 +248,26 @@ def merge_media_msg_db(db_path: list, save_path: str):
|
|||||||
return save_path
|
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")
|
decrypted_ROOT = os.path.join(os.getcwd(), "decrypted")
|
||||||
|
|
||||||
if keys is None:
|
if keys is None:
|
||||||
print("keys is None")
|
print("keys is None")
|
||||||
return False
|
exit(0)
|
||||||
if isinstance(keys, str):
|
if isinstance(keys, str):
|
||||||
keys = [keys]
|
keys = [keys]
|
||||||
|
|
||||||
@ -293,22 +307,3 @@ def main(keys=None):
|
|||||||
|
|
||||||
shutil.rmtree(decrypted_path_tmp) # 删除临时文件
|
shutil.rmtree(decrypted_path_tmp) # 删除临时文件
|
||||||
print(f"解密完成:{user}, {decrypted_path}")
|
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)
|
|
@ -319,7 +319,6 @@
|
|||||||
63488320,
|
63488320,
|
||||||
63486792,
|
63486792,
|
||||||
0,
|
0,
|
||||||
63488256,
|
63488256
|
||||||
56006136
|
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -5,7 +5,5 @@
|
|||||||
# Author: xaoyaoo
|
# Author: xaoyaoo
|
||||||
# Date: 2023/08/21
|
# Date: 2023/08/21
|
||||||
# -------------------------------------------------------------------------------
|
# -------------------------------------------------------------------------------
|
||||||
|
from .get_wx_info import read_info
|
||||||
|
from .get_wx_db import get_wechat_db
|
||||||
if __name__ == '__main__':
|
|
||||||
pass
|
|
75
app/wx_info/get_wx_db.py
Normal file
75
app/wx_info/get_wx_db.py
Normal 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}")
|
@ -84,7 +84,7 @@ def read_info(version_list):
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
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) # 读取微信信息
|
result = read_info(version_list) # 读取微信信息
|
||||||
|
|
||||||
print("=" * 32)
|
print("=" * 32)
|
@ -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
219
doc/python1.0_README.md
Normal 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,走过路过,帮忙点个[](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都会变化
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
* 解密后可拖入数据库工具查找敏感信息
|
||||||
|
* 还有一份数据的说明文档,但是我累了,不想写了
|
||||||
|
|
||||||
|
**方法**
|
||||||
|
|
||||||
|
进入目录[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
250
main.py
Normal 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)
|
Loading…
Reference in New Issue
Block a user