文章目录
一、Hook概述
1.1 什么是Hook
“Hook” 翻译过来的意思是“挂钩” “钩子”,在程序执行的时候,在适当的位置对程序运行流程进行监控、拦截即为Hook技术。
1.2 什么是APIHook
API Hook就是Hook技术的一种具体形式,实际上API Hook是改变API运行结果的一种技术手段,在Windows下进行开发就会用到Windows系统提供的API,对系统API进行拦截,API Hook是想要改变其他进程运行流程的常用技术之一。
1.3 Hook过程理解
举个例子,如果我们要去Hook Windows的API “MessageBoxA”,Hook前后的程序流程可如下图所示:
可以看到,Hook之后原程序想去调用本来的MessageBoxA,却最后调用到我们的MyMessageBoxA,在调用我们自身的MyMessageBoxA以后,在MyMessageBox函数中,可以选择是否再去调用原MessageBoxA函数。
通过上述方式,我们需要做的就是让程序在Call MessageBoxA地址的时候,最终转到的是我们的函数地址,整个过程就是一个API Hook的过程。
二、Hook分类
2.0 分类概述
Hook主要可以分为三类:Address Hook,Inline Hook, 基于异常处理的Hook,当然有些病毒的异常行为与Hook的理念是一样的,只是方式不同而已。
2.1 Address Hook
Address Hook是指通过修改数据进行Hook的方法,修改的数据一般是函数的地址或者地址偏移量。一半这种数据是存储在各类表或者结构中,也可能是某个指定地址或者特殊寄存器中。但所有数据修改都是在某个时刻成为程序执行过程中的eip。因此只要把地址替换成我们的事先准备的替代函数,就可以实现Hook。
2.2 Inline Hook
Inline Hook是指直接修改指令的Hook方式,思想是转移程序的执行流程,使其进行函数调用逻辑的变化,一般是使用jmp,call,retn之类的转移指令去实现。
2.3 基于异常处理的Hook
当程序执行过程中发生异常,系统内核的异常处理程序nt!KiDispatchException就会开始工作。没有内核调试器存在的情况下,系统会把异常处理过程交给用户态的异常处理过程。
也就是说我们在程序中安装异常处理过程,在Hook位置写入一条会引起异常的指令,然后让程序在执行到这异常位置时,跳转到我们事先安装的异常处理过中程,进而实现该位置的Hook。
三、MinHook库介绍
经过以上对Hook方式的简单介绍我们可以理解Hook究竟是什么,以及实现Hook的几种方式,详细的原理介绍在《加密与解密》第13章都有解释。不再过多的原理解释,实际上Git上有很多已经封装好的Hook实现代码,学会如何使用库比自己基于原理实现整个Hook过程简单很多。。
3.1 MinHook代码
git地址:MinHook
官方文档说明:MinHook - The Minimalistic x86/x64 API Hooking Library
MinHook就是通过Inline Hook实现的,通过生成库文件,在我们的项目中包含头文件以及对应库文件就可以实现Hook。
3.2 文件目录介绍
build目录:包含各种版本解决方案,可以选择对应的版本进行生成
include目录:需要包含到我们项目的头文件目录
每一个对应版本的解决方案目录介绍:
lib目录:生成对应的静态库目录,包含x64与x86
bin目录:生成对应的动态库目录,包含x64与x86
3.3 解决方案描述
可以直接生成第一个项目libMinHook,生成库文件,把需要的头文件MinHook.h包含到自己的项目中,需要注意的是根据需要Hook的进程是64位还是32位而选择对应生成库文件版本,否则Hook会不生效
生成第二个项目是直接可以生成静态库对应的dll,可以用于动态Loadlibrary,但是还是建议直接生成静态库直接生成到我们自己的项目中,因为我们自己的项目是需要生成一个注入目标进程的dll。
3.4 头文件定义中文说明:
/*
* MinHook - The Minimalistic API Hooking Library for x64/x86
* Copyright (C) 2009-2017 Tsuda Kageyu.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#if !(defined _M_IX86) && !(defined _M_X64) && !(defined __i386__) && !(defined __x86_64__)
#error MinHook supports only x86 and x64 systems.
#endif
#include <windows.h>
// MinHook 错误码.
typedef enum MH_STATUS
{
// 未知错误,不应退出
MH_UNKNOWN = -1,
// 成功
MH_OK = 0,
// 已经初始化过
MH_ERROR_ALREADY_INITIALIZED,
// 没有初始化或者已经退出初始化
MH_ERROR_NOT_INITIALIZED,
// 已创建指定目标函数的挂钩
MH_ERROR_ALREADY_CREATED,
// 尚未创建指定目标函数的挂钩
MH_ERROR_NOT_CREATED,
// 指定目标函数的挂钩已启用
MH_ERROR_ENABLED,
// 尚未启用或已禁用指定目标函数的挂钩
MH_ERROR_DISABLED,
// 指定的指针无效。它指向未分配和/或不可执行区域的地址
MH_ERROR_NOT_EXECUTABLE,
// 无法挂接指定的目标函数
MH_ERROR_UNSUPPORTED_FUNCTION,
// 内存分配失败
MH_ERROR_MEMORY_ALLOC,
// 更改内存保护失败
MH_ERROR_MEMORY_PROTECT,
// 未加载指定的模块
MH_ERROR_MODULE_NOT_FOUND,
// 找不到指定的函数
MH_ERROR_FUNCTION_NOT_FOUND
}
MH_STATUS;
// 可以作为参数传递
#define MH_ALL_HOOKS NULL
#ifdef __cplusplus
extern "C" {
#endif
// 初始化MinHook库,在程序调用开始前必须初始化
MH_STATUS WINAPI MH_Initialize(VOID);
// 卸载MinHook库,在程序调用结束后必须卸载
MH_STATUS WINAPI MH_Uninitialize(VOID);
// 创建一个目标函数的钩子
// Parameters:
// pTarget [in] 指向原函数(目标函数)的指针
// pDetour [in] 指向用来替代目标函数的函数指针
// ppOriginal [out] 指向调用原函数入口的指针,此参数可为NULL
MH_STATUS WINAPI MH_CreateHook(LPVOID pTarget, LPVOID pDetour, LPVOID *ppOriginal);
// 创建一个目标API函数的钩子
// Parameters:
// pszModule [in] 目标函数所在的模块名
// pszProcName [in] 目标函数名
// pDetour [in] 指向用来替代目标函数的函数指针
// ppOriginal [out] 指向调用原函数入口的指针,此参数可为NULL
MH_STATUS WINAPI MH_CreateHookApi(
LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal);
// 创建一个目标API函数的钩子
// Parameters:
// pszModule [in] 目标函数所在的模块名
// pszProcName [in] 目标函数名
// pDetour [in] 指向用来替代目标函数的函数指针
// ppOriginal [out] 指向调用原函数入口的指针,此参数可为NULL
// ppTarget [out] 指向目标函数的指针,它将与其他函数一起使用,此参数可为NULL
MH_STATUS WINAPI MH_CreateHookApiEx(
LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal, LPVOID *ppTarget);
// 移除已创建的钩子
// Parameters:
// pTarget [in] 目标函数的指针
MH_STATUS WINAPI MH_RemoveHook(LPVOID pTarget);
// 启用已创建的钩子
// Parameters:
// pTarget [in] 指向目标函数的指针
// 如果这个参数是 MH_ALL_HOOKS,那么所有创建的钩子都将一次性启用。
MH_STATUS WINAPI MH_EnableHook(LPVOID pTarget);
// 禁用已创建的钩子
// Parameters:
// pTarget [in] 指向目标函数的指针
// 如果这个参数是 MH_ALL_HOOKS,那么所有创建的钩子都将一次性禁用。
MH_STATUS WINAPI MH_DisableHook(LPVOID pTarget);
// 添加启用创建的钩子放到队列中
// Parameters:
// pTarget [in] 指向目标函数的指针
// 如果这个参数是 MH_ALL_HOOKS,那么所有创建的钩子都将排队等待启用。
MH_STATUS WINAPI MH_QueueEnableHook(LPVOID pTarget);
// 排队禁用已创建的钩子
// Parameters:
// pTarget [in] 指向目标函数的指针
// 如果这个参数是 MH_ALL_HOOKS,那么所有创建的钩子都将排队等待禁用。
MH_STATUS WINAPI MH_QueueDisableHook(LPVOID pTarget);
// 一次性应用所有排队的更改
MH_STATUS WINAPI MH_ApplyQueued(VOID);
// 通过状态返回钩子名
const char * WINAPI MH_StatusToString(MH_STATUS status);
#ifdef __cplusplus
}
#endif
四、Hook实现过程
4.1 步骤介绍
我们要实现Hook,需要我们自己生成一个可以注入目标进程的独立的dll,有了可以注入目标进程的dll以后,我们就需要一个注射器来把我们的dll注入到目标进程中。
把dll注入目标进程的目的是为了获取目标进程中的目标函数的地址,进程空间都是相互独立的,想实现Hook就需要把dll注入到目标进程中,然后获取到目标进程中windows API的函数地址,或者是其他目标进程使用的其他开源库中的函数地址。
- 注入进程的dll:实现注入后的Hook操作
- 注射器:为了把dll注入到目标进程中
- 目标进程:含有目标Hook函数的被注入进程
4.2注入进程的dll代码
新建一个dll项目,把静态库libMinHook编译进去,添加头文件,然后进行Hook代码的编写,这里我们简单Hook一个MessageboxA,大体代码如下所示,还可以去看MinHook的头文件调用其他接口。
#include "../include/MinHook.h"
#if defined _M_X64
#pragma comment(lib, "libMinHook.x64.lib")
#elif defined _M_IX86
#pragma comment(lib, "libMinHook.x86.lib")
#endif
typedef int (WINAPI* MESSAGEBOXA)(HWND, LPCSTR, LPCSTR, UINT);
MESSAGEBOXA fpMessageBoxA = NULL;//指向原MessageBoxA的指针
//用来替代原函数的MessageBox函数
int WINAPI DetourMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType)
{
//这里只做简单的修改参数,其实可以做很多事情,甚至不去调用原函数
return fpMessageBoxA(hWnd, "Hooked!", lpCaption, uType);
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
do
{
//初始化
if (MH_Initialize() != MH_OK)
{
OutputDebugStringA("Initialize err");
break;
}
//创建Hook
if (MH_CreateHook(MessageBoxA, &DetourMessageBoxA,
reinterpret_cast<LPVOID*>(&fpMessageBoxA)) != MH_OK)
{
OutputDebugStringA("MH_CreateHook err");
break;
}
//使目标函数Hook生效
if (MH_EnableHook(MessageBoxA) != MH_OK)
{
OutputDebugStringA("MH_EnableHook err");
break;
}
//使目标函数Hook失效
//if (MH_DisableHook(MessageBoxA) != MH_OK)
//{
// break;
//}
// 卸载MinHook
//if (MH_Uninitialize() != MH_OK)
//{
// break;
//}
} while (false);
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
注入工具就简单生成一个,用之前的远程线程注入方式就可以
简单的把注入的代码贴一下,我用MFC去写的一个工具Button控件响应代码
void CInjectProcessDlg::OnBnClickedHook1()
{
DWORD hpid = 0; // 目标进程pid
HANDLE htargetprocess = NULL; // 目标进程句柄
LPVOID lpAddr; // 目标线程申请内存空间的指针
BOOL bResult = FALSE; // 注入结果
do
{
hpid = GetProcessIDByName(L"TargetProcess.exe");
if (hpid == 0)
{
break;
}
htargetprocess = OpenTargetProcess(hpid);
if (htargetprocess == NULL)
{
break;
}
if (TagetAlloc(htargetprocess, lpAddr) == FALSE)
{
break;
}
if (WriteDLLToTarget(htargetprocess, lpAddr, L"Hookdll.dll") == FALSE)
{
break;
}
PTHREAD_START_ROUTINE pnfStartAddr = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"Kernel32"), "LoadLibraryW");
if (pnfStartAddr == NULL)
{
break;
}
if (CreateThreadInTarget(htargetprocess, pnfStartAddr, lpAddr) == FALSE)
{
break;
}
bResult = TRUE;
} while (false);
}
4.3效果
写个目标进程
#include <iostream>
#include <Windows.h>
int main()
{
MessageBoxA(NULL, "Orgin MessageBox", "tip", NULL); //注入前调用
Sleep(10000); //简单Sleep一下
MessageBoxA(NULL, "Orgin MessageBox", "tip", NULL); //注入后调用
}
注入前
注入后:
通过PrecessExplorer可以清楚看到进程以及模块关系
4.4 拓展
以上例子只是一个简单的Hook了MessageBoxA,但实际上,我们可以通过一些工具看到一些进程中调用的其他API,或者使用的一些开源库的一些API接口,我们只要获取到想Hook的函数地址,就可以不限于仅仅Hook Windows提供的一些API。
Hook能做的事情很多,比如开发补丁,信息截获,安全防护等。
五、结尾
这个文章也是很简单入门的介绍Hook,实际上Hook技术还是有很强的拓展性,能做的事情很多,也可以做坏事,技术没有好坏,只是看使用者用来做什么了。