驱动开发:内核实现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: me@lyshark.com
// 保存原函数地址
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: me@lyshark.com
// 挂钩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: me@lyshark.com
// 保存原函数地址
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;
}
编译并运行这段驱动程序,则你会看到挂钩成功的提示信息;

驱动开发:内核实现SSDT挂钩与摘钩的更多相关文章
- Windows驱动开发-内核常用内存函数
搞内存常用函数 C语言 内核 malloc ExAllocatePool memset RtlFillMemory memcpy RtlMoveMemory free ExFreePool
- 驱动开发:Win10内核枚举SSDT表基址
三年前面朝黄土背朝天的我,写了一篇如何在Windows 7系统下枚举内核SSDT表的文章<驱动开发:内核读取SSDT表基址>三年过去了我还是个单身狗,开个玩笑,微软的Windows 10系 ...
- 驱动开发:内核层InlineHook挂钩函数
在上一章<驱动开发:内核LDE64引擎计算汇编长度>中,LyShark教大家如何通过LDE64引擎实现计算反汇编指令长度,本章将在此基础之上实现内联函数挂钩,内核中的InlineHook函 ...
- Windows内核安全与驱动开发
这篇是计算机中Windows Mobile/Symbian类的优质预售推荐<Windows内核安全与驱动开发>. 编辑推荐 本书适合计算机安全软件从业人员.计算机相关专业院校学生以及有一定 ...
- 驱动开发:内核枚举ShadowSSDT基址
在笔者上一篇文章<驱动开发:Win10枚举完整SSDT地址表>实现了针对SSDT表的枚举功能,本章继续实现对SSSDT表的枚举,ShadowSSDT中文名影子系统服务描述表,SSSDT其主 ...
- (转)Mac OS X内核编程,MAC驱动开发资源汇总
一.Mac OS X内核编程开发官方文档: I/O Kit Fundamentals: I/O Kit基础 - Mac OS X系统内核编程 https://developer.apple.com ...
- Unix/Linux环境C编程新手教程(12) openSUSECCPP以及Linux内核驱动开发环境搭建
1. openSUSE是一款优秀的linux. watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaXRjYXN0Y3Bw/font/5a6L5L2T/font ...
- Unix/Linux环境C编程入门教程(12) openSUSECCPP以及Linux内核驱动开发环境搭建
1. openSUSE是一款优秀的linux. 2.选择默认虚拟机 3.选择稍后安装操作系统 4.选择linux opensuse 5. 选择默认虚拟机名称 6.设置处理器为双核. 7.内存设置为2 ...
- Linux驱动开发必看详解神秘内核(完全转载)
Linux驱动开发必看详解神秘内核 完全转载-链接:http://blog.chinaunix.net/uid-21356596-id-1827434.html IT168 技术文档]在开始步入L ...
- Linux内核(17) - 高效学习Linux驱动开发
这本<Linux内核修炼之道>已经开卖(网上的链接为: 卓越.当当.china-pub ),虽然是严肃文学,但为了保证流畅性,大部分文字我还都是斟词灼句,反复的念几遍才写上去的,尽量考虑到 ...
随机推荐
- 近期调研和使用 zeromq 与 cppzmq 的一些问题
关于message 消息分片 消息分片的发送 消息分片允许将多个消息封装成一条消息.在发送自定义协议数据时,我们经常需要在消息前"填充"一个包头.如下代码,在发送的时候加上 zmq ...
- Kotlin 与 JAVA 不同之处
添加kotlin混编支持 要在Android项目中添加Kotlin混编支持,需要进行以下步骤: 在项目的 build.gradle 文件中添加以下代码: kotlin android { ... // ...
- 快来玩AI画图!StableDiffusion模型搭建与使用入门~
前言 最近AI很火,先是AI画图,然后就ChatGPT,后者我已经用了一段时间了,用来写作文挺不错的,但OpenAI屏蔽了中国IP,加上用户太多啥的,用起来没那么爽,但没办法全球只此一家,只能捏着鼻子 ...
- KubeSphere 高可用集群搭建并启用所有插件
介绍 大多数情况下,单主节点集群大致足以供开发和测试环境使用.但是,对于生产环境,您需要考虑集群的高可用性.如果关键组件(例如 kube-apiserver.kube-scheduler 和 kube ...
- windows通过修改注册表来修改暂停更新时间
但通过修改注册表,我们可以将这个天数修改成自己期望的,比如10年. 在小娜或者运行中输入 regedit 打开注册表编辑器,展开至 HKEY_LOCAL_MACHINE\SOFTWARE\Micros ...
- [软件设计&系统建模] Web软件通用能力模块
0 基础工具 1 日志 2 权限 3 文件处理(下载/上传) 4 对象池 对象池 数据库连接池 线程池 5 微服务 服务网关 配置中心 注册中心 服务调用 服务熔断 健康检测 Actuator 6 缓 ...
- 带你揭开神秘的javascript AST面纱之AST 基础与功能
作者:京东科技 周明亮 AST 基础与功能 在前端里面有一个很重要的概念,也是最原子化的内容,就是 AST ,几乎所有的框架,都是基于 AST 进行改造运行,比如:React / Vue /Taro ...
- 创建SVN和设置密码以及SVN自动更新
重新创建版本库: svnadmin create /usr/local/svn/month_exam //创建一个svn版本仓库month_exam(month_exam可以随便起名字) cd ...
- Qt5.9 UI设计(七)——统一样式设计
前言 前面已经将UI设计部分实现,各页面也做了最简单的设计,本章介绍一下qss样式的使用.样式设计最终的显示效果如下图: 操作步骤 将stylesheet.qss 样式文件添加进工程 styleshe ...
- js中宏任务和微任务
宏任务包括:<script>整体代码.setTimeout.setInterval.setImmediate.Ajax.DOM事件微任务:process.nextTick.Mutation ...