Hook原理

news/2025/2/22 6:13:47/

对于会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

(免费订阅,永久学习)学习地址: Dpdk/网络协议栈/vpp/OvS/DDos/NFV/虚拟化/高性能专家-学习视频教程-腾讯课堂

更多DPDK相关学习资料有需要的可以自行报名学习,免费订阅,永久学习,或点击这里加qun免费
领取,关注我持续更新哦! ! 

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

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的地方在哪了,也不需要寻找.确实,这两歩不需要我们自己做,但并不代表不需要,这只是操作系统为我们做了而已,我们只需要提供一个回调函数即可.

原文链接:[原创]Hook原理-编程技术-看雪论坛-安全社区|安全招聘|bbs.pediy.com 


http://www.ppmy.cn/news/5803.html

相关文章

【用户交互】

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、用户交互3.1 函数返回码3.2 常用3.3 acedGetXXX函数3.3.1 常规3.3.2 acedInitGet3.3.3 选择实体3.4 选择集3.4.1 常用函数3.4.2 acedSSGet函数详解总结前言 本文就介绍了用户交互内容。 提…

Python基础知识入门(四)

Python基础知识入门&#xff08;一&#xff09; Python基础知识入门&#xff08;二&#xff09; Python基础知识入门&#xff08;三&#xff09; 一、条件控制 条件语句是通过一条或多条语句的执行结果&#xff08;True 或者False&#xff09;来决定执行的代码块。 注意&…

USB TO SPI(上海同旺电子)调试器调试MCP4822

所需设备&#xff1a; 1、USB TO SPI(上海同旺电子)&#xff1b; 2、MCP4822&#xff1a;双通道12 位电压输出DAC; 特性 • MCP4802&#xff1a;双通道8 位电压输出DAC • MCP4812&#xff1a;双通道10 位电压输出DAC • MCP4822&#xff1a;双通道12 位电压输出DAC • 轨对…

华为OD机试真题 Python 实现【最多等和不相交连续子序列】【2022.11 Q4 新题】

目录 题目 思路 考点 Code 题目 题目描述 给定一个数组,我们称其中连续的元素为连续子序列,称这些元素的和为连续子序列的和。数组中可能存在几组连续子序列,组内的连续子序列互不相交且有相同的和。求一组连续子序列,组内子序列的数目最多。输出这个数目。输入描述: 第…

C语言重点解剖预处理要点速记

1.宏定义字符串的时候一定要带上双引号。 2.程序的翻译&#xff0c;就是把文本代码翻译成二进制代码。分为4个阶段&#xff0c;即预处理&#xff0c;编译&#xff0c;汇编&#xff0c;链接。 3.预处理&#xff1a;头文件展开&#xff0c;去注释&#xff0c;宏替换&#xff0c…

[洛谷]P2234 [HNOI2002]营业额统计

[洛谷]P2234 [HNOI2002]营业额统计一、问题描述题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1提示二、问题分析1、算法标签2、思路分析三、代码实现一、问题描述 [洛谷]P2234 [HNOI2002]营业额统计 题目描述 Tiger 最近被公司升任为营业部经理&#xff0c;他上任后…

层流传递窗怎么判定压差合适

层流传递窗压差功能:显示过滤器压差(量程高效0-500Pa/中效0-250Pa)&#xff0c;精度5Pa; 层流传递窗控制功能:风机启动/停止按钮&#xff0c;配置内置式电子门互锁;设置紫外灯&#xff0c;设计单独开关&#xff0c;当两扇门关上时&#xff0c;紫外灯应出于开启状态;设置照明灯…

Java---正则表达式

目录 一、正则表达式的介绍 二、正则表达式的基本语法 &#xff08;1&#xff09;字符类 &#xff08;2&#xff09;预定义符 &#xff08;3&#xff09;数量词 三、正则表达式的具体实例 &#xff08;1&#xff09;判断电话号码是否符合规则 &#xff08;2&#xff09;…