免责声明:本文仅供学习使用,请遵守相关法律法规。

本文章仅适用于微信4.x版本。

开源:HShiDianLu/wx-dat2img

零、背景

女朋友发了张照片光速撤回导致我没看见。
(后注:4.x的微信在撤回消息后缓存文件会立即删除,因此无法还原)

最近tx封杀的好像很严,有关的资料越来越少,所以就想写一篇讲的比较全面文章从头到尾分析一遍。
本篇文章仅讨论如何解密 .dat 文件,不涉及包含聊天记录的数据库解密。

一、文件结构

4.x的微信的用户数据(聊天图片)可以在 %userprofile%/xwechat_files/wxid_xxx/msg/attach 里找到。
attach 文件夹是按照聊天对象(用户)去分类的,每个子文件夹里 Img 文件夹内存的就是缓存图片,后缀是 .dat。一个图片可能会有2个(或3个)对应的 .dat,其中文件名带 _t 的是缩略图。

二、第一层加密

2.1 加密方式

目前微信在不同版本对 .dat 的加密方式是不同的。
旧版本是纯xor,后来是xor+固定aes进行加密,而4.x是xor+动态aes加密(.dat 文件头将此加密称为V2)。
关于纯xor,就是把图片的二进制数据按位异或上一个特定值(一般是0x51或0x50,并非硬编码,可能有差异)。解密也相当容易,只需要把 .dat 再异或一次就可以。

2.2 key的获取

xor_key 的获取,在纯xor时代可以通过 png 或者 jpeg 的文件头特征码在 .dat 里反推得到,因为xor运算是可逆的。
V2不能再通过文件头,而是通过文件尾特征码反推,因为头是AES加密的。
(请注意一定要使用缩略图(文件名带 _t)反推xor,因为原图是经 WxAM 压缩过的,导致文件尾无特征码。)

aes_key 目前不清楚,应该可以靠暴力扫内存得到。
这两个key都可以用现有还存活着的(截止至2026.01.23)ycccccccy/wx_key 获取,具体方法不过多赘述。后续可能会在这里补充。

2.3 V2解密 & Approaching

根据找到现有的逆向代码分析,V2应该是通过aes、明文、xor三者拼接的方式完成加密的。用一个图直观的表示长这样:
通俗易懂版

文件头会定义一个 aes_sizexor_size,也就是aes和xor分别加密的长度(字节)。然后分段解密并拼接就可以得到原文件的二进制。

前15(0x00-0x0e)字节是头,其中:

  • 0x00-0x05是V2标识符:0x07 08 56 32 08 07\x07\x08V2\x08\x07);
  • 0x06-0x09是 aes_size(unsigned long);
  • 0x0A-0x0D是 xor_size(unsigned long);
  • 0x0E为占位符。

header

from Crypto.Cipher import AES
from Crypto.Util import Padding

def aesDecrypt(data):
    header, body = data[:0xF], data[0xF:]
    _, aes_size, xor_size = struct.unpack("<6sLLx", header)
    # Decrypt AES
    aes_size += AES.block_size - aes_size % AES.block_size  # 此处需要补一下 padding
    cipher = AES.new(AES_KEY, AES.MODE_ECB)
    aesData = Padding.unpad(cipher.decrypt(body[:aes_size]), AES.block_size)
    # Decrypt XOR
    if xor_size:
        rawData = body[aes_size:-xor_size]
        unXORData = bytes(i ^ XOR_KEY for i in body[-xor_size:])
    else:
        rawData = body[aes_size:]
        unXORData = b""
    return aesData + rawData + unXORData

2.4 限制

解密后的文件,一般来说如果是缩略图,那就可以直接查看了。
而如果是原图,你会发现这个文件是打不开的,看它的二进制数据也没有图片格式的特征头。这是因为微信使用 WxAM 对其进行了压缩,此时就需要进行第二层解密,也就是下文所说的内容。

三、第二层加密(WxAM)

3.1 关于WxAM(wxgf)

目前网络上对其的介绍极少,应该是微信内部的文件格式。其旨在对文件进行极限压缩。
从结果来看确实是的,有些图片的压缩率甚至能达到50%以上。

3.2 关于逆向

微信的 WxAM 实现在根目录下一个名为 VoipEnginedll 文件中。本人不会逆向,详细的逆向过程可以去看 WXAM(WXGF)图片格式解析
总之可以找到一个入口点 wxam_dec_wxam2pic_5(),然后用 ctypes 去调用 dll 就可以了。

3.3 文件头

wxgf 格式有也有自己的文件头,为 77 78 67 66wxgf)。

wxgf
因此如果想要检验第一层解密后的文件是不是 wxgf 格式,可以通过 startswith() 判断。

3.4 Approaching

wxam_dec_wxam2pic_5() 入口点接受5个参数,分别是(描述可能有误):

  • wxgf 二进制数据(int64
  • wxgf 字节长度(int
  • 解析后的二进制输出(int64
  • 解析后的字节长度(pointer->int
  • 配置项(Config)(int64

返回一个 int64

关于 Config
mode 代表解析格式,0jpeg1/2png3gif

部分代码实现来自 LifeArchiveProject/WeChatDataAnalysis

from ctypes import *

voip = WinDLL(DLL_PATH).wxam_dec_wxam2pic_5

class Config(Structure):
    _fields_ = [
        ("mode", c_int),
    ]

def wxgf2byte(data):
    maxSize = 64 * 1024 * 1024
    for i in [1, 2, 0, 3]:
        try:
            config = Config()
            config.mode = i
            inputBuffer = create_string_buffer(data, len(data))
            outputBuffer = create_string_buffer(maxSize)
            outputSize = c_int(maxSize)
            result = voip(
                addressof(inputBuffer),
                int(len(data)),
                addressof(outputBuffer),
                byref(outputSize),
                addressof(config),
            )
            if result != 0 or outputSize.value <= 0:
                continue
            output = outputBuffer.raw[:int(outputSize.value)]
            return output
        except Exception as e:
            continue
    return False

四、格式检查

很容易,可以通过文件头(或文件尾)检查。附注一些常用的格式:

  • png89 50 4E 47 / AE 42 60 82
  • jpegFF D8 FF / FF D9
  • gif47 49 46 38 / 00 3B

五、尾声

由于加密算法具有时效性,此种方法在后续版本中可能不再可用。
本文的一些细节还有待完善,可能会不定期更新。

再次免责声明:本文仅供学习使用,请遵守相关法律法规。

Reference / 参考

LifeArchiveProject/WeChatDataAnalysis

ycccccccy/wx_key

WXAM(WXGF)图片格式解析

如何节省 1TB 图片带宽?解密极致图像压缩

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐