ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

微信PC版图片DAT文件解密与批量恢复实战(附Python脚本)

微信PC版图片DAT文件解密与批量恢复实战(附Python脚本) 在微信PC版的数据目录里翻过一遍的人一定见过一片片的.dat文件。它们的体积看着不小但双击没有任何程序能打开。很多人在换电脑、装系统或者从老硬盘里抢救聊天记录时被这里卡住图片明明还在就是读不出来。这篇文章我会把微信DAT图片从定位到还原的完整链路讲透包括微信4.0改版后的目录变化、XOR密钥的手工推导方法、一个可以直接跑的Python批量解密脚本以及我实际恢复几千张图片时踩过的坑。适合两类人一类是单纯想把自己聊天图片找回来的普通用户另一类是拿到DAT文件后想写个小工具练手的开发者。1. 先别急着解密DAT是戴了面具的图片不是另一种格式1.1 微信PC端图片是怎么落到硬盘上的很多人以为微信聊天图片在电脑上就是普通的jpg文件打开数据目录才发现全是.dat。这其实是微信PC版的存储设计图片到达本机后并不会以原始格式直接写盘而是先经过一次轻量级的字节变换再以.dat作为扩展名保存。你可以把这个过程理解成给图片戴了个面具——文件体积基本不变但正因为面具的存在所有看图软件都无法识别它。从实际目录观察来看图片经历的大致链路是服务器下发图片数据 → 微信客户端写入本地缓存 → 按会话和月份归档到FileStorage下的MsgAttach目录 → 生成img_xxx_xxx.dat这样的文件。也就是说每一张你收到过的图片最后都会以.dat形式躺在硬盘上躲过普通人的肉眼浏览。这个设计从工程上看更像一个防呆措施而不是真正意义上的加密。它没有复杂的密钥体系没有初始化向量甚至没有防止重复使用的滚动机制。搞清楚这一点恢复工作就变得非常可控。1.2 单字节XOR伪加密整把锁只有一把钥匙微信PC端图片DAT采用的算法是典型的单字节XOR异或处理。用公式表示就是加密后字节[i] 原始图片字节[i] XOR 密钥Key其中Key是一个0到255之间的整数。整个过程没有任何分组、填充或迭代只用一个字节的钥匙对图片的每个字节做按位异或。解密就是把同一个Key再XOR回去——因为XOR运算具有自反性原始字节 加密字节 XOR Key。为什么说这能恢复因为图片文件本身在结构上有一个无法抹掉的特征文件头魔数。几乎每种常见图片格式的前几个字节都是固定的这就是门缝里透出来的光。图片格式文件头魔数十六进制常见用途JPGFF D8 FF聊天照片、原图PNG89 50 4E 47截图、部分表情/头像GIF47 49 46 38动图表情包BMP42 4D极少见WEBP52 49 46 46部分长图、新表情只要DAT文件的前两个字节和我们猜的格式魔数做异或得到同一个Key就能确定格式和密钥。这就是所谓已知明文攻击的思路——我们虽然不知道钥匙但猜得到明文的前几个字节。1.3 为什么可以一个Key解密整个账号的DAT在实际恢复过程中有个非常省事的规律同一个微信账号在本机生成的DAT文件用的通常是同一个Key。这一点并不是微信官方文档里写的而是过去几年各个开源恢复工具反复验证得出的经验观察。原因也好理解客户端在启动或初始化时生成一次Key后续所有图片都复用它省去为每张图片单独保存密钥的成本。这就意味着只要从一堆DAT文件里成功推导出任何一个文件的Key整个账号目录下的图片都能批量还原。严谨一点说不排除某些特殊账号或新版客户端会混用多个Key所以脚本里我对每个文件都做了独立识别校验遇到识别不出来的文件会单独报出来而不是闷头全解。搞明白了原理下一步就是锁定这些DAT文件到底藏在哪。2. 定位DAT文件新旧版本目录差异与三种实用姿势2.1 最稳的入口微信设置里的打开文件夹不管微信怎么改版最不会出错的定位方式永远是让微信自己告诉你数据目录在哪。打开电脑版微信进入左下角菜单的设置找到文件管理里面会显示当前文件保存路径并且有一个打开文件夹按钮。点一下资源管理器直接跳到真正的数据根目录。这个做法尤其重要因为很多人会把微信文件存到非默认位置比如D盘、移动硬盘或者自定义的E:\WeChatData。网上所有默认路径教程都可能在你的机器上失效但设置在菜单里是真实不变的。我恢复数据的第一步永远是先跑这里看一眼再去猜下面的子目录结构。另外提醒一句如果这台电脑登录过多个微信账号文件管理里的路径只是其中一个账号的数据目录其他账号一般在同级目录下按账号ID区分文件夹名扫描时要各扫各的。2.2 微信3.x时代的老目录结构如果你用的是微信3.x包括前几年各种永久可登录版默认数据根目录是C:\Users\你的用户名\Documents\WeChat Files\wxid_xxxxxxx\重点在账号文件夹里面。早期版本2.x到3.0左右的图片DAT直接放在WeChat Files\wxid_xxx\FileStorage\Image\2024-09\...后来3.x中后期版本做了调整图片数据被挪到了MsgAttach下按会话目录再套一层WeChat Files\wxid_xxx\FileStorage\MsgAttach\会话目录\Image\2024-09\...这里的会话目录通常是一串数字或十六进制字符和人名完全对不上号但它对应的是某一个聊天对象或群聊。普通用户没必要去逐个映射会话目录对应的联系人因为恢复目标是图片本身而不是还原聊天界面。直接对整个账号文件夹做递归扫描把所有.dat捞出来处理就行。2.3 微信4.0 for Windowsxwechat_files带来的变化微信4.0 for Windows发布之后数据目录最大的变化是把根目录从WeChat Files换成了xwechat_files。在我观察到的4.0.x版本上路径大致长这样C:\Users\你的用户名\Documents\xwechat_files\wxid_xxx\msg\attach\...FileStorage这套老命名在4.0里被拆掉了聊天文件、图片、音频分别归入msg下的不同子目录。图片DAT依然存在通常藏在msg\attach下面某个会话目录的Image子目录里加密规则也延续了单字节XOR这套老逻辑。这里必须说句实在话4.0目前迭代速度很快不同小版本之间的子目录命名可能有出入如果你照着我上面的路径没找到别急着怀疑数据丢了。正确操作是先在设置里打开文件夹然后在msg目录下按Image或attach关键词逐层找或者干脆让脚本递归扫整个xwechat_files。搜索依赖路径记忆扫描依赖文件系统后者永远更可靠。另外Linux版微信和企业微信的数据目录结构和Windows版不一样不一定会使用这套DAT逻辑标题里那些在Ubuntu、麒麟系统上装微信的场景先确认一下文件扩展名是不是.dat再套用本文方案。2.4 Mac端和新旧版本目录不一致的处理Mac版微信数据放在用户资源库的容器目录里大致路径是~/Library/Containers/com.tencent.xinWeChat/Data/Library/Application Support/com.tencent.xinWeChat/版本号/账号ID/Message/MessageTemp/会话哈希/Image/恢复原理与Windows一致脚本可以直接用只是路径入口不同。很多人看到这里会问电脑上同时装了微信3.x和4.0旧WeChat Files目录和新xwechat_files目录并存能不能手动改名合并我的建议是不要。微信4.0提供了旧聊天记录导入能力想完整迁移就让它自己导单纯为了恢复图片的话更不需要动目录结构直接把脚本指向旧的WeChat Files扫描即可微信没在跑也完全不影响解密效果。3. 手工推导XOR密钥一把十六进制编辑器就够了3.1 读出DAT文件头的十六进制字节在写脚本之前我建议你先亲手做一次密钥推导。这步能帮你直观理解原理也能在脚本失灵时靠手算排查问题。用HxD免费或010 Editor打开任意一个DAT文件看最前面的几个字节。比如我随便拿一个恢复过的DAT举例它开头是77 50 77 31 32 33 41 F1先别管后面的字节只看前三个77 50 77。3.2 两分钟手算钥匙假定它是JPG聊天图片里最普遍那么原图开头应该是FF D8 FF。用DAT的第一个字节和JPG第一个字节做异或0x77 XOR 0xFF 0x88用第二个字节验证0x50 XOR 0xD8 0x88再用第三个字节验证0x77 XOR 0xFF 0x88三个字节都推出同一个Key0x88几乎可以断定这是JPG图片密钥就是0x88。如果第二、第三字节算下来不是同一个数说明格式猜错了换成PNG的89 50 4E再试。再举个例子。假如某DAT开头是A1 78 66 2D ...按PNG的89 50 4E 47来算0xA1 XOR 0x89 0x28 0x78 XOR 0x50 0x28 0x66 XOR 0x4E 0x28密钥0x28PNG格式确认无误。这个流程的关键在于判断格式时不要只看第一个字节至少用前两个字节交叉验证能凑齐第三个、第四个字节成功率更高。单个字节凑巧命中的概率是1/256两个字节同时命中就是1/65536三个字节基本可以排除误判。3.3 批量操作前的最后一道确认手工算出Key后别急着对着几千个文件手动操作。先挑另外三五个DAT文件分别套用这个Key算一遍开头字节看它们是否能还原出合法的图片文件头。比如Key是0x88随便再拿一个DAT第一个字节如果是0x77异或0x88得到0xFF第二个字节如果是0x50异或得到0xD8这就说明这个文件也是同一把钥匙开的JPG。多验证几个后就可以放心进入批量解密环节了。这里多提一句如果你手头没有现成的批量工具也可以先用010 Editor这类支持二进制运算的编辑器加载整个DAT后手动对整个文件做XOR 0x88再导出为.jpg。但只适合小文件面对几千张图片效率太低所以下一节的脚本才是正经解法。4. Python批量解密实战自动识别类型、逐字节还原4.1 为什么自己写脚本而不是直接用现成的微信Dat文件查看器网上搜微信dat文件查看器能找到不少工具但我见过太多单文件查看器只支持一张一张拖进去看完全没有批量导出能力。还有一部分工具来源不明既要联网又要读取整个目录把聊天图片这种非常私密的数据交给这样的程序风险完全不值得冒。自己写脚本的好处有三个第一代码在你手里它读什么文件、传什么数据出去完全透明第二可以按原目录结构批量还原恢复几千张图也就几分钟第三遇到新版本微信改目录、改文件名规则改两行代码就能继续用。下面这个脚本我用了很多次核心逻辑只依赖Python标准库不需要安装任何第三方包。4.2 完整脚本与运行说明把下面代码保存为wechat_dat_recover.py在命令行里运行。脚本会递归扫描--src目录下所有.dat文件自动识别图片类型和密钥解密后按原路径结构输出到--dst目录最后打印成功数、失败数和类型统计。#!/usr/bin/env python3 # -*- coding: utf-8 -*- 微信DAT图片批量恢复脚本 用法: python wechat_dat_recover.py --src 待扫描目录 --dst 输出目录 可选: --key 0x88 手动指定异或密钥 import os import sys import argparse from datetime import datetime # 常见图片格式的文件头魔数前 N 个字节 MAGIC { jpg: (0xFF, 0xD8, 0xFF), png: (0x89, 0x50, 0x4E, 0x47), gif: (0x47, 0x49, 0x46, 0x38), bmp: (0x42, 0x4D), webp: (0x52, 0x49, 0x46, 0x46), } def detect_type_and_key(header): 根据文件头识别图片类型并反推 XOR 密钥 if len(header) 2: return None, None for ext, magic in MAGIC.items(): key header[0] ^ magic[0] if header[1] ^ magic[1] ! key: continue # 用剩余字节做二次校验能显著降低误判率 matched True for i in range(2, min(len(header), len(magic))): if header[i] ^ key ! magic[i]: matched False break if matched: return ext, key return None, None def decrypt_file(src_path, dst_path, key): 把 DAT 文件按单字节 XOR 还原 with open(src_path, rb) as fin, open(dst_path, wb) as fout: while True: chunk fin.read(1 20) # 1MB if not chunk: break fout.write(bytes(b ^ key for b in chunk)) def main(): parser argparse.ArgumentParser(description微信DAT图片批量恢复) parser.add_argument(--src, requiredTrue, helpDAT文件所在目录) parser.add_argument(--dst, requiredTrue, help还原结果输出目录) parser.add_argument(--key, typelambda x: int(x, 0), defaultNone, help可选手动指定密钥例如 --key 0x88) args parser.parse_args() src_dir os.path.abspath(args.src) dst_dir os.path.abspath(args.dst) if src_dir dst_dir: print(错误输出目录不能与源目录相同) sys.exit(1) os.makedirs(dst_dir, exist_okTrue) dat_files [] for root, _dirs, files in os.walk(src_dir): for name in files: if name.lower().endswith(.dat): dat_files.append(os.path.join(root, name)) total len(dat_files) print(找到 DAT 文件数量:, total) if total 0: print(没找到任何 .dat 文件请检查目录路径是否正确) return ok_count 0 failed [] type_counter {} start_time datetime.now() for index, dat_path in enumerate(dat_files, 1): rel_path os.path.relpath(dat_path, src_dir) try: with open(dat_path, rb) as f: header f.read(8) ext, key detect_type_and_key(header) if key is None: if args.key is None: failed.append((rel_path, 无法自动识别类型/密钥)) print(f[{index}/{total}] 跳过: {rel_path} (无法识别)) continue ext, key img, args.key out_path os.path.join(dst_dir, os.path.splitext(rel_path)[0] . ext) out_dir os.path.dirname(out_path) if out_dir: os.makedirs(out_dir, exist_okTrue) decrypt_file(dat_path, out_path, key) ok_count 1 type_counter[ext] type_counter.get(ext, 0) 1 print(f[{index}/{total}] 已还原: {rel_path} - {ext} (key0x{key:02X})) except Exception as exc: failed.append((rel_path, str(exc))) print(f[{index}/{total}] 失败: {rel_path} 原因: {exc}) print() print(耗时:, datetime.now() - start_time) print(成功:, ok_count, 失败:, len(failed)) print(类型统计:, type_counter) if failed: print() print(失败的 DAT 文件前50个:) for rel_path, reason in failed[:50]: print(-, rel_path, 原因:, reason) if __name__ __main__: main()脚本的核心逻辑就三块识别、解密、归档。识别函数对每个文件读取前8字节逐一用表格里的魔数反推密钥并用后续字节交叉验证解密函数按1MB分块读写避免一次性把大文件读进内存路径处理上保留原始相对路径方便恢复完后按原目录结构回看。4.3 运行示例与实测输出在Windows命令行或PowerShell里这样运行python wechat_dat_recover.py --src C:\Users\me\Documents\WeChat Files\wxid_xxx\FileStorage\MsgAttach --dst D:\wechat_recovered如果是从某个子目录恢复直接把--src指到该子目录。输出大致长这样找到 DAT 文件数量: 12840 [1/12840] 已还原: 2024-09\img_20240910_001.dat - jpg (key0x88) [2/12840] 已还原: 2024-09\img_20240910_002.dat - jpg (key0x88) [3/12840] 已还原: 2024-09\img_20240910_003.dat - png (key0x88) ... 耗时: 0:04:32 成功: 12796 失败: 44 类型统计: {jpg: 11020, png: 1701, gif: 75}在我一台普通机械硬盘的机器上两万张左右的DAT文件跑下来大概五到八分钟瓶颈主要在磁盘读取纯解密计算量其实很小。如果换到固态硬盘速度还能快不少。4.4 脚本跑完后如何验证结果恢复完成后别急着关命令行。先在输出目录里随便打开几张图片看看能否正常预览再抽查几个文件确认扩展名和真实格式一致。Linux或macOS下可以用file命令直接识别file D:\wechat_recovered\2024-09\img_20240910_001.jpg如果输出显示JPEG image data说明解密正确。如果显示data或者乱码那就是密钥或类型判断出了问题继续看下一节的排查方法。5. 恢复踩坑实录类型识别失败、扩展名错乱与二次备份教训5.1 类型识别失败的三种真实原因脚本运行完总会有一些文件报无法自动识别类型/密钥。根据我这几年的恢复经验最常见的就三种情况。第一种是目录选错。你扫描的范围不只是图片目录里面混入了微信的其他DAT文件。比如表情包缓存、头像缓存、甚至某些数据库临时文件也用了.dat作扩展名但它们根本不是图片自然无法用图片魔数识别。解决办法是缩小扫描范围尽量指向具体的Image或Image\年份\月份目录。第二种是多账号混扫。一台电脑上登过多个微信账号不同账号的DAT文件密钥不同。如果脚本在A账号目录里扫到了B账号的残留文件识别不出来很正常。按账号文件夹分开处理即可。第三种是云端占位文件。如果你的微信数据目录被OneDrive、坚果云这类工具同步过而同步尚未完成本地可能只有零字节或部分下载的占位文件。读取时文件头长度不够脚本直接跳过。处理办法是等同步完成或者暂时暂停同步后再扫。5.2 解密成功但图片打不开问题通常出在扩展名有读者遇到过这种情况脚本明明显示已还原: xxx.jpg但双击就是打不开。这多半是Windows资源管理器只看扩展名而文件真实内容并不是JPG。原因可能是脚本在识别时只用了前两个字节误判了格式。正常来说前三个字节同时匹配基本不会错但如果遇到的图片格式比较偏门比如某些WebP长图在识别时缺少足够字节校验扩展名就会标错。解法很粗暴用file命令或HxD重新检查真实格式手动改扩展名。还有一种常见情况是Windows自带的照片查看器不支持WebP。就算扩展名和格式都对老版本的Windows照片查看器照样打不开WebP文件。换成Chrome浏览器拖进去看或者装一个WebP图像扩展就能正常显示。这不是解密失败是系统缺解码器。5.3 恢复出来的图片能在聊天窗口里重新显示吗这是最容易被误解的一点。把DAT批量还原成jpg只是拿回了图片文件本身并不会让这些图片重新出现在微信聊天窗口里。聊天窗口显示图片需要微信的消息索引数据库配合文件恢复和聊天记录完整性恢复是两个层面的事。如果你的目标是从老硬盘里把图捞出来这节内容已经完全够了。如果你想的是还原后微信聊天记录像以前一样显示图片那需要连同消息数据库一起处理复杂度会高很多而且新版微信数据库结构和旧版差异较大不建议普通用户轻易尝试。5.4 动手前的一分钟保护动作这节是我个人最想强调的。恢复操作本身是只读加密文件、写出新文件的过程逻辑上不会损坏源数据但不代表可以毫无顾忌地乱跑。动手前花一分钟做三件事。第一关掉微信客户端避免文件被占用或正在写入扫描时读到半截文件会导致识别失败。第二确认输出目录在另一个盘或者至少不在源目录内部防止脚本异常时把输出文件又当成DAT扫一遍。第三如果数据特别重要先把DAT目录整体复制一份到移动硬盘再做任何操作给自己留一条绝对安全的后路。我吃过一次亏有一次为了贪快直接在源目录旁边输出结果扫描函数的目录遍历逻辑写得不严谨把输出目录里的jpg又扫了一遍虽然没删源文件但浪费了大半小时排查为什么DAT数量越扫越多。从那以后我写恢复脚本时一律强制校验源目录和输出目录不能相同这个习惯一直保留到现在。最后再分享一个实际恢复中的小技巧如果扫描结束后失败列表里有大量文件而你又确实知道这批DAT是某个特定账号的可以先用前面几节的方法手动推算其中一个文件的密钥再用--key 0x88这种强制密钥方式跑一遍。强制密钥会把所有无法识别的DAT都按指定密钥解密并标成img扩展名解完后用file命令批量识别把真正的图片挑出来其他杂项删掉即可。这条曲线救国的路子在处理那些文件头被截断或格式特殊的DAT时非常顶用。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进