【前言】
2年前joyeep大神发了个Bypass360(
XXXXXXXXXXXXXXXXXXXXXXXX/t/33675),可惜当年俺技术浅薄没能看出啥名堂来。
应该说当时这算是比较有价值的技术(可以绕过N多杀软的主动防御),而其中的实现思路也不同于一般的“正面对抗”。
时过境迁,2年之后相关技术早有人公开,并且360等杀软已经修复了自身存在的问题,因此其危害性已经不大,今天正好俺睡不着就浅析一下其中的原理和思路[s:274]
【背景知识】
1.系统服务、API
众所周知Windows NT利用IA32架构的权限机制,使得用户层(RING3)的应用程序无法执行特权指令并限制其能访问的内存地址范围,这样就限制了用户层的应用程序直接控制硬件或是破坏关键代码的能力。而系统又提供了一组接口,允许用户层通过这些接口以便在受内核控制的情况下,通过内核“代为完成”那些需要在特权指令才能完成的操作,这些服务被称为系统服务(system service)。应用程序在需要调用这些接口时,先把调用的参数存储在栈中,然后根据需要调用的服务填写服务号(把服务号存进eax寄存器),之后执行sysenter/int 0x2e指令;这时候,系统根据存储在MSR/IDT中的地址,跳转到指定位置,并且进入内核模式(RING0,完全的权限)这个时候控制权已经交给了系统内核,内核通过服务号去查一张名为SSDT(系统服务分发表)的表,得知调用的系统服务的处理函数的指针以及需要从用户层的栈中复制的参数的尺寸,并复制这些参数然后调用指定的处理函数,这些函数完成具体的请求,这一流程称为服务分发。Windows NT实现了对应用程序访问硬件的控制,在这个过程中,内核还可以进行各类访问权限检查。同时由于SSDT会随着系统版本的变化而变化,其中的服务号-系统服务的关系也可能改变,所以微软将这个调用过程在应用层的部分封装成了统一的接口函数,这就是Windows API。
整个过程可以参考俺在
《Windows NT内核浅析》的第五章,这里只引用其中的一张图:
举一个具体的例子,当用户层的应用程序调用函数WriteFile()(这是一个文档化的API函数)试图写入文件时,最终会调用ntdll中的ntdll!NtWriteFile()(这是一个未文档化的Native API函数),该函数会填写好服务号,然后调用了ntdll!KiFastSystemCall(),这个函数会执行sysenter指令将控制权交给内核中的KiFastCallEnter()从而进入系统服务的分发流程,最终从SSDT中读取到对应的处理函数nt!NtWriteFile()的地址并跳转,而NtWriteFile()会对这个写入操作进行检查和处理,如果写入是合法的并且写入的对象是一个在磁盘中的文件,NtWriteFile()会将请求交给文件系统驱动程序处理,从而在磁盘指定的位置上写入要求的内容。
系统服务机制使得用户层的应用程序可以完成对硬件的合法操作(只是由内核代为完成),同时阻止非法操作。这是现代操作系统安全的基石。
2.API HOOK
在了解了从API和系统服务的相关机制后,可以发现,如果在应用程序调用系统服务的这一系列路径上修过某些必须执行的指令,在其中写入一个跳转指令使得目标程序在调用时会被迫跳着到指定的地方,从而夺取控制权,由于此时调用系统服务的参数还在栈中,可以很方便地进行参数检查(比如我们可以检查一下写入文件时写入的内容)。这种行为被称为挂钩子(hook)。
早期的HOOK主要针对各类API/NativeAPI 函数,甚至ntdll中的ntdll!KiFastSystemCall。但是由于应用程序的编写者如果得知在所运行的系统上需要调用的系统服务的服务号和参数,就可以自行将参数压入栈中并填写服务号,最后自行执行sysenter指令将控制权交给系统内核,所以调用系统服务可以绕过API,因而任何RING3下的HOOK都可以在RING3下绕过。
同时,由于Windows NT下每个进程拥有独立的用户地址空间,因为在RING3下实现全局的HOOK比较麻烦,于是HOOK的位置逐渐转移到系统内核中。最早的内核HOOK被称为SSDT HOOK,通过替换SSDT中指定服务的处理例程指针为自己的例程出指针,来实现HOOK,但这种方法隐蔽性和兼容性较差,后来HOOK的位置不断深入……
由于RING3下单应用程序无法访问并难以绕过这些位置,因而在RING3下很难绕过RING0 HOOK。
3.主动防御
由于Windows NT的系统服务机制也可以被恶意程序利用。恶意程序只要通过系统服务使得系统安装一个恶意的驱动程序,最后恶意的驱动程序就获得了和系统相同的权限(Windows NT中通常情况下驱动程序拥有RING0权限)从而可以取得控制权,因此各大安全厂商也开始使用内核HOOK实现一些额外的检查,以阻止恶意行为,这就是所谓的主动防御。举个例子,如果一个拥有管理员权限的应用程序通过nt!NtWriteFile修改HOST文件,系统默认情况下是允许的,很多恶意程序就利用这一点篡改这个关键的系统文件。而安全软件可以HOOK调用nt!NtWriteFile的路径上在内核中的任意位置,提前检查写的对象是否是这样的关键文件,如果是则返回失败或者挂起请求并且通知安全软件作出处理。
同时由于安全软件占据了先机,可以在内核中HOOK所有加载驱动要用到的服务以阻止恶意驱动被加载从而消灭掉安全软件或者取得控制权。
【正面对抗主动防御】
有盾必有矛。主动防御技术出现后,也出现了相应的正面对抗方法。由于Windows NT不是开源的,很长一段时间内大部分安全厂商也没有机会访问完整的Windows源码和产品文档。所以在设计主动防御体系时候,可能会遗漏某些嫩起到关键作用的系统服务。另外有些(特别是国内的某些)安全厂商的检查不严格,可以采取一些小伎俩瞒天过海。这类方法是正面对抗,但这样的机会并不多并且十分宝贵,往往被发现后也不会公开并且很容易被发现。还有一类方法则是利用系统漏洞(特别是那些高权限的组件的漏洞),但这也是可遇而不可求的。
【Bypass360的侧面攻击:盲区】
一开始还以为Bypass360用了某些高深的手段,后来发现其关键部分XXXXXXXXXXXX只有100多行,并且没有利用任何系统漏洞。首先我们分析一下代码逻辑:void main()
{
CloseWrite();
return ;
}
void main()函数是程序的入口点,其一执行就调用了另一个函数CloseWrite();
这个函数做的事情也很简单,先调用SetProcessShutdownParameters()设置系统关闭时的优先级为低,随后调用API SetConsoleCtrlHandler()注册了一个控制台处理例程CtrlHandler,这个函数的实现也在该文件中。查阅MSDN(
XXXXXXXXXXXXXXXXXXXXXXXXX/en-us/library/windows/desktop/ms686227(v=vs.85).aspx)可知,SetProcessShutdownParameters()设置的优先级越低,系统关闭时这个进程被结束的越晚。而Windows NT中,提供了一系列机制使应用程序在系统被关闭时还有机会完成一些工作(比如保存重要数据),SetProcessShutdownParameter()(MSDN:
XXXXXXXXXXXXXXXXXXXXXXXXX/en-us/library/windows/desktop/ms686016(v=vs.85).aspx)的作用就是为控制台(CUI)程序注册一个例程处理控制台消息,系统关闭时会产生相应的控制台消息并调用注册的处理例程。
这里注册的处理例程即BOOL WINAPI CtrlHandler(DWORD fdwCtrlType)(在27行定义),根据MSDN中的规范fdwCtrlType即是控制台消息的类型,控制台消息有很多种,因此该例程对类型进行了判断,当符合条件时就复制一个已经准备好的恶意驱动程序到系统目录下并在注册表中设置为自启动,这样下次开机的时候这个恶意驱动程序就会被加载……
可能有童鞋会问安全软件不是一件HOOK了加载驱动的系统服务么?事实的确是这样,但是由于之前安全软件甚少考虑关机时进程结束顺序的问题,就被钻了空子。Bypass360将自己的优先级设置为最低,在系统通知其关机(并且它开始执行处理例程)时,安全软件早就以为已经关机而关闭了自己,并且关闭了检查,于是注册驱动的行为就这样“过了关”。
Bypass360“侧面解决”的思路非常巧妙,也没有使用什么复杂的技术或者未知的方法,从而实现了从RING3“逆袭”的过程,其实只是选择了一个恰当的时间(杀毒软件自己把自己关了),从而绕过了防御。
分析就到这里……睡觉去了
参考资料:
《Windows内核原理与实现》 潘爱民 电子工业出版社2010
《XXXXXXXXXXternals 6th》XXXXXXXXXssinovich Microsoft Press2012
200字以内,仅用于支线交流,主线讨论请采用回复功能。