去年的时候想做个脚本,开始是用python,但是有些实现想用Hook,然而自己本来python就是半桶水,而C++的window编程更是一窍不通,然而网上的教程又是零零散散
好吧,其实就是我没基础,大佬:接下来就不用我说了吧(超需要啊~~~)
注入需要准备一个dll,与配套的exe程序(当然,hook那么多种形式,我只是碰巧学了这么个半通不通的)
所以,在这个Demo里,有2个Dll:
- Add.dll(假设的要hook的函数所在dll)
- HookDll.dll(包含要注入用的函数所在dll)
- 2个exe项目:One.exe(假设要Hook的程序)
- Hook.exe(hook用的程序)
导包跟头文件我会发出来,但是函数修改的话,只会在
修改部分
发出
在本案例中,假设要hook程序中Add.dll中的ExportFunc(LPCTSTR pszContent)
函数
这个dll只有一个ExportFunc函数,主体One.exe会通过该module函数弹出提示窗
add.h
#pragma once
#include
__declspec(dllexport) void ExportFunc(LPCTSTR pszContent);
dllmain.cpp
HMODULE g_hModule;
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH: // 这里表示在dll加载的时候进行的操作
g_hModule = (HMODULE)hModule; //获取谁加载本dll,这样ExportFunc就能在对应的窗口弹出消息了
break;
}
return TRUE;
}
//pszContent:要弹出的提示内容
void ExportFunc(LPCTSTR pszContent) {
char sz[MAX_PATH];
GetModuleFileNameA(g_hModule, sz, MAX_PATH);
MessageBoxA(NULL, pszContent, strrchr(sz, '\\') + 1, MB_OK);
}
在本案例中,One.exe就是要被hook的程序了
One在点击界面的时候,会调用Add.dll的ExportFunc,从而弹出窗口
One.h
#pragma once
#include "resource.h"
#include
#include
#include
HMODULE hModule; //存放加载Add.dll
typedef void (*PFNEXPORTFUNC) (LPCTSTR); //声明类型(?c++真心搞不懂)
PFNEXPORTFUNC mef; //存放加载ExportFunc
One.cpp
#include "framework.h"
#include "One.h"
#include
using namespace std;
void OnOk();
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_LBUTTONDOWN:
OnOk(); //在点击界面的时候触发该函数
break;
return 0;
}
//这个是点击响应函数
void OnOk() {
/*
mef是ExportFunc的函数参数,加载过dll后,读取到的函数都会有个句柄(?应该是这么叫吧)
这个名称不重要,之所以是PFNEXPORTFUNC是因为我们这个ExportFunc返回值是void
大概就是这么理解吧=3=
这里简单的说就是,如果ExportFunc还没加载
就去看看Add.dll加载了没,没有就去加载
有就从Add.dll读取ExportFunc
然后就去调用这个ExportFunc然后弹出个success
当然,这些根本不关我们事,毕竟One.exe模拟的是被我们hook的程序,又不是我们自己的程序=3=
*/
if (mef == NULL) {
if (hModule == NULL) {
hModule = ::LoadLibrary("D:/workspace/c/yysv3/Add/Debug/Add.dll");
}
if (hModule != NULL) {
mef = (PFNEXPORTFUNC)::GetProcAddress(hModule, "ExportFunc");
}
else {
MessageBox(NULL, "加载ADD失败", "", NULL);
}
}
if (mef != NULL) {
mef("success");
}
}
上面的在实际的Hook中就是现成的程序了,我们只需要了解我们要Hook什么dll中的什么函数,这些就不是我这没入门的能理解的范畴了
下面这就是Hook的重头戏了~~
HookDll.dll就是我们要用来注入的dll了
在这里,我们也定义了一个void ExportFunc(LPCTSTR pszContent)
;这里这个函数名称不重要,重要的是,参数必须跟我们要hook的函数参数保持一致(个人理解,,)
说一下个人对于Hook的理解(markdown绘制的,将就看看吧=3=)
所以,我们需要重新写一个函数B,用B去替换原本的正主A,这时候函数的参数要跟原来的保持一致,因为其他的代码并不知道这个A已经被掉包了,所以还是带着那么些参数过来了(这里函数名无所谓是因为,函数名只是一个指代一个入口,所以叫啥最后指向的入口对了就OK了)
最开始是想着怎么注入dll,到注入后一直在找资料说怎么调用怎么调用。后来才大概明白俩点:
- 我们写的Hook.exe的注入操作,只是为了把HookDll.dll送到目标程序里面,其他的跳转什么的,全靠HookDll.dll自己去行动了
- Dll在被加载的时候,会触发一个操作,就是默认的
DllMain::DLL_PROCESS_ATTACH
,也就是说在注入的时候,我们就该把所有事情安排好了(也就是函数B入口替换A的操作要完成),完了之后,只要逻辑X过来,它走的就是函数B(也就是我们的逻辑了),这时候就是目标程序主动来调用我们的函数了,而不用我们去控制了简单说,就像一个特工要潜入目标,Hook.exe就是带路党把他带到目标营地里,而至于要搞破坏还是投资料,全都得这位特工去完成了,全凭他当前的得到的命令去做事情了。这时候,虽然说不是联系不上他,但是要重新给他发布指令也就是说外部指挥部去传递信息进去,肯定是相当麻烦且危险的事情了
dllmain.cpp
#include "pch.h"
HMODULE g_hModule;
HANDLE cur;
PROC OldProc;
unsigned long* lpAddr;
bool m_bInjected = false;
bool bHook;
DWORD dwPid;
//这里是用来保存我们hook的函数原地址(原函数)以及新地址(我们的函数)的
BYTE NewCode[5];
BYTE OldCode[5];
void ExportFunc(LPCTSTR pszContent);
typedef void( *ExportProc)(LPCSTR pszContent);
ExportProc proc;
//这里除了上面定义的用来加载HookDll中的ExportFunc外,还需要一个FARPROC,个人理解是用来记录远程的ExporProc
FARPROC fproc;
//hook操作函数
void HookOn();
void HookOff();
void Inject();
void printf(const char* msg, ...);
BOOL APIENTRY DllMain( HMODULE hModule,DWORD ul_reason_for_call,LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
//这里是加载了dll后就会执行的
case DLL_PROCESS_ATTACH:
//拿到当前的进程号
dwPid = ::GetCurrentProcessId();
cur = OpenProcess(PROCESS_ALL_ACCESS, 0, dwPid);
printf("Hook 注入");
g_hModule = GetModuleHandle("Add.dll");
//所以我们需要在这里偷天换日
Inject();
break;
case DLL_PROCESS_DETACH: //这个是dll卸载前会执行的
HookOff(); //所以在这里就要回复原状了(重要)
MessageBoxA(NULL, "Hook 卸载", "", MB_OK);
break;
}
return TRUE;
}
/**
* dll注入之后通过dll的加载机制,调用了该函数
* 首先,是先进行判断我们要hook的目标add.dll是不是加载了,避免搞错目标
* 确认了dll的位置后,就通过dll去找到我们真正要替换的目标ExportFunc
* 然后就将我们拿到的正主A转成FPROC(这个我也不大懂它的意义,不过能通过它去确定A的位置就是了)
* 然后就把目标的地址跟自身地址记录好
* 简单说,我们的特工潜入后,首先是去找找这里是不是有XX房间(避免潜入错营地的尴尬)
* 找房间后又去房间里确认A是不是有来工作(dll加载后,函数不一定有加载调用)
* 确认有后,就偷偷记录A跟自己的铭牌了
*/
void Inject()
{
if (m_bInjected == false)
{
//保证只调用1次
m_bInjected = true;
//获取add.dll中的add()函数
HMODULE hmod = GetModuleHandle("Add.dll");
if (hmod != NULL) {
proc = (ExportProc)::GetProcAddress(hmod, "ExportFunc");
//procEndScene = (EndSceneProc)::GetProcAddress(hmod, "EndScene");
}
else {
printf("fail to found add.dll");
}
fproc = (FARPROC)proc;
if (fproc == NULL)
{
printf("fail to load farproc");
}
// 将add()中的入口代码保存入OldCode[]
_asm
{
lea edi, OldCode
mov esi, fproc
cld
movsd
movsb
}
NewCode[0] = 0xe9;//实际上0xe9就相当于jmp指令
//获取Myadd()的相对地址
_asm
{
lea eax, ExportFunc
mov ebx, fproc
sub eax, ebx
sub eax, 5
mov dword ptr[NewCode + 1], eax
}
//填充完毕,现在NewCode[]里的指令相当于Jmp Myadd
HookOn(); //可以开启钩子了
}
}
/**
* HookOn跟HookOff类似
* 就是修改程序的内存,然后变更函数的入口了
* 这一步相当于我们的特工B潜入目标阵营后确认A的存在后,将自己 铭牌跟A的偷换了
* 当某人C通过铭牌来找A的时候,B接过信息并顺手转交给了A
* 由于信息格式是符合规定的,A也没怀疑就做好处理并返回给了C
* 这时候原本天知地知,AC知的事,就被B知道甚至可能偷偷做了一点手脚了
*/
void HookOn()
{
DWORD dwTemp = 0;
DWORD dwOldProtect;
//将内存保护模式改为可写,老模式保存入dwOldProtect
VirtualProtectEx(cur, fproc, 5, PAGE_READWRITE, &dwOldProtect);
//将所属进程中add()的前5个字节改为Jmp Myadd
WriteProcessMemory(cur, fproc, NewCode, 5, 0);
//将内存保护模式改回为dwOldProtect
VirtualProtectEx(cur, fproc, 5, dwOldProtect, &dwTemp);
bHook = true;
}
//将所属进程中add()的入口代码恢复
void HookOff()
{
DWORD dwTemp = 0;
DWORD dwOldProtect;
VirtualProtectEx(cur, fproc, 5, PAGE_READWRITE, &dwOldProtect);
WriteProcessMemory(cur, fproc, OldCode, 5, 0);
VirtualProtectEx(cur, fproc, 5, dwOldProtect, &dwTemp);
bHook = false;
}
/**
* 这个函数就是我们自己的函数B
* 因为我们Inject后就HookOn()在监听着进入的函数了
* 也就是在逻辑走到原程序的ExportFunc就会进入到这里了
* 所以执行完我们自己的逻辑就该将入口恢复,然后调用真正的A函数
* 而A函数运行完会返回什么就返回什么(这里是void自然就是运行完就结束了)
* 然后重新修改A的入口到我们的B上面
*/
void ExportFunc(LPCTSTR pszContent) {
printf("Hook!!!!");
HookOff();
proc(pszContent);
HookOn();
}
//封装的弹框函数,没啥特别重点的
void printf(const char* msg, ...)
{
va_list args;
const char* params;
va_start(args, msg);
params = va_arg(args, const char*);
va_end(args);
TCHAR str[MAX_PATH];
wsprintf(str, msg, params);
MessageBox(NULL, str, "", NULL);
}
Hook.exe就是我们用来注入跟卸载HookDll.dll用的,这里主要是的功能就是点击时,如果One.exe存在Add.dll就去判断HooDll.dll是否注入了,是就卸载,否则注入
这一步内容可能会稍微多一些,主要是多了许多的步骤判断,最开始是因为为了确认错误所在,后来也就没修改了,核心内容倒是不多,跟网上的其他说明就大同小异了
- 判断进程中是否存在目标
- 是的话获取进程操作句柄
- 然后开辟内存(这一步可能会失败,具体自己网上找解决)
- 将我们的dll路径赋值进去(到这里其实就是相当于给我们的特工在目标营地创建了一个身份档案)
- 然后去找LoadLibrary(这里可理解为这个世界的所有营地人事都是同一家人力公司派遣的,他们只认身份档案不认人的,然后人事部就派车出来接特工进营地了)
Hook.cpp
#include "framework.h"
#include "Hook.h"
BOOL CheckDllInProcess(DWORD dwPID, LPCTSTR szDllPath); //检查指定dll是否存在目标进程
BOOL InjectDll(DWORD dwPID, LPCTSTR szDllPath);
BOOL EjectDll(DWORD dwPID, LPCTSTR szDllPath);
void d_printf(const char* msg, ...);
bool InitalizeInject();
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_LBUTTONDOWN: //点击监听
if (CheckDllInProcess(dwPID, orignDllName))
{
if (CheckDllInProcess(dwPID, dllName))
{
//已经注入就卸载
EjectDll(dwPID, dllName);
}
else
{
//还没注入就加载
InjectDll(dwPID, dllPath);
}
}
else
{
d_printf("不存在");
}
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
/**
* 检测进程中是否包含对应的Dll
* @param dwPID 进程ID
* @param szDllPath 所要注入Dll名称
*
* 创建内存快照
* 遍历比较
*/
BOOL CheckDllInProcess(DWORD dwPID, LPCTSTR szDllPath)
{
BOOL bMore = FALSE;
HANDLE hSnapshot = INVALID_HANDLE_VALUE;
MODULEENTRY32 me = {
sizeof(me), };
if (INVALID_HANDLE_VALUE == (hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID)))
{
DWORD err = GetLastError();
d_printf("CheckDllInProcess():CreateToolHelp32Snapshot(%d)failed!!![%d]\n", dwPID, err);
return FALSE;
}
bMore = Module32First(hSnapshot, &me);
FILE* out;
errno_t err;
err = fopen_s(&out, "D:\\a.txt", "a");
for (; bMore; bMore = Module32Next(hSnapshot, &me))
{
if (out != NULL)
{
fprintf(out, "%s\t", me.szModule);
}
if (!_tcsicmp(me.szModule, szDllPath) || !_tcsicmp(me.szModule, szDllPath))
{
CloseHandle(hSnapshot);
if (out != NULL)
{
fprintf(out, "\n-------------\n");
fclose(out);
}
return TRUE;
}
}
if (out != NULL)
{
fprintf(out, "\n-------------\n");
fclose(out);
}
CloseHandle(hSnapshot);
//d_printf("找不到%s", szDllPath);
return FALSE;
}
//向指定的进程注入相应的模块
//dwPID 目标进程的PID
//szDllPath 被注入的dll的完整路径
BOOL InjectDll(DWORD dwPID, LPCTSTR szDllPath)
{
HANDLE hProcess = NULL;//保存目标进程的句柄
LPVOID pRemoteBuf = NULL;//目标进程开辟的内存的起始地址
DWORD dwBufSize = (DWORD)(_tcslen(szDllPath) + 1) * sizeof(TCHAR);//开辟的内存的大小
LPTHREAD_START_ROUTINE pThreadProc = NULL;//loadLibreayW函数的起始地址
HMODULE hMod = NULL;//kernel32.dll模块的句柄
BOOL bRet = FALSE;
HANDLE hThread = NULL;
if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)))//打开目标进程,获得句柄
{
d_printf("InjectDll() : OpenProcess(%d) failed!!! [%d]\n",
dwPID, GetLastError());
goto INJECTDLL_EXIT;
}
pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize,
MEM_COMMIT, PAGE_READWRITE);//在目标进程空间开辟一块内存
if (pRemoteBuf == NULL)
{
d_printf("InjectDll() : VirtualAllocEx() failed!!! [%d]\n",
GetLastError());
goto INJECTDLL_EXIT;
}
if (!WriteProcessMemory(hProcess, pRemoteBuf,
(LPVOID)szDllPath, dwBufSize, NULL))//向开辟的内存复制dll的路径
{
d_printf("InjectDll() : WriteProcessMemory() failed!!! [%d]\n",
GetLastError());
goto INJECTDLL_EXIT;
}
hMod = GetModuleHandle("kernel32.dll");//获得本进程kernel32.dll的模块句柄
if (hMod == NULL)
{
d_printf("InjectDll() : GetModuleHandle(\"kernel32.dll\") failed!!! [%d]\n",
GetLastError());
goto INJECTDLL_EXIT;
}
//获得LoadLibrary函数的起始地址,这个要区分对方程序是否是宽字符的
if (isUnicode)
{
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryA");
}
else
{
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryW");
}
if (pThreadProc == NULL)
{
d_printf("InjectDll() : GetProcAddress(\"LoadLibraryW\") failed!!! [%d]\n",
GetLastError());
goto INJECTDLL_EXIT;
}
hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);
if (!hThread)//执行远程线程
{
d_printf("InjectDll() : MyCreateRemoteThread() failed!!!\n");
goto INJECTDLL_EXIT;
}
INJECTDLL_EXIT:
if (hThread != NULL)
{
WaitForSingleObject(hThread, INFINITE);
}
bRet = CheckDllInProcess(dwPID, dllName); //确认结果
if (pRemoteBuf)
VirtualFreeEx(hProcess, pRemoteBuf, 0, MEM_RELEASE);
if (hProcess)
CloseHandle(hProcess);
return bRet;
}
//让指定的进程卸载相应的模块
//dwPID 目标进程的PID
//szDllPath 被注入的dll的完整路径,注意:路径不要用“/”来代替“\\”
BOOL EjectDll(DWORD dwPID, LPCTSTR szDllPath)
{
BOOL bMore = FALSE, bFound = FALSE, bRet = FALSE;
HANDLE hSnapshot = INVALID_HANDLE_VALUE;
HANDLE hProcess = NULL;
MODULEENTRY32 me = {
sizeof(me), };
LPTHREAD_START_ROUTINE pThreadProc = NULL;
HMODULE hMod = NULL;
TCHAR szProcName[MAX_PATH] = {
0, };
if (INVALID_HANDLE_VALUE == (hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID)))
{
d_printf("EjectDll() : CreateToolhelp32Snapshot(%d) failed!!! [%d]\n",
dwPID, GetLastError());
goto EJECTDLL_EXIT;
}
bMore = Module32First(hSnapshot, &me);
for (; bMore; bMore = Module32Next(hSnapshot, &me))//查找模块句柄
{
if (!_tcsicmp(me.szModule, szDllPath) || !_tcsicmp(me.szExePath, szDllPath))
{
bFound = TRUE;
break;
}
}
if (!bFound)
{
d_printf("EjectDll() : There is not %s module in process(%d) memory!!!\n", szDllPath, dwPID);
goto EJECTDLL_EXIT;
}
if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)))
{
d_printf("EjectDll() : OpenProcess(%d) failed!!! [%d]\n",
dwPID, GetLastError());
goto EJECTDLL_EXIT;
}
hMod = GetModuleHandle("kernel32.dll");
if (hMod == NULL)
{
d_printf("EjectDll() : GetModuleHandle(\"kernel32.dll\") failed!!! [%d]\n",
GetLastError());
goto EJECTDLL_EXIT;
}
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "FreeLibrary");
if (pThreadProc == NULL)
{
d_printf("EjectDll() : GetProcAddress(\"FreeLibrary\") failed!!! [%d]\n",
GetLastError());
goto EJECTDLL_EXIT;
}
if (!CreateRemoteThread(hProcess, NULL, 0, pThreadProc, me.modBaseAddr, 0, NULL))
{
d_printf("EjectDll() : MyCreateRemoteThread() failed!!!\n");
goto EJECTDLL_EXIT;
}
bRet = TRUE;
EJECTDLL_EXIT:
if (hProcess)
CloseHandle(hProcess);
if (hSnapshot != NULL && hSnapshot != INVALID_HANDLE_VALUE)
CloseHandle(hSnapshot);
return bRet;
}
void d_printf(const char* msg,...)
{
va_list args;
const char* params;
va_start(args, msg);
params = va_arg(args, const char*);
va_end(args);
TCHAR str[MAX_PATH];
wsprintf(str, msg,params);
MessageBox(NULL, str, "", NULL);
}
/**
* 初始化注入操作
*
*
* 判断是否取得窗口句柄hHwnd
* 获取窗口句柄
* 获取线程号、进程号
* 判断是否多字符类型:A/W
*
* 所要注入dll完整路径,以及对应的dll名称
*
* 验证初始化内容是否完成,否则返回失败
*/
bool InitalizeInject()
{
if (hHwnd == NULL) {
hHwnd = FindWindow("One", NULL);
dwTID = GetWindowThreadProcessId(hHwnd, &dwPID);
isUnicode = IsWindowUnicode(hHwnd);
}
if (dllPath == NULL) {
dllPath = "D:\\workspace\\c\\yysv3\\HookDll\\Debug\\HookDll.dll"; //这是要Inject的dll的绝对路径
dllName = "HookDll.dll"; //这是要Inject的dll名称
orignDllName = "Add.dll"; //这是要hook的dll名称
}
if (hHwnd == NULL)
{
d_printf("Get HWND faild!");
return false;
}
if (dwTID == NULL)
{
d_printf("Get TID&PID faild!");
return false;
}
if (dllPath == NULL)
{
d_printf("Get DLL faild!");
return false;
}
return TRUE;
}
最终效果:
可以看到,One的弹框变化(虽然没写好弹框定位,但是通过任务栏确实能确定弹框的归属)