整体重构项目,优化代码,增加命令行统一操作
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>
|
||||
|
||||
* 更新日志(发现[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)):
|
||||
* 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,走过路过,帮忙点个[](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都会变化
|
||||
|
||||

|
||||
|
||||
* 解密后可拖入数据库工具查找敏感信息
|
||||
* 还有一份数据的说明文档,但是我累了,不想写了
|
||||
|
||||
**方法**
|
||||
|
||||
进入目录[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. 等等...............
|
||||
|
||||
## 六、免责声明(非常重要!!!!!!!)
|
||||
## 四、免责声明(非常重要!!!!!!!)
|
||||
|
||||
本项目仅允许在授权情况下对数据库进行备份,严禁用于非法目的,否则自行承担所有相关责任。使用该工具则代表默认同意该条款;
|
||||
|
||||
|
@ -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
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
|
||||
# Description:
|
||||
# Author: xaoyaoo
|
||||
# Date: 2023/09/27
|
||||
# Date: 2023/10/14
|
||||
# -------------------------------------------------------------------------------
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
from .get_bias_addr import BiasAddr
|
@ -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)
|
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
|
||||
|
||||
|
||||
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)
|
@ -319,7 +319,6 @@
|
||||
63488320,
|
||||
63486792,
|
||||
0,
|
||||
63488256,
|
||||
56006136
|
||||
63488256
|
||||
]
|
||||
}
|
@ -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
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__":
|
||||
# 读取微信各版本偏移
|
||||
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)
|
@ -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