记录如何通过内存镜像提取微信密钥并解密数据库
字数 2311
更新时间 2026-04-22 13:54:29

从微信(WeChat/Weixin)NT架构内存镜像提取密钥并解密数据库教学文档

1. 概述

本文档详细讲解如何从一个正在运行或已转储的微信(4.x NT架构版本)进程内存镜像中,提取用于加密本地数据库的密钥,并完成数据库的解密操作。该方法适用于数字取证、安全研究及CTF竞赛等场景。核心原理是利用内存取证工具遍历内存中的特定数据模式,获取加密密钥,随后实现数据库文件的解密。

2. 环境与工具准备

进行本操作前,需准备以下环境与工具:

  • 内存镜像文件: 微信进程的内存转储文件(例如 .dmp 文件)。
  • Volatility 3 (vol3): 开源内存取证框架,用于分析内存镜像,提取进程信息和进行内存转储。
  • Navicat 或其它 SQLite 数据库查看工具: 用于验证解密后的数据库。
  • Python 3 环境: 用于运行密钥搜索和解密脚本,需安装 pycryptodome 库以支持AES加解密。
    • 安装命令:pip install pycryptodome
  • 文本编辑器或 IDE: 用于编写和修改Python脚本。

3. 分析步骤详解

3.1 确认微信进程与版本

首先,需要从内存镜像中定位微信进程,并确认其版本,以确定后续的分析方法。

  1. 列出进程: 使用 vol3windows.pslist 插件列出所有进程,并过滤出微信进程。在微信4.x(NT架构)中,主进程名已从 WeChat.exe 更改为 Weixin.exe

    vol -f <内存镜像文件路径> windows.pslist | grep -i "Weixin.exe"
    

    命令输出示例:

    10892    0      6736   0.0  Weixin.exe  0x900a5ae0b080  ...  N/A
    2444  10892      14   -     Weixin.exe  0x900a56cd8080  ...  N/A
    

    其中,10892 是微信的主进程PID(父PID为0),这是后续分析的关键。

  2. 确认版本号: 通过列出该进程加载的DLL模块,确认微信的确切版本。微信4.x的核心模块为 Weixin.dll

    vol -f <内存镜像文件路径> windows.dlllist --pid <主进程PID> | grep -i "Weixin.dll"
    

    命令输出示例:

    10892  Weixin.exe  0x7ffd3f300000  ...  Weixin.dll  C:\Program Files\Tencent\Weixin\4.1.8.67\Weixin.dll
    

    由此可确认微信版本为 4.1.8.67。版本信息对于判断加密方式至关重要。

3.2 转储目标进程内存

为了高效地在内存中搜索密钥,需要将目标进程的完整内存空间转储为一个单独的 dmp 文件。

  • 使用 vol3windows.memmap 插件,并指定 --dump 参数:
    vol -f <内存镜像文件路径> windows.memmap --pid <主进程PID> --dump
    
  • 命令执行成功后,会在当前目录生成一个名为 pid.<PID>.dmp 的文件(例如 pid.10892.dmp)。此文件大小通常超过1GB,包含了该进程的全部内存数据。

3.3 遍历并提取可能的密钥

微信NT架构版本(4.x)的数据库加密密钥在内存中以一种特定格式存储。我们可以通过编写Python脚本,使用正则表达式在转储的内存文件中搜索该模式。

密钥格式: 在内存中,密钥通常表现为一个连续的96位十六进制字符串,可拆分为一个64位的“衍生原始密钥”(Derived Raw Key)和一个32位的“盐”(Salt),格式类似 X'<64位key><32位salt>'

以下是搜索脚本 searchkey.py 的代码与解释:

import re
import os
import mmap

DUMP_FILE = "pid.10892.dmp"  # 修改为你的dmp文件名

def sniper_key_hunter():
    if not os.path.isfile(DUMP_FILE):
        print(f"错误:找不到文件 {DUMP_FILE}")
        return

    # 定义正则表达式,匹配 64位key + 32位salt 的连续96位十六进制模式
    pattern_ascii = re.compile(rb"[xX]'([a-fA-F0-9]{64})([a-fA-F0-9]{32})'")
    found_results = []
    seen_combinations = set()

    with open(DUMP_FILE, "rb") as f:
        # 使用mmap映射文件,提高大文件搜索效率
        with mmap.mmap(f.fileno(), length=0, access=mmap.ACCESS_READ) as mm:
            for m in pattern_ascii.finditer(mm):
                enc_key = m.group(1).decode('ascii').lower()  # 提取64位密钥
                salt = m.group(2).decode('ascii').lower()     # 提取32位盐值
                combo = (enc_key, salt)
                # 去重
                if combo not in seen_combinations:
                    found_results.append(combo)
                    seen_combinations.add(combo)

    if found_results:
        print(f"\n成功!共找到 {len(found_results)} 组可能的密钥对。\n")
        print(f"{'序号':<4} | {'衍生原始密钥 (64位 Hex)':<66} | {'盐值 (32位 Hex)':<32}")
        print("-" * 110)
        for i, (k, s) in enumerate(found_results, 1):
            print(f"{i:02d}   | {k} | {s}")
        print("\n提示:使用密钥前,请先核对加密数据库文件头的盐值是否与列表中的某一项匹配。")
    else:
        print("\n[-] 未在内存镜像中发现符合 96 位特征的数据块。")

if __name__ == "__main__":
    sniper_key_hunter()

运行此脚本,它将输出所有在内存中找到的、符合格式的密钥对。例如,可能输出:

成功!共找到 3 组可能的密钥对。

序号 | 衍生原始密钥 (64位 Hex)                            | 盐值 (32位 Hex)
--------------------------------------------------------------------------------------------------------------
01   | a1b2c3d4e5f6... (略) | 1234567890abcdef... (略)
02   | b0fb4730d908c07d3e928b5c418a7470bd954d100c9607821e0c05051c4588aa | 7d3e928b5c418a7470bd954d100c96078
03   | f8e7d6c5b4a3... (略) | 90ab12cd34ef56... (略)

关键核对步骤
找到密钥列表后,需要确定哪一对是用于解密目标数据库(如 message_0.db)的。使用十六进制编辑器或Python查看目标数据库文件的前16个字节(即文件头)。这16个字节就是该数据库加密使用的“盐值”(Salt)。将此盐值与脚本输出列表中的“盐值”列进行比对,完全匹配的那一对密钥即为所需。

3.4 解密微信数据库

微信4.x版本对本地SQLite数据库(.db 文件)的加密机制如下:

  • 加密算法: AES-256-CBC。
  • 密钥派生: 使用PBKDF2算法,对原始密码(或密钥材料)进行256,000次迭代,生成最终的AES-256密钥。注意:在内存中找到的“衍生原始密钥”(64位Hex)已经是经过PBKDF2处理后的最终AES密钥,无需再次派生。
  • 数据库结构: 数据库被分割成4096字节的“页”进行加密。每一页的结构为:
    • 前16字节:盐值(Salt,与密钥配对,已在文件头验证)。
    • 中间4000字节:AES加密后的密文数据。
    • 最后80字节:保留区,其中前16字节是本页加密使用的初始化向量(IV),后续部分用于HMAC-SHA512校验(解密时通常不处理)。

以下是解密脚本 decrypt.py 的代码与解释:

import os
from Crypto.Cipher import AES

# 配置项
DB_FILE = "message_0.db"          # 待解密的加密数据库文件
OUT_FILE = "message_0_decrypted.db" # 解密后的输出文件
PAGE_SIZE = 4096                  # 微信数据库页大小
RESERVE_SIZE = 80                 # 每页末尾保留区域大小
# 将此处替换为步骤3.3中找到的、与目标数据库盐值匹配的64位原始密钥
RAW_KEY_HEX = "b0fb4730d908c07d3e928b5c418a7470bd954d100c9607821e0c05051c4588aa"

def decrypt_wcdb_4x():
    if not os.path.exists(DB_FILE):
        print(f"错误:找不到数据库文件 {DB_FILE}")
        return

    # 将十六进制字符串的密钥转换为字节
    raw_key = bytes.fromhex(RAW_KEY_HEX)
    
    with open(DB_FILE, 'rb') as f_in, open(OUT_FILE, 'wb') as f_out:
        print(f"开始解密 {DB_FILE} ...")
        
        # 读取并处理第一页
        page1 = f_in.read(PAGE_SIZE)
        if not page1:
            return
        
        # 计算IV在第一页中的位置(页尾前80字节的起始处)
        iv_pos = PAGE_SIZE - RESERVE_SIZE
        iv = page1[iv_pos : iv_pos + 16]  # 提取IV
        
        # 提取第一页的密文:去掉头16字节盐值和尾80字节保留区
        enc_data = page1[16 : iv_pos]
        
        # 使用AES-256-CBC解密第一页
        cipher = AES.new(raw_key, AES.MODE_CBC, iv)
        dec_data = cipher.decrypt(enc_data)
        
        # 校验解密结果:有效的SQLite文件在特定偏移有固定字节
        if dec_data[5:8] != b'\x40\x20\x20':
            print("指纹校验失败,密钥或数据库可能不正确。")
            return
        print("第一页指纹校验成功!正在解密整个数据库...")
        
        # 重新读取文件,从第一页开始完整解密
        f_in.seek(0)
        page_number = 0
        
        while True:
            page = f_in.read(PAGE_SIZE)
            if not page:
                break
            page_number += 1
            
            # 处理非完整页(最后一部分)
            if len(page) < PAGE_SIZE:
                f_out.write(page)
                break
                
            # 提取当前页的IV和密文
            iv_pos = PAGE_SIZE - RESERVE_SIZE
            iv = page[iv_pos : iv_pos + 16]
            enc_data = page[16 : iv_pos]
            
            # 解密当前页
            cipher = AES.new(raw_key, AES.MODE_CBC, iv)
            dec_data = cipher.decrypt(enc_data)
            
            # 对第一页,需要手动添加SQLite文件头魔数
            if page_number == 1:
                sqlite_header = b'SQLite format 3\x00' + dec_data[16:]  # 前16字节用标准头替换
                f_out.write(sqlite_header)
            else:
                f_out.write(dec_data)
        
        print(f"解密完成!输出文件为: {OUT_FILE}")
        print("提示:可使用 Navicat、DB Browser for SQLite 等工具打开解密后的数据库文件。")

if __name__ == "__main__":
    decrypt_wcdb_4x()

脚本执行与验证

  1. RAW_KEY_HEX 变量的值替换为步骤3.3中确认的、正确的64位原始密钥。
  2. 确保 DB_FILE 变量指向待解密的数据库文件(如 message_0.db)。
  3. 运行脚本。如果密钥正确,脚本将输出解密成功的信息,并生成 message_0_decrypted.db 文件。
  4. 使用SQLite数据库工具(如Navicat)打开生成的 .db 文件,验证数据是否已可正常读取。

4. 总结与注意事项

  • 核心要点:本方法的关键在于从内存中准确提取出“衍生原始密钥”和与之配对的“盐值”,并通过盐值与数据库文件头的比对来确认正确的密钥对。
  • 版本适应性:本教学文档针对微信4.x(NT架构)版本。对于更早的3.x版本(进程为WeChat.exe),密钥的存储格式和提取方法可能不同,需参考其他资料。
  • 工具替代方案:文档中提到的 lovelymem 等自动化工具,其底层原理与上述手动步骤一致。手动分析有助于深入理解原理,而自动化工具可提升效率。
  • 法律与道德规范:此技术仅限用于授权下的安全研究、数字取证或CTF竞赛。未经授权对他人微信数据进行解密属违法行为。
相似文章
相似文章
 全屏