parent
2f2cf58c47
commit
36f0d26e45
13
README.md
13
README.md
@ -16,6 +16,7 @@
|
|||||||
<details>
|
<details>
|
||||||
<summary><strong>更新日志(点击展开):</strong></summary>
|
<summary><strong>更新日志(点击展开):</strong></summary>
|
||||||
|
|
||||||
|
* 2023.11.22 添加all命令中解密错误数据日志写入文件,修复部分bug
|
||||||
* 2023.11.16 增加聊天记录导出为html
|
* 2023.11.16 增加聊天记录导出为html
|
||||||
* 2023.11.15 添加test文件,添加自动构建可执行文件的脚本,添加版本描述
|
* 2023.11.15 添加test文件,添加自动构建可执行文件的脚本,添加版本描述
|
||||||
* 2023.11.15 [v2.2.5变化较大]重构解密脚本的返回值,重构命令行参数
|
* 2023.11.15 [v2.2.5变化较大]重构解密脚本的返回值,重构命令行参数
|
||||||
@ -49,7 +50,7 @@
|
|||||||
|
|
||||||
## 1. 项目简介
|
## 1. 项目简介
|
||||||
|
|
||||||
PyWxDump可用于:获取用户个人信息(昵称/账号/手机/邮箱/数据库密钥(用来解密聊天记录));数据库读取、解密脚本;聊天记录查看、聊天记录导出为html。
|
[PyWxDump](https://github.com/xaoyaoo/PyWxDump)可用于:获取用户个人信息(昵称/账号/手机/邮箱/数据库密钥(用来解密聊天记录));数据库读取、解密脚本;聊天记录查看、聊天记录导出为html。
|
||||||
|
|
||||||
支持多账户信息获取,支持所有微信版本。
|
支持多账户信息获取,支持所有微信版本。
|
||||||
|
|
||||||
@ -67,7 +68,7 @@ PyWxDump可用于:获取用户个人信息(昵称/账号/手机/邮箱/数据
|
|||||||
* (6)提供数据库部分字段说明
|
* (6)提供数据库部分字段说明
|
||||||
* (7)支持微信多开场景,获取多用户信息等
|
* (7)支持微信多开场景,获取多用户信息等
|
||||||
* (8)微信需要登录状态才能获取数据库密钥
|
* (8)微信需要登录状态才能获取数据库密钥
|
||||||
* (9)支持导出聊天记录为html
|
* (9)支持导出聊天记录为html,备份微信聊天记录,方便查看
|
||||||
|
|
||||||
**版本差异**
|
**版本差异**
|
||||||
|
|
||||||
@ -121,6 +122,7 @@ PyWxDump
|
|||||||
[PyWxDump](https://github.com/xaoyaoo/PyWxDump)是[SharpWxDump](https://github.com/AdminTest0/SharpWxDump)
|
[PyWxDump](https://github.com/xaoyaoo/PyWxDump)是[SharpWxDump](https://github.com/AdminTest0/SharpWxDump)
|
||||||
的经过重构python语言版本,同时添加了一些新的功能。
|
的经过重构python语言版本,同时添加了一些新的功能。
|
||||||
|
|
||||||
|
* 项目地址:https://github.com/xaoyaoo/PyWxDump
|
||||||
* 目前只在windows下测试过,linux下可能会存在问题。
|
* 目前只在windows下测试过,linux下可能会存在问题。
|
||||||
* 如发现[version_list.json](pywxdump/version_list.json)缺失或错误,
|
* 如发现[version_list.json](pywxdump/version_list.json)缺失或错误,
|
||||||
请提交[issues](https://github.com/xaoyaoo/PyWxDump/issues).
|
请提交[issues](https://github.com/xaoyaoo/PyWxDump/issues).
|
||||||
@ -331,10 +333,15 @@ else:
|
|||||||
|
|
||||||
本项目仅供学习交流使用,请勿用于非法用途,否则后果自负。
|
本项目仅供学习交流使用,请勿用于非法用途,否则后果自负。
|
||||||
|
|
||||||
本项目仅允许在授权情况下对数据库进行备份,严禁用于非法目的,否则自行承担所有相关责任。使用该工具则代表默认同意该条款;
|
您应该在下载保存,编译使用本项目的24小时内,删除本项目的源代码和(编译出的)程序。
|
||||||
|
|
||||||
|
本项目仅允许在授权情况下对数据库进行备份,严禁用于非法目的,否则自行承担所有相关责任。
|
||||||
|
|
||||||
|
下载、保存、进一步浏览源代码或者下载安装、编译使用本程序,表示你同意本警告,并承诺遵守它;
|
||||||
|
|
||||||
请勿利用本项目的相关技术从事非法测试,如因此产生的一切不良后果与项目作者无关。
|
请勿利用本项目的相关技术从事非法测试,如因此产生的一切不良后果与项目作者无关。
|
||||||
|
|
||||||
|
|
||||||
# 四、许可证
|
# 四、许可证
|
||||||
|
|
||||||
```text
|
```text
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
# Author: xaoyaoo
|
# Author: xaoyaoo
|
||||||
# Date: 2023/09/27
|
# Date: 2023/09/27
|
||||||
# -------------------------------------------------------------------------------
|
# -------------------------------------------------------------------------------
|
||||||
|
import os.path
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import pysilk
|
import pysilk
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
@ -245,5 +246,43 @@ def read_audio(MsgSvrID, is_play=False, is_wave=False, DB_PATH: str = "", rate=2
|
|||||||
return pcm_data
|
return pcm_data
|
||||||
|
|
||||||
|
|
||||||
|
def wordcloud_generator(text, out_path="", is_show=False, img_path="", font="C:\Windows\Fonts\simhei.ttf"):
|
||||||
|
"""
|
||||||
|
词云
|
||||||
|
:param is_show: 是否显示
|
||||||
|
:param img_path: 背景图片路径
|
||||||
|
:param text: 文本
|
||||||
|
:param font: 字体路径
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
from wordcloud import WordCloud
|
||||||
|
import jieba
|
||||||
|
import numpy as np
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
from matplotlib.font_manager import fontManager
|
||||||
|
except ImportError as e:
|
||||||
|
print("error", e)
|
||||||
|
raise ImportError("请安装wordcloud,jieba,numpy,matplotlib,pillow库")
|
||||||
|
words = jieba.lcut(text) # 精确分词
|
||||||
|
newtxt = ' '.join(words) # 空格拼接
|
||||||
|
# 字体路径
|
||||||
|
|
||||||
|
# 创建WordCloud对象
|
||||||
|
wordcloud1 = WordCloud(width=800, height=400, background_color='white', font_path=font)
|
||||||
|
wordcloud1.generate(newtxt)
|
||||||
|
|
||||||
|
if out_path and out_path != "":
|
||||||
|
wordcloud1.to_file("wordcloud.png") # 保存图片
|
||||||
|
if img_path and os.path.exists(img_path): # 设置背景图片
|
||||||
|
img_color = np.array(Image.open(img_path)) # 读取背景图片
|
||||||
|
img_color = img_color.reshape((img_color.shape[0] * img_color.shape[1], 3))
|
||||||
|
wordcloud1.recolor(color_func=img_color) # 设置背景图片颜色
|
||||||
|
if is_show:
|
||||||
|
# 显示词云
|
||||||
|
wordcloud_img = wordcloud1.to_image()
|
||||||
|
wordcloud_img.show()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
pass
|
wordcloud_generator("我是中国人,我喜欢吃饭", is_show=True)
|
||||||
|
@ -12,9 +12,6 @@ import json
|
|||||||
import multiprocessing
|
import multiprocessing
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import time
|
|
||||||
import winreg
|
|
||||||
import threading
|
|
||||||
import platform
|
import platform
|
||||||
|
|
||||||
import psutil
|
import psutil
|
||||||
|
@ -7,6 +7,8 @@
|
|||||||
# -------------------------------------------------------------------------------
|
# -------------------------------------------------------------------------------
|
||||||
import argparse
|
import argparse
|
||||||
import importlib.metadata
|
import importlib.metadata
|
||||||
|
import sys
|
||||||
|
import textwrap
|
||||||
|
|
||||||
from . import *
|
from . import *
|
||||||
|
|
||||||
@ -19,12 +21,12 @@ class MainBiasAddr():
|
|||||||
self.mode = "bias"
|
self.mode = "bias"
|
||||||
# 添加 'bias_addr' 子命令解析器
|
# 添加 'bias_addr' 子命令解析器
|
||||||
sb_bias_addr = parser.add_parser(self.mode, 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("--mobile", type=str, help="手机号", metavar="", required=True)
|
||||||
sb_bias_addr.add_argument("--name", type=str, help="微信昵称", required=True)
|
sb_bias_addr.add_argument("--name", type=str, help="微信昵称", metavar="", required=True)
|
||||||
sb_bias_addr.add_argument("--account", type=str, help="微信账号", required=True)
|
sb_bias_addr.add_argument("--account", type=str, help="微信账号", metavar="", required=True)
|
||||||
sb_bias_addr.add_argument("--key", type=str, help="(可选)密钥")
|
sb_bias_addr.add_argument("--key", type=str, metavar="", help="(可选)密钥")
|
||||||
sb_bias_addr.add_argument("--db_path", type=str, help="(可选)已登录账号的微信文件夹路径")
|
sb_bias_addr.add_argument("--db_path", type=str, metavar="", help="(可选)已登录账号的微信文件夹路径")
|
||||||
sb_bias_addr.add_argument("-vlp", '--version_list_path', type=str,
|
sb_bias_addr.add_argument("-vlp", '--version_list_path', type=str, metavar="",
|
||||||
help="(可选)微信版本偏移文件路径,如有,则自动更新",
|
help="(可选)微信版本偏移文件路径,如有,则自动更新",
|
||||||
default=None)
|
default=None)
|
||||||
self.sb_bias_addr = sb_bias_addr
|
self.sb_bias_addr = sb_bias_addr
|
||||||
@ -52,12 +54,13 @@ class MainWxInfo():
|
|||||||
self.mode = "info"
|
self.mode = "info"
|
||||||
# 添加 'wx_info' 子命令解析器
|
# 添加 'wx_info' 子命令解析器
|
||||||
sb_wx_info = parser.add_parser(self.mode, help="获取微信信息")
|
sb_wx_info = parser.add_parser(self.mode, help="获取微信信息")
|
||||||
sb_wx_info.add_argument("-vlp", type=str, help="(可选)微信版本偏移文件路径", default=VERSION_LIST_PATH)
|
sb_wx_info.add_argument("-vlp", '--version_list_path', metavar="", type=str,
|
||||||
|
help="(可选)微信版本偏移文件路径", default=VERSION_LIST_PATH)
|
||||||
return sb_wx_info
|
return sb_wx_info
|
||||||
|
|
||||||
def run(self, args):
|
def run(self, args):
|
||||||
# 读取微信各版本偏移
|
# 读取微信各版本偏移
|
||||||
path = args.vlp
|
path = args.version_list_path
|
||||||
version_list = json.load(open(path, "r", encoding="utf-8"))
|
version_list = json.load(open(path, "r", encoding="utf-8"))
|
||||||
result = read_info(version_list, True) # 读取微信信息
|
result = read_info(version_list, True) # 读取微信信息
|
||||||
return result
|
return result
|
||||||
@ -173,7 +176,7 @@ class MainExportChatRecords():
|
|||||||
def init_parses(self, parser):
|
def init_parses(self, parser):
|
||||||
self.mode = "export"
|
self.mode = "export"
|
||||||
# 添加 'decrypt' 子命令解析器
|
# 添加 'decrypt' 子命令解析器
|
||||||
sb_decrypt = parser.add_parser(self.mode, help="聊天记录导出为html")
|
sb_decrypt = parser.add_parser(self.mode, help="聊天记录导出为html[需要安装flask]")
|
||||||
sb_decrypt.add_argument("-u", "--username", type=str, help="微信账号", required=True, metavar="")
|
sb_decrypt.add_argument("-u", "--username", type=str, help="微信账号", required=True, metavar="")
|
||||||
sb_decrypt.add_argument("-o", "--outpath", type=str, help="导出路径", required=True, metavar="")
|
sb_decrypt.add_argument("-o", "--outpath", type=str, help="导出路径", required=True, metavar="")
|
||||||
sb_decrypt.add_argument("-msg", "--msg_path", type=str, help="解密后的 MSG.db 的路径", required=True,
|
sb_decrypt.add_argument("-msg", "--msg_path", type=str, help="解密后的 MSG.db 的路径", required=True,
|
||||||
@ -274,8 +277,11 @@ class MainAll():
|
|||||||
f'[+] success "{os.path.relpath(ret1[0], os.path.commonprefix(wxdbpaths))}" -> "{os.path.relpath(ret1[1], os.getcwd())}"')
|
f'[+] success "{os.path.relpath(ret1[0], os.path.commonprefix(wxdbpaths))}" -> "{os.path.relpath(ret1[1], os.getcwd())}"')
|
||||||
out_dbs.append(ret1[1])
|
out_dbs.append(ret1[1])
|
||||||
print("-" * 32)
|
print("-" * 32)
|
||||||
print("[-] " + f"共 {len(errors)} 个文件解密失败;")
|
print(
|
||||||
|
"[-] " + f"共 {len(errors)} 个文件解密失败(可能原因:非当前登录用户数据库;非加密数据库),详见{out_path}下‘解密失败.txt’;")
|
||||||
# print("; ".join([f'"{wxdbpaths[i]}"' for i in errors]))
|
# print("; ".join([f'"{wxdbpaths[i]}"' for i in errors]))
|
||||||
|
with open(os.path.join(out_path, "解密失败.txt"), "w", encoding="utf-8") as f:
|
||||||
|
f.write("\n".join([f'{i}' for i in errors]))
|
||||||
print("=" * 32)
|
print("=" * 32)
|
||||||
|
|
||||||
if len(out_dbs) <= 0:
|
if len(out_dbs) <= 0:
|
||||||
@ -300,13 +306,31 @@ class MainAll():
|
|||||||
MainShowChatRecords().run(args)
|
MainShowChatRecords().run(args)
|
||||||
|
|
||||||
|
|
||||||
|
PYWXDUMP_VERSION = importlib.metadata.version('pywxdump')
|
||||||
|
|
||||||
|
|
||||||
|
class CustomArgumentParser(argparse.ArgumentParser):
|
||||||
|
def format_help(self):
|
||||||
|
# 首先显示软件简介
|
||||||
|
# 定义软件简介文本并进行格式化
|
||||||
|
PYWXDUMP_VERSION = importlib.metadata.version('pywxdump')
|
||||||
|
first_line = f'\033[36m{" PyWxDump v" + PYWXDUMP_VERSION + " ":=^80}\033[0m'
|
||||||
|
brief = 'PyWxDump是一款用于获取账号信息(昵称/账号/手机/邮箱/数据库密钥)、解密数据库、查看\n聊天记录、备份导出聊天记录为html的工具。'
|
||||||
|
other = '更多详情请查看: \033[4m\033[1mhttps://github.com/xaoyaoo/PyWxDump\033[0m'
|
||||||
|
|
||||||
|
separator = f'{"options":-^80}'
|
||||||
|
|
||||||
|
# 获取帮助信息并添加到软件简介下方
|
||||||
|
help_text = super().format_help().strip()
|
||||||
|
|
||||||
|
return f'{first_line}\n{brief}\n{separator}\n{help_text}\n{separator}\n{other}\n{first_line}\n'
|
||||||
|
|
||||||
|
|
||||||
def console_run():
|
def console_run():
|
||||||
# 创建命令行参数解析器
|
# 创建命令行参数解析器
|
||||||
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
|
parser = CustomArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
|
||||||
|
PYWXDUMP_VERSION = importlib.metadata.version('pywxdump')
|
||||||
version = importlib.metadata.version('pywxdump')
|
parser.add_argument('-V', '--version', action='version', version=f"PyWxDump v{PYWXDUMP_VERSION}")
|
||||||
version = f"PyWxDump {version}"
|
|
||||||
parser.add_argument('-V', '--version', action='version', version=version)
|
|
||||||
|
|
||||||
# 添加子命令解析器
|
# 添加子命令解析器
|
||||||
subparsers = parser.add_subparsers(dest="mode", help="""运行模式:""", required=True, metavar="mode")
|
subparsers = parser.add_subparsers(dest="mode", help="""运行模式:""", required=True, metavar="mode")
|
||||||
@ -347,6 +371,12 @@ def console_run():
|
|||||||
sb_all = main_all.init_parses(subparsers)
|
sb_all = main_all.init_parses(subparsers)
|
||||||
modes[main_all.mode] = main_all
|
modes[main_all.mode] = main_all
|
||||||
|
|
||||||
|
# 检查是否需要显示帮助信息
|
||||||
|
if len(sys.argv) == 1:
|
||||||
|
sys.argv.append('-h')
|
||||||
|
elif len(sys.argv) == 2 and sys.argv[1] in modes.keys():
|
||||||
|
sys.argv.append('-h')
|
||||||
|
|
||||||
args = parser.parse_args() # 解析命令行参数
|
args = parser.parse_args() # 解析命令行参数
|
||||||
|
|
||||||
if not any(vars(args).values()):
|
if not any(vars(args).values()):
|
||||||
|
@ -24,9 +24,35 @@ def get_info_without_key(h_process, address, n_size=64):
|
|||||||
return text.strip() if text.strip() != "" else "None"
|
return text.strip() if text.strip() != "" else "None"
|
||||||
|
|
||||||
|
|
||||||
|
def pattern_scan_all(handle, pattern, *, return_multiple=False):
|
||||||
|
import sys
|
||||||
|
next_region = 0
|
||||||
|
found = []
|
||||||
|
user_space_limit = 0x7FFFFFFF0000 if sys.maxsize > 2 ** 32 else 0x7fff0000
|
||||||
|
while next_region < user_space_limit:
|
||||||
|
try:
|
||||||
|
next_region, page_found = pymem.pattern.scan_pattern_page(
|
||||||
|
handle,
|
||||||
|
next_region,
|
||||||
|
pattern,
|
||||||
|
return_multiple=return_multiple
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
break
|
||||||
|
if not return_multiple and page_found:
|
||||||
|
return page_found
|
||||||
|
if page_found:
|
||||||
|
found += page_found
|
||||||
|
if not return_multiple:
|
||||||
|
return None
|
||||||
|
return found
|
||||||
|
|
||||||
|
|
||||||
def get_info_wxid(h_process, n_size=64):
|
def get_info_wxid(h_process, n_size=64):
|
||||||
pm = pymem.Pymem("WeChat.exe")
|
pm = pymem.Pymem("WeChat.exe")
|
||||||
addrs = pymem.pattern.pattern_scan_all(pm.process_handle, b'wxid_', return_multiple=True)
|
# addrs = pymem.pattern.pattern_scan_all(pm.process_handle, b'wxid_', return_multiple=True)
|
||||||
|
addrs = pattern_scan_all(pm.process_handle, b'wxid_', return_multiple=True)
|
||||||
for addr in addrs:
|
for addr in addrs:
|
||||||
wxidtmp = get_info_without_key(h_process, addr, n_size)
|
wxidtmp = get_info_without_key(h_process, addr, n_size)
|
||||||
if wxidtmp.startswith("wxid_") and r'\FileStorage\MsgAttach' in wxidtmp:
|
if wxidtmp.startswith("wxid_") and r'\FileStorage\MsgAttach' in wxidtmp:
|
||||||
|
2
setup.py
2
setup.py
@ -3,7 +3,7 @@ from setuptools import setup, find_packages
|
|||||||
with open("README.md", "r", encoding="utf-8") as fh:
|
with open("README.md", "r", encoding="utf-8") as fh:
|
||||||
long_description = fh.read()
|
long_description = fh.read()
|
||||||
|
|
||||||
version = "2.2.7"
|
version = "2.2.8"
|
||||||
|
|
||||||
install_requires = [
|
install_requires = [
|
||||||
"psutil",
|
"psutil",
|
||||||
|
Loading…
Reference in New Issue
Block a user