修复无法获取wxid的bug,更新部分逻辑,重构解密脚本的返回值,重构命令行参数

This commit is contained in:
xaoyo 2023-11-15 15:04:45 +08:00
parent bfbf488fff
commit 54a417be60
11 changed files with 389 additions and 400 deletions

112
README.md
View File

@ -16,6 +16,7 @@
<details>
<summary><strong>更新日志(点击展开)</strong></summary>
* 2023.11.15 修复无法获取wxid的bug,更新部分逻辑,重构解密脚本的返回值,重构命令行参数
* 2023.11.14 修复部分bug
* 2023.11.11 添加聊天记录解析,查看工具,修复部分bug
* 2023.11.10 修复wxdump wx_db命令行参数错误 [#19](https://github.com/xaoyaoo/PyWxDump/issues/19)
@ -97,7 +98,8 @@ PyWxDump
## 4. 其他
PyWxDump是[SharpWxDump](https://github.com/AdminTest0/SharpWxDump)的经过重构python语言版本同时添加了一些新的功能。
[PyWxDump](https://github.com/xaoyaoo/PyWxDump)是[SharpWxDump](https://github.com/AdminTest0/SharpWxDump)
的经过重构python语言版本同时添加了一些新的功能。
* 如发现[version_list.json](pywxdump/version_list.json)缺失或错误,
请提交[issues](https://github.com/xaoyaoo/PyWxDump/issues).
@ -146,13 +148,12 @@ python -m pip install -U .
```shell script
wxdump 模式 [参数]
# 运行模式(mode):
# bias_addr 获取微信基址偏移
# wx_info 获取微信信息
# wx_db 获取微信文件夹路径
# decrypt 解密微信数据库
# show_records 显示聊天记录[需要安装flask]
# analyse 解析微信数据库(未完成)
# all 执行所有操作(除获取基址偏移、解密所有已经登陆的数据库)
# bias 获取微信基址偏移
# info 获取微信信息
# db_path 获取微信文件夹路径
# decrypt 解密微信数据库
# dbshow 聊天记录查看[需要安装flask]
# all 获取微信信息,解密微信数据库,查看聊天记录
```
*示例*
@ -160,55 +161,51 @@ wxdump 模式 [参数]
以下是示例命令:
```shell script
wxdump bias_addr -h
wxdump bias -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 (可选)已登录账号的微信文件夹路径
# -vlp VLP (可选)微信版本偏移文件路径
# -h, --help show this help message and exit
# --mobile MOBILE 手机号
# --name NAME 微信昵称
# --account ACCOUNT 微信账号
# --key KEY (可选)密钥
# --db_path DB_PATH (可选)已登录账号的微信文件夹路径
# -vlp VERSION_LIST_PATH, --version_list_path VERSION_LIST_PATH
# (可选)微信版本偏移文件路径,如有,则自动更新
wxdump wx_info -h
wxdump info -h
#usage: main.py wx_info [-h] [-vlp VLP]
#options:
# -h, --help show this help message and exit
# -vlp VLP (可选)微信版本偏移文件路径
wxdump wx_db -h
wxdump db_path -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'路径
# -r , --require_list (可选)需要的数据库名称(eg: -r MediaMSG;MicroMsg;FTSMSG;MSG;Sns;Emotion )
# -wf , --wx_files (可选)'WeChat Files'路径
# -id WXID, --wxid WXID
# (可选)wxid_,用于确认用户文件夹
wxdump 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}
# -h, --help show this help message and exit
# -k , --key 密钥
# -i , --db_path 数据库路径(目录or文件)
# -o , --out_path 输出路径(必须是目录)[默认为当前路径下decrypted文件夹]
wxdump show_records -h
wxdump dbshow -h
#usage: wxdump show_records [-h] -msg -micro -media -fs
#options:
# -h, --help show this help message and exit
# -msg , --msg_path 解密后的 MSG.db 的路径
# -micro , --micro_path 解密后的 MicroMsg.db 的路径
# -media , --media_path 解密后的 MediaMSG.db 的路径
# -fs , --filestorage_path 文件夹FileStorage的路径
wxdump analyse -h
#usage: main.py analyse [-h] [--arg ARG]
#options:
# -h, --help show this help message and exit
# --arg ARG 参数
# -msg , --msg_path 解密后的 MSG.db 的路径
# -micro , --micro_path
# 解密后的 MicroMsg.db 的路径
# -media , --media_path
# 解密后的 MediaMSG.db 的路径
# -fs , --filestorage_path
# (可选)文件夹FileStorage的路径用于显示图片
wxdump all -h
#usage: main.py all [-h]
@ -243,10 +240,35 @@ from pywxdump.decrypted import batch_decrypt
batch_decrypt("key", "db_path", "out_path")
# 5. 解析数据库
from pywxdump.analyse import read_img_dat, read_emoji, decompress_CompressContent, read_audio_buf, read_audio
```
pass
### 2.3 构建可执行文件exe
将下面的代码保存为`build.py`,然后运行`python build.py`即可。(或者执行[build_exe.py](./tests/build_exe.py)
```python
import site
import os
code = """from pywxdump.command import console_run;console_run()"""
# 创建文件夹
os.makedirs("dist", exist_ok=True)
# 将代码写入文件
with open("dist/tmp.py", "w", encoding="utf-8") as f:
f.write(code)
# 获取安装包的路径
package_path = site.getsitepackages()
if package_path:
package_path = package_path[1] # 假设取第一个安装包的路径
version_list_path = os.path.join(package_path, 'pywxdump', 'version_list.json')
# 执行打包命令
cmd = f'pyinstaller --onefile --clean --add-data "{version_list_path};pywxdump" dist/tmp.py'
print(cmd)
os.system(cmd)
else:
print("未找到安装包路径")
```
【注】:
@ -273,6 +295,8 @@ pass
4. 自行备份(日常备份自己留存)
5. 等等...............
[![Star History Chart](https://api.star-history.com/svg?repos=xaoyaoo/pywxdump&type=Date)](https://star-history.com/#xaoyaoo/pywxdump&Date)
# 四、免责声明(非常重要!!!!!!!)
本项目仅允许在授权情况下对数据库进行备份,严禁用于非法目的,否则自行承担所有相关责任。使用该工具则代表默认同意该条款;
@ -281,7 +305,6 @@ pass
# 五、许可证
```text
MIT License
@ -306,4 +329,5 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
```

View File

@ -51,19 +51,18 @@ KEY的基址即**2FFF970-000024=2FFF94C**
十进制地址为50329932
代码块中的五个十进制按顺序代表微信昵称、微信账号、微信手机号、微信邮箱高版本失效这个随便填、微信KEY、微信原始IDwxid_******
代码块中的五个十进制按顺序代表微信昵称、微信账号、微信手机号、微信邮箱高版本失效这个随便填、微信KEY
```jsx
```json
{
"微信版本号",
new List<int>
{
"微信版本号":
[
50320784,
50321712,
50320640,
38986104,
50321676
}
]
}
```

View File

@ -138,49 +138,6 @@ class BiasAddr:
return keyWinAddr - module.lpBaseOfDll
def get_wxid_bias(self):
byteLen = self.address_len # 4 if self.bits == 32 else 8 # 4字节或8字节
keyLenOffset = 0x8c if self.bits == 32 else 0xd0
keyWindllOffset = 0x90 if self.bits == 32 else 0xd8
pm = self.pm
module = pymem.process.module_from_name(pm.process_handle, "WeChatWin.dll")
keyBytes = b'wxid_'
publicWxidList = pymem.pattern.pattern_scan_all(self.pm.process_handle, keyBytes, return_multiple=True)
import ahocorasick
def search_substrings(text, substrings):
A = ahocorasick.Automaton()
for index, s in enumerate(substrings):
A.add_word(s, (index, s))
A.make_automaton()
results = []
for end_index, (insert_order, original_value) in A.iter(text):
start_index = end_index - len(original_value) + 1
results.append(int(start_index / 2))
return results
patterns = []
for addr in publicWxidList:
keyBytes = addr.to_bytes(byteLen, byteorder="little", signed=True) # 低位在前
patterns.append(keyBytes.hex())
text = pm.read_bytes(module.lpBaseOfDll, module.SizeOfImage).hex()
wxidaddrs = search_substrings(text, patterns)
# print("wxidaddrs", wxidaddrs)
# wxidaddr = 0
# for addr in wxidaddrs:
# print(addr - 63488256)
# wxidaddr = int.from_bytes(pm.read_bytes(addr + module.lpBaseOfDll, byteLen), byteorder='little')
# print("wxidaddr", hex(wxidaddr))
# wxid = pm.read_bytes(wxidaddr, 24).split(b"\x00")[0]
# print("wxid", wxid)
return wxidaddrs[-2]
def get_key_bias(self, wx_db_path, account_bias=0):
wx_db_path = os.path.join(wx_db_path, "Msg", "MicroMsg.db")
if not os.path.exists(wx_db_path):
@ -257,20 +214,17 @@ class BiasAddr:
key, bais = verify_key(maybe_key, wx_db_path)
return bais
def run(self):
def run(self, is_logging=False, version_list_path=None):
self.version = self.get_file_version(self.process_name)
if not self.islogin:
return "[-] WeChat No Run"
error = "[-] WeChat No Run"
if is_logging: print(error)
return error
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"))
try:
wxid_bias = self.get_wxid_bias()
except:
wxid_bias = 0
try:
key_bias = self.get_key_bias_test()
except:
@ -283,7 +237,17 @@ class BiasAddr:
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, wxid_bias]}
rdata = {self.version: [name_bias, account_bias, mobile_bias, 0, key_bias]}
if version_list_path and os.path.exists(version_list_path):
with open(version_list_path, "r", encoding="utf-8") as f:
data = json.load(f)
data.update(rdata)
with open(version_list_path, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=4)
if is_logging:
print("{版本号:昵称,账号,手机号,邮箱,KEY}")
print(rdata)
return rdata
if __name__ == '__main__':
@ -310,13 +274,4 @@ if __name__ == '__main__':
db_path = args.db_path
# 调用 run 函数,并传入参数
rdata = BiasAddr(account, mobile, name, key, db_path).run()
print("{版本:昵称,账号,手机号,邮箱,KEY,原始ID(wxid_******)}")
print(rdata)
# 添加到version_list.json
with open("../version_list.json", "r", encoding="utf-8") as f:
data = json.load(f)
data.update(rdata)
with open("../version_list.json", "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=4)
rdata = BiasAddr(account, mobile, name, key, db_path).run(True, "../version_list.json")

View File

@ -17,14 +17,16 @@ from . import *
class MainBiasAddr():
def init_parses(self, parser):
self.mode = "bias"
# 添加 'bias_addr' 子命令解析器
sb_bias_addr = parser.add_parser("bias_addr", help="获取微信基址偏移")
sb_bias_addr = parser.add_parser(self.mode, 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="(可选)密钥")
sb_bias_addr.add_argument("--db_path", type=str, help="(可选)已登录账号的微信文件夹路径")
sb_bias_addr.add_argument("-vlp", type=str, help="(可选)微信版本偏移文件路径,如有,则自动更新",
sb_bias_addr.add_argument("-vlp", '--version_list_path', type=str,
help="(可选)微信版本偏移文件路径,如有,则自动更新",
default=None)
self.sb_bias_addr = sb_bias_addr
return sb_bias_addr
@ -40,89 +42,61 @@ class MainBiasAddr():
account = args.account
key = args.key
db_path = args.db_path
vlp = args.vlp
vlp = args.version_list_path
# 调用 run 函数,并传入参数
rdata = BiasAddr(account, mobile, name, key, db_path).run()
print("{版本:微信昵称,微信账号,微信手机号,微信邮箱,微信KEY,微信原始ID(wxid_******)}")
print(rdata)
if vlp is not None:
# 添加到version_list.json
version_list = json.load(open(vlp, "r", encoding="utf-8"))
version_list.update(rdata)
json.dump(version_list, open(vlp, "w", encoding="utf-8"), ensure_ascii=False, indent=4)
rdata = BiasAddr(account, mobile, name, key, db_path).run(True, vlp)
return rdata
class MainWxInfo():
def init_parses(self, parser):
self.mode = "info"
# 添加 'wx_info' 子命令解析器
sb_wx_info = parser.add_parser("wx_info", help="获取微信信息")
sb_wx_info = parser.add_parser(self.mode, help="获取微信信息")
sb_wx_info.add_argument("-vlp", type=str, help="(可选)微信版本偏移文件路径", default=VERSION_LIST_PATH)
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)
path = args.vlp
version_list = json.load(open(path, "r", encoding="utf-8"))
result = read_info(version_list, True) # 读取微信信息
return result
class MainWxDbPath():
def init_parses(self, parser):
self.mode = "db_path"
# 添加 'wx_db_path' 子命令解析器
sb_wx_db_path = parser.add_parser("wx_db", help="获取微信文件夹路径")
sb_wx_db_path = parser.add_parser(self.mode, help="获取微信文件夹路径")
sb_wx_db_path.add_argument("-r", "--require_list", type=str,
help="(可选)需要的数据库名称(eg: -r MediaMSG;MicroMsg;FTSMSG;MSG;Sns;Emotion )",
default="all", metavar="")
sb_wx_db_path.add_argument("-wf", "--wx_files", type=str, help="(可选)'WeChat Files'路径", default=None,
metavar="")
sb_wx_db_path.add_argument("-id", "--wxid", type=str, help="(可选)wxid_,用于确认用户文件夹",
default=None, metavar="")
return sb_wx_db_path
def run(self, args):
# 从命令行参数获取值
require_list = args.require_list
msg_dir = args.wx_files
wxid = args.wxid
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)} 个微信账号")
user_dirs = get_wechat_db(require_list, msg_dir, wxid, True) # 获取微信数据库路径
return user_dirs
class MainDecrypt():
def init_parses(self, parser):
self.mode = "decrypt"
# 添加 'decrypt' 子命令解析器
sb_decrypt = parser.add_parser("decrypt", help="解密微信数据库")
sb_decrypt = parser.add_parser(self.mode, help="解密微信数据库")
sb_decrypt.add_argument("-k", "--key", type=str, help="密钥", required=True, metavar="")
sb_decrypt.add_argument("-i", "--db_path", type=str, help="数据库路径(目录or文件)", required=True, metavar="")
sb_decrypt.add_argument("-o", "--out_path", type=str,
help="输出路径(必须是目录),输出文件为 out_path/de_{original_name}", required=True,
sb_decrypt.add_argument("-o", "--out_path", type=str, default=os.path.join(os.getcwd(), "decrypted"),
help="输出路径(必须是目录)[默认为当前路径下decrypted文件夹]", required=False,
metavar="")
return sb_decrypt
@ -132,29 +106,32 @@ class MainDecrypt():
db_path = args.db_path
out_path = args.out_path
if not os.path.exists(db_path):
print("[-] 数据库路径不存在")
return
if not os.path.exists(out_path):
os.makedirs(out_path)
print(f"[+] 创建输出文件夹:{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)
result = batch_decrypt(key, db_path, out_path, True)
return result
class MainShowChatRecords():
def init_parses(self, parser):
self.mode = "dbshow"
# 添加 'decrypt' 子命令解析器
sb_decrypt = parser.add_parser("show_records", help="聊天记录查看[需要安装flask]")
sb_decrypt = parser.add_parser(self.mode, help="聊天记录查看[需要安装flask]")
sb_decrypt.add_argument("-msg", "--msg_path", type=str, help="解密后的 MSG.db 的路径", required=True,
metavar="")
sb_decrypt.add_argument("-micro", "--micro_path", type=str, help="解密后的 MicroMsg.db 的路径", required=True,
metavar="")
sb_decrypt.add_argument("-media", "--media_path", type=str, help="解密后的 MediaMSG.db 的路径", required=True,
metavar="")
sb_decrypt.add_argument("-fs", "--filestorage_path", type=str, help="文件夹FileStorage的路径", required=True,
sb_decrypt.add_argument("-fs", "--filestorage_path", type=str,
help="(可选)文件夹FileStorage的路径用于显示图片", required=False,
metavar="")
return sb_decrypt
@ -162,16 +139,26 @@ class MainShowChatRecords():
# 从命令行参数获取值
try:
from flask import Flask, request, jsonify, render_template, g
import logging
from .show_chat.main_window import app_show_chat, get_user_list
except Exception as e:
print(e)
print("[-] 请安装flask( pip install flask )")
return
if not os.path.exists(args.msg_path) or not os.path.exists(args.micro_path) or not os.path.exists(
args.media_path):
print(os.path.exists(args.msg_path), os.path.exists(args.micro_path), os.path.exists(args.media_path))
print("[-] 输入数据库路径不存在")
return
app = Flask(__name__, template_folder='./show_chat/templates')
app.logger.setLevel(logging.ERROR)
@app.before_request
def before_request():
g.MSG_ALL_db_path = args.msg_path
g.MicroMsg_db_path = args.micro_path
g.MediaMSG_all_db_path = args.media_path
@ -180,77 +167,96 @@ class MainShowChatRecords():
app.register_blueprint(app_show_chat)
app.run()
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"解析微信数据库(未完成)")
print("[+] 请使用浏览器访问 http://127.0.0.1:5000/ 查看聊天记录")
app.run(debug=False)
class MainAll():
def init_parses(self, parser):
self.mode = "all"
# 添加 'all' 子命令解析器
sb_all = parser.add_parser("all", help="执行所有操作(除获取基址偏移、Analyse)")
sb_all = parser.add_parser(self.mode, help="获取微信信息,解密微信数据库,查看聊天记录")
return sb_all
def run(self, args):
# 获取微信信息
args.vlp = VERSION_LIST_PATH
result_WxInfo = MainWxInfo().run(args)
keys = [i.get('key', "") for i in result_WxInfo]
if not keys:
print("[-] 未获取到密钥")
return
wxids = [i.get('wxid', "") for i in result_WxInfo]
WxInfo = read_info(VERSION_LIST, True)
args.require_list = 'all'
args.wx_files = 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)
for user in WxInfo:
key = user.get("key", "")
if not key:
print("[-] 未获取到密钥")
return
wxid = user.get("wxid", None)
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)
WxDbPath = get_wechat_db('all', None, wxid=wxid, is_logging=True) # 获取微信数据库路径
rd = {}
for key in keys:
rd[key] = batch_decrypt(key, wxdbpaths, out_path)
wxdbpaths = [path for user_dir in WxDbPath.values() for paths in user_dir.values() for path in paths]
if len(wxdbpaths) == 0:
print("[-] 未获取到数据库路径")
return
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
wxdblen = len(wxdbpaths)
print(f"[+] 共发现 {wxdblen} 个微信数据库")
print("=" * 32)
out_path = os.path.join(os.getcwd(), "decrypted", wxid) if wxid else os.path.join(os.getcwd(), "decrypted")
print(f"[*] 解密后文件夹:{out_path} ")
print(f"[*] 解密中...(用时较久,耐心等待)")
if not os.path.exists(out_path):
os.makedirs(out_path)
# 判断out_path是否为空目录
if os.listdir(out_path):
isdel = input(f"[*] 输出文件夹不为空({out_path})\n 是否删除?(y/n):")
if isdel.lower() == 'y' or isdel.lower() == 'yes':
for root, dirs, files in os.walk(out_path, topdown=False):
for name in files:
os.remove(os.path.join(root, name))
for name in dirs:
os.rmdir(os.path.join(root, name))
# 调用 decrypt 函数,并传入参数 # 解密
code, ret = batch_decrypt(key, wxdbpaths, out_path, False)
if not code:
print(ret)
return
print("[+] 解密完成")
print("-" * 32)
errors = []
out_dbs = []
for code1, ret1 in ret:
if code1 == False:
errors.append(ret1)
else:
result_Decrypt[i] = v[i]
print(
f'[+] success "{os.path.relpath(ret1[0], os.path.commonprefix(wxdbpaths))}" -> "{os.path.relpath(ret1[1], os.getcwd())}"')
out_dbs.append(ret1[1])
print("-" * 32)
print("[-] " + f"{len(errors)} 个文件解密失败;")
# print("; ".join([f'"{wxdbpaths[i]}"' for i in errors]))
print("=" * 32)
print("[+] 解密完成")
print("-" * 32)
if len(out_dbs) <= 0:
print("[-] 未获取到解密后的数据库路径")
return
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)
user_path = out_dbs[0].split("MSG")
FileStorage_path = os.path.join(user_path[0], "FileStorage")
# 查看聊天记录
MSGDB = [i for i in out_dbs if "de_MSG" in i]
MSGDB = MSGDB[-1] if MSGDB else None
MicroMsgDB = [i for i in out_dbs if "de_MicroMsg" in i]
MicroMsgDB = MicroMsgDB[-1] if MicroMsgDB else None
MediaMSGDB = [i for i in out_dbs if "de_MediaMSG" in i]
MediaMSGDB = MediaMSGDB[-1] if MediaMSGDB else None
args.msg_path = MSGDB
args.micro_path = MicroMsgDB
args.media_path = MediaMSGDB
args.filestorage_path = FileStorage_path
MainShowChatRecords().run(args)
def console_run():
@ -260,53 +266,44 @@ def console_run():
# 添加子命令解析器
subparsers = parser.add_subparsers(dest="mode", help="""运行模式:""", required=True, metavar="mode")
# 添加 'bias_addr' 子命令解析器
modes = {}
# 添加 'bias' 子命令解析器
main_bias_addr = MainBiasAddr()
sb_bias_addr = main_bias_addr.init_parses(subparsers)
modes[main_bias_addr.mode] = main_bias_addr
# 添加 'wx_info' 子命令解析器
# 添加 'info' 子命令解析器
main_wx_info = MainWxInfo()
sb_wx_info = main_wx_info.init_parses(subparsers)
modes[main_wx_info.mode] = main_wx_info
# 添加 'wx_db_path' 子命令解析器
# 添加 'db_path' 子命令解析器
main_wx_db_path = MainWxDbPath()
sb_wx_db_path = main_wx_db_path.init_parses(subparsers)
modes[main_wx_db_path.mode] = main_wx_db_path
# 添加 'decrypt' 子命令解析器
main_decrypt = MainDecrypt()
sb_decrypt = main_decrypt.init_parses(subparsers)
modes[main_decrypt.mode] = main_decrypt
# 添加 'show_chat_records' 子命令解析器
# 添加 '' 子命令解析器
main_show_chat_records = MainShowChatRecords()
sb_show_chat_records = main_show_chat_records.init_parses(subparsers)
# 添加 'parse_wx_db' 子命令解析器
main_parse_wx_db = MainAnalyseWxDb()
sb_parse_wx_db = main_parse_wx_db.init_parses(subparsers)
sb_dbshow = main_show_chat_records.init_parses(subparsers)
modes[main_show_chat_records.mode] = main_show_chat_records
# 添加 'all' 子命令解析器
main_all = MainAll()
sb_all = main_all.init_parses(subparsers)
modes[main_all.mode] = main_all
args = parser.parse_args() # 解析命令行参数
if not any(vars(args).values()):
parser.print_help()
# 根据不同的 '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 == "show_chat_records":
main_show_chat_records.run(args)
elif args.mode == "parse":
main_parse_wx_db.run(args)
elif args.mode == "all":
main_all.run(args)
modes[args.mode].run(args)
if __name__ == '__main__':

View File

@ -16,12 +16,21 @@ 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!"
"""
通过密钥解密数据库
:param key: 密钥 64位16进制字符串
:param db_path: 待解密的数据库路径(必须是文件)
:param out_path: 解密后的数据库输出路径(必须是文件)
:return:
"""
if not os.path.exists(db_path) or not os.path.isfile(db_path):
return False, 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!"
return False, f"[-] out_path:'{out_path}' File not found!"
if len(key) != 64:
return f"[-] key:'{key}' Error!"
return False, f"[-] key:'{key}' Len Error!"
password = bytes.fromhex(key.strip())
with open(db_path, "rb") as file:
blist = file.read()
@ -36,7 +45,7 @@ def decrypt(key: str, db_path, out_path):
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}' )"
return False, f"[-] Key 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)]
@ -52,18 +61,22 @@ def decrypt(key: str, db_path, out_path):
decrypted = t.decrypt(i[:-48])
deFile.write(decrypted)
deFile.write(i[-48:])
return [True, db_path, out_path, key]
return True, [db_path, out_path, key]
def batch_decrypt(key: str, db_path: Union[str, List[str]], out_path: str):
def batch_decrypt(key: str, db_path: Union[str, List[str]], out_path: str, is_logging: bool = False):
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!"
error = f"[-] (key:'{key}' or out_path:'{out_path}') Error!"
if is_logging: print(error)
return False, error
process_list = []
if isinstance(db_path, str):
if not os.path.exists(db_path):
return f"[-] db_path:'{db_path}' not found!"
error = f"[-] db_path:'{db_path}' not found!"
if is_logging: print(error)
return False, error
if os.path.isfile(db_path):
inpath = db_path
@ -81,7 +94,10 @@ def batch_decrypt(key: str, db_path: Union[str, List[str]], out_path: str):
os.makedirs(os.path.dirname(outpath))
process_list.append([key, inpath, outpath])
else:
return f"[-] db_path:'{db_path}' Error "
error = f"[-] db_path:'{db_path}' Error "
if is_logging: print(error)
return False, error
elif isinstance(db_path, list):
rt_path = os.path.commonprefix(db_path)
if not os.path.exists(rt_path):
@ -89,7 +105,9 @@ def batch_decrypt(key: str, db_path: Union[str, List[str]], out_path: str):
for inpath in db_path:
if not os.path.exists(inpath):
return f"[-] db_path:'{db_path}' not found!"
erreor = f"[-] db_path:'{db_path}' not found!"
if is_logging: print(erreor)
return False, erreor
inpath = os.path.normpath(inpath)
rel = os.path.relpath(os.path.dirname(inpath), rt_path)
@ -98,7 +116,9 @@ def batch_decrypt(key: str, db_path: Union[str, List[str]], out_path: str):
os.makedirs(os.path.dirname(outpath))
process_list.append([key, inpath, outpath])
else:
return f"[-] db_path:'{db_path}' Error "
error = f"[-] db_path:'{db_path}' Error "
if is_logging: print(error)
return False, error
result = []
for i in process_list:
@ -110,7 +130,21 @@ def batch_decrypt(key: str, db_path: Union[str, List[str]], out_path: str):
if not os.listdir(os.path.join(root, dir)):
os.rmdir(os.path.join(root, dir))
return result
if is_logging:
print("=" * 32)
success_count = 0
fail_count = 0
for code, ret in result:
if code == False:
print(ret)
fail_count += 1
else:
print(f'[+] "{ret[0]}" -> "{ret[1]}"')
success_count += 1
print("-" * 32)
print(f"[+] 共 {len(result)} 个文件, 成功 {success_count} 个, 失败 {fail_count}")
print("=" * 32)
return True, result
if __name__ == '__main__':

View File

@ -70,6 +70,8 @@ def load_base64_img_data(start_time, end_time, username_md5, FileStorage_path):
min_time = time.strftime("%Y-%m", time.localtime(start_time))
max_time = time.strftime("%Y-%m", time.localtime(end_time))
img_path = os.path.join(FileStorage_path, "MsgAttach", username_md5, "Image")
if not os.path.exists(img_path):
return {}
# print(min_time, max_time, img_path)
paths = []
for root, path, files in os.walk(img_path):

View File

@ -4,383 +4,335 @@
328122328,
328123056,
328121976,
328123020,
0
328123020
],
"3.3.0.115": [
31323364,
31323744,
31324472,
31323392,
31324436,
0
31324436
],
"3.3.0.84": [
31315212,
31315592,
31316320,
31315240,
31316284,
0
31316284
],
"3.3.0.93": [
31323364,
31323744,
31324472,
31323392,
31324436,
0
31324436
],
"3.3.5.34": [
30603028,
30603408,
30604120,
30603056,
30604100,
0
30604100
],
"3.3.5.42": [
30603012,
30603392,
30604120,
30603040,
30604084,
0
30604084
],
"3.3.5.46": [
30578372,
30578752,
30579480,
30578400,
30579444,
0
30579444
],
"3.4.0.37": [
31608116,
31608496,
31609224,
31608144,
31609188,
0
31609188
],
"3.4.0.38": [
31604044,
31604424,
31605152,
31604072,
31605116,
0
31605116
],
"3.4.0.50": [
31688500,
31688880,
31689608,
31688528,
31689572,
0
31689572
],
"3.4.0.54": [
31700852,
31701248,
31700920,
31700880,
31701924,
0
31701924
],
"3.4.5.27": [
32133788,
32134168,
32134896,
32133816,
32134860,
0
32134860
],
"3.4.5.45": [
32147012,
32147392,
32147064,
32147040,
32148084,
0
32148084
],
"3.5.0.20": [
35494484,
35494864,
35494536,
35494512,
35495556,
0
35495556
],
"3.5.0.29": [
35507980,
35508360,
35508032,
35508008,
35509052,
0
35509052
],
"3.5.0.33": [
35512140,
35512520,
35512192,
35512168,
35513212,
0
35513212
],
"3.5.0.39": [
35516236,
35516616,
35516288,
35516264,
35517308,
0
35517308
],
"3.5.0.42": [
35512140,
35512520,
35512192,
35512168,
35513212,
0
35513212
],
"3.5.0.44": [
35510836,
35511216,
35510896,
35510864,
35511908,
0
35511908
],
"3.5.0.46": [
35506740,
35507120,
35506800,
35506768,
35507812,
0
35507812
],
"3.6.0.18": [
35842996,
35843376,
35843048,
35843024,
35844068,
0
35844068
],
"3.6.5.7": [
35864356,
35864736,
35864408,
35864384,
35865428,
0
35865428
],
"3.6.5.16": [
35909428,
35909808,
35909480,
35909456,
35910500,
0
35910500
],
"3.7.0.26": [
37105908,
37106288,
37105960,
37105936,
37106980,
0
37106980
],
"3.7.0.29": [
37105908,
37106288,
37105960,
37105936,
37106980,
0
37106980
],
"3.7.0.30": [
37118196,
37118576,
37118248,
37118224,
37119268,
0
37119268
],
"3.7.5.11": [
37883280,
37884088,
37883136,
37883008,
37884052,
0
37884052
],
"3.7.5.23": [
37895736,
37896544,
37895592,
37883008,
37896508,
0
37896508
],
"3.7.5.27": [
37895736,
37896544,
37895592,
37895464,
37896508,
0
37896508
],
"3.7.5.31": [
37903928,
37904736,
37903784,
37903656,
37904700,
0
37904700
],
"3.7.6.24": [
38978840,
38979648,
38978696,
38978604,
38979612,
0
38979612
],
"3.7.6.29": [
38986376,
38987184,
38986232,
38986104,
38987148,
0
38987148
],
"3.7.6.44": [
39016520,
39017328,
39016376,
38986104,
39017292,
0
39017292
],
"3.8.0.31": [
46064088,
46064912,
46063944,
38986104,
46064876,
0
46064876
],
"3.8.0.33": [
46059992,
46060816,
46059848,
38986104,
46060780,
0
46060780
],
"3.8.0.41": [
46064024,
46064848,
46063880,
38986104,
46064812,
0
46064812
],
"3.8.1.26": [
46409448,
46410272,
46409304,
38986104,
46410236,
0
46410236
],
"3.9.0.28": [
48418376,
48419280,
48418232,
38986104,
48419244,
0
48419244
],
"3.9.2.23": [
50320784,
50321712,
50320640,
38986104,
50321676,
50592864
50321676
],
"3.9.2.26": [
50329040,
50329968,
50328896,
38986104,
50329932,
0
50329932
],
"3.9.5.81": [
61650872,
61652208,
61650680,
0,
61652144,
0
61652144
],
"3.9.5.91": [
61654904,
61656240,
61654712,
38986104,
61656176,
61677112
61656176
],
"3.9.6.19": [
61997688,
61997464,
61997496,
38986104,
61998960,
0
61998960
],
"3.9.6.33": [
62030600,
62031936,
62030408,
0,
62031872,
0
62031872
],
"3.9.7.15": [
63482696,
63484032,
63482504,
0,
63483968,
0
63483968
],
"3.9.7.25": [
63482760,
63484096,
63482568,
0,
63484032,
0
63484032
],
"3.9.7.29": [
63486984,
63488320,
63486792,
0,
63488256,
63488352
63488256
],
"3.9.8.15": [
64996632,
64997968,
64996440,
0,
64997904,
65011632
64997904
]
}

View File

@ -10,7 +10,9 @@ import re
import winreg
from typing import List, Union
def get_wechat_db(require_list: Union[List[str], str] = "all", msg_dir: str = None):
def get_wechat_db(require_list: Union[List[str], str] = "all", msg_dir: str = None, wxid: Union[List[str], str] = None,
is_logging: bool = False):
if not msg_dir:
try:
key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Tencent\WeChat", 0, winreg.KEY_READ)
@ -27,18 +29,28 @@ def get_wechat_db(require_list: Union[List[str], str] = "all", msg_dir: str = No
msg_dir = os.path.join(w_dir, "WeChat Files")
if not os.path.exists(msg_dir):
return "[-] 目录不存在"
error = "[-] 目录不存在"
if is_logging: print(error)
return error
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 wxid: # 如果指定wxid
if isinstance(wxid, str):
wxid = wxid.split(";")
for file_name in files:
if file_name in wxid:
user_dirs[os.path.join(msg_dir, file_name)] = os.path.join(msg_dir, file_name)
else: # 如果未指定wxid
for file_name in files:
if file_name == "All Users" or file_name == "Applet" or file_name == "WMPF":
continue
user_dirs[os.path.join(msg_dir, file_name)] = os.path.join(msg_dir, file_name)
if isinstance(require_list, str):
require_list = require_list.split(";")
# generate pattern
if "all" in require_list:
pattern = {"all": re.compile(r".*\.db$")}
elif isinstance(require_list, list):
@ -46,7 +58,9 @@ def get_wechat_db(require_list: Union[List[str], str] = "all", msg_dir: str = No
for require in require_list:
pattern[require] = re.compile(r"%s.*\.db$" % require)
else:
return "[-] 参数错误"
error = "[-] 参数错误"
if is_logging: print(error)
return error
# 获取数据库路径
for user, user_dir in user_dirs.items(): # 遍历用户目录
@ -57,19 +71,21 @@ def get_wechat_db(require_list: Union[List[str], str] = "all", msg_dir: str = No
if p.match(file_name):
src_path = os.path.join(root, file_name)
user_dirs[user][n].append(src_path)
if is_logging:
for user, user_dir in user_dirs.items():
print(f"[+] user_path: {user}")
for n, paths in user_dir.items():
print(f" {n}:")
for path in paths:
print(f" {path.replace(user, '')}")
print("-" * 32)
print(f"[+] 共 {len(user_dirs)} 个微信账号")
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}")
user_dirs = get_wechat_db(require_list, is_logging=True)

View File

@ -5,9 +5,9 @@
# Author: xaoyaoo
# Date: 2023/08/21
# -------------------------------------------------------------------------------
import argparse
import json
import ctypes
import pymem
from win32com.client import Dispatch
import psutil
@ -24,13 +24,15 @@ def get_info_without_key(h_process, address, n_size=64):
return text.strip() if text.strip() != "" else "None"
def get_info_wxid(h_process, address, n_size=32, address_len=8):
array = ctypes.create_string_buffer(address_len)
if ReadProcessMemory(h_process, void_p(address), array, address_len, 0) == 0: return "None"
address = int.from_bytes(array, byteorder='little') # 逆序转换为int地址key地址
wxid = get_info_without_key(h_process, address, n_size)
if not wxid.startswith("wxid_"): wxid = "None"
return wxid
def get_info_wxid(h_process, n_size=64):
pm = pymem.Pymem("WeChat.exe")
addrs = pymem.pattern.pattern_scan_all(pm.process_handle, b'wxid_', return_multiple=True)
for addr in addrs:
wxidtmp = get_info_without_key(h_process, addr, n_size)
if wxidtmp.startswith("wxid_") and r'\FileStorage\MsgAttach' in wxidtmp:
wxid = wxidtmp.split(r'\FileStorage\MsgAttach')[0]
return wxid
return "None"
# 读取内存中的key
@ -45,16 +47,18 @@ def get_key(h_process, address, address_len=8):
# 读取微信信息(account,mobile,name,mail,wxid,key)
def read_info(version_list):
def read_info(version_list, is_logging=False):
wechat_process = []
result = []
error = ""
for process in psutil.process_iter(['name', 'exe', 'pid', 'cmdline']):
if process.name() == 'WeChat.exe':
wechat_process.append(process)
if len(wechat_process) == 0:
return "[-] WeChat No Run"
error = "[-] WeChat No Run"
if is_logging: print(error)
return error
for process in wechat_process:
tmp_rd = {}
@ -64,7 +68,9 @@ def read_info(version_list):
bias_list = version_list.get(tmp_rd['version'], None)
if not isinstance(bias_list, list):
return f"[-] WeChat Current Version {tmp_rd['version']} Is Not Supported"
error = f"[-] WeChat Current Version {tmp_rd['version']} Is Not Supported"
if is_logging: print(error)
return error
wechat_base_address = 0
for module in process.memory_maps(grouped=False):
@ -72,7 +78,9 @@ def read_info(version_list):
wechat_base_address = int(module.addr, 16)
break
if wechat_base_address == 0:
return f"[-] WeChat WeChatWin.dll Not Found"
error = f"[-] WeChat WeChatWin.dll Not Found"
if is_logging: print(error)
return error
Handle = ctypes.windll.kernel32.OpenProcess(0x1F0FFF, False, process.pid)
@ -81,7 +89,6 @@ def read_info(version_list):
mobile_baseaddr = wechat_base_address + bias_list[2]
mail_baseaddr = wechat_base_address + bias_list[3]
key_baseaddr = wechat_base_address + bias_list[4]
wxid_baseaddr = wechat_base_address + bias_list[5]
addrLen = 4 if tmp_rd['version'] in ["3.9.2.23", "3.9.2.26"] else 8
@ -89,14 +96,27 @@ def read_info(version_list):
tmp_rd['mobile'] = get_info_without_key(Handle, mobile_baseaddr, 64) if bias_list[2] != 0 else "None"
tmp_rd['name'] = get_info_without_key(Handle, name_baseaddr, 64) if bias_list[0] != 0 else "None"
tmp_rd['mail'] = get_info_without_key(Handle, mail_baseaddr, 64) if bias_list[3] != 0 else "None"
tmp_rd['wxid'] = get_info_wxid(Handle, wxid_baseaddr, 24, addrLen) if bias_list[5] != 0 else "None"
tmp_rd['wxid'] = get_info_wxid(Handle, 64)
tmp_rd['key'] = get_key(Handle, key_baseaddr, addrLen) if bias_list[4] != 0 else "None"
result.append(tmp_rd)
if is_logging:
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
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--vlfile", type=str, help="手机号", required=False)
parser.add_argument("--vldict", type=str, help="微信昵称", required=False)
@ -117,14 +137,4 @@ if __name__ == "__main__":
with open(VERSION_LIST_PATH, "r", encoding="utf-8") as f:
VERSION_LIST = json.load(f)
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)
result = read_info(VERSION_LIST, True) # 读取微信信息

View File

@ -3,7 +3,7 @@ from setuptools import setup, find_packages
with open("README.md", "r", encoding="utf-8") as fh:
long_description = fh.read()
version = "2.2.2"
version = "2.2.5"
setup(
name="pywxdump",
author="xaoyaoo",