修复无法获取wxid的bug,更新部分逻辑,重构解密脚本的返回值,重构命令行参数
This commit is contained in:
parent
bfbf488fff
commit
54a417be60
112
README.md
112
README.md
@ -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. 等等...............
|
||||
|
||||
[](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.
|
||||
```
|
||||
```
|
||||
|
||||
|
@ -51,19 +51,18 @@ KEY的基址即:**2FFF970-000024=2FFF94C**
|
||||
|
||||
十进制地址为:50329932
|
||||
|
||||
代码块中的五个十进制按顺序代表:微信昵称、微信账号、微信手机号、微信邮箱(高版本失效,这个随便填)、微信KEY、微信原始ID(wxid_******)
|
||||
代码块中的五个十进制按顺序代表:微信昵称、微信账号、微信手机号、微信邮箱(高版本失效,这个随便填)、微信KEY
|
||||
|
||||
```jsx
|
||||
```json
|
||||
{
|
||||
"微信版本号",
|
||||
new List<int>
|
||||
{
|
||||
"微信版本号":
|
||||
[
|
||||
50320784,
|
||||
50321712,
|
||||
50320640,
|
||||
38986104,
|
||||
50321676
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -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")
|
||||
|
@ -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__':
|
||||
|
@ -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__':
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
]
|
||||
}
|
@ -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)
|
||||
|
@ -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) # 读取微信信息
|
||||
|
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:
|
||||
long_description = fh.read()
|
||||
|
||||
version = "2.2.2"
|
||||
version = "2.2.5"
|
||||
setup(
|
||||
name="pywxdump",
|
||||
author="xaoyaoo",
|
||||
|
Loading…
Reference in New Issue
Block a user