引言
近期的网络犯罪地下世界没有任何令人兴奋的恶意软件值得逆向分析,这导致我对新文章没有什么思路,因此我决定写两篇文章,来介绍一下rootkit拦截函数调用的相关技术,以及如何对其进行检测。第一部分会讲解一些挂钩方法,第二部分将介绍如何对其进行检测。由于本文并没有进行任何内核模式的相关工作,我将同时关注32位Windows系统上用户模式和内核模式两种情景下的挂钩技术。
执行流
为了对攻击面有一个更好的理解,针对kernel32.dll中WriteFile函数的调用过程,我绘制了一张简化版的流程图;这只是值得关注的关键点的一个示例,之所以选择WriteFile函数,一方面是因为它确实是一个很好的示例,另一方面则是因为恶意软件通常选择磁盘I/O操作进行拦截,而且本图中的大部分内容对于很多函数是通用的。
(缺图:WriteFile函数从用户模式到内核模式的调用路径)
(PS:如果你连通过单击可以放大图像都不知道的话,那么本文可能不太适合你。)
(1)
·WriteFile只是NtWriteFile函数的简单包装器。
·可以使用内部(inline),输入地址表(IAT)或输出地址表(EAT)方法进行挂钩。
·挂钩该函数将拦截对WriteFile函数的所有调用,不管挂钩过程位于哪个进程之中。
·kernel32库中所用到的所有路径基本都是Dos形式的路径(比如,C:file.txt)。
(2)
·NtWriteFile函数是一个小型的桩程序,该函数用一个32位的值(随后我将详细解释这个值)为EAX寄存器赋值,然后调用KiFastSystemCall函数。
·可以使用内部(inline),输入地址表(IAT)或输出地址表(EAT)方法进行挂钩。
·挂钩该函数将拦截对CreateFile,NtWriteFile或ZwWriteFile函数的所有调用,不管挂钩过程位于哪个进程之中。
·ntdll库中文件函数所用到的所有路径基本都是NT形式的路径(比如C:file.txt,??)。
(2.1)
·为了调用KiFastSystemCall函数,NtWriteFile函数将地址0x7FFE0300(KiFastSystemCall/KiFastSystemCall函数指针)放入EDX寄存器,然后它执行“call edx”或“call dword ptr [edx]”。
·为了实现挂钩,rootkit可以在NtWriteFile函数体中对地址0x7FFE0300进行替换。
·挂钩该函数将拦截对CreateFile,NtWriteFile或ZwWriteFile函数的所有调用,不管挂钩过程位于哪个进程之中。
·ntdll库中文件函数所用到的所有路径基本都是NT形式的路径(比如C:file.txt,??)。
(3)
·KiFastSystemCall是一个小型的桩程序,它将栈指针放入EDX寄存器,然后执行sysenter指令。
·这个桩程序只有5字节大小,同时KiFastSystemCallRet指向它的最后一句指令(RETN),这就使得程序中只剩下4个可写字节(即,对于一个近距离的call/jmp指令而言空间不足)。
而且,硬编码地址致使IAT或EAT挂钩技术不可用。
·有时KiFastSystemCall桩程序位于KUSER_SHARED_DATA结构体中,在这种情况下它对用户模式而言是不可写的。
·通过挂钩该函数,rootkit能够对内核函数的所有用户模式调用进行拦截。
(4)
·为了执行一个内核函数,SYSENTER指令将执行流从用户模式转换到内核模式。当这条指令执行时,CPU将SYSENTER_CS寄存器的内容设置为代码段,将SYSENTER_ESP寄存器的内容设置为栈指针,并将SYSENTER_EIP寄存器的内容设置为EIP的值。SYSENTER_EIP寄存器指向ntoskrnl中的KiFastCallEntry函数,因此CPU将开始执行KiFastCallEntry。
·这些寄存器被称为模式相关寄存器(MSR,Model Specific Register),它们只能使用CPU指令RDMSR(读MSR)进行读取,或者使用WRMSR(写MSR)指令进行写入。这两条指令都需要相应的权限(只能在ring 0层执行),因此必须加载一个内核驱动才能实现挂钩。
·通过修改SYSENTER_EIP寄存器的值,rootkit能够对内核函数的所有用户模式调用进行拦截;但我们不能拦截任何内核模式调用,因为只有用户模式调用需要使用SYSENTER指令。
(5)
·KiFastCallEntry函数负责从EAX寄存器中提取32位的值(即我们在第2小节所提到的值)。其中,前11位代表系统服务描述符表(SSDT)函数的序号,可以使用(SSDT_Address+(Ordinal*4))形式的表达式对其进行检索,第12和13位确定要用到的SSDT,最后剩余的位忽略。一旦函数确定要用到的SSDT,它将调用按照给定序号在表中索引到的地址。
·可以使用内部挂钩技术对其进行挂钩。
·通过挂钩该函数,rootkit可以拦截对内核函数的所有用户模式调用,以及对以“Zw”开头函数的所有内核模式调用,但是不能拦截对以“Nt”开头函数的内核模式调用。
(5.1)
·因为SSDT是一个函数指针的列表,所以也可以通过替换SSDT中的指针来实现对调用进行挂钩操作。对于ntdll库中的每一个内核函数来说,在SSDT中都有一个等价指针,因此我们仅仅通过替换指针就可以实现随意挂钩任何函数。通过这种方法我们也可以挂钩对以“Zw”开头函数的所有内核模式调用,但我们无法挂钩对以“Nt”开头函数的内核模式调用。
(6)
·再一次调用NtWriteFile。我们在第2小节中看到了对NtWriteFile函数的一次调用,但那只是ntdll.dll文件中一个用于进入内核模式的桩程序,现在这个才是实际调用根据给定SSDT序号找到的地址所指向的NtWriteFile函数。
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!