内核前置——Hook技术
总问题
地址问题
这个我遇到过几次,首先:
地址大小: 在 32 位系统中,指针的大小是 4 字节,而在 64 位系统中,指针的大小是 8 字节。因此,对于 64 位系统,函数的地址计算、指针运算需要使用 64 位大小的地址。
所以网上的脚本有些是32位脚本,无法运行的话就要考虑这个
Detour函数
Detour 函数一般指的是在程序执行过程中,修改或替换某个函数的执行路径的技术,也叫做 函数钩子(Function Hooking) 或 函数劫持(Function Detouring)。这个过程允许我们在不改变原函数代码的情况下,插入自定义的逻辑或代码。
TrampolineFun函数
该函数不是一个完整的函数,是调用原函数的入口
Hook分类
对于hook,我们不难发现,大致可以分为两种类型,一种是对引用函数的地址进行操作,另一种是对引用函数的地址的内容进行操作
举个例子,下面是一个c语言程序,目的是将原本打印的A换成B
1 |
|
通过三种结果不难看出,第一种方法是通过改变地址,实现的打印改变,第二种不改变地址,但是改变指针指向的值
在这个简单的例子之后,去了解别的Hook方式就不难了。名目繁多的Hook,总结起来其实只有两种:
Address Hook 和** Inline Hook**
感悟
在InlineHook这里,学习的很多,感觉比之前悟了很多
hook原理就是跳跳跳
问题
那么首先第一个我卡住的是HotPatch Hook
这个hook遇到的问题就是跳转的时候,距离太大了,源地址:00007FFE556D61B6 ,跳转地址是000000014001213B,就是一个是APT函数,一个是自己写的hook函数,距离太远了所以跳转实现不了
第二个遇到问题是在InlineHook里面的InlineHook,其中本来应该,小跳到IAT表中地址然后对地址跳转,通过hook后变成小跳到申请的地址中,对地址中的地址挑战,但遇到的问题和上面一样
为什么有问题
对于一个64位程序来说,一个普普通通的程序开始的地方,都在1 40 00 00 00如此的高地址起步,IAT表一般都会在非常非常高的位置如00007FFE556D61B6 ,然而自己申请的地址又在十分低的位置,这样想实现通过本来的小跳转实现hook就很艰难了
解决
对于这些的解决,其实就是找64位程序的跳转方法,找实现远距离大跳的字节码,基本都是得通过修改字节码来实现跳转了(除非这两个很近)
收集到的大跳字节码
1 | 0x48,0xB8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //mov rax,xxx |
1 | 0x68,0x00,0x00,0x00,0x00//push 000000 |
1 | 0xff,0x25,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00//jmp qword ptr [00000000 00000000] |
这个可以跳的很远,但是计算的是偏移
Inline Hook
Inline Hook是指直接修改指令的hook
因为其关键是转移程序的执行流程,所以一般是用jmp、call、retn之类的转移指令
那么根据不同场合会有不同的改变指令方式,如下是五种模式:
1.jmp xxxxxxxx(5字节)
直接跳转
2.push xxxxxxxx(6字节)
retn
压栈返回实现跳转
3.mov eax,xxxxxxxx (7字节)
jmp eax
先将转移地址放入寄存器,在实现跳转(注:这里使用eax寄存器是因为eax通常用于存储返回值,在函数开始时不会影响什么)
4.call Hook(更换指令或输入表)
5.HotPatch Hook
这个要细说,重新开一个hook
InlineHook-movjmp
https://zhuanlan.zhihu.com/p/142621824
原理
原理大致是,在一个API函数开始前,会有一个call函数执行,我们通过改变API函数的构造,来做到hook
以MessageBoxA举例,这是API函数,所以在这个函数所在模块会记录这个函数如果被调用的话全过程是什么,这个函数正常执行流程是call MessageBoxA,那么在这个模块中,MessageBoxAadress部分第一项就会使call MessageBoxA,所以我们用过使用API函数所在模块的句柄,获得函数地址,再通过函数地址,使用 WriteProcessMemory函数写入准备好的同字节大小跳转函数,即可完成hook

源码
1 |
|
超详细解读程序
第一段函数void InlineHook()
初始定义
1 | HMODULE hModule_User32 = LoadLibrary(L"user32.dll"); |
先获得user32.dll模块的句柄,再用句柄获得模块中MessageBoxA函数的地址,同时打印MyMessageBox的地址和MessageBoxA的地址
读取前14字节
1 | // 读MessageBoxA函数的前14个字节 |
这里先读取MessageBoxA函数里的前十四个字节,存储在_OldCode中,在打印前十四字节
解析一下ReadProcessMemory函数: 用于从指定进程的内存空间中读取数据。
1 | BOOL ReadProcessMemory( |
Hook14字节
1 | DWORD64 JmpAddress = (DWORD64)MyMessageBoxA; // 64 位地址 |
这些作用就是用十四字节实现,mov eax MyMessageBoxA jmp eax,于是函数就被hook成自己的函数了,创造之后看看字节码对不对
写入hook
1 | //DWORD dwOldProtect = 0; //旧保护属性 |
这个其实没啥好说的看注释就好,函数猜的话也猜出来了,于是这段函数就结束了
于是程序返回执行MessageBoxA,本来应该call MassageBoxA,变成了mov与jmp,hook成了进入自己的函数
InlineHook–HotPatch
一般的hook需要5字节,lock 什么的,也最多同时操作4字节,如果要修改5字节,严格说来是要使用同步的,以防止其他CPU访问正被修改的指令。
hotpatch的技法则是首先在函数上方,一般会因为对齐,或者编译时使用/hotpatch选项而预留出几字节的空间,首先在这些空间里写入一个5字节的远程跳转,然后再将函数的俩字节mov edi,edi指令换成一个近跳转,跳到上方的长跳转中,2字节的修改。此种技法完全不用考虑同步…
hotpatch 的hook 方式_hotpatch hook-CSDN博客
“热补丁”Hook,多线程下InlineHook解决方法-CSDN博客
源码
1 | // HotPatch.cpp : 定义控制台应用程序的入口点。 |
分析
老规矩,按照主函数调用顺序分析
SetPrivilege
1 | BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege) |
设置权限函数
HOOKByHotpatch
1 | BOOL HOOKByHotpatch(LPWSTR wzDllName, LPCSTR szFuncName, PROC pfnNewFunc) |
这里一直在报错一直搞不定,等开始写项目了回来修补,大致意思看两遍就知道了
InlineHook–call
原理
这里分为两种,我们知道在运行一个函数之前,会call <你用的函数>,所以在这里改动,使其变成
call <我的函数>
如果调用的是IAT函数,将先使用 FF 15 <IAT表>,对IAT表中的函数地址,实现跳转,由此我们可以自己先申请一块地址,再把自己写的函数放在地址上,实现跳转,就相当于和上文一样实现了FF 15 <申请的地址表>,对自己申请的地址中存放的地址,实现跳转,不过这里会出现问题我都会在感悟中提到
如果用的是E8call跳转,那就直接换E8后面的值就好
基于异常处理的Hook
这个比我想象的好玩
原理
向量化异常处理(Vectored Exception Handling,VEH):这是一个扩展的异常处理机制,允许注册异常处理程序,以便在程序发生异常时接管处理。
手动的对你接下来要进行的函数设置断点(0xCC),在捕获异常后用自己的方法修复异常,在修复的时候就可以进项hook了
源码
1 | /*----------------------------------------------------------------------- |
解析
实现流程,先获得先获得地址,获得之后安装一个解决方案,然后设置接下来进行函数的断点,然后调用即可
main函数
1 | int main(int argc, char* argv[]) |
在printf(“All Finished!\n”)之前是对异常的准备工作,后面的是清理工作,这里需要重点研究的是,三种异常处理方法函数和安装函数
三种异常函数
共同部分
1 | LONG lResult = EXCEPTION_CONTINUE_SEARCH; |
EXCEPTION_RECORD 结构体
EXCEPTION_RECORD 结构体包含了异常的基本信息,主要字段有:
ExceptionCode:异常的代码,表示异常类型,例如EXCEPTION_ACCESS_VIOLATION、EXCEPTION_BREAKPOINT等。ExceptionAddress:异常发生时的地址(通常是程序崩溃的指令地址)。ExceptionFlags:异常的标志位,用来表示异常的特殊类型或处理方式。NumberParameters:异常的参数数量。ExceptionInformation[]:包含异常相关的附加信息,具体信息取决于异常类型。
CONTEXT 结构体
CONTEXT 结构体保存了线程的寄存器状态。对于 x86 和 x64 平台,结构体的成员有所不同。
- 在 x64 系统中,
CONTEXT结构体包含以下寄存器:Rax, Rbx, Rcx, Rdx等通用寄存器。Rip:指令指针,表示当前执行的指令地址。Rsp:堆栈指针,指向栈的当前位置。Rbp:基址指针。Rdi, Rsi等其他寄存器
先设置一个默认的返回值,没有完全处理异常
初始化了两个指针,一个指向异常的详细信息,一个指向保存当前CPU寄存器状况的环境
**EXCEPTION_BREAKPOINT**:这是一个常量,值为0x80000003,表示这是一个断点异常。通常是由调试器或某些程序内的显式int 3指令(触发断点)引发的。断点异常一般在调试程序时用来暂停程序的执行。
这里检测断点是不是自己设置的0xCC,并且检测是不是在MessageBoxA里的
VectoredHandler1
1 | //x64上前四个参数依次为RCX,RDX,R8,R9 |
这个比较好理解,把函数调用进去的寄存器改成需要的值,然后将rip回正
VectoredHandler2
1 | //x64上前四个参数依次为RCX,RDX,R8,R9 |
直接先调用一个你需要的函数,传参在保存上下文中有,然后传入需要不同的地方
因为这里是刚进入函数的时候报错的,所以栈中只保存了返回地址,所以uESP中保存的是返回地址,后面调用完函数之后,将Rip回正,Rsp跳过返回地址
VectoredHandler3
1 | printf("RSP = 0x%p\n",pContextRecord->Rsp) ; |
这里直接将保存环境中的Rip改回返回值,栈跳过返回值,然后返回
InstallVEHHook函数
1 | BOOL InstallVEHHook(PVECTORED_EXCEPTION_HANDLER Handler) |
这里很好分析,首先确定你使用的是哪个处理方案,然后通过AddVectoredExceptionHandler函数来将解决方案加入到VEH中,于是就hook了
Address Hook(待完善)(偏内核)
前言
通过改变函数的地址(函数在各类表或结构中,或者某个指定的地址处),但他们共同点就是调用函数了,就会成为eip,因此把本来的地址换成我们自己的就实现hook了
这里先看一眼这个文章预备一下前置知识
《逆向工程核心原理》第13章——PE文件格式(2):IAT与EAT_od查看iat-CSDN博客
各类表中的地址
PE中的IAT
导入地址表(Import Adress Table)。简而言之IAT是一种表格,用来记录程序正在使用哪些库中的哪些函数。
PE中的EAT
EAT是一种核心机制,使不同的应用程序可以调用库文件中提供的函数,只有通过EAT才能准确求得从相应库中到处函数的起始地址。
user32.dll的回调函数表
存储系统回调函数的表
IDT
SSDT和Shadow SSDT
C++类的虚函数表–这里的虚函数hook需要注意
注意的地方是,子类函数中重载函数的地址会被替换成虚函数表中对应的地址(其继承函数)
COM借口的功能函数表
处理历程地址
书上所说,这部分内容常用于内核中
这里补习一下例程的概念
例程≈ 函数?或者说例程>=函数?
DRIVER_OBJECT 的 MajorFunction 及FasIo 派遣历程地址
在这本书的这里,我还没有学习他说的例子,并且有很多生名词,我先把不知道的自己简单搜索的标注在这里
什么是 ntfs.sys
该过程的Ntfs.sys也被称为NT文件系统驱动程序,它是由微软公司所拥有。该的Ntfs.sys过程用于创建和存储
与使用NTFS的消防处的应用程序。在Windows操作系统检验机构(因为只有合法的用户才能运行此),其次是I
/ O管理,将所述文件句柄到一个文件对象的指标。这个过程之后,得到的NTFS文件在磁盘使用的文件对象指标。 NTFS的收集与使用文件对象指针的文件属性的渣打银行(流控制块)。流控制块是由单一的属性文件表示,并包含有关如何获取文件点的国有商业银行的数据建设FCB(文件控制块)指定的属性信息。该FCB具有指定MFT(主文件表)的文件记录的指标
书中的例子也没太看懂,以后懂了回来解释补充,总之看到了,FSD Hook 是修改的FileSystem Driver
StartIo等特殊例程的地址
这个例程主要用于IRP的串行处理,在nt!IoStartPacket中被调用
OBJECT_TYPE 中 _OBJECT_TYPE_INITIALIZER包含的各种处理过程
看不懂思密达,以后再说吧
特殊寄存器中的地址
好像还是内核
特殊的函数指针
前面说的还正常后面又是内核
好像是结束了,那么综上所看,addresshook多用于内核,以后回来看
IATHook
IAT
IAT:导入地址表(Import Adress Table)。简而言之IAT是一种表格,用来记录程序正在使用哪些库中的哪些函数。
原理
每个调用的 API 函数地址都保存在 IAT 表中。 API 函数调用时,每个输入节( IMPORT SECTION )所指向的 IAT结构如下图所示。
程序中每个调用 API 函数的 CALL 指令所使用的地址都是相应函数登记在 IAT 表的地址。所以为了截获 API 函数,我们将 IAT 表中的地址换成用户自己的 API PROXY 函数地址,这样每个 API 调用都是先调用用户自己的 API PROXY 函数。在这个函数中我们可以完成函数名称的记录、参数的记录、调用原来的过程,并在返回时记录结果。
1 | int main(int argc, char *argv[ ]) |
如上操作,如果按照正常输出的话,只会有三个框,但是经过IAT_InstallHook函数之后,box表就会改变,使用了修改IAT的方法拦截了API的调用,所以称为IAT hook,调用地址欺骗
源码
1 | ///////////////////////////////////////////////////////////////// |
解析
这里只解析这个关键函数
1 | BOOL IAT_InstallHook() |
第一段
BOOL bResult = FALSE ;
HMODULE hCurExe = GetModuleHandle(NULL);
定义两个初始值,一个是返回是否成功,一个是获取当前线程的句柄
其中函数解析GetMouduleHandle
是一个win32API函数,用于获取句柄,当传入参数为NULL的时候代表获取当前进程中主模块的句柄
1 | PULONG_PTR pt; |
第二段
定义了pt(原函数地址),OrginalAddr(存储目标函数地址)
其中的函数解析InstallModuleIATHook,函数定义如下
1 | BOOL InstallModuleIATHook( |
简单来说就是传入要修改的模块句柄和名称以及具体的函数,然后在传入要替换的函数的地址,最后保存源函数地址的指针和目标函数的地址
第三段
1 | if (bResult) |
这里根据上一个函数的返回值也就是TRUE或FULL来判断是否成功,并打印结果,最后保存一下原函数地址与指针就结束了
虚函数hook
虚函数表
原理
十分符合Address Hook的原理,将虚函数表中的函数地址换成自己的函数地址
源码
1 | /*----------------------------------------------------------------------- |
函数分析
这里主要分析hook函数,也是这个函数的核心
1 | void HookClassMemberByAnotherClassMember() { |
步骤 1: 获取虚拟函数表地址
vfTableToHook = (ULONG_PTR*)*(ULONG_PTR*)pbase获取到base类实例的虚拟函数表指针。vfTableTrampoline = (ULONG_PTR*)*(ULONG_PTR*)&Trampoline获取到TrampolineClass的虚拟函数表指针。
步骤 2: 修改虚拟函数表
- 保存原始函数地址:通过
VirtualQuery和VirtualProtect修改内存权限,使得可以修改虚拟函数表。在vfTableTrampoline[0]中保存原来base::Add函数的地址。 - 替换为 Detour 函数:将
vfTableToHook[0]替换为DetourClass::DetourFun的地址,从而使得base::Add函数被替换为DetourClass::DetourFun。
步骤 3: 恢复虚拟函数表修改
- 通过
VirtualProtect恢复内存页的保护,以保证修改后的内存区域可以正常访问。
步骤 4: 调用虚拟函数
int result = pbase->Add(1, 2)调用了被 Hook 的base::Add函数,但由于我们替换了虚拟函数表中的地址,实际上调用的是DetourClass::DetourFun,最终输出和返回的值是通过DetourClass::DetourFun处理后的。
二次hook注意事项
1.不Hook
Hook更改稍有不慎就可能造成程序错误的后果(蓝屏警告),尤其是偏向内核的东西AddressHook,在早期病毒查杀市场,各大病毒查杀软件,就是在各个风险的点添加Hook,但是病毒查杀软件之间添加Hook,并且这些Hook并不兼容,谁也不能保准哪个Hook刚好会覆盖掉另一个的Hook,这样的问题也持续至今,所以到如今这个时代病毒查杀软件也最好只安装一种
所以有时候Hook随意更改会带来风险
2.恢复Hook前的指令
这种方法的缺点是,该段的Hook会是唯一性的,一个程序里只能有一个Hook
3.换个目标地址Hook
就跟题目一样,换个地方
4.Hook上一个Hook过程的Detour函数
这个也和题目一样理解就可以