【转】微信Hook实战记录2:动手实现恶意dll内存插入器
来源:https://blog.csdn.net/weixin_30230009/article/details/105100172
Hook任何软件,整体思路都是通过内存调试软件对软件运行时内存进行断点调试,找到想要hook的内存地址,转为可以通过程序主dll可以获得的相对地址,然后再此处插入自己的恶意汇编代码,如果代码比较复杂,还需要编写寄存器保护逻辑,避免自己的恶意代码修改了寄存器中的值后,程序正常的逻辑无法运行。
前言
在上一篇文章「微信Hook实战记录 1:找到个人信息」中,介绍了如何使用OD与CE来找到微信中存放个人信息的内存位置,本篇文章尝试通过C++编写一个简单的内存注入工具,将我们自己的恶意dll注入到微信的内存中。
本文记录了大量细节,完全可以模仿复现相同的效果。
内存注入工具的编写
打开 Visual Studio 2019(下载的时候勾选 C++ 桌面支持,6.9G左右),选择创建新项目
选择创建 Windows 桌面向导
然后创建就可以了,这里创面项目名为 Project1
创建完后,VS 会为我们创建一个默认的界面,我们在 解决方案 -> 源文件 中找到 Project1.cpp,这个文件就是我们要写逻辑的主要界面
将其中多余的代码删除,就留下如下代码
// Project1.cpp : 定义应用程序的入口点。 // #include "framework.h" #include "Project1.h" int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) { return 0; }
wWinMain 是界面程序的入口,所有不必删除
接着将 解决方案 --> 资源文件 中的内容删除,这是 VS 默认创建好的,删除后,自己新建一个资源文件
选择 Dialog,即弹出框
调整一下创建出的按钮位置以及整体大小
要设置按钮与整体的属性,如按钮要设置其内容与ID,而整体Dialog也要设置一下其ID,可以右键点击 --> 属性
其他控制以相同的方式去设置,设置后后,就可以写代码了
首先修改一下 Project1.h 中的内容,如下
#pragma once #include "resource1.h"
具体内容要看你VS为你生成了什么,前面我们通过图像界面操作后,VS会生成的相应的头文件,这里我生成了 resource1.h 的头文件,其内容如下:
//{{NO_DEPENDENCIES}} // Microsoft Visual C++ 生成的包含文件。 // 供 Project1.rc 使用 // #define ID_MAIN 101 #define UN_DLL 1001 #define INJECT_DLL 1002 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 103 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1003 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif #endif
可以看出,其实就是我们设置的ID等内容
接着来修改一下 Project1.cpp, 其 wWinMain 方法修改如下:
#include "framework.h" #include "Project1.h" #include <TlHelp32.h> #include <stdio.h> // 微信进程名 #define WECHAT_PROCESS_NAME "WeChat.exe" int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) { // hInstance 将句柄传递给弹窗 // 传入 Dialgo 主体ID // DialogProc 回调函数,是一个指针,即该函数的地址 DialogBox(hInstance, MAKEINTRESOURCE(ID_MAIN), NULL, &DialogProc); return 0; }
因为创建的资源是 Dialog ,所以这里通过 DialogBox 来使用它,它在 Windows.h 的头文件中,如果你使用的 VS 2019,其 framework.h 文件就包含Windows.h了。
DialogBox的参数可以通过微软的文档来查看
void DialogBoxA( hInstance, lpTemplate, hWndParent, lpDialogFunc );
hInstance,类型:HINSTANCE,包含对话框模板的模块句柄。如果此参数为 NULL,则使用当前可执行文件。lpTemplate,类型:LPCTSTR,对话框模板。可以使用MAKEINTRESOURCE宏来创建此值。hWndParent,类型:HWND,拥有该对话框的窗口的句柄。lpDialogFunc,类型:DLGPROC,指向对话框过程的指针。
根据文档提示,完成代码
#include "framework.h" #include "Project1.h" #include <TlHelp32.h> #include <stdio.h> #define WECHAT_PROCESS_NAME "WeChat.exe" // 注册函数,方便其他模块使用 INT_PTR CALLBACK DialogProc(_In_ HWND hwndDlg, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam); VOID InjectDll(); int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) { DialogBox(hInstance, MAKEINTRESOURCE(ID_MAIN), NULL, &DialogProc); return 0; } INT_PTR CALLBACK DialogProc(_In_ HWND hwndDlg, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam) { if (uMsg == WM_INITDIALOG) { MessageBox(NULL, "首次加载", "标题", 0); } // 关闭界面操作 if (uMsg == WM_CLOSE) { EndDialog(hwndDlg, 0); } // 所有的界面上的按钮事件都会走这个宏 if (uMsg == WM_COMMAND) { if (wParam == INJECT_DLL) { InjectDll(); //注入 Dll } if (wParam == UN_DLL) { } } return FALSE; }
接着来实现 InjectDll() 方法,完成注入 DLL 的功能,整体分 3 步走。
1.获取微信的PID,通过PID获得微信进程的句柄,从而拥有了权限 2.申请内存,内存大小为 DLL 路径大小 3.将 DLL 路径注入到微信进程中
DWORD ProcessNameFindPID(LPCSTR ProcessName) { // #include <TlHelp32.h> // 获取进程快照 HANDLE ProcessAll = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);// 获取整个系统的进程快照 // 在快照中对比过 processname 进程名称 PROCESSENTRY32 processInfo = { 0 }; processInfo.dwSize = sizeof(PROCESSENTRY32); do { // WeChat.exe // th32ProcessID 进程ID, szExeFile进程名 if (strcmp(ProcessName, (char*)processInfo.szExeFile) == 0) { return processInfo.th32ProcessID; } } while (Process32Next(ProcessAll, &processInfo)); return 0; }
上述方法,需要 windows 平台 C++ 基础。其实主要就是对 Windows.h 中方法的调用,接着完成如下效果
VOID InjectDll() { // 自己恶意dll的路径 CHAR pathStr[0x100] = { "D://GetWeChatInfo.dll" }; // 获取微信PID DWORD PID = ProcessNameFindPID(WECHAT_PROCESS_NAME); if (PID == 0) { MessageBox(NULL, "没有找到微信进程或微信没有启动", "错误", 0); return; } // 通过PID获取句柄 C++中句柄类型为 HANDLE HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID); if (NULL == hProcess) { MessageBox(NULL, "进程打开失败,可能权限不足或关闭了微信应用", "错误", 0); return; } //申请内存,内存大小为我们要注入 DLL 的大小 LPVOID dllAdd = VirtualAllocEx(hProcess, NULL, sizeof(pathStr), MEM_COMMIT, PAGE_READWRITE); if (NULL == dllAdd) { MessageBox(NULL, "内存分配失败", "错误", 0); return; } // 将 DLL 写入到对应的内存地址中 if(WriteProcessMemory(hProcess, dllAdd, pathStr, strlen(pathStr), NULL) == 0) { MessageBox(NULL, "路径写入失败", "错误", 0); return; } // 创建一块内存用来打印一段文本,方便看效果 CHAR test[0x100] = { 0 }; sprintf_s(test, "写入的地址为:%p", dllAdd); OutputDebugString(test); }
完成后,直接编译运行,如果编译后的效果依旧是之前代码的效果,你可以 重新生成解决方案,然后再编译。
点击注入,获得注入的位置,通过 Cheat Engine 来查看该位置的内容
通过CE查看相应的内存地址,发现该内存地址为dll的路径,注入dll成功
注入成功后,还需要调用 Kernel32.dll 中的方法,调用该路径下的 dll 就完成 dll的注入了,代码如下:
// 省略显示前面的代码 // 创建一块内存用来打印一段文本,方便看效果 CHAR test[0x100] = { 0 }; sprintf_s(test, "写入的地址为:%p", dllAdd); OutputDebugString(test); //将地址写入后,通过 Kernel32.dll 调用该地址的 DLL 就完成了 HMODULE k32 = GetModuleHandle("Kernel32.dll"); LPVOID loadAdd = GetProcAddress(k32, "LoadLibraryA"); // 远程执行我们的dll,通过注入的dll地址以及CreateRemoteThread方法让微信进程调用起我们的进程 HANDLE exec = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)loadAdd, dllAdd, 0, NULL); if (exec == NULL) { MessageBox(NULL, "远程注入失败", "错误", 0); return; }
此时 DLL 就相当于在微信进程中了。
DLL中的函数就可以操作微信进程中的内存了,从而实现获取微信信息的目的。
如果没有调用其DLL,则需要注意一下 LoadLibraryA 方法,该方法会调用 ASCII的路径地址,如果你是unicode的话,则需要调用 LoadLibraryW 方法,如果不成功,可以尝试替换一下。
没有Windows平台开发C++的朋友看完上面代码可能会感到疑惑,为什么将恶意dll路径插入内存后,可以让微信进程自己去载入这个恶意dll呢?
简单解释一下,在windows中,每个进程在启动时都会载入Kernel32.dll,微信进程会载入Kernel32.dll,浏览器进程也会载入Kernel32.dll,而我们可以通过Kernel32.dll中的LoadLibraryA()方法或LoadLibraryW()方法去动态载入新的dll,需要解释一下,我们在自己的进程中获取LoadLibraryA()方法的内存位置,这个内存位置对windows中其他进程而言是相同的。
但关键点是通过微信进程去使用Kernel32.dll中的LoadLibraryA()方法载入新的dll,为了实现这个目的,就需要使用CreateRemoteThread()方法,该方法可以远程调用其他进程的中的方法,前提是要有该进程的句柄(即执行权限),而我们在一开始就获得了微信进程的执行权限。
总结一下整个流程:
-
1.将恶意dll路径插入到微信进程运行内存中
-
2.获取当前进程(我们自己的进程,而非微信进程)Kernel32.dll中LoadLibraryA()方法或LoadLibraryW()方法,因为微信进程也载入了Kernel32.dll,所以微信进程可以在相同的内存地址处找到LoadLibraryA()方法或LoadLibraryW()方法
-
3.通过CreateRemoteThread()方法,让微信进程去调用LoadLibraryA()方法载入恶意dll
结尾
本文并没有给出恶意dll的具体实现逻辑,后面将实现一个简单的dll,来获取微信的个人信息,其实基于上一篇文章,找到了个人信息的具体内存位置,该dll要做的只是从中读取信息,仅此而已。
但恶意dll还可以实现很多复杂的功能,比如监控聊天信息、自动收红包、自动踢人等各种功能,这些功能在后面有机会再分享给大家。