Object Hook是通过挂钩OBJECT_HEADER其中一些字段来实现的。在Windows中通过对象管理器来管理所有对象,可以使用WinObj来观察一下WinXP SP3下的对象类型:
Windows中包含三种对象:
- 执行体对象
- 内核对象
- GDI/User对象
这里我们挂钩的就是执行体对象。
这里实验机器是WinXP SP3, 首先来看一下进程对象的数据结构:
然后随便找一个进程对象,寻找他的对象结构体,比如之前打开的WinObj进程,
通过!object命令查找到了其进程对象的基本信息,并找到了对应ObjectHeader位置,接下去看一下其头部有什么内容:
看到了吧,这就是WinObj进程的头部,看到其中的Type字段,这就是那张简图中的对象类型,我们最终是要找到其中的方法并对其挂钩。
上图就是找到最终方法的步骤,其中重要字段我已经框出。这里演示挂钩ParseProcedure方法。
在这里可以清晰看到该方法拥有的形参类型,由此我定义如下函数指针:
Object Hook挂钩的整体步骤如下:
- 通过ZwOpenFile打开具体某个文件并获取对应文件句柄
- 通过ObReferenceObjectByHandle获取文件句柄对应的文件对象指针
- 通过文件对象指针获取文件对象的头部,即OBJECT_HEADER
- 通过对象头部获取其对象类型字段
- 通过类型字段替换其中的ParseProcedure字段为我们自己的函数
- 挂钩完成
由于关于对象的几个关键数据结构在wdk头文件中都是没有定义的,所以需要自己定义,网上有很多关于这方面的介绍,实在不行,可以通过windbg查看其数据成员:
各个数据成员在windbg下都是可以清楚看到的。给出如下定义:
typedef struct _OBJECT_TYPE_INITIALIZER {
USHORT Length;
BOOLEAN UseDefaultObject;
BOOLEAN CaseInsensitive;
ULONG InvalidAttributes;
GENERIC_MAPPING GenericMapping;
ULONG ValidAccessMask;
BOOLEAN SecurityRequired;
BOOLEAN MaintainHandleCount;
BOOLEAN MaintainTypeList;
POOL_TYPE PoolType;
ULONG DefaultPagedPoolCharge;
ULONG DefaultNonPagedPoolCharge;
PVOID DumpProcedure;
PVOID OpenProcedure;
PVOID CloseProcedure;
PVOID DeleteProcedure;
PVOID ParseProcedure;
PVOID SecurityProcedure;
PVOID QueryNameProcedure;
PVOID OkayToCloseProcedure;
} OBJECT_TYPE_INITIALIZER, *POBJECT_TYPE_INITIALIZER;
typedef struct _OBJECT_TYPE {
ERESOURCE Mutex;
LIST_ENTRY TypeList;
UNICODE_STRING Name;
PVOID DefaultObject;
ULONG Index;
ULONG TotalNumberOfObjects;
ULONG TotalNumberOfHandles;
ULONG HighWaterNumberOfObjects;
ULONG HighWaterNumberOfHandles;
OBJECT_TYPE_INITIALIZER TypeInfo;
#ifdef POOL_TAGGING
ULONG Key;
#endif
} OBJECT_TYPE, *POBJECT_TYPE;
typedef struct _OBJECT_CREATE_INFORMATION {
ULONG Attributes;
HANDLE RootDirectory;
PVOID ParseContext;
KPROCESSOR_MODE ProbeMode;
ULONG PagedPoolCharge;
ULONG NonPagedPoolCharge;
ULONG SecurityDescriptorCharge;
PSECURITY_DESCRIPTOR SecurityDescriptor;
PSECURITY_QUALITY_OF_SERVICE SecurityQos;
SECURITY_QUALITY_OF_SERVICE SecurityQualityOfService;
} OBJECT_CREATE_INFORMATION, *POBJECT_CREATE_INFORMATION;
typedef struct _OBJECT_HEADER {
LONG PointerCount;
union {
LONG HandleCount;
PSINGLE_LIST_ENTRY SEntry;
};
POBJECT_TYPE Type;
UCHAR NameInfoOffset;
UCHAR HandleInfoOffset;
UCHAR QuotaInfoOffset;
UCHAR Flags;
union
{
POBJECT_CREATE_INFORMATION ObjectCreateInfo;
PVOID QuotaBlockCharged;
};
PSECURITY_DESCRIPTOR SecurityDescriptor;
QUAD Body;
} OBJECT_HEADER, *POBJECT_HEADER;
接下去通过调试来看一下ParseProcedure字段到底调用了哪一个函数:
看到了吧,其最终调用的实际上就是IopParseFile。
最后贴出完整代码:
#include <ntddk.h>
#define OBJECT_TO_OBJECT_HEADER(o) \
CONTAINING_RECORD((o), OBJECT_HEADER, Body)
typedef struct _OBJECT_TYPE_INITIALIZER {
USHORT Length;
BOOLEAN UseDefaultObject;
BOOLEAN CaseInsensitive;
ULONG InvalidAttributes;
GENERIC_MAPPING GenericMapping;
ULONG ValidAccessMask;
BOOLEAN SecurityRequired;
BOOLEAN MaintainHandleCount;
BOOLEAN MaintainTypeList;
POOL_TYPE PoolType;
ULONG DefaultPagedPoolCharge;
ULONG DefaultNonPagedPoolCharge;
PVOID DumpProcedure;
PVOID OpenProcedure;
PVOID CloseProcedure;
PVOID DeleteProcedure;
PVOID ParseProcedure;
PVOID SecurityProcedure;
PVOID QueryNameProcedure;
PVOID OkayToCloseProcedure;
} OBJECT_TYPE_INITIALIZER, *POBJECT_TYPE_INITIALIZER;
typedef struct _OBJECT_TYPE {
ERESOURCE Mutex;
LIST_ENTRY TypeList;
UNICODE_STRING Name;
PVOID DefaultObject;
ULONG Index;
ULONG TotalNumberOfObjects;
ULONG TotalNumberOfHandles;
ULONG HighWaterNumberOfObjects;
ULONG HighWaterNumberOfHandles;
OBJECT_TYPE_INITIALIZER TypeInfo;
#ifdef POOL_TAGGING
ULONG Key;
#endif
} OBJECT_TYPE, *POBJECT_TYPE;
typedef struct _OBJECT_CREATE_INFORMATION {
ULONG Attributes;
HANDLE RootDirectory;
PVOID ParseContext;
KPROCESSOR_MODE ProbeMode;
ULONG PagedPoolCharge;
ULONG NonPagedPoolCharge;
ULONG SecurityDescriptorCharge;
PSECURITY_DESCRIPTOR SecurityDescriptor;
PSECURITY_QUALITY_OF_SERVICE SecurityQos;
SECURITY_QUALITY_OF_SERVICE SecurityQualityOfService;
} OBJECT_CREATE_INFORMATION, *POBJECT_CREATE_INFORMATION;
typedef struct _OBJECT_HEADER {
LONG PointerCount;
union {
LONG HandleCount;
PSINGLE_LIST_ENTRY SEntry;
};
POBJECT_TYPE Type;
UCHAR NameInfoOffset;
UCHAR HandleInfoOffset;
UCHAR QuotaInfoOffset;
UCHAR Flags;
union
{
POBJECT_CREATE_INFORMATION ObjectCreateInfo;
PVOID QuotaBlockCharged;
};
PSECURITY_DESCRIPTOR SecurityDescriptor;
QUAD Body;
} OBJECT_HEADER, *POBJECT_HEADER;
POBJECT_TYPE pType = NULL;
POBJECT_HEADER pObjectHdr = NULL;
PVOID pOldParseProcedure = NULL;
VOID
Unload(
IN PDRIVER_OBJECT pDriverObject
)
{
KdPrint(("卸载驱动!\n"));
}
typedef NTSTATUS(NTAPI *PFNPARSEPROCEDURE)(
IN PVOID ParseObject,
IN PVOID ObjectType,
IN OUT PACCESS_STATE AccessState,
IN KPROCESSOR_MODE AccessMode,
IN ULONG Attributes,
IN OUT PUNICODE_STRING CompleteName,
IN OUT PUNICODE_STRING RemainingName,
IN OUT PVOID Context OPTIONAL,
IN PSECURITY_QUALITY_OF_SERVICE SecurityQos OPTIONAL,
OUT PVOID *Object
);
NTSTATUS NewParseProcedure(
IN PVOID ParseObject,
IN PVOID ObjectType,
IN OUT PACCESS_STATE AccessState,
IN KPROCESSOR_MODE AccessMode,
IN ULONG Attributes,
IN OUT PUNICODE_STRING CompleteName,
IN OUT PUNICODE_STRING RemainingName,
IN OUT PVOID Context OPTIONAL,
IN PSECURITY_QUALITY_OF_SERVICE SecurityQos OPTIONAL,
OUT PVOID *Object
)
{
NTSTATUS status;
KdPrint(("Object is hooked!\n"));
PFNPARSEPROCEDURE pfnParseProcedure = NULL;
pfnParseProcedure = (PFNPARSEPROCEDURE)pOldParseProcedure;
status = pfnParseProcedure(ParseObject, ObjectType, AccessState, AccessMode,
Attributes, CompleteName, RemainingName, Context, SecurityQos, Object);
return(status);
}
NTSTATUS
ObjectHook()
{
NTSTATUS status;
HANDLE hFile;
UNICODE_STRING ustrName;
OBJECT_ATTRIBUTES objAttr;
IO_STATUS_BLOCK ioStatusBlock;
PVOID pObject = NULL;
// 其中xxxx是文件
RtlInitUnicodeString(&ustrName, L"\\Device\\HarddiskVolume1\\1.txt");
InitializeObjectAttributes(&objAttr, &ustrName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, 0, NULL);
// 打开C盘下的文件
status = ZwOpenFile(&hFile, GENERIC_ALL, &objAttr, &ioStatusBlock, 0, FILE_NON_DIRECTORY_FILE);
if (!NT_SUCCESS(status))
{
return(status);
}
// 获取1.txt文本文件的对象指针
status = ObReferenceObjectByHandle(hFile, GENERIC_ALL, NULL, KernelMode, &pObject, NULL);
if (!NT_SUCCESS(status))
{
return(status);
}
// 获取指向_OBJECT_HEADER对象头部指针
pObjectHdr = OBJECT_TO_OBJECT_HEADER(pObject);
// 获取其中的对象类型
pType = pObjectHdr->Type;
// 保存打开创建方面的例程
pOldParseProcedure = pType->TypeInfo.ParseProcedure;
// 关闭写保护
__asm
{
cli
mov eax, cr0
and eax, not 10000h
mov cr0, eax
}
// 进行HOOK操作
pType->TypeInfo.ParseProcedure = NewParseProcedure;
// 打开写保护
__asm
{
mov eax, cr0
or eax, 10000h
mov cr0, eax
sti
}
// 关闭文件
status = ZwClose(hFile);
return(status);
}
NTSTATUS
DriverEntry(
IN PDRIVER_OBJECT pDriverObject,
IN PUNICODE_STRING pRegistryPath
)
{
KdPrint(("加载驱动!\n"));
pDriverObject->DriverUnload = Unload;
ObjectHook();
return(STATUS_SUCCESS);
}
最终的实现效果:
对了, 可能有人会对这个产生困惑,这是一个路径名。
在内核态下使用路径名都是\Device\开头的,这些叫做设备名,在用户态下的想要访问驱动设备用的都是符号链接名, 一般调用IoCreateSymbolicLink将符号链接名和设备名关联起来。可以想象成符号链接名是设备名的软链接,这样应该更好理解。符号链接名也就是我们在用户态下看到的名称。符号链接名一般采用\??\开头的格式。也就是说如果某个驱动设备希望被用户态进程访问到,就必须提供一个符号链接名以供进程访问。
实际上我们在Windows中看到的盘符都是一个个逻辑设备。其真实的物理设备就是硬盘。Windows将其抽象成了很多个逻辑设备。比如C盘,就是其符号链接名是\??\C:
来看看起真实的设备名:
看见了吧,就是\Device\HarddiskVolume1。由于我们的驱动是在内核态运行的。所以必须使用真实的设备名, 所以上面那个路径实际上就是C:\\1.txt。
(完)