对于会Hook的人来说,Hook其实也就那么回事.对于没有Hook过的人来说,会感觉Hook很高大上(其实也没毛病).
那么今天我们就来探讨一些Hook的原理是什么.
我认为任何Hook都可以分为以下三步(简称WFH):
-
需要Hook的是什么,在哪里(后面简称Where).
-
寻找到Hook的地方.(后面简称Find)
-
进行Hook.(后面简称Hook)
当然了同一个类型的Hook所在的地方一般是一样的.但寻找到Hook的地方,和进行Hook却会有许多不同的方法.我们要抓住的是不变的地方.
根据这3点我们举例来验证一下.
Hook API
第一个我尽量说得详细一些.
举例子:Hook API:OpenProcess 让win10 64位的任务管理器关闭不了任何程序.
1. Where.
Hook API:OpenProcess. 在kernelbase.dll里面.
2.Find.
方式1:
-
通过LoadLibrary加载kernelbase.dll模块基地址.
-
通过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的第一种方式,后面不再说明):
-
新建一个dll文件:
-
在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
(免费订阅,永久学习)学习地址: 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
-
新建一个驱动程序:
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的地方在哪了,也不需要寻找.确实,这两歩不需要我们自己做,但并不代表不需要,这只是操作系统为我们做了而已,我们只需要提供一个回调函数即可.
原文链接:[原创]Hook原理-编程技术-看雪论坛-安全社区|安全招聘|bbs.pediy.com