開啟被獨佔的檔案方法(二) -- 修改控制代碼訪問許可權
修改控制代碼訪問許可權
所有被佔用的檔案通常都可以用讀屬性(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;
p; 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
&n
bsp; #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程式中(只是要經過長期的測試和除錯!)。因為這種方法有不能勝任的情形,我們轉入下一種方法。