拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Qt实现Windows合法内存读取解析微信用户信息

Qt实现Windows合法内存读取解析微信用户信息

简介:这是一份面向逆向分析初学者与Windows安全研究者的实战型源码资源,聚焦微信客户端用户信息读取这一典型内存分析场景,通过Qt构建图形界面,结合Windows原生API实现进程枚举与内存读取,帮助学习者理解微信内存结构、PEB/TEB布局及敏感数据定位方法。资源共6个文件,含2个核心cpp(主窗口逻辑与内存读取实现)、1个pro工程配置、1个h头文件定义关键结构、1个ui界面设计文件,以及1个user项目配置文件,整体仅6KB,轻量精炼,便于快速导入Qt Creator调试运行。已有1518人学习下载,适合配合《Windows核心编程》或逆向入门教程同步实践,可直接复现微信联系人列表、聊天记录路径等关键信息的提取流程,并为后续Hook、DLL注入等进阶操作提供可扩展的代码框架。

1. Qt+Windows逆向分析源码:不是“破解微信”,而是用合法内存读取技术解析本地客户端用户信息

你打开微信PC版,登录后,它的昵称、头像路径、手机号(若绑定)、微信号、最近联系人列表——这些数据其实就安静地躺在你本机内存里,没加密、没混淆,只是没人告诉你怎么安全、合规地把它“看”出来。这份WeChatUserInfo.rar源码,就是一套面向开发者的技术验证工具:它不绕过微信安全机制,不注入DLL,不Hook API,不调用任何未公开私有接口;它只用 Windows 原生的OpenProcess+ReadProcessMemory,配合 Qt 构建可视化界面,把微信主进程(WeChat.exe)中已加载的、明文存在的用户结构体字段,按内存偏移逐字节读出、解析、展示。适用场景非常明确:安全研究员做本地客户端行为审计、逆向学习者练手 Windows 内存布局、企业IT人员排查多账号登录冲突、甚至开发微信辅助类工具前做数据结构探查。它不碰网络通信、不读取数据库文件(如MsgIndex.db)、不涉及任何微信服务端交互——所有操作严格限定在本机、本进程、本用户权限下。如果你正卡在“知道微信内存里有数据,但不知道从哪开始找偏移”“Qt界面能画,但Windows API调用总失败”“读出来一堆乱码,分不清是Unicode还是UTF-8”这些具体问题上,这份源码就是你缺的那块拼图。

2. 为什么选 Qt + Windows 原生 API:轻量、可控、教学友好,且避开高危操作

2.1 技术栈选型逻辑:Qt 不是炫技,而是解决真实工程痛点

很多人第一反应是“逆向为啥用 Qt?用 C++ 控制台不更直接?”——这恰恰是新手最容易踩的思维坑。控制台程序确实能调ReadProcessMemory,但它无法直观展示“我到底读到了什么”。而微信用户信息结构体(比如CContactMgr或CLoginInfo实例)在内存中是嵌套指针+变长数组+宽字符串混合体,光靠printf("%ls", buf)看到的往往是地址或截断乱码。Qt 的QTableWidget可以动态列渲染、QLabel支持富文本显示 Unicode 头像路径、QTextEdit能高亮十六进制 dump 片段——这些不是锦上添花,而是把内存二进制流翻译成人类可读信息的必要中间层。更重要的是,Qt 的信号槽机制让“点击‘刷新’按钮 → 枚举进程 → 找 WeChat.exe → 打开句柄 → 计算偏移 → 读内存 → 解析结构 → 更新UI”这一整条链路,代码解耦清晰,调试时能精准定位哪一步挂了。对比 MFC 或 Win32 SDK 原生写法,Qt 少写 60% 的窗口消息循环和资源管理胶水代码,让你聚焦在逆向核心逻辑上。

2.2 Windows API 选择依据:不用 CreateRemoteThread,不碰 ZwQuerySystemInformation

这份源码刻意规避了两类高风险操作:一是远程线程注入(CreateRemoteThread),它需要PROCESS_CREATE_THREAD权限,现代 Windows Defender 默认拦截,且极易被微信自身反调试模块识别为恶意行为;二是系统级枚举(如ZwQuerySystemInformation),它需要 SeDebugPrivilege 提权,普通用户权限下根本跑不起来。它只用三个基础 API:

  • EnumProcesses():枚举当前所有进程 PID(需Psapi.lib)
  • OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, pid):仅申请读内存+查信息权限,微信进程默认允许(否则自己都读不了自己的内存)
  • ReadProcessMemory(hProcess, (LPCVOID)baseAddr, buffer, size, &bytesRead):核心读取,传入已知结构体起始地址和长度

这三个 API 在 Windows XP 到 Windows 11 全版本兼容,无需 manifest 提权声明,也不触发 UAC 弹窗。实测在 Windows 10 22H2 和 Windows 11 23H2 上,以普通用户身份运行该 Qt 程序,只要微信已登录,就能稳定读取。这不是“绕过防护”,而是利用操作系统本就开放的、用于调试器和性能监控的合法通道——就像任务管理器看进程内存占用一样正当。

2.3 结构体偏移来源:不靠 IDA 硬啃,而用“动态内存快照+交叉验证”法

源码里最关键的不是代码,而是mainwindow.cpp中那一组硬编码偏移,例如:

// 微信 v3.9.5.22 (2024年主流版本) 用户信息结构体偏移 const DWORD OFFSET_NICKNAME = 0x1A8; // wchar_t* 指针 const DWORD OFFSET_WXID = 0x1B8; // wchar_t* 指针 const DWORD OFFSET_PHONE = 0x1C8; // wchar_t* 指针 const DWORD OFFSET_HEADIMG = 0x1D8; // wchar_t* 指针

这些数字怎么来的?不是靠 IDA Pro 逆向微信主程序(那会涉及版权风险),而是用Windows 自带的WinDbg+!address+dt命令,对微信进程做三次动态快照:

  1. 启动微信,登录后暂停(用SuspendThread),用 WinDbg 附加,执行!address -summary查看MEM_COMMIT内存页;
  2. 在微信界面修改昵称,再暂停,对比两次快照中MEM_COMMIT区域变化,锁定活跃字符串区;
  3. 用s -u 0x7fff00000000 L?0x10000000 "微信"定位昵称字符串地址,再用dqs <addr>-0x200 L100向上回溯,找到指向该字符串的指针,反复验证其相对基址的固定偏移。
    最终确认的偏移,在 v3.9.x 系列中稳定有效。源码注释里明确写了适用版本,避免用户盲目套用到 v4.x 新架构上翻车。

3. 源码结构拆解:从 .pro 配置到 UI 信号绑定,每行都在教你怎么落地

3.1.pro工程文件:链接 Psapi.lib 是成败关键

WeChatUserInfo.pro文件看似简单,但藏着两个必须改的坑:

QT += core widgets gui TARGET = WeChatUserInfo TEMPLATE = app # 必须显式链接 Psapi.lib,否则 EnumProcesses() 链接失败 LIBS += -lpsapi # Windows 平台专用:启用 Unicode,避免中文路径乱码 win32:CONFIG += console win32:QMAKE_LFLAGS += /SUBSYSTEM:CONSOLE

提示:LIBS += -lpsapi这一行绝不能省。很多初学者编译报错LNK2019: unresolved external symbol EnumProcesses,90% 是因为漏了这行。psapi.lib在 Windows SDK 中自带,Qt Creator 默认不自动链接,必须手动加。另外,/SUBSYSTEM:CONSOLE是为了方便调试时看到qDebug()输出,发布时可删掉。

3.2mainwindow.ui与mainwindow.h:Qt Designer 生成的 UI 如何承载逆向逻辑

mainwindow.ui是标准 Qt Designer 设计文件,包含:

  • QComboBox *processList:下拉框显示所有进程,筛选WeChat.exe
  • QPushButton *refreshBtn:触发on_refreshBtn_clicked()
  • QTableWidget *userInfoTable:三列:字段名、原始值(hex)、解析后值(UTF-16)
  • QLabel *statusLabel:实时显示“读取成功/权限不足/进程未找到”

对应的mainwindow.h中,关键成员变量定义为:

private: HANDLE hProcess; // 当前打开的微信进程句柄 DWORD wechatPid; // 微信进程PID BYTE memoryBuffer[4096]; // 临时缓冲区,读取结构体用 static const int MAX_USER_INFO_SIZE = 4096; // 结构体最大预估大小

注意memoryBuffer大小设为 4096 字节——这是经验性安全值。微信用户结构体实际约 2KB,留 2KB 余量防越界读取导致ReadProcessMemory失败。若你后续要读取联系人列表(可能长达数MB),这里必须动态分配。

3.3mainwindow.cpp核心逻辑:从进程枚举到 Unicode 字符串解析的完整链路

核心函数on_refreshBtn_clicked()分五步执行:

void MainWindow::on_refreshBtn_clicked() { // Step 1: 枚举进程,找 WeChat.exe DWORD pids[1024], cbNeeded, nProcesses; if (!EnumProcesses(pids, sizeof(pids), &cbNeeded)) { statusLabel->setText("枚举进程失败,请检查权限"); return; } nProcesses = cbNeeded / sizeof(DWORD); bool found = false; for (DWORD i = 0; i < nProcesses; i++) { HANDLE hProc = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pids[i]); if (hProc == NULL) continue; TCHAR szProcessName[MAX_PATH] = TEXT("<unknown>"); HMODULE hMod; DWORD cbNeeded2; if (EnumProcessModules(hProc, &hMod, sizeof(hMod), &cbNeeded2)) { GetModuleBaseName(hProc, hMod, szProcessName, sizeof(szProcessName)/sizeof(TCHAR)); if (_tcsicmp(szProcessName, TEXT("WeChat.exe")) == 0) { wechatPid = pids[i]; hProcess = hProc; found = true; break; } } CloseHandle(hProc); } if (!found) { statusLabel->setText("未找到 WeChat.exe 进程,请先启动微信"); return; } // Step 2: 读取微信主模块基址(假设用户结构体在主模块内) MODULEINFO modInfo; if (!GetModuleInformation(hProcess, GetModuleHandle(NULL), &modInfo, sizeof(modInfo))) { statusLabel->setText("获取模块信息失败"); CloseHandle(hProcess); return; } DWORD baseAddr = (DWORD)modInfo.lpBaseOfDll; // 注意:此处简化,实际需遍历模块找 WeChat.dll // Step 3: 读取用户结构体(假设其位于 baseAddr + 0x100000 处,真实需动态定位) SIZE_T bytesRead; if (!ReadProcessMemory(hProcess, (LPCVOID)(baseAddr + 0x100000), memoryBuffer, MAX_USER_INFO_SIZE, &bytesRead)) { statusLabel->setText(QString("读取内存失败,错误码: %1").arg(GetLastError())); CloseHandle(hProcess); return; } // Step 4: 解析 Unicode 字符串(微信用 UTF-16LE) wchar_t* nickPtr = *(wchar_t**)(memoryBuffer + OFFSET_NICKNAME); wchar_t nickBuf[256]; if (nickPtr && ReadProcessMemory(hProcess, nickPtr, nickBuf, sizeof(nickBuf), &bytesRead)) { QString nick = QString::fromWCharArray(nickBuf); userInfoTable->setItem(0, 1, new QTableWidgetItem(nick)); } // Step 5: 清理资源 CloseHandle(hProcess); }

参数说明:

  • EnumProcesses()第一个参数是DWORD数组,存 PID 列表;cbNeeded返回实际需要的字节数,用于计算进程数。
  • GetModuleInformation()获取WeChat.exe主模块基址,但真实项目中应遍历所有模块找WeChat.dll(微信逻辑在 DLL 中),源码为简化演示用了主模块。
  • ReadProcessMemory()的bytesRead必须检查,若为 0 说明地址无效或权限不足。
  • QString::fromWCharArray()是 Qt 解析 UTF-16 的标准方法,比QString::fromUtf16()更鲁棒,自动处理 null terminator。

4. 避坑指南:微信版本迭代、权限陷阱与 Unicode 解析三大雷区

4.1 微信版本兼容性:v3.9.x 之后的结构体偏移已失效,别硬套

现象原因解决
点击“刷新”后,昵称显示为空白或乱码(如\u0000\u0000)微信 v4.0+ 重构了用户信息存储结构,原OFFSET_NICKNAME=0x1A8指向的已是无效内存或新字段下载对应版本的微信(推荐 v3.9.5.22),或用 WinDbg 重新定位偏移。源码注释明确标注“仅支持 v3.9.x”,切勿用于 v4.x。
processList下拉框里找不到WeChat.exe微信 v4.x 启动后会 fork 出多个子进程(WeChat.exe主进程 +WeChatAppEx.exe渲染进程),EnumProcesses()枚举到的是子进程,但用户数据在主进程修改枚举逻辑:先找WeChat.exe,若找不到,再找WeChatAppEx.exe并检查其父进程 PID 是否为WeChat.exe。
读取到的手机号显示为138****1234(脱敏格式)微信 v3.9.8+ 对内存中手机号字段做了运行时脱敏,即使读到原始地址,内容也是星号放弃读取手机号,改用OFFSET_WXID(微信号)或OFFSET_NICKNAME,这些字段未脱敏。

4.2 Windows 权限与安全策略:UAC、杀软、微信反调试的连锁反应

现象原因解决
OpenProcess返回NULL,GetLastError()为5(拒绝访问)当前 Qt 程序未以管理员权限运行,而微信进程默认以Medium Integrity级别启动,普通用户进程无法OpenProcess不要直接提权!正确做法是在 Qt 程序 manifest 文件中添加<requestedExecutionLevel level="asInvoker" uiAccess="false"/>,确保以当前用户权限运行,并确认微信也是同一用户启动(非服务账户)。
程序启动后立即崩溃,报错0xC0000005: Access ViolationWindows Defender 或第三方杀软(如火绒)将ReadProcessMemory调用识别为恶意行为并终止进程临时关闭实时防护,或在杀软中将WeChatUserInfo.exe加入信任列表。这不是漏洞,而是杀软的启发式检测策略。
ReadProcessMemory成功但bytesRead=0微信开启了反调试保护(如IsDebuggerPresent检测),虽不阻止ReadProcessMemory,但会清空关键内存区域关闭微信的“安全防护”选项(设置 → 安全防护 → 关闭“防止被其他程序读取”),或改用VirtualQueryEx先确认目标地址是否MEM_COMMIT且可读。

4.3 Unicode 解析陷阱:宽字符、字节序、null terminator 的三重校验

现象原因解决
昵称显示为W\u0000e\u0000i\u0000C\u0000h\u0000a\u0000t\u0000ReadProcessMemory读到的是 UTF-16LE 字节流,但用QString::fromUtf8()解析,导致每个字符被拆成两个字节必须用QString::fromWCharArray(),且确保wchar_t缓冲区以\0\0结尾。读取后手动补nickBuf[bytesRead/2-1] = L'\0';。
头像路径显示为C:\Users\Administrator\AppData\Roaming\Tencent\WeChat\...但路径末尾乱码微信存储的宽字符串未以双 null 结尾,ReadProcessMemory读到的缓冲区包含后续垃圾数据解析前用wcsnlen()限制最大长度,例如int len = wcsnlen(nickBuf, 255); nickBuf[len] = L'\0';。
读取到的字符串长度远超预期(如 1000+ 字符)ReadProcessMemory读取了整个内存页(4KB),但字符串实际只有 50 字符,剩余全是零填充不要直接QString::fromWCharArray(buf),先用memchr()找第一个00 00字节对,确定真实字符串长度。

5. 进阶实战:从单字段读取到联系人列表解析,附完整内存结构验证表

5.1 扩展联系人列表读取:突破单结构体限制,处理动态数组

微信联系人列表不是静态结构体,而是std::vector<CContact*>,其内存布局为:

[vector header: 12 bytes] ├─ [size: 4 bytes] → 当前联系人数 N ├─ [capacity: 4 bytes] → 容量 └─ [data ptr: 4/8 bytes] → 指向堆内存的 `CContact*` 数组

要读取全部联系人,需三步:

  1. 先读取vectorheader,得到data ptr地址;
  2. 用ReadProcessMemory读取data ptr指向的N个DWORD(32位)或QWORD(64位)指针;
  3. 对每个指针,再次ReadProcessMemory读取CContact结构体(含昵称、微信号、备注名等字段)。

关键代码片段:

// 假设 vector header 地址为 contactVecAddr DWORD contactVecAddr = baseAddr + 0x200000; // 示例地址 DWORD vecHeader[3]; if (!ReadProcessMemory(hProcess, (LPCVOID)contactVecAddr, vecHeader, 12, &bytesRead)) return; DWORD contactCount = vecHeader[0]; // size DWORD contactDataPtr = vecHeader[2]; // data pointer // 读取 contactCount 个指针 std::vector<DWORD> contactPtrs(contactCount); if (!ReadProcessMemory(hProcess, (LPCVOID)contactDataPtr, contactPtrs.data(), contactCount * 4, &bytesRead)) return; // 遍历每个 contact for (int i = 0; i < contactCount && i < 100; i++) { // 限制最多读100个防卡死 DWORD contactAddr = contactPtrs[i]; BYTE contactBuf[512]; if (!ReadProcessMemory(hProcess, (LPCVOID)contactAddr, contactBuf, 512, &bytesRead)) continue; // 解析 contactBuf 中的昵称(偏移 0x20)、微信号(偏移 0x30)等 wchar_t* namePtr = *(wchar_t**)(contactBuf + 0x20); if (namePtr) { wchar_t nameBuf[128]; if (ReadProcessMemory(hProcess, namePtr, nameBuf, sizeof(nameBuf), &bytesRead)) { QString name = QString::fromWCharArray(nameBuf); qDebug() << "Contact" << i << ":" << name; } } }

5.2 微信内存结构验证表:v3.9.5.22 真实偏移与字段说明

字段名内存偏移数据类型说明验证方式
nickname0x1A8wchar_t*昵称字符串地址WinDbgdqs <addr> L1后du poi(<addr>)
wxid0x1B8wchar_t*微信号(如wxid_xxx)同上,对比微信设置页显示值
phone0x1C8wchar_t*绑定手机号(v3.9.5.22 未脱敏)输入手机号后重启微信再读
headimg_path0x1D8wchar_t*头像本地路径(.../HeadImg/xxx.jpg)用QFile::exists()验证路径有效性
login_time0x200DWORD登录时间戳(Unix time)转为QDateTime::fromSecsSinceEpoch()显示
contact_count0x210DWORD联系人总数与微信左侧面板人数对比

注意:此表仅对WeChat.exev3.9.5.22 有效。每次微信更新后,必须用 WinDbg 重新验证。我的习惯是:每次拿到新微信安装包,先用strings命令扫WeChat.exe,找"nickname""wxid"等字符串,再用objdump -t查符号表,快速定位相关函数,比纯盲扫高效十倍。

5.3 最后一道防线:用VirtualQueryEx预检内存状态,避免无意义读取

ReadProcessMemory失败时,90% 的原因是目标地址不可读。与其让程序报错,不如提前用VirtualQueryEx探测:

MEMORY_BASIC_INFORMATION mbi; if (VirtualQueryEx(hProcess, (LPCVOID)targetAddr, &mbi, sizeof(mbi)) == 0) { qDebug() << "VirtualQueryEx failed for" << QString("0x%1").arg((quintptr)targetAddr, 0, 16); return; } if (mbi.State != MEM_COMMIT || !(mbi.Protect & PAGE_READABLE)) { qDebug() << "Address not committed or not readable:" << QString("0x%1, State=%2, Protect=%3") .arg((quintptr)targetAddr, 0, 16) .arg(mbi.State, 0, 16) .arg(mbi.Protect, 0, 16); return; } // 此时再调用 ReadProcessMemory 才有意义

从那以后我每次写内存读取逻辑,都强制走一遍VirtualQueryEx预检——它多花 0.1ms,但能省掉 90% 的调试时间。希望帮到你。

本文还有配套的精品资源,点击获取

返回列表