Windows注入与拦截(3) -- 使用钩子方式完成DLL注入

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/china_jeffery/article/details/79789463

一. 钩子技术介绍

前面介绍了《 Windows注入与拦截(2) – 使用注册表方式完成DLL注入》,本文介绍使用钩子的方式将DLL注入到进程的地址空间中。

Windows提供了3个API来让我们可以很方便使用钩子技术将DLL文件注入到进程之中:

// 安装指定消息类型的钩子到钩子链中
HHOOK SetWindowsHookEx(int idHook,
    HOOKPROC lpfn,
    HINSTANCE hMod,
    DWORD dwThreadId
);

// 从钩子链中删除钩子
BOOL UnhookWindowsHookEx(HHOOK hhk
);

// 将消息转发到钩子链上的下一个钩子
LRESULT CallNextHookEx(HHOOK hhk,
    int nCode,
    WPARAM wParam,
    LPARAM lParam
);

Hook中文名“钩子”,我们可以把它想象成一个“鱼钩”,用来勾住指定类型的消息。“钩子”可以指定需要勾住哪个线程的消息,可以是当前线程,也可以是所有线程。当“指定线程”的“指定消息”被勾住时,系统就会将我们的DLL(如果钩子的处理过程位于DLL中)加载到该线程所属进程的地址空间中,并且在该地址空间中调用我们的DLL中的钩子处理过程函数,从而实现了注入功能。

我们通过SetWindowsHookEx函数来安装一个钩子,操作系统同时也允许开发人员为“同一个线程”的“同一个消息类型”指定多个钩子,这样就形成了一个“钩子链”Hook Chain)。

1.1 SetWindowsHookEx

idHook参数:指定我们需要勾住的消息类型;
lpfn参数:函数指针。当idHook指定的消息触发时,系统将会调用lpfn函数指针。
hMod参数:lpfn函数指针所在DLL的句柄。有2种情况下这个参数需要传NULL:

  1. lpfn函数的代码位于本进程内时。
  2. 只需要勾住本进程的消息时,即dwThreadId参数指定的线程位于当前进程。

dwThreadId参数:线程ID,用于指定勾住哪个线程的消息。如果传0,则表示勾住所有线程的指定消息。

SetWindowsHookEx详细的参数解释可以参考MSDN:https://msdn.microsoft.com/en-us/library/windows/desktop/ms644990(v=vs.85).aspx

1.2 CallNextHookEx

当我们的钩子处理函数将消息处理完之后,我们可以选择将消息丢弃,不让钩子链上的后面的钩子处理;也可以选择将消息继续传递下去,从而让其他钩子有处理的机会。

如果需要让其他钩子有处理的机会,我们可以在钩子处理函数的最后调用CallNextHookEx函数。

1.3 UnhookWindowsHookEx

UnhookWindowsHookEx函数用于将指定钩子从钩子链中移除。

即使不调用UnhookWindowsHookEx,在调用SetWindowsHookEx的进程退出后,钩子也将被自动移除。

二、实例

SetWindowsHookEx函数返回一个HHOOK类型的钩子句柄,CallNextHookExUnhookWindowsHookEx函数都需要使用这个句柄作为参数。假设我们将“注入逻辑”放在独立的exe中,将“钩子处理过程”放到独立的dll中,那么为了在“钩子处理过程”中调用CallNextHookEx时能够拿到钩子的句柄,我们需要通过其他途径将该句柄从exe传递到dll中。

所以为了避免传递“钩子句柄”的麻烦,我们将“注入逻辑”和“钩子处理过程”都写入到一个DLL之中。我们只需要调用这个DLL的导出函数就可以将这个DLL注入到指定线程所属的进程中。

示例DLL名称为InjectDLL,用于勾住指定窗口的WH_GETMESSAGE消息,我们也可以指定其他的消息类型,如键盘消息等。完整的消息类型可以参考MSDN上关于SetWindowsHookEx函数的解释。

InjectDLL的完整代码如下:

#include <stdio.h>
#include <windows.h>
#include <tchar.h>

HHOOK g_hook = NULL;

BOOL APIENTRY DllMain(HMODULE hModule, DWORD  fdwReason, LPVOID lpReserved) {
    switch(fdwReason) {
        case DLL_PROCESS_ATTACH:
        {
            break;
        }
        case DLL_THREAD_ATTACH:
        {
            break;
        }
        case DLL_THREAD_DETACH:
        {
            break;
        }
        case DLL_PROCESS_DETACH:
        {
            break;
        }
    }
    return TRUE;
}



// 提权函数
// 参考:https://blog.csdn.net/china_jeffery/article/details/79173417
//
BOOL EnablePrivilege(LPCTSTR szPrivilege, BOOL fEnable) {
    BOOL fOk = FALSE;
    HANDLE hToken = NULL;

    if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) {
        TOKEN_PRIVILEGES tp;
        tp.PrivilegeCount = 1;
        LookupPrivilegeValue(NULL, szPrivilege, &tp.Privileges[0].Luid);
        tp.Privileges->Attributes = fEnable ? SE_PRIVILEGE_ENABLED : 0;
        AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL);
        fOk = (GetLastError() == ERROR_SUCCESS);

        CloseHandle(hToken);
    }

    return fOk;
}

// 钩子处理过程
LRESULT CALLBACK HookProc_GetMsg(int code, WPARAM wParam, LPARAM lParam) {
    char szMsg[512] = { 0 };
    sprintf_s(szMsg, 512, "code: %d, wParam: %d, lParam: %d", code, wParam, lParam);
    OutputDebugStringA(szMsg);

    return CallNextHookEx(g_hook, code, wParam, lParam);;
}

// 导出函数
HHOOK InjectDllByHook(HWND hwnd) {
    DWORD dwThreadId = 0;
    HHOOK hHook = NULL;

    __try {
        if (!EnablePrivilege(SE_DEBUG_NAME, TRUE)) {
            __leave;
        }

        // 通过窗口句柄获取到窗口所属线程
        dwThreadId = GetWindowThreadProcessId(hwnd, NULL);
        if (dwThreadId == 0) {
            __leave;
        }

        // 获取DLL自身的句柄
        HMODULE hModule = NULL;
        GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
            (LPCWSTR)InjectDllByHook, &hModule);

        hHook = SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)HookProc_GetMsg, hModule, dwThreadId);
    }
    __finally {

    }

    return (hHook);
}

// 导出函数
BOOL EjectDllByHook(HHOOK hook) {
    return UnhookWindowsHookEx(hook);
}

我们可以直接调用该DLL的导出函数InjectDllByHook,实现Hook指定窗口(也可以改成线程,因为InjectDllByHook函数内部也是通过窗口查找到对应线程的)的指定消息。当指定的消息类型被勾住时,我们的DLL也就被加载到了该窗口所属的进程地址空间了,从而实现了注入。

完整的示例代码下载地址: https://download.csdn.net/download/china_jeffery/10323108

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页