-
-
[翻译]Ring3/Ring0层Rootkit Hook检测技术(二)
-
发表于:
2018-1-4 22:32
11969
-
[翻译]Ring3/Ring0层Rootkit Hook检测技术(二)
IAT挂钩
描述
输入地址表(IAT)是一个跳转列表,列表条目形如“jmp dword ptr ds:[address]”。由于dll文件中的函数地址会发生改变,所以应用程序将调用其自身跳转列表中的相关跳转,而不是直接调用一个dll库中的函数。当应用程序执行时,加载器会将一个指向适当位置处的所需dll函数的指针,放入IAT中。
(缺图:输入地址表)
(我并不确定这样做会有什么实际帮助)
如果一个rootkit程序将自身注入到应用程序中,并修改IAT中的地址,那么它将能够对目标函数的每一次调用过程实施控制。
绕过方法
因为每个dll库的输出地址表(EAT)保持完整,因此应用程序可以通过调用GetProcAddress函数来获取每个dll函数的真实地址,从而轻松绕过IAT挂钩方法。为了防止这种简单绕过方法,rootkit可能会挂钩GetProcAddress/LdrGetProcedureAddress函数,并使用它来返回虚假地址。这种挂钩技术可以通过编写你自己的GetProcAddress函数实现代码,并使用它来获取真正的函数地址来绕过。
内部挂钩
描述
内部挂钩是在函数被调用时,而又在函数完成自身工作之前实施控制的一种技术。通过修改目标函数的首部几(通常是5)字节,该技术对执行流程进行重定向。标准的实现方式是,使用一条跳转到恶意代码的指令覆盖函数的前5字节,然后恶意代码就可以读取函数参数,并完成它需要完成的工作。如果恶意代码需要使用原始函数(即被挂钩的函数)的执行结果,它可以通过执行所覆盖的5字节指令,然后跳过5个字节进入原始函数,这样就可以绕过恶意的跳转/调用,从而避免无限循环/重定向。
(缺图:内部函数挂钩)
(一个通过内部挂钩跳转到恶意代码,然后执行原始函数的示例)
绕过/检测方法(Ring3层)
在用户模式中,内部挂钩通常置于dll库所导出的函数之中。检测和绕过这些挂钩最精确的方法,就是将每个dll库和原始代码相比对。首先,程序需要获取一个包括每个已加载dll库的列表,找到原始文件并读取,将区块载入内存并对齐,然后进行基址的重定位。在dll库的新副本载入内存之后,应用程序可以遍历输出地址表,并将每一个函数与原始dll库中的对应函数进行比对。然后为了绕过挂钩,应用程序可以使用新加载dll库中的代码来替换被覆盖的代码,或者,它可以解析新加载dll库中的导入函数,并转而使用解析结果(需要注意的是,某些dll库在加载多个实例的情况下可能无法正常工作)。
这种绕过dll库挂钩的方法实际上包括了编写你自己的LoadLibrary函数实现代码,因此它对于新手或者胆怯的人来说并不适合。哪怕我能够编写公布用于完成这项工作的代码,我也不会这样做,因为它可能(并且必将)会被脚本小子们用来绕过用户模式的反病毒沙箱或者与其他的rootkit技术相对抗。
(我们也可以使用手动的dll库加载技术来检测/修复EAT挂钩,我不会对这方面内容深入讲解,因为EAT挂钩技术非常不寻常)。
绕过/检测方法(Ring0层)
在内核模式中,模块之间的跳转很罕见。ntoskrnl中的挂钩通常可以通过反汇编每个函数中的每一条指令,然后寻找指向ntoskrnl模块外部的跳转或调用(转入驱动程序体内,等待)来进行检测。也可以使用用户模式挂钩检测中所介绍的相同方法来实现:一个驱动程序可以从磁盘中读取每一个ntoskrnl模块,将其载入内存,并将指令与原始代码相比对。
对于驱动程序中的内部挂钩而言,扫描指向驱动程序体外部的跳转/调用指令很有可能得到假阳性结果,然而标准内核驱动内的跳转/调用以非标准驱动为目标则应该引起警惕。也可以从磁盘中读取驱动;由于驱动一般不会导出很多函数,并且IRP主函数指针只在运行时初始化,所以你可能需要对原始驱动和新驱动的整个代码区块进行比对。特别值得关注的是,相对应的调用/跳转在重定位期间很容易受影响而改变,这意味着在原始驱动和新驱动之间本来就存在不同之处,然而相对应的调用/跳转应该指向同一区域。
SSDT挂钩
描述
系统服务调度表(SSDT)是一个由多个Zw/Nt函数指针所组成的列表,可以从用户模式进行调用。恶意软件可以用指向其自身代码的指针来替换SSDT中的指针。
(缺图:Windows系统调用路径)
(Nt/Zw函数的调用路径示例)
检测方法(Ring0层)
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!