首页 > 其他分享 >驱动开发:内核实现SSDT挂钩与摘钩

驱动开发:内核实现SSDT挂钩与摘钩

时间:2023-06-05 09:12:34浏览次数:37  
标签:PVOID ULONG pNewAddress 挂钩 pMdl 内核 NULL OPTIONAL SSDT

在前面的文章《驱动开发:内核解析PE结构导出表》中我们封装了两个函数KernelMapFile()函数可用来读取内核文件,GetAddressFromFunction()函数可用来在导出表中寻找指定函数的导出地址,本章将以此为基础实现对特定SSDT函数的Hook挂钩操作,与《驱动开发:内核层InlineHook挂钩函数》所使用的挂钩技术基本一致,不同点是前者使用了CR3的方式改写内存,而今天所讲的是通过MDL映射实现,此外前者挂钩中所取到的地址是通过GetProcessAddress()取到的动态地址,而今天所使用的方式是通过读取导出表寻找。

挂钩的目的就是要为特定函数增加功能,挂钩的实现方式无非就是替换原函数地址,我们以内核函数ZwQueryDirectoryFile()为例,ZwQueryDirectoryFile例程返回给定文件句柄指定的目录中文件的各种信息,其微软定义如下;

NTSYSAPI NTSTATUS ZwQueryDirectoryFile(
  [in]           HANDLE                 FileHandle,
  [in, optional] HANDLE                 Event,
  [in, optional] PIO_APC_ROUTINE        ApcRoutine,
  [in, optional] PVOID                  ApcContext,
  [out]          PIO_STATUS_BLOCK       IoStatusBlock,
  [out]          PVOID                  FileInformation,
  [in]           ULONG                  Length,
  [in]           FILE_INFORMATION_CLASS FileInformationClass,
  [in]           BOOLEAN                ReturnSingleEntry,
  [in, optional] PUNICODE_STRING        FileName,
  [in]           BOOLEAN                RestartScan
);

如果需要Hook一个函数则你需要去微软官方得到该函数的具体声明部分包括其返回值,而Hook的目的只是为函数增加或处理新功能,则在执行完自定义函数后一定要跳回到原始函数上,此时定义一个typedef_ZwQueryDirectoryFile函数指针在调用结束后即可很容易的跳转回原函数上,保证流程被正确执行,如果需要Hook其他函数其编写模板也是如下所示;

// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]

// 保存原函数地址
PVOID gOldFunctionAddress = NULL;

// Hook后被替换的新函数
NTSTATUS MyZwQueryDirectoryFile(
	IN HANDLE               FileHandle,
	IN HANDLE               Event OPTIONAL,
	IN PIO_APC_ROUTINE      ApcRoutine OPTIONAL,
	IN PVOID                ApcContext OPTIONAL,
	OUT PIO_STATUS_BLOCK    IoStatusBlock,
	OUT PVOID               FileInformation,
	IN ULONG                Length,
	IN FILE_INFORMATION_CLASS FileInformationClass,
	IN BOOLEAN              ReturnSingleEntry,
	IN PUNICODE_STRING      FileMask OPTIONAL,
	IN BOOLEAN              RestartScan
	)
{
	NTSTATUS status = STATUS_SUCCESS;

	// 定义函数指针
	typedef NTSTATUS(*typedef_ZwQueryDirectoryFile)(
		IN HANDLE               FileHandle,
		IN HANDLE               Event OPTIONAL,
		IN PIO_APC_ROUTINE      ApcRoutine OPTIONAL,
		IN PVOID                ApcContext OPTIONAL,
		OUT PIO_STATUS_BLOCK    IoStatusBlock,
		OUT PVOID               FileInformation,
		IN ULONG                Length,
		IN FILE_INFORMATION_CLASS FileInformationClass,
		IN BOOLEAN              ReturnSingleEntry,
		IN PUNICODE_STRING      FileMask OPTIONAL,
		IN BOOLEAN              RestartScan
		);

	DbgPrint("MyZwQueryDirectoryFile 自定义功能 \n");

	// 执行原函数
	status = ((typedef_ZwQueryDirectoryFile)gOldFunctionAddress)(FileHandle,
		Event,
		ApcRoutine,
		ApcContext,
		IoStatusBlock,
		FileInformation,
		Length,
		FileInformationClass,
		ReturnSingleEntry,
		FileMask,
		RestartScan);

	return status;
}

接着就是如何挂钩并让其中转到我们自己的代码流程中的问题,由于挂钩与恢复代码是一样的此处就以挂钩为例,首先调用MmCreateMdl()创建MDL,接着调用MmBuildMdlForNonPagedPool()接收一个 MDL,该MDL指定非分页虚拟内存缓冲区,并对其进行更新以描述基础物理页。调用MmMapLockedPages()将此段内存提交为锁定状态,最后就是调用RtlCopyMemory()将新函数地址写出到内存中实现替换,最后释放MDL句柄即可,这段代码如下所示,看过驱动读写篇的你一定很容易就能理解。

// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]

// 挂钩SSDT函数
BOOLEAN SSDTFunctionHook(ULONG64 FunctionAddress)
{
	PMDL pMdl = NULL;
	PVOID pNewAddress = NULL;
	ULONG ulNewFuncAddr = 0;

	gOldFunctionAddress = FunctionAddress;

	// 使用MDL修改SSDT
	pMdl = MmCreateMdl(NULL, &FunctionAddress, sizeof(ULONG));
	if (NULL == pMdl)
	{
		return FALSE;
	}

	MmBuildMdlForNonPagedPool(pMdl);

	// 锁定内存
	pNewAddress = MmMapLockedPages(pMdl, KernelMode);
	if (NULL == pNewAddress)
	{
		IoFreeMdl(pMdl);
		return FALSE;
	}

	// 写入新函数地址
	ulNewFuncAddr = (ULONG)MyZwQueryDirectoryFile;
	RtlCopyMemory(pNewAddress, &ulNewFuncAddr, sizeof(ULONG));

	// 释放
	MmUnmapLockedPages(pNewAddress, pMdl);
	IoFreeMdl(pMdl);

	return TRUE;
}

Hook核心代码如下所示,为了节约篇幅,如果您找不到程序中的核心功能,请看前面的几篇文章,这里就不在赘述了。

// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]

// 保存原函数地址
PVOID gOldFunctionAddress = NULL;

// Hook后被替换的新函数
NTSTATUS MyZwQueryDirectoryFile(
	IN HANDLE               FileHandle,
	IN HANDLE               Event OPTIONAL,
	IN PIO_APC_ROUTINE      ApcRoutine OPTIONAL,
	IN PVOID                ApcContext OPTIONAL,
	OUT PIO_STATUS_BLOCK    IoStatusBlock,
	OUT PVOID               FileInformation,
	IN ULONG                Length,
	IN FILE_INFORMATION_CLASS FileInformationClass,
	IN BOOLEAN              ReturnSingleEntry,
	IN PUNICODE_STRING      FileMask OPTIONAL,
	IN BOOLEAN              RestartScan
	)
{
	NTSTATUS status = STATUS_SUCCESS;

	// 定义函数指针
	typedef NTSTATUS(*typedef_ZwQueryDirectoryFile)(
		IN HANDLE               FileHandle,
		IN HANDLE               Event OPTIONAL,
		IN PIO_APC_ROUTINE      ApcRoutine OPTIONAL,
		IN PVOID                ApcContext OPTIONAL,
		OUT PIO_STATUS_BLOCK    IoStatusBlock,
		OUT PVOID               FileInformation,
		IN ULONG                Length,
		IN FILE_INFORMATION_CLASS FileInformationClass,
		IN BOOLEAN              ReturnSingleEntry,
		IN PUNICODE_STRING      FileMask OPTIONAL,
		IN BOOLEAN              RestartScan
		);

	DbgPrint("MyZwQueryDirectoryFile 自定义功能 \n");

	// 执行原函数
	status = ((typedef_ZwQueryDirectoryFile)gOldFunctionAddress)(FileHandle,
		Event,
		ApcRoutine,
		ApcContext,
		IoStatusBlock,
		FileInformation,
		Length,
		FileInformationClass,
		ReturnSingleEntry,
		FileMask,
		RestartScan);

	return status;
}

// 挂钩SSDT函数
BOOLEAN SSDTFunctionHook(ULONG64 FunctionAddress)
{
	PMDL pMdl = NULL;
	PVOID pNewAddress = NULL;
	ULONG ulNewFuncAddr = 0;

	gOldFunctionAddress = FunctionAddress;

	// 使用MDL修改SSDT
	pMdl = MmCreateMdl(NULL, &FunctionAddress, sizeof(ULONG));
	if (NULL == pMdl)
	{
		return FALSE;
	}

	MmBuildMdlForNonPagedPool(pMdl);

	// 锁定内存
	pNewAddress = MmMapLockedPages(pMdl, KernelMode);
	if (NULL == pNewAddress)
	{
		IoFreeMdl(pMdl);
		return FALSE;
	}

	// 写入新函数地址
	ulNewFuncAddr = (ULONG)MyZwQueryDirectoryFile;
	RtlCopyMemory(pNewAddress, &ulNewFuncAddr, sizeof(ULONG));

	// 释放
	MmUnmapLockedPages(pNewAddress, pMdl);
	IoFreeMdl(pMdl);

	return TRUE;
}

// 恢复SSDT函数
BOOLEAN SSDTFunctionUnHook(ULONG64 FunctionAddress)
{
	PMDL pMdl = NULL;
	PVOID pNewAddress = NULL;
	ULONG ulOldFuncAddr = 0;

	gOldFunctionAddress = FunctionAddress;

	// 使用MDL修改SSDT
	pMdl = MmCreateMdl(NULL, &FunctionAddress, sizeof(ULONG));
	if (NULL == pMdl)
	{
		return FALSE;
	}

	MmBuildMdlForNonPagedPool(pMdl);

	// 锁定内存
	pNewAddress = MmMapLockedPages(pMdl, KernelMode);
	if (NULL == pNewAddress)
	{
		IoFreeMdl(pMdl);
		return FALSE;
	}

	// 写入新函数地址
	ulOldFuncAddr = (ULONG)gOldFunctionAddress;
	RtlCopyMemory(pNewAddress, &ulOldFuncAddr, sizeof(ULONG));

	// 释放
	MmUnmapLockedPages(pNewAddress, pMdl);
	IoFreeMdl(pMdl);

	return TRUE;
}

// 关闭驱动
VOID UnDriver(PDRIVER_OBJECT driver)
{
	SSDTFunctionUnHook(gOldFunctionAddress);
	DbgPrint("驱动卸载 \n");
}

// 驱动入口
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	DbgPrint("hello lyshark.com \n");

	NTSTATUS status = STATUS_SUCCESS;

	HANDLE hFile = NULL;
	HANDLE hSection = NULL;
	PVOID pBaseAddress = NULL;
	UNICODE_STRING FileName = { 0 };
	ULONG64 FunctionAddress = 0;

	// 初始化字符串
	RtlInitUnicodeString(&FileName, L"\\??\\C:\\Windows\\System32\\ntdll.dll");

	// 内存映射文件
	status = KernelMapFile(FileName, &hFile, &hSection, &pBaseAddress);
	if (NT_SUCCESS(status))
	{
		DbgPrint("读取内存地址 = %p \n", pBaseAddress);
	}

	// 获取指定模块导出函数地址
	FunctionAddress = GetAddressFromFunction(FileName, "ZwQueryDirectoryFile");
	DbgPrint("ZwQueryVirtualMemory内存地址 = %p \n", FunctionAddress);

	// 开始Hook挂钩
	if (FunctionAddress != 0)
	{
		BOOLEAN ref = SSDTFunctionHook(FunctionAddress);
		if (ref == TRUE)
		{
			DbgPrint("[+] Hook已挂钩 \n");
		}
	}

	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}

编译并运行这段驱动程序,则你会看到挂钩成功的提示信息;

标签:PVOID,ULONG,pNewAddress,挂钩,pMdl,内核,NULL,OPTIONAL,SSDT
From: https://www.cnblogs.com/LyShark/p/17144310.html

相关文章

  • Linux 内核等待队列
    Linux内核中的等待队列是一种延时机制,其用于当前进程需要等待某些资源而进入一种sleep状态,当等待条件为真时,进程被唤醒,继续执行。显然,这里涉及三个方面,即,一是等待时当前进程处理,二是进程等待时所关注的资源处理,三时进程何时被唤醒继续执行。所以,我们这里需要几个数据结构,主要描......
  • 特性和错误多到无法列出” 的新文件系统准备进入内核主线
    导读介绍一下特性和错误多到无法列出的问题。Bcachefs是一个写时复制(CoW)的文件系统,其源自于 Linux 内核的块缓存Bcache。本周二,Bcachefs的补丁集已正式递交审查,有望被纳入内核。开发者希望能提供类似XFS/EXT4的性能,以及类似Btrfs和ZFS的特性。其主要开发者表示......
  • 手动更新内核到6.3.5
    在家闲着没事,就试试手动更新内核版本,从5.19升级到6.3.5一开始按照SageAi给出的方法,makeinstall然后update-grub,然后总是不成功,更新完了就进入initramfs了,说是找不到UUID,不启动了。后来搜索到知乎上,需要先makemodules_install然后再makeinstall不需要update-grub果然成功......
  • 驱动开发:内核解析PE结构节表
    在笔者上一篇文章《驱动开发:内核解析PE结构导出表》介绍了如何解析内存导出表结构,本章将继续延申实现解析PE结构的PE头,PE节表等数据,总体而言内核中解析PE结构与应用层没什么不同,在上一篇文章中LyShark封装实现了KernelMapFile()内存映射函数,在之后的章节中这个函数会被多次用到,为了......
  • 驱动开发:内核解析PE结构节表
    在笔者上一篇文章《驱动开发:内核解析PE结构导出表》介绍了如何解析内存导出表结构,本章将继续延申实现解析PE结构的PE头,PE节表等数据,总体而言内核中解析PE结构与应用层没什么不同,在上一篇文章中LyShark封装实现了KernelMapFile()内存映射函数,在之后的章节中这个函数会被多次用到,为......
  • Linux 内核 net_proto_family
    staticconststructnet_proto_familyinet_family_ops={.family=PF_INET,.create=inet_create,.owner=THIS_MODULE,};(void)sock_register(&inet_family_ops);/***sock_register-addasocketprotocolhandler*@ops:descriptiono......
  • Linux 内核时钟架构之时钟源读取计数
    前面我们讲到,时钟源是给timekeeping使用的,timekeeping会定时更新,这就依赖timekeeping模块需要读取clocksource的计数,计算时间流逝。然后对时间进行叠加,得到当前时间。 ktime_get()--->tk_core.timekeeperclocksource.read()timekeeping_get_ns()--》read()......
  • Linux 内核时钟架构之时钟事件设备与tick_device
    每个CPU定义了一个tick_device,其用于对本cpu使用的时钟事件设备跟踪。也就是说,tick_device是有的,但是这里面有没有clock_event_device我们并不清楚,但是内核在启动时候,如果注册clock_event_device设备,那么内核尝试用时钟事件设备与tick_device设备绑定。这样,两则就关联起来了。......
  • Linux 内核时钟架构之时钟事件设备注册
    voidclockevents_register_device(structclock_event_device*dev);voidclockevents_config_and_register(structclock_event_device*dev,u32freq,unsignedlongmin_delta,unsignedlongmax_delta);相关的一个是配置函数voidclocke......
  • Linux 内核时钟之timer初始化
    init_timersvoid__initinit_timers(void){init_timer_cpus();init_timer_stats();open_softirq(TIMER_SOFTIRQ,run_timer_softirq);}staticvoid__initinit_timer_cpu(intcpu){structtimer_base*base;inti;for(i=0;i<NR_BASES;i+......