天天看点

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里面。

Hook原理

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文件:

Hook原理

(2)在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);

}

把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);

}

```

注入之后看效果

Hook原理
Hook原理
Hook原理

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

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查看

Hook原理

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

Hook原理

3.Hook

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

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

例子:F2H1

新建一个驱动程序:

Hook原理

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;

}

加载驱动程序(自己写的一个小工具,也可以网上下载)

Hook原理

4.查看效果

Hook原理
Hook原理
Hook原理

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。

转载于:https://www.cnblogs.com/davidwang456/articles/9212053.html

继续阅读