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

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

在前面的文章《驱动开发:内核解析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;
}

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

image.png

目录
相关文章
|
5月前
|
Linux API
设备树知识小全(八):中断连接
设备树知识小全(八):中断连接
116 0
|
5月前
|
监控 安全 API
5.9 Windows驱动开发:内核InlineHook挂钩技术
在上一章`《内核LDE64引擎计算汇编长度》`中,`LyShark`教大家如何通过`LDE64`引擎实现计算反汇编指令长度,本章将在此基础之上实现内联函数挂钩,内核中的`InlineHook`函数挂钩其实与应用层一致,都是使用`劫持执行流`并跳转到我们自己的函数上来做处理,唯一的不同的是内核`Hook`只针对`内核API`函数,但由于其身处在`最底层`所以一旦被挂钩其整个应用层都将会受到影响,这就直接决定了在内核层挂钩的效果是应用层无法比拟的,对于安全从业者来说学会使用内核挂钩也是很重要。
96 1
5.9 Windows驱动开发:内核InlineHook挂钩技术
|
存储 缓存 Unix
Linux设备驱动程序(三)——字符驱动
本章的目的是编写一个完整的字符设备驱动,我们开发一个字符驱动是因为这一类适合大部分简单硬件设备,字符驱动也比块驱动易于理解。
222 0
|
Windows
驱动开发:内核扫描SSDT挂钩状态
在笔者上一篇文章`《驱动开发:内核实现SSDT挂钩与摘钩》`中介绍了如何对`SSDT`函数进行`Hook`挂钩与摘钩的,本章将继续实现一个新功能,如何`检测SSDT`函数是否挂钩,要实现检测`挂钩状态`有两种方式,第一种方式则是类似于`《驱动开发:摘除InlineHook内核钩子》`文章中所演示的通过读取函数的前16个字节与`原始字节`做对比来判断挂钩状态,另一种方式则是通过对比函数的`当前地址`与`起源地址`进行判断,为了提高检测准确性本章将采用两种方式混合检测。
202 0
|
监控 API 索引
驱动开发:恢复SSDT内核钩子
SSDT 中文名称为系统服务描述符表,该表的作用是将Ring3应用层与Ring0内核层,两者的API函数连接起来,起到承上启下的作用,SSDT并不仅仅只包含一个庞大的地址索引表,它还包含着一些其它有用的信息,诸如地址索引的基址、服务函数个数等,SSDT 通过修改此表的函数地址可以对常用 Windows 函数进行内核级的Hook,从而实现对一些核心的系统动作进行过滤、监控的目的。
399 0
驱动开发:恢复SSDT内核钩子
|
安全 API
驱动开发:内核层InlineHook挂钩函数
在上一章`《驱动开发:内核LDE64引擎计算汇编长度》`中,`LyShark`教大家如何通过`LDE64`引擎实现计算反汇编指令长度,本章将在此基础之上实现内联函数挂钩,内核中的`InlineHook`函数挂钩其实与应用层一致,都是使用`劫持执行流`并跳转到我们自己的函数上来做处理,唯一的不同的是内核`Hook`只针对`内核API`函数,但由于其身处在`最底层`所以一旦被挂钩其整个应用层都将会受到影响,这就直接决定了在内核层挂钩的效果是应用层无法比拟的,对于安全从业者来说学会使用内核挂钩也是很重要。
463 0
驱动开发:内核层InlineHook挂钩函数
|
监控 编译器 API
驱动开发:挂接SSDT内核钩子
SSDT 中文名称为系统服务描述符表,该表的作用是将Ring3应用层与Ring0内核层,两者的API函数连接起来,起到承上启下的作用,SSDT并不仅仅只包含一个庞大的地址索引表,它还包含着一些其它有用的信息,诸如地址索引的基址、服务函数个数等,SSDT 通过修改此表的函数地址可以对常用 Windows 函数进行内核级的Hook,从而实现对一些核心的系统动作进行过滤、监控的目的,接下来将演示如何通过编写简单的驱动程序,来实现搜索 SSDT 函数的地址,并能够实现简单的内核 Hook 挂钩。
341 0
驱动开发:摘链DKOM进程隐藏
DKOM 即直接内核对象操作,我们所有的操作都会被系统记录在内存中,而驱动进程隐藏就是操作进程的EPROCESS结构与线程的ETHREAD结构、链表,要实现进程的隐藏我们只需要将某个进程中的信息,在系统EPROCESS链表中摘除即可实现进程隐藏。
267 0
驱动开发:DKOM 实现进程隐藏
DKOM 就是直接内核对象操作技术,我们所有的操作都会被系统记录在内存中,而驱动进程隐藏的做旧就是操作进程的EPROCESS结构与线程的ETHREAD结构、链表,要实现进程的隐藏我们只需要将某个进程中的信息,在系统EPROCESS链表中摘除即可实现进程隐藏。
424 0