Mobile wallpaper 1Mobile wallpaper 2Mobile wallpaper 3Mobile wallpaper 4Mobile wallpaper 5Mobile wallpaper 6
2477 字
12 分钟
inject and hook

前言#

钩子和注入是逆向的灵魂所在,本篇先介绍基本的Windows编程,然后介绍如何通过Windows编程实现钩子和注入。之后的工具篇会以这一篇为基础。

Windows编程#

这里的Windows编程并不涉及消息或者窗口。我们仅使用WindowsDLL提供的导出函数来使用WindowsAPI。因此,这里的Windows编程和普通编程唯一的区别就是使用了Windows定义的宏类型和WindowsAPI。为什么说是宏类型,是因为其中的各个类型都是预定义的宏,和C自带的类型没有任何本质上的区别,只是起了一个更好听的名字。

现在来写一个Hello World吧

HelloWorld#

#include<Windows.h>
int main(void) {
MessageBoxA(NULL, "Hello, World!", "Greetings", MB_OK);
MessageBoxW(NULL, L"Hello, World!", L"Greetings", MB_OK);
return 0;
}

执行以上代码,将会生成两个框,且可点击OK。

可以发现第一个函数的后缀为A,第二个函数的后缀为W。这是Windows为了Unicode兼容性提供的两个版本的函数。A后缀表示这个函数接受的字符串为ascii字符串。W后缀表示这个函数接受的字符串为宽字符串(Wide)。对于字面量来说,直接写L前缀就可以表示宽字符。

可以直接使用不带后缀的宏函数,来自动使用当前的系统设置,一般来说,此时的设置都是wide。

下面是另一个稍微复杂的例子:

#include <Windows.h>
int main(void) {
// 申请一个1024大小的只读内存页
LPVOID pBuffer = VirtualAlloc(NULL, 1024, MEM_COMMIT | MEM_RESERVE, PAGE_READONLY);
if (pBuffer == NULL) {
WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), L"VirtualAlloc failed\r\n", 19, NULL, NULL);
return 1;
}
DWORD dwData = 0x12345678;
DWORD dwOldProtect = 0;
// 修改内存页的保护属性为可读写
if (!VirtualProtect(pBuffer, 1024, PAGE_READWRITE, &dwOldProtect)) {
WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), L"change protect failed\r\n", 23, NULL, NULL);
VirtualFree(pBuffer, 0, MEM_RELEASE);
return 1;
}
// 写入数据
*(LPDWORD)pBuffer = dwData;
// 读数据
WCHAR buf[64];
// 先格式化
wsprintfW(buf, L"pBuffer value = 0x%08X\r\n", *(LPDWORD)pBuffer);
// 恢复内存页的保护属性
if (!VirtualProtect(pBuffer, 1024, dwOldProtect, &dwOldProtect)) {
WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), L"restore protect failed\r\n", 24, NULL, NULL);
// 仍然继续尝试输出并释放内存
}
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
if (hOut == INVALID_HANDLE_VALUE || hOut == NULL) {
VirtualFree(pBuffer, 0, MEM_RELEASE);
return 1;
}
DWORD ft = GetFileType(hOut);
if (ft == FILE_TYPE_CHAR) {
// 直接写控制台
DWORD written = 0;
WriteConsoleW(hOut, buf, lstrlenW(buf), &written, NULL);
}
// 释放内存页
VirtualFree(pBuffer, 0, MEM_RELEASE);
return 0;
}

一些内容#

这里有一些重要的类型和函数需要了解:

PVOID,PBYTE,PWORD,PDWORD,PDWORD64// 表示指针类型,去掉P表示对应的长度类型,前面加L表示长指针即64位,由于本身就是在64位环境,因此不需要加L,有的前面会加C表示const
CHAR,WCHAR// 字符和长字符
WSTR//长字符串
HANDLE,HMODULE,HWND//句柄,类似于指针,后面两个一个是模块句柄,一个是窗口句柄
// e.g.
LPCWSTR // LP C WSTR 指向const的长字符串指针
VirtualAlloc()// 申请内存
VirtualProtect()// 修改内存属性
VirtualFree()// 释放内存
LoadLibrary() //加载DLL
FreeLibrary()// 卸载DLL
CreateThread()//创建线程
CreateRemoteThread()//创建远程线程
CreateProcess()//创建进程

函数可以遇到了再学,上面的函数如何使用就不多说了,直接在官网查看即可。

Hook#

钩子是指在执行目标代码片段前后获取其参数或返回值。Hook有很多层级,这里用一个Python代码演示:

def hook():
import secret
# 保存原始的encodePath1
originalencode = secret.encodePathFinal
original_encodePath1 = secret.encodePath1
# 创建包装函数
def wrap(func, name):
def wrapper(*args, **kwargs):
print(f"Entering {name} with args: {args}")
result = func(*args, **kwargs)
print(f"Exiting {name} with result: {result}")
return result
return wrapper
# 替换函数
secret.encodePathFinal = wrap(originalencode, "encodePathFinal")
secret.encodePath1 = wrap(original_encodePath1, "encodePath1")
print("Hooks installed successfully for encodePathFinal and encodePath1")

执行以上代码后,你可以尝试自行实现secret中的encode函数,令其调用encodePath1和encodePathFinal,观察区别。改代码全是了hook的作用,不只是打印内容,还可以修改,这让调试和逆向更为方便。

本段直接搬运了老博客的内容

Inline Hook#

本节主要介绍的是汇编(C/CPP)上的hook,以下为inline Hook的原理图:

a422ab06-ba79-462c-a813-f5844b1cbe76

具体实现方法是:

int main()
{
hooker(MessageBoxW, mybox, 5);
MessageBoxW(0, L"Hello", 0, 0);
return 0;
}

main函数先调用hooker函数,修改messagebox的内容,然后执行被修改的messagebox函数

那么hooker函数就要传入messagebox函数的地址,和我们想要修改成的函数的地址,这里的长度可以是固定的,可以不写5

void hooker(void* src, void* dst, int len) {
DWORD old;
VirtualProtect(src, len, 0x40, &old);
memcpy(back, src, len);
*(BYTE*)src = 0xE9;
uintptr_t ra = (uintptr_t)dst - (uintptr_t)src - 5;
*(DWORD*)((BYTE*)src + 1) = ra;
VirtualProtect(src, len, old, &old);
}

在函数外面创建一个备份BYTE back[5];用来保存原始数据,然后用VirtualProtect修改函数前5个字节变成可写属性(0x40),然后备份原始属性到old并修改,核心公式是:

*(BYTE*)src = 0xE9;
uintptr_t ra = (uintptr_t)dst - (uintptr_t)src - 5;

然后改回去

最后就是实现自己的函数,一定要注意函数的传入参数必须要和原函数相同

int WINAPI mybox(_In_opt_ HWND hWnd,
_In_opt_ LPCWSTR lpText,
_In_opt_ LPCWSTR lpCaption,
_In_ UINT uType)
{
lpText = L"hooked";//想要修改的效果
unhook(MessageBoxW, back, 5);
MessageBoxW(hWnd, lpText, lpCaption, uType);
hooker(MessageBoxW, mybox, 5);
return 0;
}

这里通过去官网直接抄传入参数就可以直接得到参数名 同时注意要先解除钩子再调用原函数不然会陷入循环递归

void unhook(void* src, void* back, int len) {
DWORD old;
VirtualProtect(src, len, 0x40, &old);
memcpy(src, back, 5);
VirtualProtect(src, len, old, &old);
}

在调用完目标函数后,还要再次钩住函数,等待下一次调用

Trampoline Hook#

由于刚刚的hook函数,每次必须要先hook,然后再unhook,十分麻烦。同时,每次调用messagebox的效果都是一样的,如果想要实现显示其它东西,需要另一种方法。

对于第一个问题:如果不对hook过的函数unhook,那么就会陷入死循环,无限调用自己,如果现在有一个和messagebox的函数效果相同的函数,在hook后,不调用messagebox,而是调用这个函数,就不会内存溢出

对于第二个问题:如果有一个和messagebox函数相同的函数,那么直接调用那个函数并修改参数就可以显示其它东西。

因此,只要能构造出一个和我们想hook的函数功能相同的函数就可以解决上面两个问题。

图示:

1d9fe555-1550-4d61-86c5-336e9afd100d

那么先构造trampoline函数

void* trampoline(void* src, void* dst, int len)
{
BYTE* boxin = (BYTE*)VirtualAlloc(0, len + 5, 0x00001000, 0x40);
memcpy(boxin, src, len);
*(boxin + len) = 0xe9;
*(DWORD*)(boxin + len + 1) = (BYTE*)src - boxin - 5;
//以下为原hooker的内容
DWORD old;
VirtualProtect(src, len, 0x40, &old);
*(BYTE*)src = 0xE9;
uintptr_t ra = (uintptr_t)dst - (uintptr_t)src - 5;
*(DWORD*)((BYTE*)src + 1) = ra;
VirtualProtect(src, len, old, &old);
return boxin;
}

前4句都是在创建功能相同函数,直接申请内存把messagebox前5个字节复制过来,然后jmp到messagebox里(偷懒)

第5句到10句的hooker和之前的hooker一样

void hooker(void* src, void* dst, int len) {
DWORD old;
VirtualProtect(src, len, 0x40, &old);
*(BYTE*)src = 0xE9;
uintptr_t ra = (uintptr_t)dst - (uintptr_t)src - 5;
*(DWORD*)((BYTE*)src + 1) = ra;
VirtualProtect(src, len, old, &old);
}

最后一句return是为了之后使用不被hook的函数

然后先把相同功能函数的调用写出来

using PFUN = int (WINAPI*)(
_In_opt_ HWND hWnd,
_In_opt_ LPCWSTR lpText,
_In_opt_ LPCWSTR lpCaption,
_In_ UINT uType);
PFUN pfun = nullptr;
int main()
{
pfun = (PFUN)trampoline(MessageBoxW, mybox, 5);
MessageBoxW(0, L"Hello", 0, 0);
MessageBoxW(0, L"Hello", 0, 0);
MessageBoxW(0, L"Hello", 0, 0);
pfun(0, L"123123", 0, 0);
return 0;
}

最后添上hook函数

int WINAPI mybox(_In_opt_ HWND hWnd,
_In_opt_ LPCWSTR lpText,
_In_opt_ LPCWSTR lpCaption,
_In_ UINT uType)
{
lpText = L"hooked";
pfun(hWnd, lpText, lpCaption, uType);
return 0;
}

这样调用时,会先显示3个hook,在最后显示123123.

Inject#

好了,上面的hook代码都属于进程内Hook,也就是说如果想要实现hook,目标代码片段必须和Hook安装/卸载的代码在同一个内存空间中。那么,如何将自己写的Hook代码安装到其它进程中呢?这就是注入的作用,将外部代码写入目标进程,然后执行的一种技术。

通过以上内容,我们可以发现,注入分为写入和执行。这两种都有多种方法且可以任意组合。例如执行可以是通过CreateRemoteThread创建远程线程或者pNtQueueApcThreadEx挂载APC,写入可以是LoadLibrary或者ManualMap。同时需要注意:如果是注入DLL,那么注入器,DLL,目标进程的架构需要相同,都是x64或者x86才可能成功。

CreateRemoteThread+LoadLibrary#

这里就以最基本的CreateRemoteThread+LoadLibrary来演示。

具体流程为:

  • 将DLL路径写入目标进程

  • 获取目标进程的LoadLibrary地址

  • 以LoadLibrary为目标函数创建远程线程执行,参数为DLL路径

有几个需要注意的地方:

  1. 目标进程地址空间和注入器的地址空间不同,需要将DLL路径写入目标进程,否则目标进程无法获取路径地址。
  2. 同时也需要获取目标进程的LoadLibrary函数地址,但是由于kernel32.dll在各个进程映射路径相同,可以直接获取本地地址,该地址同时也是目标进程的LoadLibrary函数地址。
  3. DLL注入如果失败提示没有权限,需要先获取SeDebugPrivilege权限或提升至管理员
int inject(DWORD dwPID, LPCTSTR szDllPath) {
HANDLE hProcess = 0, hThread = 0;
if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)))//取得对应PID句柄
{
_tprintf(_T("open %d failed\n"), dwPID);
return FALSE;
}
DWORD dwBufSize = (DWORD)(_tcslen(szDllPath) + 1) * sizeof(TCHAR);
LPVOID pBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);//申请内存
if (pBuf == 0) {
_tprintf(_T("memory alloc failed\n"));
return FALSE;
}
WriteProcessMemory(hProcess, pBuf, (LPVOID)szDllPath, dwBufSize, NULL);//写入地址
HMODULE kernel = GetModuleHandle(L"kernel32.dll");
if (kernel == NULL)
return FALSE;
LPTHREAD_START_ROUTINE pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(kernel, "LoadLibraryW");//获取LodaLibrary地址
hThread = CreateRemoteThread(hProcess,
NULL,
0,
pThreadProc,//线程回调函数
pBuf,//传参
0,
NULL);
if (hThread == NULL) {
_tprintf(_T("create hThread failed\n"));
return FALSE;
}
WaitForSingleObject(hThread, INFINITE);//等待线程结束
CloseHandle(hThread);
CloseHandle(hProcess);
return TRUE;
}

直接同上编写即可。


总结#

通过以上内容,可以实现将自己的Hook注入至目标进程,实现自定义效果。

inject and hook
https://pri87.vip/posts/inject-and-hook/
作者
pRism
发布于
2026-01-17
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

封面
Sample Song
Sample Artist
封面
Sample Song
Sample Artist
0:00 / 0:00