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 还可以实现很多复杂的功能,比如监控聊天信息、自动收红包、自动踢人等各种功能,这些功能在后面有机会再分享给大家。
如果本文对你有帮助,点击「在看」支持二两,下篇文章见。