微信号:programmer_club

介绍:程序员第一自媒体,与你探讨码农人生路上遇到的各类泛技术话题,定期为你推荐码农人生思考、感悟以及启迪!

Hook原理

2018-06-22 22:00 程序员之家

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


(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);
}
```


注入之后看效果


 

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



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


新建一个驱动程序:



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;
}

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



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


本文转载自【看雪社区】


公众号内回复“1”带你进粉丝群

 
程序员之家 更多文章 Java程序员涨薪必备技能(1-5年必看!!!) 为什么我的儿子不沉迷游戏?一位游戏策划家长的分享 大张伟侮辱了恩克,优酷侮辱了世界杯 程序员如何向父母介绍自己的工作? 被腾讯抄袭致死的公司有哪些? 网友留言: 无爆款不抄袭的腾讯!
猜您喜欢 Go聚会抢码赛!(第2轮) 浪潮发布新一代智能存储G2,开启数据智能时代 Chat推荐:从0到1快速开发钉钉微应用 中小企业如何优雅地统一管理多机房服务器? 漫画:什么是动态规划?