mirror of
https://github.com/LifeArchiveProject/WeChatDataAnalysis.git
synced 2026-02-19 14:20:51 +08:00
feat: 添加微信数据库解密工具的基本功能
This commit is contained in:
0
src/__init__.py
Normal file
0
src/__init__.py
Normal file
5
src/wechat_decrypt_tool/__init__.py
Normal file
5
src/wechat_decrypt_tool/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""微信数据库解密工具
|
||||
"""
|
||||
|
||||
__version__ = "0.1.0"
|
||||
__author__ = "WeChat Decrypt Tool"
|
||||
128
src/wechat_decrypt_tool/api.py
Normal file
128
src/wechat_decrypt_tool/api.py
Normal file
@@ -0,0 +1,128 @@
|
||||
"""微信解密工具的FastAPI Web服务器"""
|
||||
|
||||
from typing import Optional
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from pydantic import BaseModel
|
||||
|
||||
from .wechat_decrypt import decrypt_wechat_databases
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
title="微信数据库解密工具",
|
||||
description="现代化的微信数据库解密工具,支持微信信息检测和数据库解密功能",
|
||||
version="0.1.0"
|
||||
)
|
||||
|
||||
# Enable CORS for React frontend
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
|
||||
class DecryptRequest(BaseModel):
|
||||
"""解密请求模型"""
|
||||
key: str
|
||||
db_storage_path: Optional[str] = None # 可选的数据库存储路径,如 ......\{微信id}\db_storage
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@app.get("/", summary="根端点")
|
||||
async def root():
|
||||
"""根端点"""
|
||||
return {"message": "微信数据库解密工具 API"}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@app.get("/api/wechat-detection", summary="详细检测微信安装信息")
|
||||
async def detect_wechat_detailed():
|
||||
"""详细检测微信安装信息,包括版本、路径、消息目录等。"""
|
||||
try:
|
||||
from .wechat_detection import detect_wechat_installation
|
||||
info = detect_wechat_installation()
|
||||
|
||||
# 添加一些统计信息
|
||||
stats = {
|
||||
'total_databases': len(info['databases']),
|
||||
'total_user_accounts': len(info['user_accounts']),
|
||||
'total_message_dirs': len(info['message_dirs']),
|
||||
'has_wechat_installed': info['wechat_install_path'] is not None,
|
||||
'detection_time': __import__('datetime').datetime.now().isoformat()
|
||||
}
|
||||
|
||||
return {
|
||||
'status': 'success',
|
||||
'data': info,
|
||||
'statistics': stats
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
'status': 'error',
|
||||
'error': str(e),
|
||||
'data': None,
|
||||
'statistics': None
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@app.post("/api/decrypt", summary="解密微信数据库")
|
||||
async def decrypt_databases(request: DecryptRequest):
|
||||
"""使用提供的密钥解密微信数据库
|
||||
|
||||
参数:
|
||||
- key: 解密密钥(必选)
|
||||
- db_storage_path: 数据库存储路径(可选),如 ......\\{微信id}\\db_storage
|
||||
|
||||
如果不提供db_storage_path,将自动检测所有微信数据库
|
||||
"""
|
||||
try:
|
||||
# 验证密钥格式
|
||||
if not request.key or len(request.key) != 64:
|
||||
raise HTTPException(status_code=400, detail="密钥格式无效,必须是64位十六进制字符串")
|
||||
|
||||
# 使用新的解密API
|
||||
results = decrypt_wechat_databases(
|
||||
db_storage_path=request.db_storage_path,
|
||||
key=request.key
|
||||
)
|
||||
|
||||
if results["status"] == "error":
|
||||
raise HTTPException(status_code=400, detail=results["message"])
|
||||
|
||||
return {
|
||||
"status": "completed" if results["status"] == "success" else "failed",
|
||||
"total_databases": results["total_databases"],
|
||||
"success_count": results["successful_count"],
|
||||
"failure_count": results["failed_count"],
|
||||
"output_directory": results["output_directory"],
|
||||
"message": results["message"],
|
||||
"processed_files": results["processed_files"],
|
||||
"failed_files": results["failed_files"]
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@app.get("/api/health", summary="健康检查端点")
|
||||
async def health_check():
|
||||
"""健康检查端点"""
|
||||
return {"status": "healthy", "service": "微信解密工具"}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||
385
src/wechat_decrypt_tool/wechat_decrypt.py
Normal file
385
src/wechat_decrypt_tool/wechat_decrypt.py
Normal file
@@ -0,0 +1,385 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
微信4.x数据库解密工具
|
||||
基于SQLCipher 4.0加密机制,支持批量解密微信数据库文件
|
||||
|
||||
使用方法:
|
||||
python wechat_decrypt.py
|
||||
|
||||
密钥: 请通过参数传入您的解密密钥
|
||||
"""
|
||||
|
||||
import os
|
||||
import hashlib
|
||||
import hmac
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
|
||||
# 默认密钥已移除,请通过参数传入
|
||||
WECHAT_KEY = None
|
||||
|
||||
# SQLite文件头
|
||||
SQLITE_HEADER = b"SQLite format 3\x00"
|
||||
|
||||
def setup_logging():
|
||||
"""设置日志配置"""
|
||||
import logging
|
||||
|
||||
# 创建日志目录
|
||||
now = datetime.now()
|
||||
log_dir = Path("output/logs") / str(now.year) / f"{now.month:02d}" / f"{now.day:02d}"
|
||||
log_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# 设置日志文件名
|
||||
date_str = now.strftime("%d")
|
||||
log_file = log_dir / f"{date_str}_decrypt.log"
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s | %(levelname)s | %(message)s',
|
||||
handlers=[
|
||||
logging.FileHandler(log_file, encoding='utf-8'),
|
||||
logging.StreamHandler()
|
||||
]
|
||||
)
|
||||
|
||||
logging.info(f"日志系统初始化完成,日志文件: {log_file}")
|
||||
return log_dir
|
||||
|
||||
|
||||
|
||||
class WeChatDatabaseDecryptor:
|
||||
"""微信4.x数据库解密器"""
|
||||
|
||||
def __init__(self, key_hex: str):
|
||||
"""初始化解密器
|
||||
|
||||
参数:
|
||||
key_hex: 64位十六进制密钥
|
||||
"""
|
||||
if len(key_hex) != 64:
|
||||
raise ValueError("密钥必须是64位十六进制字符串")
|
||||
|
||||
try:
|
||||
self.key_bytes = bytes.fromhex(key_hex)
|
||||
except ValueError:
|
||||
raise ValueError("密钥必须是有效的十六进制字符串")
|
||||
|
||||
def decrypt_database(self, db_path: str, output_path: str) -> bool:
|
||||
"""解密微信4.x版本数据库
|
||||
|
||||
使用SQLCipher 4.0参数:
|
||||
- PBKDF2-SHA512, 256000轮迭代
|
||||
- AES-256-CBC加密
|
||||
- HMAC-SHA512验证
|
||||
- 页面大小4096字节
|
||||
"""
|
||||
import logging
|
||||
|
||||
logging.info(f"开始解密数据库: {db_path}")
|
||||
|
||||
try:
|
||||
with open(db_path, 'rb') as f:
|
||||
encrypted_data = f.read()
|
||||
|
||||
logging.info(f"读取文件大小: {len(encrypted_data)} bytes")
|
||||
|
||||
if len(encrypted_data) < 4096:
|
||||
logging.warning(f"文件太小,跳过解密: {db_path}")
|
||||
return False
|
||||
|
||||
# 检查是否已经是解密的数据库
|
||||
if encrypted_data.startswith(SQLITE_HEADER):
|
||||
logging.info(f"文件已是SQLite格式,直接复制: {db_path}")
|
||||
with open(output_path, 'wb') as f:
|
||||
f.write(encrypted_data)
|
||||
return True
|
||||
|
||||
# 提取salt (前16字节)
|
||||
salt = encrypted_data[:16]
|
||||
|
||||
# 计算mac_salt (salt XOR 0x3a)
|
||||
mac_salt = bytes(b ^ 0x3a for b in salt)
|
||||
|
||||
# 使用PBKDF2-SHA512派生密钥
|
||||
kdf = PBKDF2HMAC(
|
||||
algorithm=hashes.SHA512(),
|
||||
length=32,
|
||||
salt=salt,
|
||||
iterations=256000,
|
||||
backend=default_backend()
|
||||
)
|
||||
derived_key = kdf.derive(self.key_bytes)
|
||||
|
||||
# 派生MAC密钥
|
||||
mac_kdf = PBKDF2HMAC(
|
||||
algorithm=hashes.SHA512(),
|
||||
length=32,
|
||||
salt=mac_salt,
|
||||
iterations=2,
|
||||
backend=default_backend()
|
||||
)
|
||||
mac_key = mac_kdf.derive(derived_key)
|
||||
|
||||
# 解密数据
|
||||
decrypted_data = bytearray()
|
||||
decrypted_data.extend(SQLITE_HEADER)
|
||||
|
||||
page_size = 4096
|
||||
iv_size = 16
|
||||
hmac_size = 64 # SHA512的HMAC是64字节
|
||||
|
||||
# 计算保留区域大小 (对齐到AES块大小)
|
||||
reserve_size = iv_size + hmac_size
|
||||
if reserve_size % 16 != 0:
|
||||
reserve_size = ((reserve_size // 16) + 1) * 16
|
||||
|
||||
total_pages = len(encrypted_data) // page_size
|
||||
successful_pages = 0
|
||||
failed_pages = 0
|
||||
|
||||
# 逐页解密
|
||||
for cur_page in range(total_pages):
|
||||
start = cur_page * page_size
|
||||
end = start + page_size
|
||||
page = encrypted_data[start:end]
|
||||
|
||||
page_num = cur_page + 1 # 页面编号从1开始
|
||||
|
||||
if len(page) < page_size:
|
||||
logging.warning(f"页面 {page_num} 大小不足: {len(page)} bytes")
|
||||
break
|
||||
|
||||
# 确定偏移量:第一页(cur_page == 0)需要跳过salt
|
||||
offset = 16 if cur_page == 0 else 0 # SALT_SIZE = 16
|
||||
|
||||
# 提取存储的HMAC
|
||||
hmac_start = page_size - reserve_size + iv_size
|
||||
hmac_end = hmac_start + hmac_size
|
||||
stored_hmac = page[hmac_start:hmac_end]
|
||||
|
||||
# 按照wechat-dump-rs的方式验证HMAC
|
||||
data_end = page_size - reserve_size + iv_size
|
||||
hmac_data = page[offset:data_end]
|
||||
|
||||
# 分步计算HMAC:先更新数据,再更新页面编号
|
||||
mac = hmac.new(mac_key, digestmod=hashlib.sha512)
|
||||
mac.update(hmac_data) # 包含加密数据+IV
|
||||
mac.update(page_num.to_bytes(4, 'little')) # 页面编号(小端序)
|
||||
expected_hmac = mac.digest()
|
||||
|
||||
if stored_hmac != expected_hmac:
|
||||
logging.warning(f"页面 {page_num} HMAC验证失败")
|
||||
failed_pages += 1
|
||||
continue
|
||||
|
||||
# 提取IV和加密数据用于AES解密
|
||||
iv = page[page_size - reserve_size:page_size - reserve_size + iv_size]
|
||||
encrypted_page = page[offset:page_size - reserve_size]
|
||||
|
||||
# AES-CBC解密
|
||||
try:
|
||||
cipher = Cipher(
|
||||
algorithms.AES(derived_key),
|
||||
modes.CBC(iv),
|
||||
backend=default_backend()
|
||||
)
|
||||
decryptor = cipher.decryptor()
|
||||
decrypted_page = decryptor.update(encrypted_page) + decryptor.finalize()
|
||||
|
||||
# 按照wechat-dump-rs的方式重组页面数据
|
||||
decrypted_data.extend(decrypted_page)
|
||||
decrypted_data.extend(page[page_size - reserve_size:]) # 保留区域
|
||||
|
||||
successful_pages += 1
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"页面 {page_num} AES解密失败: {e}")
|
||||
failed_pages += 1
|
||||
continue
|
||||
|
||||
logging.info(f"解密完成: 成功 {successful_pages} 页, 失败 {failed_pages} 页")
|
||||
|
||||
# 写入解密后的文件
|
||||
with open(output_path, 'wb') as f:
|
||||
f.write(decrypted_data)
|
||||
|
||||
logging.info(f"解密文件大小: {len(decrypted_data)} bytes")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"解密失败: {db_path}, 错误: {e}")
|
||||
return False
|
||||
|
||||
def decrypt_wechat_databases(db_storage_path: str = None, key: str = None) -> dict:
|
||||
"""
|
||||
微信数据库解密API函数
|
||||
|
||||
参数:
|
||||
db_storage_path: 数据库存储路径,如 ......\\{微信id}\\db_storage
|
||||
如果为None,将自动搜索数据库文件
|
||||
key: 解密密钥,如果为None,将使用默认密钥
|
||||
|
||||
返回值:
|
||||
dict: 解密结果统计信息
|
||||
{
|
||||
"status": "success" | "error",
|
||||
"message": "描述信息",
|
||||
"total_databases": 总数据库数量,
|
||||
"successful_count": 成功解密数量,
|
||||
"failed_count": 失败数量,
|
||||
"output_directory": "输出目录路径",
|
||||
"processed_files": ["解密成功的文件列表"],
|
||||
"failed_files": ["解密失败的文件列表"]
|
||||
}
|
||||
"""
|
||||
import logging
|
||||
|
||||
# 初始化日志系统
|
||||
setup_logging()
|
||||
|
||||
# 验证密钥是否提供
|
||||
if not key:
|
||||
return {
|
||||
"status": "error",
|
||||
"message": "解密密钥是必需的参数",
|
||||
"total_databases": 0,
|
||||
"successful_count": 0,
|
||||
"failed_count": 0,
|
||||
"output_directory": "",
|
||||
"processed_files": [],
|
||||
"failed_files": []
|
||||
}
|
||||
|
||||
decrypt_key = key
|
||||
|
||||
logging.info("=" * 60)
|
||||
logging.info("微信4.x数据库解密工具 - API模式")
|
||||
logging.info("=" * 60)
|
||||
|
||||
# 创建输出目录
|
||||
output_dir = Path("output/databases")
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
logging.info(f"输出目录: {output_dir.absolute()}")
|
||||
|
||||
# 查找数据库文件
|
||||
if db_storage_path:
|
||||
# 使用指定路径查找数据库
|
||||
database_paths = []
|
||||
storage_path = Path(db_storage_path)
|
||||
if storage_path.exists():
|
||||
for db_file in storage_path.glob("*.db"):
|
||||
if db_file.is_file() and db_file.name != 'key_info.db':
|
||||
database_paths.append(str(db_file))
|
||||
logging.info(f"在指定路径找到 {len(database_paths)} 个数据库文件")
|
||||
else:
|
||||
return {
|
||||
"status": "error",
|
||||
"message": f"指定的数据库路径不存在: {db_storage_path}",
|
||||
"total_databases": 0,
|
||||
"successful_count": 0,
|
||||
"failed_count": 0,
|
||||
"output_directory": str(output_dir.absolute()),
|
||||
"processed_files": [],
|
||||
"failed_files": []
|
||||
}
|
||||
else:
|
||||
# 使用检测函数获取数据库列表
|
||||
try:
|
||||
from .wechat_detection import detect_wechat_installation
|
||||
wechat_info = detect_wechat_installation()
|
||||
if wechat_info and wechat_info.get('databases'):
|
||||
database_paths = [db['path'] for db in wechat_info['databases']]
|
||||
logging.info(f"通过检测函数找到 {len(database_paths)} 个数据库文件")
|
||||
else:
|
||||
database_paths = []
|
||||
logging.warning("检测函数未找到数据库文件")
|
||||
except Exception as e:
|
||||
logging.error(f"检测函数调用失败: {e}")
|
||||
database_paths = []
|
||||
|
||||
if not database_paths:
|
||||
return {
|
||||
"status": "error",
|
||||
"message": "未找到微信数据库文件!请确保微信已安装并有数据,或提供正确的db_storage路径",
|
||||
"total_databases": 0,
|
||||
"successful_count": 0,
|
||||
"failed_count": 0,
|
||||
"output_directory": str(output_dir.absolute()),
|
||||
"processed_files": [],
|
||||
"failed_files": []
|
||||
}
|
||||
|
||||
# 创建解密器
|
||||
try:
|
||||
decryptor = WeChatDatabaseDecryptor(decrypt_key)
|
||||
logging.info("解密器初始化成功")
|
||||
except ValueError as e:
|
||||
return {
|
||||
"status": "error",
|
||||
"message": f"密钥错误: {e}",
|
||||
"total_databases": len(database_paths),
|
||||
"successful_count": 0,
|
||||
"failed_count": 0,
|
||||
"output_directory": str(output_dir.absolute()),
|
||||
"processed_files": [],
|
||||
"failed_files": []
|
||||
}
|
||||
|
||||
# 批量解密
|
||||
success_count = 0
|
||||
total_count = len(database_paths)
|
||||
processed_files = []
|
||||
failed_files = []
|
||||
|
||||
for db_path in database_paths:
|
||||
# 生成输出文件名
|
||||
db_name = os.path.basename(db_path)
|
||||
output_path = output_dir / f"decrypted_{db_name}"
|
||||
|
||||
# 解密数据库
|
||||
if decryptor.decrypt_database(db_path, str(output_path)):
|
||||
success_count += 1
|
||||
processed_files.append(str(output_path))
|
||||
else:
|
||||
failed_files.append(db_path)
|
||||
logging.error(f"解密失败: {db_path}")
|
||||
|
||||
# 返回结果
|
||||
result = {
|
||||
"status": "success" if success_count > 0 else "error",
|
||||
"message": f"解密完成: 成功 {success_count}/{total_count}",
|
||||
"total_databases": total_count,
|
||||
"successful_count": success_count,
|
||||
"failed_count": total_count - success_count,
|
||||
"output_directory": str(output_dir.absolute()),
|
||||
"processed_files": processed_files,
|
||||
"failed_files": failed_files
|
||||
}
|
||||
|
||||
logging.info("=" * 60)
|
||||
logging.info("解密任务完成!")
|
||||
logging.info(f"成功: {success_count}/{total_count}")
|
||||
logging.info(f"失败: {total_count - success_count}/{total_count}")
|
||||
logging.info(f"输出目录: {output_dir.absolute()}")
|
||||
logging.info("=" * 60)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数 - 保持向后兼容"""
|
||||
result = decrypt_wechat_databases()
|
||||
if result["status"] == "error":
|
||||
print(f"错误: {result['message']}")
|
||||
else:
|
||||
print(f"解密完成: {result['message']}")
|
||||
print(f"输出目录: {result['output_directory']}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
447
src/wechat_decrypt_tool/wechat_detection.py
Normal file
447
src/wechat_decrypt_tool/wechat_detection.py
Normal file
@@ -0,0 +1,447 @@
|
||||
"""微信数据库检测模块
|
||||
|
||||
提供微信安装检测和数据库发现功能。
|
||||
基于PyWxDump的检测逻辑。
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import psutil
|
||||
import ctypes
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Any, Union
|
||||
from ctypes import wintypes
|
||||
|
||||
|
||||
|
||||
def get_wx_db(msg_dir: str = None,
|
||||
db_types: Union[List[str], str] = None,
|
||||
wxids: Union[List[str], str] = None) -> List[dict]:
|
||||
r"""
|
||||
获取微信数据库路径(基于PyWxDump逻辑)
|
||||
:param msg_dir: 微信数据库目录 eg: C:\Users\user\Documents\WeChat Files (非wxid目录)
|
||||
:param db_types: 需要获取的数据库类型,如果为空,则获取所有数据库
|
||||
:param wxids: 微信id列表,如果为空,则获取所有wxid下的数据库
|
||||
:return: [{"wxid": wxid, "db_type": db_type, "db_path": db_path, "wxid_dir": wxid_dir}, ...]
|
||||
"""
|
||||
result = []
|
||||
|
||||
if not msg_dir or not os.path.exists(msg_dir):
|
||||
print(f"[-] 微信文件目录不存在: {msg_dir}, 将使用默认路径")
|
||||
msg_dir = get_wx_dir_by_reg()
|
||||
|
||||
if not os.path.exists(msg_dir):
|
||||
print(f"[-] 目录不存在: {msg_dir}")
|
||||
return result
|
||||
|
||||
wxids = wxids.split(";") if isinstance(wxids, str) else wxids
|
||||
if not isinstance(wxids, list) or len(wxids) <= 0:
|
||||
wxids = None
|
||||
db_types = db_types.split(";") if isinstance(db_types, str) and db_types else db_types
|
||||
if not isinstance(db_types, list) or len(db_types) <= 0:
|
||||
db_types = None
|
||||
|
||||
wxid_dirs = {} # wx用户目录
|
||||
if wxids or "All Users" in os.listdir(msg_dir) or "Applet" in os.listdir(msg_dir) or "WMPF" in os.listdir(msg_dir):
|
||||
for sub_dir in os.listdir(msg_dir):
|
||||
if os.path.isdir(os.path.join(msg_dir, sub_dir)) and sub_dir not in ["All Users", "Applet", "WMPF"]:
|
||||
wxid_dirs[os.path.basename(sub_dir)] = os.path.join(msg_dir, sub_dir)
|
||||
else:
|
||||
wxid_dirs[os.path.basename(msg_dir)] = msg_dir
|
||||
|
||||
for wxid, wxid_dir in wxid_dirs.items():
|
||||
if wxids and wxid not in wxids: # 如果指定wxid,则过滤掉其他wxid
|
||||
continue
|
||||
for root, dirs, files in os.walk(wxid_dir):
|
||||
# 只处理db_storage目录下的数据库文件
|
||||
if "db_storage" not in root:
|
||||
continue
|
||||
for file_name in files:
|
||||
if not file_name.endswith(".db"):
|
||||
continue
|
||||
# 排除不需要解密的数据库
|
||||
if file_name in ["key_info.db"]:
|
||||
continue
|
||||
db_type = re.sub(r"\d*\.db$", "", file_name)
|
||||
if db_types and db_type not in db_types: # 如果指定db_type,则过滤掉其他db_type
|
||||
continue
|
||||
db_path = os.path.join(root, file_name)
|
||||
result.append({"wxid": wxid, "db_type": db_type, "db_path": db_path, "wxid_dir": wxid_dir})
|
||||
return result
|
||||
|
||||
# Windows API 常量和结构
|
||||
PROCESS_QUERY_INFORMATION = 0x0400
|
||||
PROCESS_VM_READ = 0x0010
|
||||
MAX_PATH = 260
|
||||
TH32CS_SNAPPROCESS = 0x00000002
|
||||
|
||||
# Windows API 函数
|
||||
kernel32 = ctypes.windll.kernel32
|
||||
psapi = ctypes.windll.psapi
|
||||
|
||||
OpenProcess = kernel32.OpenProcess
|
||||
CloseHandle = kernel32.CloseHandle
|
||||
GetModuleFileNameExW = psapi.GetModuleFileNameExW
|
||||
CreateToolhelp32Snapshot = kernel32.CreateToolhelp32Snapshot
|
||||
Process32FirstW = kernel32.Process32FirstW
|
||||
Process32NextW = kernel32.Process32NextW
|
||||
|
||||
class PROCESSENTRY32W(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('dwSize', wintypes.DWORD),
|
||||
('cntUsage', wintypes.DWORD),
|
||||
('th32ProcessID', wintypes.DWORD),
|
||||
('th32DefaultHeapID', ctypes.POINTER(wintypes.ULONG)),
|
||||
('th32ModuleID', wintypes.DWORD),
|
||||
('cntThreads', wintypes.DWORD),
|
||||
('th32ParentProcessID', wintypes.DWORD),
|
||||
('pcPriClassBase', wintypes.LONG),
|
||||
('dwFlags', wintypes.DWORD),
|
||||
('szExeFile', wintypes.WCHAR * MAX_PATH)
|
||||
]
|
||||
|
||||
|
||||
# 删除了WeChatDecryptor类,解密功能已移至独立的wechat_decrypt.py脚本
|
||||
|
||||
|
||||
def find_wechat_databases() -> List[str]:
|
||||
"""在新的xwechat_files目录中查找微信数据库文件
|
||||
|
||||
返回值:
|
||||
数据库文件路径列表
|
||||
"""
|
||||
db_files = []
|
||||
|
||||
# 获取用户的Documents目录
|
||||
documents_dir = Path.home() / "Documents"
|
||||
|
||||
# 检查新的微信4.0+目录结构
|
||||
wechat_dirs = [
|
||||
documents_dir / "xwechat_files", # 新版微信4.0+
|
||||
documents_dir / "WeChat Files" # 旧版微信
|
||||
]
|
||||
|
||||
for wechat_dir in wechat_dirs:
|
||||
if not wechat_dir.exists():
|
||||
continue
|
||||
|
||||
# 查找用户目录(wxid_*模式)
|
||||
for user_dir in wechat_dir.iterdir():
|
||||
if not user_dir.is_dir():
|
||||
continue
|
||||
|
||||
# 跳过系统目录
|
||||
if user_dir.name in ['All Users', 'Applet', 'WMPF']:
|
||||
continue
|
||||
|
||||
# 查找Msg目录
|
||||
msg_dir = user_dir / "Msg"
|
||||
if msg_dir.exists():
|
||||
# 查找数据库文件
|
||||
for db_file in msg_dir.glob("*.db"):
|
||||
if db_file.is_file():
|
||||
db_files.append(str(db_file))
|
||||
|
||||
# 同时检查Multi目录
|
||||
multi_dir = msg_dir / "Multi"
|
||||
if multi_dir.exists():
|
||||
for db_file in multi_dir.glob("*.db"):
|
||||
if db_file.is_file():
|
||||
db_files.append(str(db_file))
|
||||
|
||||
return db_files
|
||||
|
||||
|
||||
def get_process_exe_path(process_id):
|
||||
"""获取进程可执行文件路径"""
|
||||
h_process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, False, process_id)
|
||||
if not h_process:
|
||||
return None
|
||||
|
||||
exe_path = ctypes.create_unicode_buffer(MAX_PATH)
|
||||
if GetModuleFileNameExW(h_process, None, exe_path, MAX_PATH) > 0:
|
||||
CloseHandle(h_process)
|
||||
return exe_path.value
|
||||
else:
|
||||
CloseHandle(h_process)
|
||||
return None
|
||||
|
||||
def get_process_list():
|
||||
"""获取系统进程列表"""
|
||||
h_process_snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)
|
||||
if h_process_snap == ctypes.wintypes.HANDLE(-1).value:
|
||||
return []
|
||||
|
||||
pe32 = PROCESSENTRY32W()
|
||||
pe32.dwSize = ctypes.sizeof(PROCESSENTRY32W)
|
||||
process_list = []
|
||||
|
||||
if not Process32FirstW(h_process_snap, ctypes.byref(pe32)):
|
||||
CloseHandle(h_process_snap)
|
||||
return []
|
||||
|
||||
while True:
|
||||
process_list.append((pe32.th32ProcessID, pe32.szExeFile))
|
||||
if not Process32NextW(h_process_snap, ctypes.byref(pe32)):
|
||||
break
|
||||
|
||||
CloseHandle(h_process_snap)
|
||||
return process_list
|
||||
|
||||
def auto_detect_wechat_data_dirs():
|
||||
"""
|
||||
自动检测微信数据目录 - 多策略组合检测
|
||||
:return: 检测到的微信数据目录列表
|
||||
"""
|
||||
detected_dirs = []
|
||||
|
||||
# 策略1:注册表检测已移除
|
||||
|
||||
# 策略2和策略3:注册表相关检测已移除
|
||||
|
||||
# 策略1:常见驱动器扫描微信相关目录
|
||||
common_wechat_patterns = [
|
||||
"WeChat Files", "wechat_files", "xwechat_files", "wechatMSG",
|
||||
"WeChat", "微信", "Weixin", "wechat"
|
||||
]
|
||||
|
||||
# 扫描常见驱动器
|
||||
drives = ['C:', 'D:', 'E:', 'F:']
|
||||
for drive in drives:
|
||||
if not os.path.exists(drive):
|
||||
continue
|
||||
|
||||
try:
|
||||
# 扫描驱动器根目录和常见目录
|
||||
scan_paths = [
|
||||
drive + os.sep,
|
||||
os.path.join(drive + os.sep, "Users"),
|
||||
]
|
||||
|
||||
for scan_path in scan_paths:
|
||||
if not os.path.exists(scan_path):
|
||||
continue
|
||||
|
||||
try:
|
||||
for item in os.listdir(scan_path):
|
||||
item_path = os.path.join(scan_path, item)
|
||||
if not os.path.isdir(item_path):
|
||||
continue
|
||||
|
||||
# 检查是否匹配微信目录模式
|
||||
for pattern in common_wechat_patterns:
|
||||
if pattern.lower() in item.lower():
|
||||
# 检查是否包含wxid目录
|
||||
if has_wxid_directories(item_path):
|
||||
if item_path not in detected_dirs:
|
||||
detected_dirs.append(item_path)
|
||||
print(f"[DEBUG] 目录扫描检测成功: {item_path}")
|
||||
break
|
||||
except (PermissionError, OSError):
|
||||
continue
|
||||
except (PermissionError, OSError):
|
||||
continue
|
||||
|
||||
# 策略2:进程内存分析(简化版)
|
||||
try:
|
||||
process_list = get_process_list()
|
||||
for pid, process_name in process_list:
|
||||
if process_name.lower() in ['weixin.exe', 'wechat.exe']:
|
||||
# 尝试获取进程的工作目录
|
||||
try:
|
||||
import psutil
|
||||
proc = psutil.Process(pid)
|
||||
cwd = proc.cwd()
|
||||
# 从进程工作目录向上查找可能的数据目录
|
||||
parent_dirs = [cwd]
|
||||
current = cwd
|
||||
for _ in range(3): # 向上查找3级目录
|
||||
parent = os.path.dirname(current)
|
||||
if parent != current:
|
||||
parent_dirs.append(parent)
|
||||
current = parent
|
||||
else:
|
||||
break
|
||||
|
||||
for parent_dir in parent_dirs:
|
||||
for pattern in common_wechat_patterns:
|
||||
potential_dir = os.path.join(parent_dir, pattern)
|
||||
if os.path.exists(potential_dir) and has_wxid_directories(potential_dir):
|
||||
if potential_dir not in detected_dirs:
|
||||
detected_dirs.append(potential_dir)
|
||||
print(f"[DEBUG] 进程分析检测成功: {potential_dir}")
|
||||
except:
|
||||
pass
|
||||
except:
|
||||
pass
|
||||
|
||||
return detected_dirs
|
||||
|
||||
|
||||
# 删除了所有解密相关函数,解密功能已移至独立的wechat_decrypt.py脚本
|
||||
|
||||
def has_wxid_directories(directory):
|
||||
"""
|
||||
检查目录是否包含wxid格式的子目录
|
||||
:param directory: 要检查的目录
|
||||
:return: 是否包含wxid目录
|
||||
"""
|
||||
try:
|
||||
for item in os.listdir(directory):
|
||||
item_path = os.path.join(directory, item)
|
||||
if os.path.isdir(item_path) and (item.startswith('wxid_') or len(item) > 10):
|
||||
# 进一步检查是否包含数据库文件
|
||||
for root, _, files in os.walk(item_path):
|
||||
for file in files:
|
||||
if file.endswith('.db'):
|
||||
return True
|
||||
return False
|
||||
except:
|
||||
return False
|
||||
|
||||
def get_wx_dir_by_reg(wxid="all"):
|
||||
"""
|
||||
通过多种方法获取微信目录 - 改进的自动检测
|
||||
:param wxid: 微信id,如果为"all"则返回WeChat Files目录,否则返回具体wxid目录
|
||||
:return: 微信目录路径
|
||||
"""
|
||||
if not wxid:
|
||||
return None
|
||||
|
||||
# 使用新的自动检测方法
|
||||
detected_dirs = auto_detect_wechat_data_dirs()
|
||||
|
||||
if not detected_dirs:
|
||||
print(f"[DEBUG] 未检测到任何微信数据目录")
|
||||
return None
|
||||
|
||||
# 返回第一个检测到的目录
|
||||
wx_dir = detected_dirs[0]
|
||||
print(f"[DEBUG] 使用检测到的微信目录: {wx_dir}")
|
||||
|
||||
# 如果指定了具体的wxid,返回wxid目录
|
||||
if wxid and wxid != "all":
|
||||
wxid_dir = os.path.join(wx_dir, wxid)
|
||||
return wxid_dir if os.path.exists(wxid_dir) else None
|
||||
|
||||
return wx_dir if os.path.exists(wx_dir) else None
|
||||
|
||||
def detect_wechat_installation() -> Dict[str, Any]:
|
||||
"""
|
||||
检测微信安装情况 - 完全按照PyWxDump的逻辑实现
|
||||
"""
|
||||
result = {
|
||||
"wechat_version": None,
|
||||
"wechat_install_path": None,
|
||||
"wechat_exe_path": None,
|
||||
"wechat_data_dirs": [],
|
||||
"message_dirs": [],
|
||||
"databases": [],
|
||||
"version_detected": None,
|
||||
"is_running": False,
|
||||
"user_accounts": [],
|
||||
"detection_errors": [],
|
||||
"detection_methods": []
|
||||
}
|
||||
|
||||
# 进程检测 - 只检测Weixin.exe(按照用户要求)
|
||||
result["detection_methods"].append("进程检测")
|
||||
process_list = get_process_list()
|
||||
|
||||
for pid, process_name in process_list:
|
||||
# 只检查Weixin.exe进程
|
||||
if process_name.lower() == 'weixin.exe':
|
||||
try:
|
||||
exe_path = get_process_exe_path(pid)
|
||||
if exe_path:
|
||||
result["wechat_exe_path"] = exe_path
|
||||
result["wechat_install_path"] = os.path.dirname(exe_path)
|
||||
result["is_running"] = True
|
||||
result["detection_methods"].append(f"检测到微信进程: {process_name} (PID: {pid})")
|
||||
|
||||
# 尝试获取版本信息
|
||||
try:
|
||||
import win32api
|
||||
version_info = win32api.GetFileVersionInfo(exe_path, "\\")
|
||||
version = f"{version_info['FileVersionMS'] >> 16}.{version_info['FileVersionMS'] & 0xFFFF}.{version_info['FileVersionLS'] >> 16}.{version_info['FileVersionLS'] & 0xFFFF}"
|
||||
result["wechat_version"] = version
|
||||
result["detection_methods"].append(f"获取到微信版本: {version}")
|
||||
except ImportError:
|
||||
result["detection_errors"].append("win32api库未安装,无法获取版本信息")
|
||||
except Exception as e:
|
||||
result["detection_errors"].append(f"版本获取失败: {e}")
|
||||
break
|
||||
except Exception as e:
|
||||
result["detection_errors"].append(f"进程信息获取失败: {e}")
|
||||
|
||||
if not result["is_running"]:
|
||||
result["detection_methods"].append("未检测到微信进程")
|
||||
|
||||
# 2. 使用自动检测逻辑获取微信目录和数据库
|
||||
result["detection_methods"].append("目录自动检测")
|
||||
try:
|
||||
wx_dir = get_wx_dir_by_reg()
|
||||
if wx_dir and os.path.exists(wx_dir):
|
||||
result["wechat_data_dirs"].append(wx_dir)
|
||||
result["detection_methods"].append(f"通过自动检测找到微信目录: {wx_dir}")
|
||||
|
||||
# 使用PyWxDump的get_wx_db函数获取数据库信息
|
||||
db_list = get_wx_db(msg_dir=wx_dir) # 移除db_types限制,获取所有.db文件
|
||||
|
||||
# 统计用户账户和消息目录
|
||||
user_accounts_set = set()
|
||||
message_dirs_set = set()
|
||||
|
||||
for db_info in db_list:
|
||||
wxid = db_info["wxid"]
|
||||
wxid_dir = db_info["wxid_dir"]
|
||||
db_path = db_info["db_path"]
|
||||
db_type = db_info["db_type"]
|
||||
|
||||
# 添加用户账户
|
||||
user_accounts_set.add(wxid)
|
||||
message_dirs_set.add(wxid_dir)
|
||||
|
||||
# 添加数据库信息
|
||||
if os.path.exists(db_path):
|
||||
result["databases"].append({
|
||||
"path": db_path,
|
||||
"name": os.path.basename(db_path),
|
||||
"type": db_type,
|
||||
"size": os.path.getsize(db_path),
|
||||
"user": wxid,
|
||||
"user_dir": wxid_dir
|
||||
})
|
||||
|
||||
# 转换为列表
|
||||
result["user_accounts"] = list(user_accounts_set)
|
||||
result["message_dirs"] = list(message_dirs_set)
|
||||
|
||||
result["detection_methods"].append(f"检测到 {len(result['user_accounts'])} 个用户账户")
|
||||
result["detection_methods"].append(f"检测到 {len(result['databases'])} 个数据库文件")
|
||||
|
||||
# 按数据库类型统计
|
||||
db_type_count = {}
|
||||
for db in result["databases"]:
|
||||
db_type = db["type"]
|
||||
db_type_count[db_type] = db_type_count.get(db_type, 0) + 1
|
||||
|
||||
if db_type_count:
|
||||
type_summary = ", ".join([f"{k}({v})" for k, v in db_type_count.items()])
|
||||
result["detection_methods"].append(f"数据库类型分布: {type_summary}")
|
||||
else:
|
||||
result["detection_methods"].append("自动检测未找到微信目录")
|
||||
except Exception as e:
|
||||
result["detection_errors"].append(f"目录检测失败: {str(e)}")
|
||||
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def get_wechat_info() -> Dict[str, Any]:
|
||||
"""获取微信安装和数据库信息
|
||||
|
||||
返回值:
|
||||
包含微信信息的字典
|
||||
"""
|
||||
return detect_wechat_installation()
|
||||
Reference in New Issue
Block a user