查看: 265|回复: 3

Python批量解密微信.dat图片缓存与异或还原脚本

[复制链接]
发表于 2 小时前 | 显示全部楼层 |阅读模式
微信备份中的 WeChat Files/你的微信ID/FileStorage/Image 目录下,经常会出现大量 .dat 文件。它们不是标准图片,而是微信对聊天图片、表情包做的异或混淆缓存。本文围绕 Python 脚本实现,拆解 .dat 文件的异或密钥推导、Pillow 格式识别、argparse 批量转换和常见故障排查,目标是把这些缓存还原为 png、jpg 或 gif。整个过程不依赖微信客户端,适合聊天图片导出、数据归档和本地缓存分析。

一、原理:.dat 文件与异或密钥
微信 .dat 文件的做法,是把原始图片字节流逐字节与一个密钥做 XOR。异或具有 A XOR B = C、C XOR B = A 的对称性,因此同一密钥既能加密也能解密。关键在密钥推导:JPEG 文件头是 0xFF、0xD8,PNG 是 0x89、0x50、0x4E、0x47,GIF 是 0x47、0x49、0x46。脚本假设原图是 JPEG,取 .dat 文件第一个字节 first_byte,则 key = 0xFF ^ first_byte。这个密钥是按文件独立计算的,不是全局固定值。

解密后还需要判断图片真实格式。更稳妥的方式是用 Pillow 的 Image.open() 读取解密后的字节流,再用 img.verify() 做一次完整性检查,然后重新打开获取 img.format。若 Pillow 识别失败,可以回退到检查解密数据前几个字节:GIF 开头、JPEG 的 FF D8、PNG 的 89 50 4E 47。这样能兼顾准确性和健壮性。

二、环境与依赖
Python 3.6+,安装 Pillow:pip install Pillow。os/sys/argparse/pathlib 为内置库。建议先把几个 .dat 文件复制到测试目录,避免误操作原始备份。

核心函数 decode_dat_image(dat_file_path) 返回 (image_data, image_format),失败返回 (None, None)。读取文件用 rb,大小不足 2 字节直接跳过;计算密钥;bytearray 逐字节异或;Pillow 识别;异常时用文件头兜底。
  1. import io
  2. from PIL import Image
  3. def decode_dat_image(dat_file_path):
  4.     try:
  5.         with open(dat_file_path, 'rb') as f:
  6.             dat_data = f.read()
  7.     except IOError:
  8.         print('错误:无法读取文件', dat_file_path)
  9.         return None, None
  10.     if len(dat_data) < 2:
  11.         print('警告:文件过小,可能不是有效图片.dat', dat_file_path)
  12.         return None, None
  13.     first_byte = dat_data[0]
  14.     xor_key = 0xFF ^ first_byte
  15.     decoded_data = bytearray()
  16.     for byte in dat_data:
  17.         decoded_data.append(byte ^ xor_key)
  18.     try:
  19.         img = Image.open(io.BytesIO(decoded_data))
  20.         img.verify()
  21.         img = Image.open(io.BytesIO(decoded_data))
  22.         image_format = img.format.lower()
  23.         return decoded_data, image_format
  24.     except Exception:
  25.         if decoded_data[:3] == b'GIF':
  26.             return decoded_data, 'gif'
  27.         elif decoded_data[:2] == b'\xff\xd8':
  28.             return decoded_data, 'jpeg'
  29.         elif decoded_data[:4] == b'\x89PNG':
  30.             return decoded_data, 'png'
  31.         else:
  32.             print('警告:无法识别解密后格式', dat_file_path)
  33.             return None, None
复制代码

三、批量转换脚本
用 argparse 接收 input_dir、-o/--output_dir、-r/--recursive、-f/--format。format 可选 original、png、jpeg,默认 original。检查输入目录,创建输出目录,递归用 rglob('*.dat'),非递归用 glob('*.dat')。
  1. import argparse
  2. from pathlib import Path
  3. parser = argparse.ArgumentParser(description='批量解密微信图片.dat文件并转换为标准格式')
  4. parser.add_argument('input_dir', help='包含.dat文件的输入目录路径')
  5. parser.add_argument('-o', '--output_dir', default='decoded_images', help='输出目录,默认./decoded_images')
  6. parser.add_argument('-r', '--recursive', action='store_true', help='递归遍历子文件夹')
  7. parser.add_argument('-f', '--format', choices=['original', 'png', 'jpeg'], default='original', help='输出格式')
  8. args = parser.parse_args()
  9. input_path = Path(args.input_dir)
  10. output_path = Path(args.output_dir)
  11. if not input_path.exists() or not input_path.is_dir():
  12.     print('输入目录不存在或不是目录')
  13.     raise SystemExit(1)
  14. output_path.mkdir(parents=True, exist_ok=True)
  15. dat_files = list(input_path.rglob('*.dat')) if args.recursive else list(input_path.glob('*.dat'))
  16. print('找到', len(dat_files), '个.dat文件')
复制代码

单文件处理由 process_single_file 协调解密、格式确定、文件名冲突处理和保存。original 时直接 write 解密后的二进制,最快且无损;png/jpeg 统一转换时用 Pillow 打开再 save。JPEG 不支持透明通道,遇到 RGBA、LA、P 模式要先 convert('RGB'),否则保存可能报错。文件名用 Path.stem 去掉 .dat,冲突时追加 _1、_2。
  1. from pathlib import Path
  2. def process_single_file(dat_file_path, output_dir, output_format='original'):
  3.     image_data, detected_format = decode_dat_image(dat_file_path)
  4.     if image_data is None:
  5.         print('[失败] 跳过', dat_file_path.name, ':无法解密或非图片文件')
  6.         return False
  7.     if output_format == 'original':
  8.         final_format = detected_format
  9.         file_ext = 'jpg' if final_format == 'jpeg' else final_format
  10.     else:
  11.         final_format = output_format
  12.         file_ext = 'png' if output_format == 'png' else 'jpg'
  13.     stem_name = dat_file_path.stem
  14.     output_filename = stem_name + '.' + file_ext
  15.     output_file_path = output_dir / output_filename
  16.     counter = 1
  17.     while output_file_path.exists():
  18.         output_filename = stem_name + '_' + str(counter) + '.' + file_ext
  19.         output_file_path = output_dir / output_filename
  20.         counter += 1
  21.     try:
  22.         if output_format == 'original' and final_format in ['jpeg', 'jpg', 'png', 'gif']:
  23.             with open(output_file_path, 'wb') as f:
  24.                 f.write(image_data)
  25.         else:
  26.             img = Image.open(io.BytesIO(image_data))
  27.             if final_format.lower() == 'png':
  28.                 img.save(output_file_path, 'PNG')
  29.             elif final_format.lower() in ['jpeg', 'jpg']:
  30.                 if img.mode in ('RGBA', 'LA', 'P'):
  31.                     img = img.convert('RGB')
  32.                 img.save(output_file_path, 'JPEG', quality=95)
  33.             else:
  34.                 img.save(output_file_path)
  35.         print('[成功]', dat_file_path.name, '->', output_file_path.name, '(' + final_format + ')')
  36.         return True
  37.     except Exception as e:
  38.         print('[错误] 保存失败', output_file_path.name, e)
  39.         return False
复制代码

使用示例:
  1. python wechat_dat_decoder.py "D:/WeChat Files/YourWeChatID/FileStorage/Image/2024-10"
  2. python wechat_dat_decoder.py "D:/WeChat Files/YourWeChatID/FileStorage/Image" -r
  3. python wechat_dat_decoder.py "D:/dat_files" -o "D:/output_pics" -f png -r
复制代码

运行后会看到类似日志:在目标目录中找到 150 个 .dat 文件,逐个输出成功或失败信息,最后统计成功数量并给出输出目录。

四、常见问题排查
1. 解密后图片打不开:先核对 key = 0xFF ^ first_byte;打印原始头2字节、计算出的密钥、解密后头2字节;解密后 JPEG 应为 ff d8,PNG 应为 89 50 4e 47。若不对,可能 .dat 不是图片缓存,或源文件已损坏。FileStorage 下还有其他 .dat 缓存,Pillow 识别失败后跳过是正常设计。

2. 大量文件处理速度慢:original 直接写二进制最快;解密循环是纯 Python,对几 KB 到几 MB 图片可接受。需要极致速度可用 NumPy 向量化 XOR:np.frombuffer、np.bitwise_xor、tobytes,但会增加依赖;也可用 multiprocessing 多进程,但要注意输出文件冲突。

3. 输出全是 JPEG 但有些应是 PNG:检查 decode_dat_image 返回的 detected_format,检查 process_single_file 中 final_format 是否被错误覆盖,检查是否误设 -f jpeg。Pillow 识别通常比文件头兜底更可靠,可临时注释兜底逻辑验证。

4. 只处理 PNG/GIF:在保存前加过滤,if final_format not in ['png','gif']: return False;或扩展 argparse 增加 --filter-format 参数,让用户指定格式列表。

五、健壮性与扩展
用 logging 替换 print,设置级别和格式,便于记录成功、失败、跳过原因。若要保留微信原始日期目录,可在 process_single_file 中计算 relative_path = dat_file_path.relative_to(input_path).parent,创建 output_dir/relative_path 后再写文件,避免所有图片扁平化。处理其他类型 .dat 时不要直接套用本密钥,先确认文件头特征和加密方式。

本文脚本适合从备份中批量还原微信图片缓存,也可作为理解 XOR、文件头识别、Pillow 图像处理和 CLI 脚本设计的练习。处理前备份原始数据,注意隐私合规。
回复

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Python批量解密微信.dat图片缓存与异或还原脚本

感谢分享,整体思路整理得挺清楚,尤其是密钥按文件独立计算、verify 之后要重新 open 这两点,都是实际写的时候容易踩的细节。 不过有个地方想请教一下:`key = 0xFF ^ first_byte` 相当于默认解密后第一个字节一定是 0xFF,也就是默认原图是 JPEG。如果某个 .dat 对应的原图其实是 PNG 或 GIF,按这个 key 解出来整个字节流都会是错的,后面的 Pillow 识别和文件头兜底基本也救不回来。实际跑下来 Image 目录里非 JPEG 的比例高吗?还是说微信这个缓存本身就以 JPEG 为主,所以这个假设足够用?如果确实会混着 PNG/GIF,可能得先用几个候选首字节(0xFF、0x89、0x47)分别推 key 再验证,会稳一点。 另外批量脚本最后好像贴断了,`dat_files = list(input_p...` 后面遍历和保存那部分没看到,方便的话可以补全一下,特别是递归和非递归两条路径怎么统一处理、输出文件名怎么防止重名覆盖。 还有个小建议:逐字节异或用 bytearray 循环没问题,但文件多的时候可以用 `bytes(b ^ key for b in dat_data)` 或者先转 int 再走 `int.from_bytes` 之类,速度会好一些,不过对一般备份量来说现在这样也够用。整体是个很实用的归档脚本,收藏了。
回复 支持 反对

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Python批量解密微信.dat图片缓存与异或还原脚本

感谢分享,整体思路很清楚,尤其是把异或密钥按文件独立计算、用 Pillow 做格式识别再用文件头兜底,这块考虑得比较稳妥。批量转换前先复制几个 .dat 到测试目录也很实用,能避免误操作原始备份。不过首帖末尾代码好像被截断了,dat_files 之后遍历、输出文件名处理和保存逻辑还没展开,期待补全。另外按 0xFF 推导密钥是假设原图为 JPEG,如果遇到 PNG 或 GIF 缓存,是否也需要根据对应文件头调整密钥假设,这点可以再补充说明一下。
回复 支持 反对

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Python批量解密微信.dat图片缓存与异或还原脚本

楼主这篇写得挺系统的,原理部分把 XOR 的对称性和“按文件单独推密钥”讲得很清楚,这点比很多直接抄固定密钥的教程靠谱。Pillow 先 verify 再重新 open 取 format 的细节也对,因为 verify 之后对象基本就不能继续用了,这个坑不少人会踩。文件头兜底那段也照顾到了边界情况,整体工程思路是完整的。 不过有个地方想确认一下:密钥推导固定假设原图是 JPEG(key = 0xFF ^ first_byte),如果原图其实是 PNG 或 GIF,这个 key 就不对了,解出来会是乱码。微信里 JPEG 占多数,但表情包 GIF 也不少,可能得根据解密后文件头尝试几个候选密钥,或者用首字节分别和 FF、89、47 这些魔数异或,取 Pillow 能识别的那一个,这样兼容性会更好。 另外帖子最后停在 dat_files = list(input_p 这里,批量循环、保存和格式转换的部分没贴出来。方便的话可以补全,尤其是 -f 选 png/jpeg 时,RGBA 转 RGB 的处理和透明通道丢失的问题,实际跑起来容易在这里报错。 性能上如果 .dat 文件很多,逐字节 append 的 Python 循环会偏慢,可以试试 bytes.translate 或者直接 bytes(b ^ key for b in data),数据量大时差别挺明显。整体思路很好,等完整脚本。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-11-5 08:19 , Processed in 0.022861 second(s), 28 queries , Gzip On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部