修复无法获取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

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 获取微信文件夹路径
# bias 获取微信基址偏移
# info 获取微信信息
# db_path 获取微信文件夹路径
# decrypt 解密微信数据库
# show_records 显示聊天记录[需要安装flask]
# analyse 解析微信数据库(未完成)
# all 执行所有操作(除获取基址偏移、解密所有已经登陆的数据库)
# dbshow 聊天记录查看[需要安装flask]
# all 获取微信信息,解密微信数据库,查看聊天记录
```
*示例*
@ -160,7 +161,7 @@ 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
@ -169,46 +170,42 @@ wxdump bias_addr -h
# --account ACCOUNT 微信账号
# --key KEY (可选)密钥
# --db_path DB_PATH (可选)已登录账号的微信文件夹路径
# -vlp VLP (可选)微信版本偏移文件路径
# -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}
# -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 参数
# -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
@ -307,3 +330,4 @@ 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,78 +167,97 @@ 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:
WxInfo = read_info(VERSION_LIST, True)
for user in WxInfo:
key = user.get("key", "")
if not key:
print("[-] 未获取到密钥")
return
wxids = [i.get('wxid', "") for i in result_WxInfo]
wxid = user.get("wxid", None)
WxDbPath = get_wechat_db('all', None, wxid=wxid, is_logging=True) # 获取微信数据库路径
wxdbpaths = [path for user_dir in WxDbPath.values() for paths in user_dir.values() for path in paths]
if len(wxdbpaths) == 0:
print("[-] 未获取到数据库路径")
return
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(f"[+] 共发现 {wxdblen} 个微信数据库")
print("=" * 32)
out_path = os.path.join(os.getcwd(), "decrypted")
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)
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]
# 判断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 = []
for i in range(wxdblen):
if isinstance(result_Decrypt[i], str):
errors.append(i)
out_dbs = []
for code1, ret1 in ret:
if code1 == False:
errors.append(ret1)
else:
print(
f'[+] success "{os.path.relpath(result_Decrypt[i][1], os.path.commonprefix(wxdbpaths))}" -> "{os.path.relpath(result_Decrypt[i][2], 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])
print("-" * 32)
print("[-] " + f"{len(errors)} 个文件解密失败;")
# print("; ".join([f'"{wxdbpaths[i]}"' for i in errors]))
print("=" * 32)
if len(out_dbs) <= 0:
print("[-] 未获取到解密后的数据库路径")
return
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)
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[file_name] = os.path.join(msg_dir, file_name)
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"
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",