恶意代码分析-Rootkit初探

TDL3

TDL3可将其代码加载到Windows内核中,因此微软在64位Windows系统上引入核完整性度量来使其无效。目前TDL3已经被TDL4取代,后者有大量逃逸和反取证功能,还用Bootkit突破64位系统的内核模式代码签名机制。

TDL3 Rootkit于2010年被首次发现,是当时开发的最复杂的恶意软件之一。该恶意软件家族被称为TDSS、Olmarik、Alureon等。TDL3通过子公司DogmaMillions和GangstaBucks以按安装付费PPI商业模式进行分发。

TDL3通过在驱动程序的二进制文件中注入恶意代码来感染加载操作系统所必需的一个引导启动驱动程序。在操作系统初始化过程早期阶段,这些引导启动驱动程序与内核映像一起加载,恶意代码控制启动过程。感染例程搜索支持核心操作系统组件的引导启动驱动程序列表,并随机选择一个作为感染目标。列表中每个条目用一个KLDR_DATA_TABLE_ENTRY结构描述,该结构由DRIVER_OBJECT结构中DriverSection字段引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

typedef struct _KLDR_DATA_TABLE_ENTRY {
LIST_ENTRY InLoadOrderLinks;//这个成员把系统所有加载(可能是停止没被卸载)已经读取到内存中 我们关心第一个 我们要遍历链表 双链表 不管中间哪个节点都可以遍历整个链表 本驱动的驱动对象就是一个节点
LIST_ENTRY InMemoryOrderLinks;//系统已经启动 没有被初始化 没有调用DriverEntry这个历程的时候 通过这个链表进程串接起来
LIST_ENTRY InInitializationOrderLinks;//已经调用DriverEntry这个函数的所有驱动程序
PVOID DllBase;
PVOID EntryPoint;//驱动的进入点 DriverEntry
ULONG SizeOfImage;
UNICODE_STRING FullDllName;//驱动的满路径
UNICODE_STRING BaseDllName;//不带路径的驱动名字
ULONG Flags;
USHORT LoadCount;
USHORT TlsIndex;
union {
LIST_ENTRY HashLinks;
struct {
PVOID SectionPointer;
ULONG CheckSum;
};
};
union {
struct {
ULONG TimeDateStamp;
};
struct {
PVOID LoadedImports;
};
};
} KLDR_DATA_TABLE_ENTRY, *PKLDR_DATA_TABLE_ENTRY;

一旦选择一个目标驱动程序,则用一个恶意加载程序覆盖他的资源节区.rsrc的前几百字节,修改驱动程序在内存中的映像,该加载程序在启动时从硬盘上加载它需要的其余恶意代码。资源节区内容仍是驱动程序正确运行所需要的, TDL3将其先存储在rsrc.dat文件中,该文件位于由恶意软件维护的隐藏文件系统中。修改资源节区后,将PE头的入口点函数改为指向资源节区。

TDL3拦截了读写I/O请求发送到硬盘的存储/微型端口驱动程序,即为硬件存储媒体驱动发新的最底层存储驱动程序栈。例如在文件系统驱动程序ntfs.sys、fastfat.sys下,有存储类驱动程序disk.sys,其下又有存储端口驱动程序scsiport.sys、storport.sys等。存储端口驱动包含SCSI微型端口、Storport微型端口、ATA微型端口、IDE微型端口等,这些是TDL3的内核钩子的目标。

为了实现上述挂接技术,TDL3先获得一个指向对应设备对象的微型端口驱动对象指针。特别地,钩子代码尝试打开“\??\PhysicalDriveXX”的句柄时(XX表示硬盘驱动编码),该字符串实际指向设备对象“\Device\HardDisk0\DR0”的符号链接,该对象由存储类驱动程序创建。从设备对象向下移动设备展,可在最底部找到微型端口存储设备对象,之后即可跟踪记录的DEVICE_OBJECT结构中DriverObject字段,获取指向驱动对象的指针。此时恶意软件有了挂接存储驱动程序栈所需的所有信息。

接下来TDL3创建一个新的恶意驱动对象,并用指向新创建字段的指针覆盖微型端口驱动对象的DriverObject字段,这将允许恶意软件拦截对底层硬盘驱动器的读写请求,因为所有处理程序的地址都在相关驱动程序对象结构中指定DRIVER_OBJECT结构的MajorFunction数组。恶意主处理器拦截了IOCTL_ATA_PASS_THROUGH_DIRECT和IOCTL_ATA_PASS_THROUGH来监控和修改硬盘的读写请求。

TDL3为止包含受保护数据的硬盘驱动器扇区被Windows工具读取或被Windows文件系统意外覆盖,当遇到读操作时,TDL3在I/O操作完成时返回缓冲区的零输出,在写数据请求时跳过整个读请求。TDL3的修改不涉及任何经常受保护和监视的区域,包括系统模块、SSDT、GDT和IDT等。

TDL3是第一个将配置文件和有效负载存储再目标系统隐藏的加密存储区域的恶意软件系统,不依赖于操作系统提供的文件系统服务,已被Rovnix Bootkit、ZeroAccess、Avatar、Gapz等威胁所采用和适应。但恶意软件能使用传统Windows API接口如CreateFileReadFileWriteFileCloseHandle访问隐藏文件系统内容,简化恶意软件有效负载的开发。TDL3在操作系统文件系统未占用的扇区中分配硬盘隐藏文件系统的映像,从末端向起始段增长,这意味着足够大会覆盖用户的文件系统数据。映像分为1024字节的块,在硬盘驱动器末尾的第一个块包含一个文件表,其条目描述文件系统中包含的文件,信息有:文件名(限制16字符,包括末尾空字符)、文件大小、实际文件偏移量、文件系统创建时间。

文件系统内容按照每个块使用自定义加密算法加密,如RC4等,使用与每个块对应的第一个扇区的逻辑块地址LBA作为密钥。

用户模式下,有效负载打开“\Device\XXXXXXXX\YYYYYYYY”设备对象的句柄来访问隐藏存储,其中XXXXXXXX和YYYYYYYY是随机生成的十六进制数字。

Festi

Win32/Festi僵尸网络是发现的最先进的垃圾邮件和分布式拒绝服务DDoS僵尸网络。Festi拥有强大的垃圾邮件发送、DDoS和Rootkit功能,使得它可连接到文件系统和系统注册表而不被发现。Festi Rookit通过PPI方案分发。Dropper是一种特殊的感染类型,它携带有效负载到受害者系统内部,负载经常被压缩、加密和混淆。一旦执行,Dropper从它的映像中提取有效负载,并安装到受害系统上。与Downloader不同,后者自身不携带有效负载,而是从远程服务器下载。

Festi僵尸网络只针对Windows x86平台,其内核模式驱动程序有俩主要职责:从命令控制C&C服务器请求配置信息,以插件形式下载和执行恶意模块。每个插件用于特定任务,如对执行网络资源执行DDoS攻击,或向C&C服务器提供电子邮件列表发送垃圾邮件。插件不存储在系统硬盘驱动器上,而是易失性内存中,当受感染计算机关闭或重启时,插件从系统内存中消失。唯一存储在硬盘上的是主内核模式驱动程序,它不包含有效负载和攻击目标。

为使Festi能够与C&C服务器通信,Festi提供三种预定义的配置信息:C&C服务器域名、加密bot僵尸主机和C&C之间传输数据的密钥、bot版本信息,这些被硬编码在驱动程序的.cdata可写节,用一个4字节密钥进行异或加密。部分Festi配置数据的加密字符串:

字符串 用途
\Device\Tcp、\Device\Udp 用于通过网络发送和接收数据的设备对象名
\REGISTRY\MACHINE\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\StandardProfile\GloballyOpenPorts\List 带有Windows防火墙参数的注册表项的路径,被恶意软件用于禁用本地防火墙
ZwDeleteFile、ZwQueryInformationFIle、ZwLoadDriver、KdDebuggerEnabled、ZwDeleteValueKey、ZwLoadDriver 恶意软件使用的系统服务名

Festi具有面向对象的架构,主要组件包括:内存管理器负责分配和释放内存缓冲区、网络套接字通过网络发送和接收数据、C&C协议解析器解析C&C消息并执行接收到的命令、插件管理器管理下载插件。其中内存管理器是其他组件的中心组件,网络套接字和插件管理器都与C&C协议解析器交互。

为了有效地管理下载的插件,Festi维护了一个指针数组,该指针指向一个结构,每个结构对应内存中一个插件。

1
2
3
4
5
6
7
8
9
10
struct PLUGIN_INTERFACE {
PVOID Initialize; //初始化插件
PVOID Release; //释放插件执行清除操作
PVOID GetVersionInfo_1; //获取插件版本信息
PVOID GetVersionInfo_2;
PVOID WriteIntoTcpStream; //向TCP流中写入具体插件信息
PVOID ReadFromTcpStream; //从TCP流读取特定于插件的信息并解析数据
PVOID Reserved_1;
PVOID Reserved_2;
};

安装时主恶意内核模式驱动程序实现两个内置插件:配置信息管理器和僵尸主机插件管理器。配置信息管理器插件请求配置信息并定期连接到C&C服务器以下载插件,两个连续请求之间的延迟由C&C服务器决定,以规避杀软用来检测感染的静态模式。僵尸主机插件管理器维护下载的插件数组,从C&C服务器接收远程命令,加载和卸载以压缩形式交付到系统特定插件。

每个插件都有默认入口点DriverEntry,并导出例程CreateModuleDeleteModuleCreateModule例程在插件初始化时执行,返回一个指向PLUGIN_INTERFACE结构的指针。DeleteModule例程在卸载插件并释放分配的所有资源时执行。

恶意软件先将插件解压到内存缓冲区中,再将其映射到内核模式地址空间中,作为一个PE映像。插件管理器初始化导入地址表IAT并将其重定位到映射的映像,在该算法中Festi仿真了一个典型操作系统的运行时加载器和操作系统模块的动态链接器。根据插件被加载还是被卸载,插件管理器执行CreateModuleDeleteModule例程。若插件被加载,则插件管理器获取插件ID和版本信息,将其注册到PLUGIN_INTERFACE结构。若插件被卸载,则释放之前分配给插件映像的所有内存。

Festi检测它是否在VMware虚拟机中运行,以规避沙箱和自动恶意软件分析环境,用以下代码获取现有VMware软件版本:

1
2
3
4
5
mov eax, 'VMXh'
mov ebx, 0
mov ecx, 0Ah
mov edx, 'VX'
in eax, dx

当Festi检测到虚拟环境存在时,仍像在物理机上一样继续执行。当其从C&C服务器请求插件时会提交信息以指示是否在虚拟环境中执行。如果是,C&C服务器可能不会返回任何插件。Festi还检查系统上是否存在网络流量监控软件,如寻找内核模式驱动程序npf.sys网络包过滤器,该驱动属于WIndows包捕获库WinPcap。

Festi检查从操作系统内核映像导出的KdDebuggerEnable变量来检查系统中是否存在内核调试器,若系统附加了系统调试器,则该变量为TRUE,否则FALSE。Festi定期将调试寄存器DR0~DR3调零来抵消系统调试器,这些寄存器用于存储断点地址,删除硬件断点会妨碍调试过程。

1
2
3
4
5
6
7
char _thiscall ProtoHandler_1(STRUCT_4_4 *this, PKEVENT a1) {
__writedr(0,0);
__writedr(1u,0);
__writedr(2u,0);
__writedr(3u,0);
return _ProtoHandler(&this->struct43,a1);
};

为了保护和隐藏存储在硬盘上的恶意内核模式驱动程序的映像,Festi挂在文件系统驱动程序,拦截和修改发送到文件系统驱动程序的所有请求,以排除它存在的证据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
NTSTATUS __stdcall SetHookOnSystemRoot(PDRIVER_OBJECT DriverObject,int** HookParams){
RtlInitUnicodeString(&DestinationString,L"\\SystemRoot");
ObjectAttributes.Length=24;
ObjectAttributes.RootDirectory=0;
ObjectAttributes.Attributes=64;
ObjectAttributes.ObjectName=&DestinationString;
ObjectAttributes.SecurityDescriptor=0;
ObjectAttributes.SecurityQualityOfService=0;
NTSTATUS Status=IoCreateFile(&hSystemRoot,0x80000000,&ObjectAttributes,&IoStatusBlock,0,0,3u,1u,1u,0,0,0,0,0x100u);
if(Status<0)
return Status;
Status=ObReferenceObjectByHandle(hSystemRoot,1u,0,0,&SystemRootFileObject,0);
if(Status<0)
return Status;
PDEVICE_OBJECT TargetDevice=IoGetRelatedDeviceObject(SystemRootFileObject);
if(!_TargetDevice)
return STATUS_UNSUCCESSFUL;
ObReferenceObject(TargetDevice);
Status=IoCreateDevice(DriverObject,0xCu,0,TargetDev->DeviceType,TargetDevice->Characteristics,0,&SourceDevice);
if(Status<0)
return Status;
PDEVICE_OBJECT DeviceAttachedTo=IoAttachDeviceToDeviceStack(SourceDevice,TargetDevice);
if(!DeviceAttachedTo){
IoDeleteDevice(SourceDevice);
return STATUS_uNSUCCESSFUL;
};
return STATUS_SUCCESS;
};

第9行恶意软件尝试获取一个特定系统文件SystemRoot的句柄,该文件对应Windows安装目录。第12行获得一个指向FILE_OBJECT的指针,对应SystemRoot的句柄。FILE_OBJECT是操作系统用来管理设备对象访问的特殊数据结构,包含一个指向相关设备对象的指针。第15行获得指向DEVICE_OBJECT的指针,创建一个新设备对象。第22行将其附加到获得的设备对象指针,这意味着对文件系统的IRP被重新路由到恶意软件,允许Festi改变请求和向文件系统驱动返回数据来隐藏自己。

Festi用IRP_MJ_DIRECTORY_CONTROL请求代码来监视IRP,该请求代码用于查询目录内容,监视与恶意软件驱动程序所在位置相关的查询。

位于HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services中注册表项包含Festi驱动程序类型和文件系统上驱动程序映像路径。为隐藏它,Festi挂在ZwEnumerateKey系统服务,修改SSDT处该地址替换为钩子地址。钩子监视发送到上述注册表项的请求,修改自建列表以排除与其驱动程序相对应的条目。Festi还先执行系统例程IoRegisterShutdownNotification,以便在系统关机时接收关闭通知。当注册表被杀软发现并在关机期间删除,Festi检查关闭通知处理程序,查看系统中相应注册表项是否还存在,不存在则回复,保证它在重启过程中一直存在。

Festi与C&C服务器通信,有些服务器用于发送垃圾邮件,有些执行DDoS攻击。Festi通信协议由两个阶段组成:初始化阶段和工作阶段。

在初始化阶段,恶意软件获得C&C服务器IP地址,服务器域名存储在僵尸主机的二进制文件中。恶意软件手动构造DNS请求包来解析C&C服务器域名,并发送到8.8.8.8:53或8.8.4.4:53,这两台主机都是谷歌DNS服务器。作为回应,Festi接收一个IP地址。若不手动解析域名,Festi不得不依赖当地ISP的DNS服务器来解析,这使得ISP可能修改DNS信息来阻止对&C服务器的访问。手动解析域名绕过了ISP的DNS基础设施,当执法机构发布组织这些域名的搜查令时,这删除更加困难。

在工作阶段, Festi向C&C服务器请求信息,通信通过TCP执行,发送的网络包请求布局如下:

消息头 消息尾
消息头 插件1数据 插件2数据 后面字节

消息头由配置管理器插件生成,包含:Festi版本信息、是否有系统调试器、是否有虚拟化软件VMware、是否有网络流量监控软件WinPcap、操作系统版本信息。插件数据由结构数组组成:

含义
标记Tag 一个16位整数,指定标记后面值的类型
值Value 数据,可能是字节、字、双字、以空字符结尾的字符串、二进制数组等形式
项Term 条目结束字0xABCD

数据发送前通过一种加密算法进行模糊处理,伪代码为:

1
2
3
4
key=(0x17,0xFB,0x71,0x5C)
def decr_data(data):
for ix in xrange(len(data)):
data[ix]^=key[ix%4]

安全软件监控设备对象访问的常见方式是连接ZwCreateFile系统服务处理程序,拦截所有打开设备的尝试,并附加到\Device\Tcp或\Device\Udp以拦截发送所有IRP请求。Festi规避了这两种技术。

一方面,Festi没有使用ZwCreateFile而是实现了自己的系统服务,其功能几乎与原初系统服务相同。首先用ObCreateObject创建文件对象,初始化所创建文件对象的安全属性,用ObInsertObject将创建的文件对象插入FILE_OBJECT类型列表中,创建IRP请求且主函数代码设为IRP_MJ_CREATE,将创建的IRP请求直接发送到tcpip.sys系统驱动程序,不经过过滤驱动程序栈。

另一方面,Festi直接获取\Device\Tcp或\Device\Udp设备对象指针:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
RtlInitUnicodeString(&DriverName,L"\\Driver\\Tcpip");
RtlInitUnicodeString(&tcp_name,L"\\Device\\Tcp");
RtlInitUnicodeString(&udp_name,L"\\Device\\Udp");
if(!ObReferenceObjectByName(&DriverName,64,0,0x1F01FF,IoDriverObjectType,0,0,&TcpipDriver)){
DevObj=TcpipDriver->DeviceObject;
while(DevObj){
if(!ObQueryNameString(DevObj,&Objname,256,&v8)){
if(RtlCompareUnicodeString(&tcp_name,&Objname,1u)){
if(!RtlCompareUnicodeString(&udp_name,&Objname,1u)){
ObfReferenceObject(DevObj);
this->DeviceUdp=DevObj;
};
}
else{
ObfReferenceObject(DevObj);
this->DeviceTcp=Devobj;
};
};
DevObj=DevObj->NextDevice;
};
ObfDereferenceObject(TcpipDriver);
};

当Festi的主C&C服务器被注销后,它用域名生成算法DGA作为一种备用机制,所有生成的域名都是伪随机的。僵尸网络控制者仍可通过回滚到DGA上重新获得对僵尸网络的控制。

日期
07/11/2012 fzcbihskf.com
08/11/2012 pzcaihszf.com
09/11/2012 dzcxifsff.com
10/11/2012 azcgnfsmf.com
11/11/2012 bzcfnfsif.com

Festi有三种插件的类型:BotSpam.sys滥发电子邮件、BotDos.sys执行DDoS攻击、BotSocks.sys提供代理服务,不同C&C服务器提供不同类型的插件。

对于BotSpam.sys的工作流为:首先BotSpam.sys向C&C服务器发起一个加密连接,C&C服务器先返回发送器参数和电子邮件地址列表,再返回SMTP服务器列表和垃圾邮件消息模板。然后把垃圾信件分发给收件人,同时向C&C服务器报告状态,并请求更新电子邮件列表和垃圾邮件模板。之后,该插件扫描来自SMTP服务器的响应来检查发送的电子邮件的状态字符串,若找到没有指定地址收件人、没有收到电子邮件、收到的邮件被归为垃圾邮件等,则终止与当前SMTP服务器的会话,并获取列表中下一个地址。该措施避免被SMTP服务器列入垃圾邮件发送者黑名单,阻止恶意软件发送更多垃圾邮件。

对于BotDos.sys插件,它支持针对远程主机的TCP Flood、UDP Fload、DNS Flood、HTTP Flood等DDoS攻击类型,具体类型取决于从C&C接收到的配置数据。

对于BotSocks.sys插件,它通过TCP和UDP实现SOCKS服务器,为供给制提供远程代理服务,允许攻击者通过受感染的机器连接到远程服务器以实现匿名。该插件不适用任何反代机制来绕过NAT,使得多台计算机共享一个公网IP。端口号是随机的,范围4000~65536。该插件还尝试绕过Winodws防火墙,要不被阻止打开端口。其向注册表项SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\Domain-Profile\GloballyOpenPorts\List下添加两个子密钥,以便启用来自任何目的地的CP和UDP连接。

Rootkit感染

Rootkit必需在操作系统的特定位置拦截控制,以防止反Rootkit工具启动或初始化,可拦截的操作系统机制主要有3种。

第一种方法是通过事件通知回调来拦截系统事件。例如用CmRegisterCallbackEx例程注册一个回调函数后,当有人在系统注册表项上执行操作时,该例程将执行。每个内核模式驱动程序在系统注册表中的HEKY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services键下都有一个专用条目,指定驱动程序的名称、类型、映像在磁盘上的位置以及加载时间(按需加载、引导时加载、系统初始化时加载等),若该条目被删除则操作系统无法加载内核模式驱动程序。另外Rootkit还经常滥用PsSetLoadImageNotifyRoutine例程,向目标进程的用户模式地址注入恶意负载。

第二种方法时拦截系统调用 。文件子系统与I/O子系统紧密集成,驱动程序分为三类,在存储设备驱动栈上从下到上分别为:存储设备驱动、存储卷驱动和文件系统驱动。存储设备驱动是与特定设备(端口、总线和驱动器)的控制器交互的底层驱动程序,大多数存储设备驱动是即插即用PnP的,由PnP管理器加载和控制。存储卷驱动时控制存储设备分区上的卷抽象的中层驱动程序,为了与磁盘子系统较低层交互,这些驱动创建一个物理设备对象PDO来表示每个分区。当一个文件系统挂载在一个分区上时,文件系统驱动创建一个卷设备对象VDO。文件系统驱动实现特定文件系统(FAT32、NTFS、CDFS等),还创建一对对象VDO和控制设备对象CDO,他们表示给定的文件系统,这些CDO设备名称为\Device\Ntfs。

在存储设备驱动程序层,有SCSI适配器(上层)和磁盘设备对象(下层),他们由三个不同的驱动程序创建和管理:PCI总线驱动,枚举和发现PCI总线上可用的存储适配器;SCSI端口/微型端口驱动,初始化和控制枚举的SCSI存储适配器;磁盘类驱动,控制附加到SCSI存储适配器的磁盘设备。

在存储卷驱动层,磁盘分区1设备对象PDO(上层)和磁盘分区0设备对象(下层),他们都由磁盘类驱动创建和管理的。其中分区0为整个原始磁盘并始终存在,无论磁盘是否分区。分区1为磁盘设备上第一个分区,还有可能更多。分区1必需公开给用户,以便他们能存储和访问存储在磁盘设备上的文件。要公开分区1则需要文件系统驱动在存储卷驱动层顶部创建一个卷设备对象VDO,操作系统用文件系统控制设备对象CDO来控制文件系统驱动,进而控制该创建的卷设备对象VDO。

Rootkit一般在顶层文件系统驱动层拦截文件操作,这样可看到应用程序的所有操作,而不必查找和解析程序员看不见的文件系统结构。Rootkit有三种拦截方法:将过滤驱动附加到目标设备的驱动程序栈;替换驱动程序描述符结构中指向IRP或FastIO处理函数的指针;替换这些IRP或FastIO驱动程序函数的代码。Festi采用第一种方法,TDL4、Olmasco、Rovnix Bootkit采用第二种方法,Gapz Bootkit采用第三种方法。

为了执行I/O操作,IRP要遍历整个存储设备栈,从最顶层设备对象一直到底层。这时若用FastIO方法,数据直接在用户模式缓冲区和系统缓存之间传输,绕过文件系统和存储驱动程序栈,使得对缓存文件I/O操作更快。