打开被独占的文件方法(二) -- 修改句柄访问权限

打开被独占的文件方法(二) -- 修改句柄访问权限
2010年06月08日 星期二 11:40

修改句柄访问权限
所有被占用的文件通常都可以用读属性(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程序中(只是要经过长期的测试和调试!)。因为这种方法有不能胜任的情形,我们转入下一种方法。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值