API HOOK检测工具编写,以及过程中遇到的问题总结

这几天GitHub上面一个作者的源码,于是自己决定了也来实现一下API HOOK检测工具GayHub源码

目录

一 、IAT,EAT,Inline HOOK原理:

二 、检测原理:

三 、检测步骤:

四、写的过程中所遇到的问题、以及没有解决的一些东西:


一 、IAT,EAT,Inline HOOK原理:

1.IAT Hook:

HOOK导入表,IID数组的FirstThunk在PE加载内存后会填充为相应函数的RVA,更改这个RVA到自己的Hook函数的RVA,就能实现HOOK。一般这个HOOK针对于编译时候进行调用,比如call ds:[MessageBoxA] 这一类。

2.EAT Hook:

HOOK导出表,主要是针对于GetProcAddress这类调用,导出表中有三个地址:Ent,Eat,NameOrdTable。GetProcAddress原理是:如果要用函数名取函数地址,首先在Ent获取到函数名的索引name_index,再在NameOrdTable中获取NameOrdTable[name_index] 值 Oridinal,最后用Oridinal取Eat[Oridinal]值,这就是函数在模块中的RVA。如果用函数索引取函数地址,则将Index - ExportDirectory.Base,再将这个值在Eat中进行索引获取到函数的RVA。HOOK的时候也就是修改这个RVA,使调用GetProcAddress的时候获取到HOOK函数的地址。

3.Inline_Hook:

在要HOOK的函数的头或着稍微后面一点的位置,JMP到自己的HOOK函数处,从而实现HOOK。

二 、检测原理:

1、IAT HOOK检测方法:

从磁盘上获取该进程文件,读取每一个IID求得每一个IID中DLL名字,以及每一个IID中的OriThunk或FirstThunk(因为有可能OriThunk没有INT,这个时候就会从FirstThunk中获取INT),获取到每一个导入函数的序号或名称,通过查找第二步从磁盘读取加载到内存中的对应DLL模块,获取到EAT,再通过INT或序号去查找对应函数的RVA,加上这个DLL在目标进程的基地址就获取到了原本的函数地址,拿这个地址和从目标进程DUMP下来的模块内存中的对应IAT中的函数地址进行比较是否相同来判断是否被HOOK。
主要针对编译时候的函数调用,如call ds:[xxxx]这类。

2、EAT HOOK检测方法:
    一样每一个模块的每一个函数,如果有导出函数,获取到NumberOfFunctions值和NumberOfNames的值,一个循环NumberOfFunctions次中索引每一个的Eat中的对应值,再去和目标进程中的Eat相应索引的值比较,如果不同那么可能有两种情况:1.导出转向获取到的RVA在ExportRva - ExportRva + ExportSize之间,并且这个RVA指向的地址是另一个模块名称 + 函数名或序号的地址,比如HeapSize转向到NTDLL.RtlSizeHeap),2.被HOOK。那么遇到不同的时候先进行是否导出转向的判断,如果是的话将转向之后Rva获取了再和目标进程中的Rva进行判断,如果还是不同则说明被HOOK。

3、Inline HOOK检测方法:

在检测EAT时候一起就检测了,因为检测Eat会遍历模块的每一个函数。检测原理:将从磁盘加载的文件和进程dump下内存进行函数部分字节的对比,如果发现不一样的并且字节码为0xE9,0xEB开头的就说明被HOOK了(这里应该加一个反汇编引擎来对比更好,现在写这个API HOOK时候我并没有实现反汇编引擎,以后如果写了反汇编引擎加上就可以检测更多的Inline Hook方式了,比如push + ret ... mov eax,address  + call eax ... call xxx....Jmp xxx)。

三 、检测步骤:

1.先把目标进程的每一个模块信息保存下来,包括模块的路径,名字,在目标进程中的基址,模块的大小。
2.读取目标进程的所有模块内存以及磁盘中相对应模块内存。
3.修复从磁盘中读取到内存中模块的区段,按FileAlignment和SectionAlignment对齐,再修复重定位表。(先从磁盘中读取,然后再分配相同大小空间来存放,修复后memcpy到这段空间)。
4.开始每个模块的每一个进程的HOOK检测。
5.清理分配的内存空间。

四、写的过程中所遇到的问题、以及没有解决的一些东西:

1.导出转向的问题,以前并不知道还有这样一个东西,直到写的时候发现打印了HeapSize被HOOK,而HOOK的地址是RtlHeapSize,这个时候才去看作者的源码才知道自己没有处理导出转向,导出转向主要是在EAT中,根据Name或者Oridinal获取到Rva时候,这个Rva并不是在代码段,而仍然是在ExportDirectory的Rva到ExportDirectory + Size这一区间。查看这个RVA会发现是一个模块名+ . + 函数名,这个时候要去这一个模块中查找后面的函数名才能得到这个导出函数真正的RVA。

2.修复重定向时候我只处理了0,3这两种type,作者把所有的重定向类型都给处理了。

3.以前对于VirtualSize对齐后 > SizeOfRawData的理解是从PointerToRawData读取SizeOfRawData大小数据到内存,剩余的部分填充00,但是在读取Kernel32模块时候发现Kernel32的区段都有读取权限,但是ReadProcessMemory一直返回299(读取一部分)错误,我调试发现根本一个字节都没读取到,Kernel32区段的VirtualSize < SizeOfRawData,但是SectionAlignment对齐值为0x10000,对齐后VirtualSize > SizeOfRawData,Kernel32这个是读取了SizeOfRawData大小字节,然后其他的部分根本没有分配内存空间,导致了区段与区段之间地址根本不连续,读取就出错。这个具体是什么原因暂时还是不清楚。解决方法是每0x1000字节就读取一次,读取出现299错误就跳过继续读取后面的,一直把SizeOfImage读取完。

4.在检测Inline Hook时候没有用反汇编引擎,导致检测不完整。

5.原作者在读取Kernel32这类模块时候之间用ReadProcessMemory读取,导致读取产生299错误,所以他不能检测Kernel32的Hook。

6.FirstThunk或OriFirstThunk中如果出现0x8000xxxx,这类就直接是序号导入,不再是用INT来找函数的RVA。写的时候有个宏:IMAGE_ORDINAL_FLAG32(0x80000000)来进行&操作判断是。

7.对于api-xxx 的API在win10上面还没有处理,主要是apisetschema.dll只有在System32才有,是个64位的dll,调试了原作者以及52版主的HOOK工具,发现对apisetschema.dll的处理都是失败的。读取System32\\apisetschema.dll获取版本号都为0.

    

猜你喜欢

转载自blog.csdn.net/a893574301/article/details/82081788