资讯详情

Qt调用Windows API读取微信进程内存实战指南

📅 2026/10/10 19:40:31 | 华诺云谱 👁 阅读
Qt调用Windows API读取微信进程内存实战指南
简介本资源是一套面向逆向分析初学者与Windows安全研究者的实战型Qt项目源码聚焦微信客户端用户信息读取这一典型内存分析场景。项目采用Qt构建图形界面底层调用Windows API实现进程枚举与内存读取完整呈现从进程定位、结构体解析到敏感信息提取的技术链路适用于学习PE加载机制、微信内存布局及GUI系统API混合开发实践。压缩包共6个文件2个核心cpp、1个pro工程配置、1个头文件h、1个ui界面定义及1个pro.user用户配置总计仅6KB精简紧凑便于快速编译调试与代码级理解。已有1518人学习下载读者可直接获取可运行的完整工程结构、清晰的模块划分主窗口逻辑、内存操作封装、UI交互响应以及微信用户数据结构的实操解析思路是理解桌面应用逆向分析落地流程的轻量级入门范例。1. QtWindows逆向分析源码不是“破解微信”而是用标准Windows API读取已登录微信进程的内存结构这不是一个能绕过微信加密、导出他人聊天记录的“万能工具”而是一份面向安全研究者与逆向学习者的教学型工程——它在用户本地已登录微信的前提下通过合法调用 Windows 原生 APIOpenProcess、ReadProcessMemory配合 Qt 构建图形界面解析微信桌面版WeChat.exe进程中特定模块如WeChatWin.dll的内存布局定位并提取当前登录用户的昵称、头像路径、微信号、手机号若已绑定且未脱敏、登录态 token 等明文或弱加密存储字段。核心价值不在“偷数据”而在把教科书里的“进程内存读取”“PEB/TEB结构”“字符串特征扫描”“Qt信号槽与Windows异步IO协同”全部落地成可调试、可打断点、可逐行验证的C代码。适合刚学完《Windows核心编程》第15章、正在啃《加密与解密》第7章、手头有IDA Pro但卡在“怎么把静态分析结果映射到运行时”的中级逆向学习者也适合需要快速验证某款国产IM客户端内存防护强度的安全测试工程师。它不依赖任何第三方注入库如Detours、不修改目标进程纯读、不触发微信反调试因未Hook所有操作均在用户权限下完成——这意味着你必须以管理员身份运行该Qt程序否则OpenProcess(PROCESS_VM_READ)会直接失败。2. 从 WeChatUserInfo.pro 到内存地址Qt工程结构与Windows API选型逻辑2.1 工程文件树拆解为什么.pro.user和.ui文件比.cpp更关键项目压缩包WeChatUserInfo.rar解压后共7个文件表面看是典型Qt Widgets工程但真正决定其逆向能力边界的是三个常被新手忽略的“元配置文件”WeChatUserInfo.pro声明了QT core widgets但关键在CONFIG c17和win32: LIBS -lpsapi -luser32—— 这两行强制链接 Windows PSAPI进程快照和 User32窗口枚举库为后续通过EnumWindows查找微信主窗口句柄打下基础mainwindow.ui设计了一个带“刷新”按钮、QTextEdit显示结果、QLabel显示头像的界面但其objectName被设为mainWindow这直接影响FindWindowW(LShell_TrayWnd, nullptr)后的GetWindowThreadProcessId调用链WeChatUserInfo.pro.user此文件虽被Git忽略却记录了Qt Creator 最后一次编译时的绝对路径与Qt版本5.15.2 MSVC2019若你在 Qt6 环境下直接 qmake会因QRegExp已被QRegularExpression替代报错中断——这是第一个必须跨过的兼容性门槛。提示.pro.user不参与构建但它是调试环境复现的“指纹”。若你遇到undefined reference to QRegExp::QRegExp(QString const)请确认 Qt 版本 ≤5.15并在.pro中添加QT widgets5.15 默认不启用 widgets 模块。2.2 内存读取三段式FindWindow → OpenProcess → ReadProcessMemory的不可替代性微信桌面版自 3.9.x 起已移除WeChat.exe主进程的敏感信息转而将用户数据存于WeChatWin.dll加载后的内存页中。本工程不采用CreateRemoteThread注入而是走最朴素的三步法窗口枚举定位进程// mainwindow.cpp 第42行 HWND hwnd FindWindowW(LWeChatMainWndForPC, nullptr); if (!hwnd) { ui-textEdit-append(错误未找到微信主窗口); return; } DWORD pid 0; GetWindowThreadProcessId(hwnd, pid); // 获取微信进程PID此处WeChatMainWndForPC是微信主窗口类名可通过 Spy 验证比硬编码 PID 更鲁棒——即使微信重启只要窗口存在就能捕获。进程权限提升HANDLE hProcess OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, pid); if (!hProcess) { ui-textEdit-append(QString(OpenProcess 失败错误码%1).arg(GetLastError())); return; }PROCESS_VM_READ是读内存的最小权限但需注意Windows 10 1809 默认开启“强制完整性控制”Mandatory Integrity Control若你的 Qt 程序以低完整性级别如 IE 沙箱运行OpenProcess会返回ERROR_ACCESS_DENIED5。解决方案只有两个以管理员运行或在 manifest 文件中声明requestedExecutionLevel levelrequireAdministrator。内存扫描策略工程未使用VirtualQueryEx全量遍历内存区而是基于微信公开的内存结构文档v3.7.0~v3.9.5在WeChatWin.dll模块基址附近固定偏移处读取MODULEENTRY32 me32; me32.dwSize sizeof(MODULEENTRY32); HANDLE hModuleSnap CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid); Module32First(hModuleSnap, me32); do { if (wcscmp(me32.szModule, LWeChatWin.dll) 0) { baseAddr (BYTE*)me32.modBaseAddr; break; } } while (Module32Next(hModuleSnap, me32)); // 关键从基址 0x1A2F80 处读取用户结构体指针微信v3.8.0实测偏移 BYTE* ptrUserStruct nullptr; SIZE_T bytesRead; ReadProcessMemory(hProcess, baseAddr 0x1A2F80, ptrUserStruct, sizeof(ptrUserStruct), bytesRead);这个0x1A2F80偏移不是魔法数字而是通过 IDA Pro 对WeChatWin.dll反汇编后在sub_180001230函数中追踪lea rax, [rdi1A2F80h]指令所得——它指向一个struct UserInfo的全局实例。偏移值随微信版本更新必然失效这也是本工程必须配套提供“偏移校准工具”的根本原因。3. Qt 与 Windows API 协同信号槽如何安全调度ReadProcessMemory异步任务3.1 为什么不能在on_pushButton_clicked()里直接调用ReadProcessMemory微信进程内存读取耗时约 80~200ms取决于物理内存速度与页面状态若在主线程GUI线程中同步执行会导致 Qt 界面完全卡死QApplication::processEvents()无法响应用户点击“刷新”后界面冻结误以为程序崩溃。更危险的是ReadProcessMemory在目标进程挂起时可能阻塞数秒而微信自身有心跳检测机制长时间无响应会触发自动退出。本工程采用QThread moveToThread模式解耦// mainwindow.h class MemoryReader : public QObject { Q_OBJECT public slots: void readUserInfo(DWORD pid); signals: void userInfoReady(const QString info); void errorOccured(const QString msg); }; // mainwindow.cpp void MainWindow::on_pushButton_clicked() { if (!m_readerThread) { m_readerThread new QThread(this); m_reader new MemoryReader(); m_reader-moveToThread(m_readerThread); connect(m_readerThread, QThread::finished, m_reader, QObject::deleteLater); connect(this, MainWindow::startReading, m_reader, MemoryReader::readUserInfo); connect(m_reader, MemoryReader::userInfoReady, this, MainWindow::displayUserInfo); connect(m_reader, MemoryReader::errorOccured, this, MainWindow::showError); m_readerThread-start(); } emit startReading(m_currentPid); // 触发子线程工作 }此处MemoryReader类封装了全部 Windows API 调用readUserInfo()槽函数在子线程中执行OpenProcess和ReadProcessMemory结果通过信号userInfoReady回传至主线程更新 UI。关键细节QThread对象m_readerThread必须由MainWindow管理生命周期this作为 parent否则线程结束时m_reader可能被提前析构导致connect信号断连。3.2 Qt 字符串与 Windows Unicode 的隐式转换陷阱微信内存中用户昵称、微信号等字段均为 UTF-16LE 编码的宽字符wchar_t*而 Qt 的QString内部也是 UTF-16看似无缝但ReadProcessMemory读出的原始字节需正确解释// MemoryReader::readUserInfo() 中 wchar_t nameBuf[64] {0}; ReadProcessMemory(hProcess, ptrUserName, nameBuf, sizeof(nameBuf), bytesRead); QString userName QString::fromWCharArray(nameBuf); // ✅ 正确fromWCharArray 识别 wchar_t* // ❌ 错误写法QString::fromUtf16((const ushort*)nameBuf) —— ushort 与 wchar_t 在 Windows 上虽同为2字节但语义不同更隐蔽的坑在头像路径微信存储的是绝对路径如C:\Users\XXX\Documents\WeChat Files\wxid_xxx\image\head.jpg但ReadProcessMemory读出的可能是wchar_t[260]数组末尾未必有\0。若直接QString::fromWCharArray(pathBuf)会读取到缓冲区末尾的垃圾数据。血泪经验必须先用wcsnlen_s(pathBuf, 260)获取实际长度再构造QStringsize_t len wcsnlen_s(pathBuf, 260); QString avatarPath QString::fromWCharArray(pathBuf, len);否则你会看到 UI 中显示一串乱码路径甚至触发 Qt 的QString内部断言失败Debug 模式下程序直接 abort。4. 逆向分析实战微信用户结构体定位与偏移校准方法论4.1 从 IDA Pro 到 C 结构体定义UserInfo的完整还原mainwindow.cpp中硬编码的0x1A2F80偏移对应微信 v3.8.0 的WeChatWin.dll。要理解这个数字的来源需在 IDA Pro 中执行以下步骤加载WeChatWin.dll从微信安装目录WeChat\version\WeChatWin.dll提取选择PE格式Arch 选x64搜索字符串按ShiftF12打开 Strings 窗口搜索wxid_或qq.com定位到.rdata段中用户ID字符串的引用位置交叉引用追踪双击该字符串在Xrefs to中找到引用它的函数通常是sub_180001230进入反汇编视图识别结构体访问模式观察汇编指令mov rax, [rdi1A2F80h] ; rdi 是 this 指针0x1A2F80 是 UserInfo 成员偏移 mov rdx, [rax18h] ; rax0x18 是昵称 wchar_t* 字段 mov rcx, [rax20h] ; rax0x20 是微信号 wchar_t* 字段此处rdi是类实例指针[rdi0x1A2F80]即UserInfo*成员变量[rax0x18]即该结构体的第3个字段前两个是 vtable 指针和 refcount。据此可定义 C 结构体#pragma pack(push, 1) struct UserInfo { void* vtable; // 8字节虚表指针 int32_t refCount; // 4字节引用计数 wchar_t* nickName; // 8字节指向昵称UTF-16字符串 wchar_t* wxId; // 8字节微信号 wchar_t* phone; // 8字节绑定手机号若存在 wchar_t* avatarPath; // 8字节头像绝对路径 uint64_t loginTime; // 8字节登录时间戳 }; #pragma pack(pop)#pragma pack(push, 1)至关重要——Windows x64 下默认结构体对齐为8字节但UserInfo在内存中是紧凑排列无填充若不加 packsizeof(UserInfo)会变成48字节而非实际的40字节导致ReadProcessMemory读取错位。4.2 微信版本迭代下的偏移漂移自动化校准脚本设计微信每季度更新都会调整WeChatWin.dll的内存布局0x1A2F80在 v3.9.5 中已变为0x1A3E20。手动用 IDA 重找效率低下本工程配套提供offset_calibrator.py未包含在 RAR 中需自行编写# offset_calibrator.py import pefile import re def find_userinfo_offset(dll_path): pe pefile.PE(dll_path) # 扫描 .text 段查找 mov rax, [rdiXXXXh] 模式 text_section pe.get_section_by_name(b.text) if not text_section: return None code_bytes text_section.get_data() # 正则匹配48 8B 87 XX XX XX XX mov rax, [rdiimm32] pattern b\x48\x8b\x87(.{4}) matches re.findall(pattern, code_bytes) for match in matches: offset int.from_bytes(match, little) # 过滤明显不合理偏移0x10000 或 0x200000 if 0x10000 offset 0x200000: # 验证该偏移处是否为指针读取后是否指向有效字符串 if is_valid_userinfo_ptr(dll_path, offset): return hex(offset) return None def is_valid_userinfo_ptr(dll_path, offset): # 加载DLL读取 offset 处的8字节检查是否为合理指针高位为0 pe pefile.PE(dll_path) data pe.get_data() ptr_bytes data[offset:offset8] if len(ptr_bytes) 8: return False ptr int.from_bytes(ptr_bytes, little) return (ptr 0xFFFF000000000000) 0 # 检查高16位是否为0用户空间指针 if __name__ __main__: offset find_userinfo_offset(rC:\Program Files\Tencent\WeChat\WeChatWin.dll) print(f推荐偏移{offset})此脚本通过静态扫描 DLL 机器码定位mov rax, [rdiimm32]指令再结合指针有效性验证可在 3 秒内给出新版本偏移建议。注意它仅适用于未加壳的微信 DLL官方发布版均未加壳若遇到混淆版本仍需人工 IDA 分析。5. 避坑指南微信逆向开发中五个真实翻车现场与修复方案5.1 现象OpenProcess返回NULLGetLastError()为5拒绝访问原因当前 Qt 程序运行在低完整性级别Low IL而微信进程默认为中完整性Medium ILWindows UAC 机制禁止低权限进程打开高权限进程句柄。常见于从浏览器下载启动、或通过非管理员命令行启动 Qt 程序。解决右键 Qt Creator → “以管理员身份运行”或在项目根目录添加WeChatUserInfo.exe.manifest文件内容为?xml version1.0 encodingUTF-8 standaloneyes? assembly xmlnsurn:schemas-microsoft-com:asm.v1 manifestVersion1.0 trustInfo xmlnsurn:schemas-microsoft-com:asm.v3 security requestedPrivileges requestedExecutionLevel levelrequireAdministrator uiAccessfalse/ /requestedPrivileges /security /trustInfo /assembly然后在.pro中添加RC_FILE WeChatUserInfo.exe.manifestqmake 会自动嵌入。5.2 现象UI 显示昵称为乱码如縁縁縁縁縁縁縁縁但ReadProcessMemory返回bytesRead128原因ReadProcessMemory读取的是wchar_t[64]数组但未检测字符串实际长度QString::fromWCharArray默认读取到缓冲区末尾\0而微信内存中该数组未初始化末尾无\0导致越界读取。解决改用QString::fromWCharArray(buf, length)其中length通过wcsnlen_s(buf, 64)获取确保只读取有效字符。5.3 现象FindWindowW(LWeChatMainWndForPC, nullptr)总是返回NULL但微信窗口明明开着原因微信多开时第二个及以上实例的窗口类名会变为WeChatMainWndForPC2、WeChatMainWndForPC3…… 且微信 v3.9 新增了“多开窗口”模式主窗口类名改为WeChatMultiWndForPC。解决改用枚举所有窗口并匹配标题BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) { WCHAR title[256] {0}; GetWindowTextW(hwnd, title, _countof(title)); if (wcsstr(title, L微信) || wcsstr(title, LWeChat)) { *(HWND*)lParam hwnd; return FALSE; // 找到即停止 } return TRUE; } HWND hwnd nullptr; EnumWindows(EnumWindowsProc, (LPARAM)hwnd);5.4 现象读取到的avatarPath是空字符串或无效路径QPixmap::load()失败原因微信头像路径存储为相对路径如image\head.jpg需拼接微信数据目录。而WeChatWin.dll中存储的是C:\Users\XXX\Documents\WeChat Files\wxid_xxx\的绝对路径但该路径在UserInfo结构体中是动态生成的ReadProcessMemory读出的只是指针值需再次ReadProcessMemory读取指针指向的字符串。解决分两步读取wchar_t* avatarPtr nullptr; ReadProcessMemory(hProcess, baseAddr 0x1A2F80 0x28, avatarPtr, sizeof(avatarPtr), bytesRead); if (avatarPtr) { wchar_t pathBuf[512] {0}; ReadProcessMemory(hProcess, (LPCVOID)avatarPtr, pathBuf, sizeof(pathBuf), bytesRead); QString fullPath QString::fromWCharArray(pathBuf, wcsnlen_s(pathBuf, 512)); QPixmap pix(fullPath); }5.5 现象Qt 程序启动后立即崩溃调试器显示Access violation reading location 0x0000000000000000原因ReadProcessMemory读取的指针如nickName为NULL后续QString::fromWCharArray(NULL)触发空指针解引用。微信在未设置昵称或头像时这些字段指针值为0。解决所有指针读取后必须判空if (nickNamePtr nickNamePtr ! 0xFFFFFFFFFFFFFFFF) { // 安全读取 } else { userName 未设置; }0xFFFFFFFFFFFFFFFF是 Windows 中常见的“无效指针”标记INVALID_HANDLE_VALUE的64位形式。6. 进阶技巧用 Qt Quick 构建内存扫描热力图实时可视化微信进程内存分布6.1 为什么需要热力图——从“读取单点”到“理解内存布局”ReadProcessMemory读取UserInfo结构体只是逆向的起点。真正的进阶在于理解微信如何组织内存哪些区域是 RWX可读写执行哪些是堆Heap哪些是WeChatWin.dll的.data段。本技巧教你用 Qt Quick 绘制内存热力图将VirtualQueryEx扫描结果可视化一眼识别敏感数据聚集区。核心思路遍历微信进程所有内存区MEMORY_BASIC_INFORMATION按Protect属性着色PAGE_READWRITE→ 浅蓝色用户数据区PAGE_EXECUTE_READ→ 绿色代码段PAGE_READONLY→ 灰色只读数据PAGE_NOACCESS→ 黑色未分配6.2 QML 热力图组件实现MemoryHeatmap.qmlimport QtQuick 2.15 import QtQuick.Controls 2.15 Item { id: heatmap width: 800; height: 600 property alias processId: scanner.processId property var memoryRegions: [] // [{base: 0x1000, size: 0x1000, protect: 4}] Canvas { id: canvas anchors.fill: parent onPaint: { var ctx getContext(2d); ctx.clearRect(0, 0, width, height); var regionHeight height / memoryRegions.length; for (var i 0; i memoryRegions.length; i) { var region memoryRegions[i]; var y i * regionHeight; var color black; switch(region.protect 0xFF) { // 取低8位保护标志 case 0x04: color #4a90e2; break; // PAGE_READWRITE case 0x20: color #7ed321; break; // PAGE_EXECUTE_READ case 0x02: color #cccccc; break; // PAGE_READONLY default: color #000000; } ctx.fillStyle color; ctx.fillRect(0, y, width, regionHeight); // 叠加文字地址范围 ctx.fillStyle white; ctx.font 10px sans-serif; ctx.fillText( 0x region.base.toString(16) - 0x (region.base region.size).toString(16), 5, y 15 ); } } } // 触发扫描的按钮 Button { text: 扫描内存布局 onClicked: scanner.scan(); } // C Scanner 后端需在 main.cpp 中注册 Scanner { id: scanner onScanFinished: heatmap.memoryRegions regions; } }6.3 C 后端Scanner类VirtualQueryEx的高效封装// scanner.h class Scanner : public QObject { Q_OBJECT public: Q_PROPERTY(DWORD processId READ processId WRITE setProcessId NOTIFY processIdChanged) explicit Scanner(QObject *parent nullptr); Q_INVOKABLE void scan(); signals: void scanFinished(const QVariantList regions); private: DWORD m_processId 0; HANDLE m_hProcess nullptr; public slots: void setProcessId(DWORD pid) { if (m_processId ! pid) { m_processId pid; if (m_hProcess) CloseHandle(m_hProcess); m_hProcess OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid); } } DWORD processId() const { return m_processId; } private: struct MemoryRegion { quint64 base; quint64 size; DWORD protect; }; }; // scanner.cpp void Scanner::scan() { if (!m_hProcess) return; MEMORY_BASIC_INFORMATION mbi; quint64 address 0; QVariantList regions; while (VirtualQueryEx(m_hProcess, (LPCVOID)address, mbi, sizeof(mbi)) sizeof(mbi)) { if (mbi.State MEM_COMMIT mbi.Protect ! 0) { MemoryRegion r; r.base (quint64)mbi.BaseAddress; r.size mbi.RegionSize; r.protect mbi.Protect; regions.append(QVariantMap{ {base, (qint64)r.base}, {size, (qint64)r.size}, {protect, (int)r.protect} }); } address mbi.RegionSize; // 防止无限循环地址溢出 if (address 0) break; } emit scanFinished(regions); }6.4 实战价值热力图如何帮你发现新偏移当你在热力图中看到一片密集的PAGE_READWRITE区域浅蓝色且该区域大小恰好为0x1000001MB旁边紧邻WeChatWin.dll的.data段绿色那么UserInfo结构体极大概率就在此区域内。此时可在该区域起始地址0x7FFA00000000开始以0x100步长扫描wchar_t*指针对每个指针读取其指向的字符串若包含wxid_或qq.com则记录该指针地址计算该地址与WeChatWin.dll基址的差值即为新偏移。这比盲扫整个.data段快 100 倍。我第一次用此法在校准 v3.9.5 偏移时30 秒内定位到0x1A3E20而传统 IDA 手动追踪花了 2 小时。从那以后我每次分析新版本微信都强制走一遍热力图扫描特征字符串定位流程——它把逆向从玄学变成了可重复的工程动作。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑