首页
论坛
专栏
课程

[原创]Hook原理

chpeagle 2018-6-20 14:46 8099

目录
  •          Hook原理
  •                  Hook API
  •                          1. Where.
  •                          2.Find.
  •                          3.Hook.
  •                  SSDT Hook.
  •                          1.Where
  •                          2.Find
  •                          3.Hook
  •                  SYSENTRY Hook
  •                          1.Where
  •                          2.Find
  •                          3.Hook
  •                  Object Hook
  •                          1.Where
  •                          2.Find
  •                          3.Hook
  •                  IDT Hook
  •                          1.Where
  •                          2.Find
  •                          3.Hook
  •          总结:

Hook原理

对于会Hook的人来说,Hook其实也就那么回事.对于没有Hook过的人来说,会感觉Hook很高大上(其实也没毛病).

 

那么今天我们就来探讨一些Hook的原理是什么.

 

我认为任何Hook都可以分为以下三步(简称WFH):

  1. 需要Hook的是什么,在哪里(后面简称Where).
  2. 寻找到Hook的地方.(后面简称Find)
  3. 进行Hook.(后面简称Hook)

当然了同一个类型的Hook所在的地方一般是一样的.但寻找到Hook的地方,和进行Hook却会有许多不同的方法.我们要抓住的是不变的地方.

 

根据这3点我们举例来验证一下.

Hook API

第一个我尽量说得详细一些.

 

举例子:Hook API:OpenProcess 让win10 64位的任务管理器关闭不了任何程序.

1. Where.

Hook API:OpenProcess. 在kernelbase.dll里面.

 

2.Find.

方式1:

  1. 通过LoadLibrary加载kernelbase.dll模块基地址.
  2. 通过GetProcAddress获取OpenProcess的地址.

方式2:编程直接引用OpenProcess的地址,因为在Windows下3大模块user32.dll,kernelbase.dll,ntdll.dll的加载基地址在每个应用程序中都是一样的.

 

方式3:通过寻找目标的IAT找到OpenProcess

3.Hook.

方式1:通过注入dll到目标进程进行,可以替换kernelbase.dll里面的OpenProcess的前面5个字节为jmp跳转到我们自己的地址,也可以修改目标进程的IAT.

 

方式2:通过WriteProcessMemory写入代码,修改目标进程的OpenProcess跳转到我们的代码.

 

代码实例:F1+H1(Find的第二种方式,Hook的第一种方式,后面不再说明):

  1. 新建一个dll文件:

  1. 在dll文件里面写如下代码

    如果你的win10是64位的就编译64位的,32位就编译32位的

      // dllmain.cpp : 定义 DLL 应用程序的入口点。
      DWORD oldProtect;
      BYTE  JmpBtye[5];
      BYTE  OldByte[5];
      void * OpenProcessaddr;
      bool H1_OpenProcess();
      void UnHook();
      BOOL APIENTRY DllMain( HMODULE hModule,
                             DWORD  ul_reason_for_call,
                             LPVOID lpReserved
                           )
      {
          switch (ul_reason_for_call)
          {
          case DLL_PROCESS_ATTACH:
              H1_OpenProcess();
              break;
          case DLL_PROCESS_DETACH:
              UnHook();
              break;
          }
          return TRUE;
      }

      HANDLE MyOpenProcess(
          DWORD dwDesiredAccess,
          BOOL  bInheritHandle,
          DWORD dwProcessId)
      {
          dwDesiredAccess &= ~PROCESS_TERMINATE;//去掉关闭程序的权限
          UnHook();//恢复Hook 任何调整到原来的地方执行.
          HANDLE h = OpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId);
          H1_OpenProcess();
          return h;
      }



      void * F1_OpenProcess()
      {
          //寻找到OpenProcess的地址
          void * addr = 0;
          //加载kernel32.dll
          HMODULE hModule = LoadLibraryA("kernelbase.dll");
          //获取OpenProcess的地址
          addr=(void *)GetProcAddress(hModule, "OpenProcess");
          return addr;
      }


      void * F2_OpenProcess()
      {
          return (void *)OpenProcess;
      }


      bool H1_OpenProcess()
      {
          //1.开始寻找地址
          void * addr = F1_OpenProcess();
          OpenProcessaddr = addr;
          //判断是否寻找成功
          if (addr == 0)
          {
              MessageBoxA(NULL,"寻找地址失败",NULL,0);
              return false;
          }
          //2.进行Hook

          /*
          一般代码段是不可写的,我们需要把其改为可读可写.
          */
          VirtualProtect((void *)addr, 5, PAGE_EXECUTE_READWRITE,&oldProtect);

          //修改前面的5个字节为jmp 跳转到我们的代码.
          //内联Hook 跳转偏移计算方式:跳转偏移=目标地址-指令地址-5
          //jmp 的OpCode 为:0xE9

          JmpBtye[0] = 0xE9;
          *(DWORD *)&JmpBtye[1] = (DWORD)((long long)MyOpenProcess - (long long)addr - 5);
          //保存原先字节
          memcpy(OldByte, (void *)addr, 5);
          //替换原先字节
          memcpy((void *)addr, JmpBtye, 5);
      }

      void UnHook()
      {
          //恢复原先字节
          memcpy((void *)OpenProcessaddr, OldByte, 5);
          //恢复属性
          DWORD p;
          VirtualProtect((void *)OpenProcessaddr, 5, oldProtect, &p);
      }
  1. 把dll注入任务管理器,因为注入不是我们主题,所以这里我只是简单的贴出代码,直接拿来用就可以

    ```

    #include <windows.h>

  //获取进程句柄
  HANDLE GetThePidOfTargetProcess(HWND hwnd)
  {
      DWORD pid;
      GetWindowThreadProcessId(hwnd, &pid);
      HANDLE hProcee = ::OpenProcess(PROCESS_ALL_ACCESS | PROCESS_CREATE_THREAD, 0, pid);
      return hProcee;
  }
  //提升权限
  void Up()
  {
      HANDLE hToken;
      LUID luid;
      TOKEN_PRIVILEGES tp;
      OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);
      LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid);
      tp.PrivilegeCount = 1;
      tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
      tp.Privileges[0].Luid = luid;
      AdjustTokenPrivileges(hToken, 0, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL);
  }

  //进程注入

  BOOL DoInjection(char *DllPath, HANDLE hProcess)
  {
      DWORD BufSize = strlen(DllPath)+1;
      LPVOID AllocAddr = VirtualAllocEx(hProcess, NULL, BufSize, MEM_COMMIT, PAGE_READWRITE);
      WriteProcessMemory(hProcess, AllocAddr, DllPath, BufSize, NULL);
      PTHREAD_START_ROUTINE pfnStartAddr = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryA");

      HANDLE hRemoteThread;
      hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, pfnStartAddr, AllocAddr, 0, NULL);
      if (hRemoteThread)
      {
          MessageBox(NULL, TEXT("注入成功"), TEXT("提示"), MB_OK);
          return true;
      }
      else
      {
          MessageBox(NULL, TEXT("注入失败"), TEXT("提示"), MB_OK);
          return false;
      }
  }


  int main()
  {
      //这里填写窗口标题
      HWND hwnd=FindWindowExA(NULL, NULL, NULL, "任务管理器");
      Up();
      HANDLE hP = GetThePidOfTargetProcess(hwnd);
      //开始注入
      //这里填写Dll路径
      DoInjection("E:\\studio\\VS2017\\F2H1.MessageBox\\x64\\Release\\F2H1.MessageBox.dll", hP);
  }
  ```
  1. 注入之后看效果

)

 

 

其实还有很多方式,剩下的方式你就可以自己慢慢尝试了.

SSDT Hook.

刚才说了用户层的Hook,接下来我们再说一下内核层的Hook,其实还是3歩曲.WFH

 

实现相似的功能,让所有程序关闭不了自己的程序.

1.Where

Windows 操作系统共有4个系统服务描述符.其中只用了两个,第一个是SSDT,第二个是ShadowSSDT

 

系统描述符结构如下:

typedef struct _KSYSTEM_SERVICE_TABLE
{
    ULONG *ServiceTableBase;        // 服务表基址 第一个表示SSDT 紧接着下一个ShadowSSDT
    ULONG *ServiceCounterTableBase; // 计数表基址
    ULONG NumberOfServices;         // 表中项的个数
    UCHAR *ParamTableBase;          // 参数表基址
}KSYSTEM_SERVICE_TABLE, *PKSYSTEM_SERVICE_TABLE;

SSDT Hook:NtOpenProcess,在ntkrnlpa.exe内核模块中的系统服务描述符表中的SSDT表中的第190号.

 

使用PCHunter32查看

2.Find

方式1:在Win7 32下,系统服务描述符表直接导出符号KeServiceDescriptorTable,可以直接获取其地址,然后通过其第一个ServiceTableBase就是SSDT的地址,接着找到第190号函数.

 

方式2:可以通过PsGetCurrentThread 获取ETHREAD结构,该结构的第一个字段KTHREAD有一个字段ServiceTable保存着系统描述符表的地址KeServiceDescriptorTable.通过其第一个ServiceTableBase就是SSDT的地址,接着找到第190号函数.

0: kd> u PsGetCurrentThread
nt!PsGetCurrentThread:
840473f1 64a124010000    mov     eax,dword ptr fs:[00000124h]  ;ETHREAD
840473f7 c3              ret

3.Hook

方式1:替换找到的地方,换成我们自己的函数

 

方式2:获取找到的地方的函数指针,改变其代码跳转到自己的代码(其实就是inline Hook).

 

例子:F2H1

  1. 新建一个驱动程序:

2.代码如下:

   #include <ntifs.h>
   #pragma pack(1)
   typedef struct _KSYSTEM_SERVICE_TABLE
   {
       ULONG *ServiceTableBase;        // 服务表基址 第一个表示SSDT 紧接着下一个是ShadowSSDT
       ULONG *ServiceCounterTableBase; // 计数表基址
       ULONG NumberOfServices;         // 表中项的个数
       UCHAR *ParamTableBase;          // 参数表基址
   }KSYSTEM_SERVICE_TABLE, *PKSYSTEM_SERVICE_TABLE;
   #pragma pack()


   void *OldNtProcess = 0;

   // 导入系统描述符表
   extern "C" NTSYSAPI KSYSTEM_SERVICE_TABLE KeServiceDescriptorTable;

   typedef NTSTATUS(NTAPI *NTOPENPROCESS)(PHANDLE  ProcessHandle,
       ACCESS_MASK  DesiredAccess,
       POBJECT_ATTRIBUTES  ObjectAttributes,
       PCLIENT_ID  ClientId);

   NTOPENPROCESS g_NtOpenProcess = NULL;

   NTSTATUS NTAPI MyOpenProcess(
       PHANDLE  ProcessHandle,
       ACCESS_MASK  DesiredAccess,
       POBJECT_ATTRIBUTES  ObjectAttributes,
       PCLIENT_ID  ClientId
   )
   {


       if (ClientId->UniqueProcess == (HANDLE)916)//指定保护的进程ID
       {
           return STATUS_ABANDONED;
       }

       return g_NtOpenProcess(ProcessHandle, DesiredAccess, ObjectAttributes, ClientId);
   }




   void OffProtect()
   {
       __asm { //关闭内存保护
           push eax;
           mov eax, cr0;
           and eax, ~0x10000;//关闭CR0.WP位,关闭页保护
           mov cr0, eax;
           pop eax;
       }
   }
   void OnProtect()
   {
       __asm { //恢复内存保护
           push eax;
           mov eax, cr0;
           or eax, 0x10000;//开启CR0.WP位,开启页保护
           mov cr0, eax;
           pop eax;
       }
   }
   void * F1_NtOpenProcess()
   {

       return (void *)&KeServiceDescriptorTable.ServiceTableBase[190];
   }


   void * F2_NtOpenProcess()
   {
       PETHREAD eThread = PsGetCurrentThread();
       PKSYSTEM_SERVICE_TABLE kServiceTable = (PKSYSTEM_SERVICE_TABLE)(*(ULONG *)((ULONG)eThread + 0xbc));
       return (void *)&kServiceTable->ServiceTableBase[190];

   }

   bool H1_NtOpenProcess()
   {
       OldNtProcess = F2_NtOpenProcess();//Find
       g_NtOpenProcess = (NTOPENPROCESS)(*(ULONG *)OldNtProcess);//保存就地址
       OffProtect();//由于SSDT表是只读的所以需要关闭页写入保护
       (*(ULONG *)OldNtProcess) = (ULONG)MyOpenProcess;//写入自己的函数地址
       OnProtect();//恢复
       return true;
   }

   void UnHook()
   {
       OffProtect();//由于SSDT表是只读的所以需要关闭页写入保护
       (*(ULONG *)OldNtProcess) = (ULONG)g_NtOpenProcess;//恢复函数
       OnProtect();//恢复
   }



   void Unload(PDRIVER_OBJECT pDri)
   {
       UNREFERENCED_PARAMETER(pDri);
       UnHook();
   }

   extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT pDri, PUNICODE_STRING pRegStr)
   {

       UNREFERENCED_PARAMETER(pRegStr);
       pDri->DriverUnload = Unload;
       H1_NtOpenProcess();
       return STATUS_SUCCESS;
   }
  1. 加载驱动程序(自己写的一个小工具,也可以网上下载)

)

 

4.查看效果

 

 

SYSENTRY Hook

这里我再说一些Hook,也是3歩曲WFH.但是我不再提供具体实现.

  我们知道以前windows系统是通过int 2e中断进入系统内核的,但是现在是通过cpu提供的一个功能sysentry进入系统的(32位是sysentry,64位是syscall).这是一个CPU指令,如果对该指令不知道的话,可以查看我另外一篇文章:

1.Where

  SYSENTRY Hook:190号功能号,功能号保存在eax中.

  SYSENTRY指令进入系统内核的地址保存在MSR寄存器里面的**IA32_SYSENTER_EIP** (0x176)号寄存器.

2.Find

  通过指令rdmsr读取**IA32_SYSENTER_EIP** MSR寄存器.其中ecx保存的是读取msr的序号,也就是0x176号,返回的结果保存在edx:eax(64位,edx保存高32位,eax保存低32位).因为是32位系统,所以只需要eax的值即可.

3.Hook

  通过wrmsr写入我们自己的地址,地址放在edx:eax(64位,edx保存高32位,eax保存低32位).即可完成Hook.

Object Hook

每一个不同的内核对象,都对应着一个不同的类型索引:TypeIndex.通过该索引可以找到该内核对象的类型:OBJECT_TYPE

1.Where

在内核对象的TypeInfo中.

2.Find

通过ObGetObjectType内核函数获取内核对象类型(OBJECT_TYPE)的OBJECT_TYPE中有一个字段TypeInfo(类型_OBJECT_TYPE_INITIALIZER),其中保存着,在创建内核对象,销毁内核对象的一系列构造函数.

 

对应结构:

//OBJECT_TYPE-->TypeInfo:_OBJECT_TYPE_INITIALIZER
ntdll!_OBJECT_TYPE
   +0x000 TypeList                     : _LIST_ENTRY
   +0x010 Name                         : _UNICODE_STRING
   +0x020 DefaultObject                : Ptr64 Void
   +0x028 Index                        : UChar
   +0x02c TotalNumberOfObjects         : Uint4B
   +0x030 TotalNumberOfHandles         : Uint4B
   +0x034 HighWaterNumberOfObjects     : Uint4B
   +0x038 HighWaterNumberOfHandles     : Uint4B
   +0x040 TypeInfo                     : _OBJECT_TYPE_INITIALIZER  //1.这个
   +0x0b0 TypeLock                     : _EX_PUSH_LOCK
   +0x0b8 Key                          : Uint4B
   +0x0c0 CallbackList                 : _LIST_ENTRY


ntdll!_OBJECT_TYPE_INITIALIZER
   +0x000 Length                       : Uint2B
   +0x002 ObjectTypeFlags              : UChar
   +0x002 CaseInsensitive              : Pos 0, 1 Bit
   +0x002 UnnamedObjectsOnly         : Pos 1, 1 Bit
   +0x002 UseDefaultObject             : Pos 2, 1 Bit
   +0x002 SecurityRequired             : Pos 3, 1 Bit
   +0x002 MaintainHandleCount         : Pos 4, 1 Bit
   +0x002 MaintainTypeList             : Pos 5, 1 Bit
   +0x002 SupportsObjectCallbacks     : Pos 6, 1 Bit
   +0x004 ObjectTypeCode               : Uint4B
   +0x008 InvalidAttributes            : Uint4B
   +0x00c GenericMapping               : _GENERIC_MAPPING
   +0x01c ValidAccessMask              : Uint4B
   +0x020 RetainAccess                 : Uint4B
   +0x024 PoolType                     : _POOL_TYPE
   +0x028 DefaultPagedPoolCharge     : Uint4B
   +0x02c DefaultNonPagedPoolCharge : Uint4B
   +0x030 DumpProcedure                : Ptr64     void 
   +0x038 OpenProcedure                : Ptr64     long//打开 回调函数
   +0x040 CloseProcedure               : Ptr64     void//关闭 回到函数
   +0x048 DeleteProcedure              : Ptr64     void 
   +0x050 ParseProcedure               : Ptr64     long 
   +0x058 SecurityProcedure         : Ptr64     long 
   +0x060 QueryNameProcedure         : Ptr64     long //查询名称 回调函数
   +0x068 OkayToCloseProcedure         : Ptr64     unsigned char

3.Hook

根据找到的位置替换里面回调函数指针为我们自己写的函数即可.比如替换OpenProcedure.

IDT Hook

1.Where

在中断描述符表(IDT)中.

2.Find

idtr寄存器指向中断描述符表.通过idtr找到.

 

说明:idtr是一个48位寄存器,其中低16位保存中断描述符表长度.高32位是中断描述符表.的基地址.

3.Hook

通过构造一个中断门或者陷阱门,其中中断门或陷阱门的偏移地址写自己的地址.然后把中断门或者陷阱门写入都相应的IDT表项中.

总结:

从上面我们可以看到,其实Hook都是一样的,只是对应的地方不同,寻找的方法不同,替换(修改)的方法不同而已.

 

有的人可能就要反问了,SetWindowsHookEx,就不要知道Hook的地方在哪了,也不需要寻找.确实,这两歩不需要我们自己做,但并不代表不需要,这只是操作系统为我们做了而已,我们只需要提供一个回调函数即可.

 

所以下面我留下一个小测试:就是自己自己实现SetWindowsHookEx.



[推荐]看雪企服平台,提供APP等级保护加固等安全服务!

本主题帖已收到 2 次赞赏,累计¥3.00
最新回复 (25)
AperOdry 2018-6-20 21:47
2

0

感谢分析,最近在写  object  hook  时候发现每个系统的回调函数原型都会发生改变。。
只能对照WRK大致得到伪代码,不知兄台可有这方面的心得或者资料?
//Win7  x64
typedef  NTSTATUS(*OB_OPEN_METHOD)(
IN  PVOID  Unknown,
IN  OB_OPEN_REASON  OpenReason,
IN  PEPROCESS  Process  OPTIONAL,
IN  PVOID  Object,
IN  PACCESS_MASK  GrantedAccess
);
这个是大致得到的伪代码,虽然测试没问题但还是有点心灵洁癖,... 
fengyunabc 1 2018-6-20 22:40
3

0

感谢分享!
聖blue 2018-6-20 22:52
4

0

rongge 2018-6-21 05:46
5

0

x64不能内嵌汇编了,有什么好办法?
chpeagle 2018-6-21 07:48
6

0

rongge x64不能内嵌汇编了,有什么好办法?
x64还是可以内联汇编的只是稍微麻烦一点.
https://bbs.pediy.com/thread-219221.htm 11楼
最后于 2018-6-21 07:54 被chpeagle编辑 ,原因:
chpeagle 2018-6-21 07:49
7

0

AperOdry 感谢分析,最近在写 object hook 时候发现每个系统的回调函数原型都会发生改变。。 只能对照WRK大致得到伪代码,不知兄台可有这方面的心得或者资料? //Win7 x64 typedef ...
据我所知,最最直接有效的方法就是逆向出函数原型.还有google
Lthis 1 2018-6-21 10:47
8

0

额,,这个就是汇编,并非内联汇编。
如果想要内联,感觉只能换编译器了
xie风腾 2018-6-21 10:50
9

0


多谢楼主分享,学习了
TopC 2018-6-21 11:46
10

0

inline  hook不好用,想用原函数的时候还要恢复,不如直接改IAT
chpeagle 2018-6-21 13:50
11

0

有时候确实是,不过内联可以使用"热补丁",

修改之后
图片描述

 

而且IAT 有个缺陷就是,动态加载的模块(通过LoadLibrary)是不会被写入IAT.
任务管理器的OpenProcess也不是放进常规模块中,而是放入:
.
这个模块在每个不同版本的Windows上可能不同的,也就不具备通用性了.

ArryBoom 2018-6-21 15:26
12

0

非常详细,思路非常清晰,感觉可以收录到年度精华集,感谢分享!
xieshun 2018-6-21 15:31
13

0

精彩的文章,瞬间明白好多
呼噜 2018-6-21 18:33
14

0

有两处小问题:
1. 这里的操作没上锁
    
2. 这里只锁了一个核
    
chpeagle 2018-6-21 20:15
15

0

呼噜 有两处小问题:1. 这里的操作没上锁&nbsp; &nbsp;&nbsp;2. 这里只锁了一个核&nbsp; &nbsp;&nbsp;
谢谢提醒,确实会存在这样的问题.
evwel 2018-6-22 00:20
16

0

thanks  for  sharing
严启真 2018-6-22 10:48
17

0

谢谢分享
hzqst 2 2018-6-22 14:37
18

0

听说你想勾sysenter  /  idt  入口?
1、PatchGuard之类老生常谈的东西我就不提了
2、页表隔离了解一下?UserDirectoryTable不映射你代码你能怎么办
3、DeviceGuard了解一下?只要敢乱动msr  /  idtr寄存器,微软马上给你安排得明明白白的
总结就是四个字:加大力度  蓝屏就完事了
最后于 2018-6-22 14:37 被hzqst编辑 ,原因:
柒雪天尚 2018-6-22 21:21
19

0

x64不是有很多inline  Hook的帖子了吗,咋还在问  汇编的事情-  -
Lmxczq 2018-11-8 20:45
20

0

W10 64   注入器Debug x64 DLL Debug x64 注入成功,任务管理闪退
niuzuoquan 2018-11-9 17:25
21

0

mark
MOVESP 2018-11-17 13:10
22

0

远程注入的时候成功,为什么在调用自定义函数中的openprocess的时候返回0?以至于任务管理器中的某个函数调用这个返回值的时候发生读写错误奔溃
老牜lyh 2018-11-29 19:14
23

0

感谢老师的教程,涨芝士了
wx_clay 1 2018-12-7 17:58
24

0

为啥APIHOOK 你保护进程不被结束 hook的是OpenProcess?
chpeagle 2018-12-10 17:10
25

0

wx_clay 为啥APIHOOK 你保护进程不被结束 hook的是OpenProcess?
权限过滤
我叫莫妮卡 2018-12-11 02:57
26

0

好 感谢
返回