首页 > 其他分享 >4.2 Inline Hook 挂钩技术

4.2 Inline Hook 挂钩技术

时间:2023-09-14 12:46:00浏览次数:32  
标签:12 函数 4.2 LPVOID 0x00 Hook 0x90 Inline

InlineHook 是一种计算机安全编程技术,其原理是在计算机程序执行期间进行拦截、修改、增强现有函数功能。它使用钩子函数(也可以称为回调函数)来截获程序执行的各种事件,并在事件发生前或后进行自定义处理,从而控制或增强程序行为。Hook技术常被用于系统加速、功能增强、开发等领域。本章将重点讲解Hook是如何实现的,并手动封装实现自己的Hook挂钩模板。

x32 Inline Hook

对于4.1中所提到的Hook方法还是过于复杂,我们可以将上述代码定义为MyHook类,构造函数用来初始化,析构函数用来恢复钩子,在Hook()成员函数中完成了3项工作,首先是获得了被HOOK函数的函数地址,接下来是保存了被HOOK函数的前5字节,最后是用构造好的跳转指令来修改被HOOK函数的前5字节的内容。

如下封装中实现了三个类内函数,其中Hook()用于开始Hook函数,此函数接收三个参数,参数1为需要Hook的动态链接库名,参数2为需要挂钩的函数名,参数3为自定以中转函数地址,其中UnHook()用于恢复函数挂钩,最后的ReHook()用于重新挂钩,以下是该类提供的功能的简要摘要:

  • m_pfnRig:成员变量,在挂接之前存储原始函数地址。
  • m_bOldBytes:成员变量,用于存储函数入口代码的原始5个字节。
  • m_bNewBytes:成员变量,用于存储将替换原始函数代码的内联钩子代码。
  • Hook():成员函数,通过将函数入口代码的前5个字节替换为JMP指令,将控制流重定向到指定的钩子函数,从而在指定的模块中钩子指定的函数。此函数返回一个BOOL,指示挂钩是否成功。
  • UnHook():成员函数,用于删除钩子并恢复原始函数代码。此函数返回一个BOOL,指示解除挂钩是否成功。
  • ReHook():成员函数,它使用之前存储的钩子代码重新钩子之前未钩子的函数。此函数返回一个BOOL,指示重新挂钩是否成功。
// 自己封装的Hook组件
class MyHook
{
    public:
        PROC m_pfnOrig;       // 保存函数地址
        BYTE m_bOldBytes[5];  // 保存函数入口代码
        BYTE m_bNewBytes[5];  // 保存Inlie Hook代码
    public:

        // 构造函数
        MyHook()
        {
            m_pfnOrig = NULL;
            ZeroMemory(m_bOldBytes, 5);
            ZeroMemory(m_bNewBytes, 5);
        }

        // 析构函数清理现场
        ~MyHook()
        {
            UnHook();
            m_pfnOrig = NULL;
            ZeroMemory(m_bOldBytes, 5);
            ZeroMemory(m_bNewBytes, 5);
        }

        // 开始挂钩
        BOOL Hook(LPSTR pszModuleName, LPSTR pszFuncName, PROC pfnHookFunc)
        {
            BOOL bRet = FALSE;

            // 获取指定模块中函数的地址
            m_pfnOrig = (PROC)GetProcAddress(GetModuleHandleA(pszModuleName), pszFuncName);

            if (m_pfnOrig != NULL)
            {
                // 保存该地址处 5 字节的内容
                DWORD dwNum = 0;
                ReadProcessMemory(GetCurrentProcess(), m_pfnOrig, m_bOldBytes, 5, &dwNum);

                // 构造 JMP 指令
                m_bNewBytes[0] = '\xe9'; // jmp Opcode

                // pfnHookFunc 是 HOOK 后的目标地址
                // m_pfnOrig 是原来的地址
                // 5 是指令长度
                *(DWORD*)(m_bNewBytes + 1) = (DWORD)pfnHookFunc - (DWORD)m_pfnOrig - 5;

                // 将构造好的地址写入该地址处
                WriteProcessMemory(GetCurrentProcess(), m_pfnOrig, m_bNewBytes, 5, &dwNum);
                bRet = TRUE;
            }
            return bRet;
        }

        // 取消挂钩
        BOOL UnHook()
        {
            if (m_pfnOrig != 0)
            {
                DWORD dwNum = 0;
                WriteProcessMemory(GetCurrentProcess(), m_pfnOrig, m_bOldBytes, 5, &dwNum);
            }
            return TRUE;
        }

        // 重新挂钩
        BOOL ReHook()
        {
            BOOL bRet = FALSE;
            if (m_pfnOrig != 0)
            {
                DWORD dwNum = 0;
                WriteProcessMemory(GetCurrentProcess(), m_pfnOrig, m_bNewBytes, 5, &dwNum);
                bRet = TRUE;
            }
            return bRet;
        }
};

同样我们以替换自身弹窗为例子具体讲解一下该库如何使用,如下代码中首先我们自定义一个MyMessageBoxA函数,该函数的原型读者可通过微软官方文档获取,以下是MessageBoxA函数的原型(函数声明):

int MessageBoxA(
  HWND   hWnd,
  LPCSTR lpText,
  LPCSTR lpCaption,
  UINT   uType
);

其中,参数的含义如下:

  • hWnd: 指向包含消息框的窗口的句柄,通常为父窗口的句柄。可以设为NULL,表示没有父窗口。
  • lpText: 指向要显示的消息文本的字符串指针。
  • lpCaption: 指向要显示在消息框标题栏上的字符串指针,通常用于指定消息框的标题。
  • uType: 用于指定消息框的按钮和图标样式,可以使用预定义的常量值进行设置,如MB_OK、MB_YESNO等。

有了函数原型声明部分读者则可以自己实现一个MyMessageBoxA函数,需注意参数传递必须与原函数保持一致,在自定以函数内部我们首先通过MsgHook.UnHook();恢复之前的钩子,并调用原函数实现功能替换,当调用结束后记得使用MsgHook.ReHook();重新挂钩恢复钩子。

// 定义全局类
MyHook MsgHook;

// 定义自定义Hook函数
int WINAPI MyMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType)
{
    // 先来恢复Hook 之所以要恢复是因为我们需要调用原始的MsgBox弹窗
    MsgHook.UnHook();

    MessageBoxA(hWnd, "hi hook api", lpCaption, uType);

    // 弹窗完成后重新Hook
    MsgHook.ReHook();
    return 0;
}

在主函数中我们通过调用MsgHook.Hook()函数,挂钩住user32.dll模块内的MessageBoxA函数,并将该函数请求转发到MyMessageBoxA上面做处理,当此时调用MessageBoxA时读者可观察弹出提示是否为我们所期望的,最后通过MsgHook.UnHook();用于解除钩子;

// 调用Hook组件
int main(int argc, char* argv[])
{
    // 开始Hook
    MsgHook.Hook((char *)"user32.dll", (char *)"MessageBoxA", (PROC)MyMessageBoxA);

    // 调用函数
    MessageBoxA(NULL, "hello lyshark", "Msg", MB_OK);

    // 结束Hook
    MsgHook.UnHook();
    return 0;
}

读者可自行运行上述代码,当尝试调用MessageBox函数并传入hello lyshark参数时,输出的结果却变成了hi hook api如下图所示,则说明内联挂钩生效了。

x64 Inline Hook

32位钩子的封装实现详细读者已经能够理解了,接着我们来实现64位钩子的封装,64位与32位系统之间无论从寻址方式,还是语法规则都与x32架构有着本质的不同,由于64位编译器无法直接内嵌汇编代码,导致我们只能调用C库函数内嵌机器码来实现Hook的中转。

首先实现去MessageBox弹窗,由于64位编译器无法直接内嵌汇编代码,所以在我们需要Hook时只能将跳转机器码以二进制字节方式写死在程序里,如下代码是一段去弹窗演示案例。

#include <stdio.h>
#include <Windows.h>

BYTE OldCode[12] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
BYTE HookCode[12] = { 0x48, 0xB8, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0xFF, 0xE0 };
DWORD_PTR base;

// 自己实现中转函数
int WINAPI MyMessageBoxW(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType)
{
  return 1;
}

int main(int argc,char * argv[])
{
  HMODULE hwnd = GetModuleHandle(TEXT("user32.dll"));
  DWORD_PTR base = (DWORD_PTR)GetProcAddress(hwnd, "MessageBoxW");
  DWORD OldProtect;

  if (VirtualProtect((LPVOID)base, 12, PAGE_EXECUTE_READWRITE, &OldProtect))
  {
    memcpy(OldCode, (LPVOID)base, 12);                  // 拷贝原始机器码指令
    *(PINT64)(HookCode + 2) = (INT64)&MyMessageBoxW;    // 填充90为指定跳转地址
  }
  memcpy((LPVOID)base, &HookCode, sizeof(HookCode));      // 拷贝Hook机器指令

  MessageBoxW(NULL, L"hello lyshark", NULL, NULL);

  return 0;
}

接着我们在上面代码的基础上继续进行完善,添加恢复钩子的功能,该功能时必须要有的,因为我们还是需要调用原始的弹窗代码,所以要在调用时进行暂时恢复,调用结束后再继续Hook挂钩。

#include <stdio.h>
#include <Windows.h>

void Hook();
void UnHook();

BYTE Ori_Code[12] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
BYTE HookCode[12] = { 0x48, 0xB8, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0xFF, 0xE0 };

/* Hook 机器码的原理如下所示
MOV RAX, 0x9090909090909090
JMP RAX
*/

// 定义函数指针
static int (WINAPI *OldMessageBoxW)(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType) = MessageBoxW;

// 执行自己的弹窗
int WINAPI MyMessageBoxW(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType)
{
  UnHook();                                                                  // 恢复Hook
  int ret = OldMessageBoxW(hWnd, TEXT("Hook Inject"), lpCaption, uType);     // 调用原函数
  Hook();                                                                    // 继续hook
  return ret;
}

// 开始Hook
void Hook()
{
  DWORD OldProtect;
  if (VirtualProtect(OldMessageBoxW, 12, PAGE_EXECUTE_READWRITE, &OldProtect))
  {
    memcpy(Ori_Code, OldMessageBoxW, 12);               // 拷贝原始机器码指令
    *(PINT64)(HookCode + 2) = (INT64)&MyMessageBoxW;    // 填充90为指定跳转地址
  }
  memcpy(OldMessageBoxW, &HookCode, sizeof(HookCode));    // 拷贝Hook机器指令
}

// 结束Hook
void UnHook()
{
  memcpy(OldMessageBoxW, &Ori_Code, sizeof(Ori_Code));    // 恢复hook原始代码
}

int main(int argc, char *argv [])
{
  Hook();
  MessageBoxW(NULL, TEXT("hello lyshark"), TEXT("MsgBox"), MB_OK);
  UnHook();
  MessageBoxW(NULL, TEXT("hello lyshark"), TEXT("MsgBox"), MB_OK);

  return 0;
}

将上面所写的代码进行函数化封装,实现一个完整的钩子处理程序。代码如下所示。

#include <stdio.h>
#include <Windows.h>

BYTE OldCode[12] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
BYTE HookCode[12] = { 0x48, 0xB8, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0xFF, 0xE0 };

void Hook(LPCWSTR lpModule, LPCSTR lpFuncName, LPVOID lpFunction)
{
  DWORD_PTR FuncAddress = (UINT64)GetProcAddress(GetModuleHandle(lpModule), lpFuncName);
  DWORD OldProtect = 0;

  if (VirtualProtect((LPVOID)FuncAddress, 12, PAGE_EXECUTE_READWRITE, &OldProtect))
  {
    memcpy(OldCode, (LPVOID)FuncAddress, 12);                   // 拷贝原始机器码指令
    *(PINT64)(HookCode + 2) = (UINT64)lpFunction;               // 填充90为指定跳转地址
  }
  memcpy((LPVOID)FuncAddress, &HookCode, sizeof(HookCode));       // 拷贝Hook机器指令
  VirtualProtect((LPVOID)FuncAddress, 12, OldProtect, &OldProtect);
}

void UnHook(LPCWSTR lpModule, LPCSTR lpFuncName)
{
  DWORD OldProtect = 0;
  UINT64 FuncAddress = (UINT64)GetProcAddress(GetModuleHandle(lpModule), lpFuncName);
  if (VirtualProtect((LPVOID)FuncAddress, 12, PAGE_EXECUTE_READWRITE, &OldProtect))
  {
    memcpy((LPVOID)FuncAddress, OldCode, sizeof(OldCode));
  }
  VirtualProtect((LPVOID)FuncAddress, 12, OldProtect, &OldProtect);
}

int WINAPI MyMessageBoxW(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType)
{
  // 首先恢复Hook代码
  UnHook(L"user32.dll", "MessageBoxW");
  int ret = MessageBoxW(0, L"hello lyshark", lpCaption, uType);

  // 调用结束后,再次挂钩
  Hook(L"user32.dll", "MessageBoxW", (PROC)MyMessageBoxW);
  return ret;
}

bool APIENTRY DllMain(HANDLE handle, DWORD dword, LPVOID lpvoid)
{
  switch (dword)
  {
  case DLL_PROCESS_ATTACH:
    Hook(L"user32.dll", "MessageBoxW", (PROC)MyMessageBoxW);
    break;
  case DLL_PROCESS_DETACH:
    UnHook(L"user32.dll", "MessageBoxW");
    break;
  }
  return true;
}

上方的代码还是基于过程化的案例,为了能更加通用,我们将其封装成MyHook类,这样后期可以直接引入项目调用了。

#include <iostream>
#include <Windows.h>

class MyHook
{
  public:
    FARPROC m_pfnOrig;       // 保存函数地址
    BYTE m_bOldBytes[12];    // 保存函数入口代码
    BYTE m_bNewBytes[12];    // 保存Inlie Hook代码
  public:

    // 构造函数
    MyHook()
    {
      BYTE OldCode[12] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
      BYTE NewCode[12] = { 0x48, 0xB8, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0xFF, 0xE0 };

      RtlMoveMemory(MyHook::m_bNewBytes, NewCode, 12);
      memset(MyHook::m_bOldBytes, 0, 12);
      m_pfnOrig = NULL;
    }

    // 析构函数
    ~MyHook()
    {
      m_pfnOrig = NULL;
      ZeroMemory(MyHook::m_bNewBytes, 12);
      ZeroMemory(MyHook::m_bOldBytes, 12);
    }

    // 开始挂钩
    BOOL Hook(LPCWSTR lpModule, LPCSTR lpFuncName, LPVOID lpFunction)
    {
      DWORD_PTR FuncAddress = (UINT64)GetProcAddress(GetModuleHandle(lpModule), lpFuncName);
      DWORD OldProtect = 0;

      if (VirtualProtect((LPVOID)FuncAddress, 12, PAGE_EXECUTE_READWRITE, &OldProtect))
      {
        memcpy(m_bOldBytes, (LPVOID)FuncAddress, 12);                           // 拷贝原始机器码指令
        *(PINT64)(MyHook::m_bNewBytes + 2) = (UINT64)lpFunction;                // 填充90为指定跳转地址
      }
      memcpy((LPVOID)FuncAddress, &m_bNewBytes, sizeof(m_bNewBytes));             // 拷贝Hook机器指令
      VirtualProtect((LPVOID)FuncAddress, 12, OldProtect, &OldProtect);
      return TRUE;
    }

    // 结束挂钩
    BOOL UnHook(LPCWSTR lpModule, LPCSTR lpFuncName)
    {
      DWORD OldProtect = 0;
      UINT64 FuncAddress = (UINT64)GetProcAddress(GetModuleHandle(lpModule), lpFuncName);
      if (VirtualProtect((LPVOID)FuncAddress, 12, PAGE_EXECUTE_READWRITE, &OldProtect))
      {
        memcpy((LPVOID)FuncAddress, m_bOldBytes, sizeof(m_bOldBytes));
      }
      VirtualProtect((LPVOID)FuncAddress, 12, OldProtect, &OldProtect);
      return TRUE;
    }
};

// 定义全局类
MyHook MsgHook;

// 自己实现中转函数
int WINAPI MyMessageBoxW(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType)
{
  // 首先恢复Hook代码
  MsgHook.UnHook(L"user32.dll", "MessageBoxW");
  
  int ret = MessageBoxW(0, L"hi hook api", lpCaption, uType);

  // 调用结束后,再次挂钩
  MsgHook.Hook(L"user32.dll", "MessageBoxW", (PROC)MyMessageBoxW);
  return ret;
}

int main(int argc, char* argv[])
{
  // 开始Hook
  MsgHook.Hook(L"user32.dll", "MessageBoxW", (PROC)MyMessageBoxW);

  MessageBoxW(NULL, L"hello lyshark", L"Msg", MB_OK);

  // 结束Hook
  MsgHook.UnHook(L"user32.dll", "MessageBoxW");

    return 0;
}

读者可自行运行上述代码,当尝试调用MessageBox函数并传入hello lyshark参数时,输出的结果却变成了hi hook api如下图所示,则说明内联挂钩生效了。

本文作者: 王瑞
本文链接: https://www.lyshark.com/post/ad20f53e.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

标签:12,函数,4.2,LPVOID,0x00,Hook,0x90,Inline
From: https://www.cnblogs.com/LyShark/p/17702214.html

相关文章

  • 4.1 应用层Hook挂钩原理分析
    InlineHook是一种计算机安全编程技术,其原理是在计算机程序执行期间进行拦截、修改、增强现有函数功能。它使用钩子函数(也可以称为回调函数)来截获程序执行的各种事件,并在事件发生前或后进行自定义处理,从而控制或增强程序行为。Hook技术常被用于系统加速、功能增强、等领域。本章将......
  • RunTime.getRunTime().addShutdownHook用法
    Runtime.getRuntime().addShutdownHook(shutdownHook);这个方法的含义说明:这个方法的意思就是在jvm中增加一个关闭的钩子,当jvm关闭的时候,会执行系统中已经设置的所有通过方法addShutdownHook添加的钩子,当系统执行完这些钩子后,jvm才会关闭。所以这些钩子可以在jvm关闭的时候进......
  • react hook
    当我们想在两个函数之间共享逻辑时,我们会把它提取到第三个函数中。必须以“use”开头吗?必须如此。这个约定非常重要。不遵循的话,由于无法判断某个函数是否包含对其内部Hook的调用,React将无法自动检查你的Hook是否违反了Hook的规则小练习使用自定义hooks实现鼠标移动......
  • ShutdownHook妙用
    上期文章分享了ShutdownHook的API和基本使用,但是少了一些实际工作中的案例,总感觉没啥大用一样。最近总结工作中可以用到ShutdownHook来解决一些实际问题的例子,分享给大家。任务统计FunTester测试框架定义了好几个自定义的异步关键字,例如fun、funny、funner等。一旦使用到异步,肯......
  • react18-webchat网页聊天实例|React Hooks+Arco Design仿微信桌面端
    React18Hooks+Arco-Design+Zustand仿微信客户端聊天ReactWebchat。react18-webchat基于react18+vite4.x+arco-design+zustand等技术开发的一款仿制微信网页版聊天实战项目。实现发送带有emoj消息文本、图片/视频预览、红包/朋友圈、局部模块化刷新/美化滚动条等功能。使用技术......
  • HookWinInet库实现类似fiddler的替换url
    fiddler正常情况下只能捕获WinInet库的请求,所以,只要浏览器设置代理服务器为fiddler,且fiddler可以正常抓包,就可以推测这些请求所使用的网络库是WinInet库。本文想要通过hook的方式实现类似于fiddler的替换响应,也就是替换一个url链接,访问的时候,响应变成了另外一个服务器发出来的。......
  • Git Hooks
    GitHooks定义GitHooks是Git的一个重要特性,它让你可以在Git仓库中定义一些自动化的脚本,这些脚本可以在特定的Git事件(如提交代码、接收代码等)发生时被触发执行。它们是在Git仓库目录中的 .git/hooks/ 下的一组可执行文件。具体来说,每个Git仓库中都有一个名为".git/hooks"的隐......
  • Ubentu 16.04.2 LTS安装mysql,jdk1.8
    一、网络设置1、网络设置sudovim/etc/network/interfaces文件中写入以下内容,写完后wq保存退出。#设置网卡名称autoeth0#设置静态IP,如果是使用自动IP用dhcp,后面的不用设置ifaceeth0inetstatic#设置IP地址addressxxx.xxx.xxx.xxx#设置子网掩码netmaskxxx.xxx.xxx.......
  • react hooks 中useContext的使用
    父组件中:importReact,{useState,createContext}from'react'import'./App.css';importChildOnefrom'./components/ChildOne';importChildTwofrom'./components/ChildTwo';exportconstCountContext=createContext(......
  • CentOS Linux release 7.6.1810 Zabbix 4.2 快速入门与实践:构建强大的企业级资源监控
    目录:0x00Zabbix介绍0x01Zabbix安装0x02Zabbix配置0x03Zabbix-Web配置与使用0x04Zabbix实战配置0x0nZabbix入坑配置0x00Zabbix介绍描述:zabbix是一个开源的企业级性能监控解决方案,可以实时监控服务器/网络设备等硬件资源与其相关的各项指标是否是正常的,而且能够更加方便......