从微信(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 确认微信进程与版本
首先,需要从内存镜像中定位微信进程,并确认其版本,以确定后续的分析方法。
-
列出进程: 使用
vol3的windows.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),这是后续分析的关键。 -
确认版本号: 通过列出该进程加载的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 文件。
- 使用
vol3的windows.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()
脚本执行与验证:
- 将
RAW_KEY_HEX变量的值替换为步骤3.3中确认的、正确的64位原始密钥。 - 确保
DB_FILE变量指向待解密的数据库文件(如message_0.db)。 - 运行脚本。如果密钥正确,脚本将输出解密成功的信息,并生成
message_0_decrypted.db文件。 - 使用SQLite数据库工具(如Navicat)打开生成的
.db文件,验证数据是否已可正常读取。
4. 总结与注意事项
- 核心要点:本方法的关键在于从内存中准确提取出“衍生原始密钥”和与之配对的“盐值”,并通过盐值与数据库文件头的比对来确认正确的密钥对。
- 版本适应性:本教学文档针对微信4.x(NT架构)版本。对于更早的3.x版本(进程为
WeChat.exe),密钥的存储格式和提取方法可能不同,需参考其他资料。 - 工具替代方案:文档中提到的
lovelymem等自动化工具,其底层原理与上述手动步骤一致。手动分析有助于深入理解原理,而自动化工具可提升效率。 - 法律与道德规范:此技术仅限用于授权下的安全研究、数字取证或CTF竞赛。未经授权对他人微信数据进行解密属违法行为。