常用的文件隐藏方法有基于用户模式的 Rootkit 也有基于内核模式的 Rootkit
在 Windows 中,应用层的大多数功能都是通过调用 Native API 完成的。以 Windows7 为例,Native API 通过 sysenter 指令进入内核,进而调用 SSDT(System
Service Dispatch Table,系统服务调度表)中对应的函数来完成功能。因此,利用用户模
式的 Rootkit 实现的文件隐藏,很容易被使用内核接口枚举文件的工具绕过,达不到隐
藏的效果
基于内核模式 Rootkit 的文件隐藏,通常对 Windows 内核中相关文件浏览函数挂钩,甚至对 Windows 文件系统的内核模块进行修改。此类隐藏方法实现上更偏系统底层,在系统中有更大的控制权限,还能针对大部分反病毒软件和 Anti-Rootkit,进行有针对性的处理,能达到更好的隐藏效果。
Windows 系统提供了遍历文件、目录的函数 FindFirstFileA,该函数实际上是对 FindFirstFileExw 的简单封装。对 FindFirstFileExW 进一步分析后发现,该函数是调用 ntdll!ZwQueryDirectoryFile 函数,该函数是内核 Ntoskrnl.exe 实现的,该函数的地址保存在 SSDT 中
SSDT 是 Windows 内核导出的一个数据结构。Windows 内核组件中,SSDT 包含了 Windows 执行体提供的系统功能支持接口,这是 Ntoskml.exe 里实现的系统核心服务SSDT以数组的形式存储这些接口对应函数的地址,接口在数组中的索引也就是该接口的系统调用编号,这个索引或者系统调用编号可以被用于定位函数的内存地址
要挂钩 ZwqueryDirectowFile 函数,首先需要定位到该函数的地址,再确定采用的 Hook 方式,然后编写自定义的具体代码或者函数
一般可以采用两种方式对 SSDT 里的函数进行Hook:
Hook ZwQueryDirectoryFile 函数的具体过程如下:
FSDs(File System Driver,文件系统驱动)主要用来管理系统文件格式。不同于其他标准的 Windows 内核驱动,FSDs 驱动必须经过 I/O 管理器注册才能使用,并且在运行的过程中与内存管理器产生大量交互操作。另外,为了增强系统性能,文件系统驱动经常依赖 Windows 系统 cache 管理器的服务。因此,与标准驱动相比,FSDs 大量使用了 Ntoskrnl.exe 的导出函数
Windows 有两种类型的文件系统驱动:
nt!NtQueryDirectoryFile 函数是内核为用户模式提供的接口,处在文件浏览功能整个调用链的顶端,挂钩该函数实现的文件隐藏容易被其他反病毒工具或 Anti-Rootkit 工具发现
在 Ntfs.sys 中 Ntfs!NtfaFsdDirectoryControl 函数负责处理 flmgr.sys 发来的 IRP,进一步对 Ntfs!NtfsFsdDirectoryControl 函数进行分析,确定该函数调用 ntfs!NtfsCommonDirectoryControl,其又调用了 ntfs!NtfsQueryDirectory
导出表中没有的函数,不能通过解析 PE 文件导出表获取该函数的地址,需要使用该函数的特征码:
1)利用 IDA 等反汇编工具分析该函数的代码,选取搜索使用的特征码,也就是一段独一无二能标明该函数的指令
2)通过解析 ntfs.sys 的文件格式,获取到 OEP 的地址
3)通过获取 ntfs 在内存中加载的基址,然后从 ntfs 的 OEP 的虚拟地址开始,使用 1 到的特征码搜索,直至搜索出想要的函数
在 Windows 系统中,使用位于内核模式的文件系统驱动管理文件系统格式。本地文件系统驱动通过存储设备驱动管理底层的硬盘,并通过 I/O 管理器与上层的应用层进行交互,Windows 在 I/O 管理器之上,针对各种文件操作的 IRP 进行了封装,提供了一系列的文件操作和访问函数,如 NtcreateFile、NtReadFile、NtwriteFile 和 NtqueryDirectorylnformation 等。文件系统驱动通过卷驱动管理器以及更下层的分区管理器对硬盘J的文科进行管玾和操作
综合整个过程可以看到,处于硬盘分区之上的文件和数据,都可以通过文件系统驱动查看和操作。实际上,在硬盘进行分区的时候,经常会有一部分硬盘区域未被划分为分区,作为空白区域存在。由于该部分硬盘空间不属于任何分区,通过文件系统是无法查看和操作到该部分硬盘空间的内容。另外,在硬盘的第一个扇区(MBR)和第一个分区(VBR)的起始扇区之间的区域也是一部分空白区域,但该部分的空间相对较小
Windows 系统中,镜像加载器管理进程加载的所有模块,这些加载的模块被维护在与进程相关的 PEB(Process Environment Block)结构中。PEB 包含了镜像加载器、堆管理器示其他 Windows 组件需要的信息。PEB 结构中的成员 Ldr 包含了三个重要的链表:
通常,PEB 的地址需要通过 TEB(Thread Environment Block)获取。TEB 是系统维护的一个与线程相关的结构,它存储镜像加载器和多个 Windows DLL 的上下文信息。由于进程中加载的 DLL 组件运行在用户模式,它们需要 TEB 在用户态可写,故 TEB 和 PEB 是存在于用户空间而不是系统内核地址空间。在 Windbg 中使用 !thread
命令找到 TEB 的地址。从图中可以看到,TEB 的地址是 O×7ffdf000,这个地址正好是进程中用户空间的地址(32 位系统中,用户空间的地址范围是 0~O×7FFFFFFF)
在具体的木马编码实现时,在 32 位系统中常通过 FS 寄存器的值来得到 TEB 的地址,进而解析 TEB 得到 PEB 地址;在 64 位系统,常通过 GS 寄存器来获取 TEB 的地址
Windows 系统中有静态和动态两种 DLL 模块加载方式:
通常,查看进程所加载模块的工具,都是基于 PEB 结构中的模块信息链表来显示的。如果不使用系统标准的加载接口,自行将 DLL 模块加载到内存并初始化,DLL 模块的信息则不会被添加在 PEB 的 Ldr 结构中,也就起到了隐藏的效果
要自行加载 DLL 到进程中,则需要模仿 LoadLibrary 等函数,实现一个 DLL 模块的加载器,这个加载器通常称为 PE Loader。根据对 Windows 镜像加载器,尤其是 LoadLibrary 等函数的分析,一个比较典型的 PE 加载器的执行流程如下:
通过 Hook tcpip!TCPQueryInformationEx 函数,对该函数执行后返回的结果进行修改就可以实现端口的隐藏,但是由于 tcpip.sys 没有导出 TcpQueryInformationEx 和 TCPDispatchDeviceControl 函数,需要使用特征码搜索才能确定这些函数的地址,相比
较而言,tcpip!TCPDispatchDevicecontrol位于tcpip!TCPQueryInformationEx 的上层,若从 tcpip!GsDriverEntry 函数开始搜索,更容易先搜索到 tcpip!TCPDispatchDeviceControl 函数,故这里确定 Hook tcpip!TCPDispatchDeviceControl 函数,实现网络通信端口隐藏
从 Windows Vista 系统开始,系统内核网络组件发生了较大变化。网络连接信息不再由 tcpip.sys 进行管理,而是引入了新的内核网络组件 Nsiproxy.sys 和 netio.sys 等
以 Windows 7 系统为例,对它自带的 netstat.exe 可执行文件格式进行分析,从它的导入表中发现了多个从 IPHLPAPI.dll 导入的函数,但没有发现 GetTcpStatsFromStackEx 函数。根据函数名称观察,IPHLPAPI!InternalGetTcpTable2 函数比较可疑。
经过调试分析,该函数被 netstat.exe 用来获取端口和网络连接。在 IPHLPAPI.dll 中,
IPHLPAPI!InternalGetTcpTable2 函数调用 IPHLPAPI!GetTcpTable2 函数来实现功能,而 IPHLPAPI!GetTcpTable2 函数是对 IPHLPAPI!GetTcpTableIntenal 函数的封装
端口复用也是木马常用的端口隐藏方法。端口复用是重复使用系统中已经开启的端口,从而绕过防火墙的拦截。在应用程序进行网络通信时,需要将本地化和特定的端口绑定到一个套接字上,然后用该套接字进行通信。当系统接收到数据包时,会根据包中的端口号找到特定的应用程序进行转发。当木马程序使用端口复用技术接收到数据包后,系统先将该数据包下发给木马程序判断模块,由其根据数据包的特征结构判断该数据包应该先转发给木马端口复用模块还是直接转发给应用程序