SSDT Hook实现内核级的进程保护

2024-02-05 20:08

本文主要是介绍SSDT Hook实现内核级的进程保护,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

  1. SSDT Hook效果图
  2. SSDT简介
  3. SSDT结构
  4. SSDT HOOK原理
  5. Hook前准备
  6. 如何获得SSDT中函数的地址呢
  7. SSDT Hook流程
  8. SSDT Hook实现进程保护
  9. Ring3与Ring0的通信
  10. 如何安装启动停止卸载服务
  11. 参考文献
  12. 源码附件
  13. 版权

SSDT Hook效果图

加载驱动并成功Hook  NtTerminateProcess函数:
当对 指定的进程进行保护后,尝试使用“任务管理器”结束进程的时候,会弹出“拒绝访问”的窗口,说明,我们的目的已经达到:

SSDT简介

SSDT 的全称是 System Services Descriptor Table,系统服务描述符表。

 

这个表就是一个把 Ring3 的 Win32 API 和 Ring0 的内核 API 联系起来。

SSDT 并不仅仅只包含一个庞大的地址索引表,它还包含着一些其它有用的信息,诸如地址索引的基地址、服务函数个数等。

通过修改此表的函数地址可以对常用 Windows 函数及 API 进行 Hook,从而实现对一些关心的系统动作进行过滤、监控的目的。

一些 HIPS、防毒软件、系统监控、注册表监控软件往往会采用此接口来实现自己的监控模块。

 

SSDT结构

SSDT即系统服务描述符表,它的结构如下(参考《Undocument Windows 2000 Secretes》第二章):
复制代码
// KSYSTEM_SERVICE_TABLE 和 KSERVICE_TABLE_DESCRIPTOR
// 用来定义 SSDT 结构
typedef struct _KSYSTEM_SERVICE_TABLE
{PULONG  ServiceTableBase;                               // SSDT (System Service Dispatch Table)的基地址PULONG  ServiceCounterTableBase;                        // 用于 checked builds, 包含 SSDT 中每个服务被调用的次数ULONG   NumberOfService;                                // 服务函数的个数, NumberOfService * 4 就是整个地址表的大小ULONG   ParamTableBase;                                 // SSPT(System Service Parameter Table)的基地址
} KSYSTEM_SERVICE_TABLE, *PKSYSTEM_SERVICE_TABLE;typedef struct _KSERVICE_TABLE_DESCRIPTOR
{KSYSTEM_SERVICE_TABLE   ntoskrnl;                       // ntoskrnl.exe 的服务函数KSYSTEM_SERVICE_TABLE   win32k;                         // win32k.sys 的服务函数(GDI32.dll/User32.dll 的内核支持)
    KSYSTEM_SERVICE_TABLE   notUsed1;KSYSTEM_SERVICE_TABLE   notUsed2;
}KSERVICE_TABLE_DESCRIPTOR, *PKSERVICE_TABLE_DESCRIPTOR;
复制代码
内核中有两个系统服务描述符表,一个是KeServiceDescriptorTable(由ntoskrnl.exe导出),一个是KeServieDescriptorTableShadow(没有导出)。
两者的区别是,KeServiceDescriptorTable仅有ntoskrnel一项,KeServieDescriptorTableShadow包含了ntoskrnel以及win32k。一般的Native API的服务地址由KeServiceDescriptorTable分派,gdi.dll/user.dll的内核API调用服务地址由KeServieDescriptorTableShadow分派。还有要清楚一点的是win32k.sys只有在GUI线程中才加载,一般情况下是不加载的,所以要Hook KeServieDescriptorTableShadow的话,一般是用一个GUI程序通过IoControlCode来触发(想当初不明白这点,蓝屏死机了N次都想不明白是怎么回事)。

SSDT HOOK原理

关于内核 Hook 有多种类型,下面也给出一副图示:
SSDT HOOK只是其中一种Hook技术,本篇文章主要讲解SSDT Hook的使用。
SSDT HOOK原理图
通过Kernel Detective工具,我们可以发现,SSDT Hook前后,NtTerminateProcess的当前地址会发生变化,其中,变化后的当前地址:0xF885A110为我们自定义的Hook函数(即:HookNtTerminateProcess)的地址。这样,以后每次执行NtTerminateProcess的时候,就会根据执行“当前地址”所指向的函数了,这也就是SSDT Hook的原理。
另外,看雪的"堕落天才"写的不错,我直接引用下:
SSDT HOOK 的原理其实非常简单,我们先实际看看KeServiceDescriptorTable是什么样的。 
 lkd> dd KeServiceDescriptorTable8055ab80  804e3d20 00000000 0000011c 804d9f488055ab90  00000000 00000000 00000000 000000008055aba0  00000000 00000000 00000000 000000008055abb0  00000000 00000000 00000000 00000000 

  如上,80587691 805716ef 8057ab71 80581b5c 这些就是系统服务函数的地址了。比如当我们在ring3调用OpenProcess时,进入sysenter的ID是0x7A(XP SP2),然后系统查KeServiceDescriptorTable,大概是这样KeServiceDescriptorTable.ntoskrnel.ServiceTableBase(804e3d20) + 0x7A * 4 = 804E3F08,然后804E3F08 ->8057559e 这个就是OpenProcess系统服务函数所在,我们再跟踪看看: 

lkd> u 8057559ent!NtOpenProcess:8057559e 68c4000000      push    0C4h805755a3 6860b54e80      push    offset nt!ObReferenceObjectByPointer+0x127 (804eb560)805755a8 e8e5e4f6ff      call    nt!InterlockedPushEntrySList+0x79 (804e3a92)805755ad 33f6            xor     esi,esi
原来8057559e就是NtOpenProcess函数所在的起始地址。  
    嗯,如果我们把8057559e改为指向我们函数的地址呢?比如 MyNtOpenProcess,那么系统就会直接调用MyNtOpenProcess,而不是原来的NtOpenProcess了。这就是SSDT HOOK 原理所在。
另外,关于Ring3层转入Ring0层的具体流程,可以参考下我的这篇博文,对加深理解SSDT Hook技术还是有帮助的: Ring3转入Ring0跟踪

Hook前准备

我们要修改SSDT表,首先这个表必须是可写的,但在xp以后的系统中他都是只读的,三个办法来修改内存保护机制
(1) 更改注册表 
恢复页面保护:HKLM\SYSTEM\CurrentControlset\Control\Session Manger\Memory Management\EnforceWriteProtection=0
去掉页面保护:HKLM\SYSTEM\CurrentControlset\Control\Session Manger\Memory Management\DisablePagingExecutive=1
(2)改变CR0寄存器的第1位
Windows对内存的分配,是采用的分页管理。其中有个CR0寄存器,如下图:
其中第1位叫做保护属性位,控制着页的读或写属性。如果为1,则可以读/写/执行;如果为0,则只可以读/执行。
SSDT,IDT的页属性在默认下都是只读,可执行的,但不能写。
代码如下:
复制代码
//设置为不可写
void DisableWrite()
{__try{_asm{mov eax, cr0 or  eax, 10000h mov cr0, eax sti }}__except(1){DbgPrint("DisableWrite执行失败!");}
}
// 设置为可写
void EnableWrite()
{__try{_asm{climov eax,cr0and eax,not 10000h //and eax,0FFFEFFFFh
            mov cr0,eax}}__except(1){DbgPrint("EnableWrite执行失败!");}
}
复制代码
(3)通过Memory Descriptor List(MDL)

具体做法可以google下,这里就不介绍了

 

如何获得SSDT中函数的地址呢?

  这里主要使用了两个宏:

①获取指定服务的索引号:SYSCALL_INDEX

②获取指定服务的当前地址:SYSCALL_FUNCTION

这两个宏的具体定义如下:

//根据 ZwServiceFunction 获取 ZwServiceFunction 在 SSDT 中所对应的服务的索引号 #define SYSCALL_INDEX(ServiceFunction) (*(PULONG)((PUCHAR)ServiceFunction + 1)) //根据ZwServiceFunction 来获得服务在 SSDT 中的索引号,然后再通过该索引号来获取ntServiceFunction的地址 #define SYSCALL_FUNCTION(ServiceFunction) KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[SYSCALL_INDEX(ServiceFunction)]

SSDT Hook流程

在驱动的入口函数中(DriverEntry),对未进行SSDT Hook前的SSDT表进行了备份(用一个数组保存),备份时,一个索引号对应一个当前地址,如上图所示。

这样,在解除Hook的时候,就可以从全局数组中根据索引号获取未Hook前的服务名的当前地址,以便将原来的地址写回去,这一步很重要。

当用户选择保护某个进程的时候,就会通过DeviceIoControl发送一个IO_INSERT_PROTECT_PROCESS控制码给驱动程序,此时驱动程序会生成一个IRP:IRP_MJ_DEVICE_CONTROL,我们事先已经在驱动程序中为

IRP_MJ_DEVICE_CONTROL指定了一个派遣函数:SSDTHook_DispatchRoutine_CONTROL。在该派遣函数中:我们通过获取控制码(是保护进程还是取消保护进程),如果是要保护某个进程,则通过
DeviceIoControl的第3个参数将要保护的进程的pid传递给驱动程序。然后在派遣函数SSDTHook_DispatchRoutine_CONTROL中从缓冲区中读取该pid,如果是要保护进程,则将要“保护进程”的pid添加到一个数组中,如果是要“取消保护进程”,则将要取消保护的进程PID从数组中移除。
在Hook NtTermianteProcess函数后,会执行我们自定义的函数:HookNtTerminateProcess,在HookNtTerminateProcess函数中,我们判断当前进程是否在要保护的进程数组中,如果该数组中存在该pid,则我们返回一个“权限不够”的异常,如果进程保护数组中不存在该pid,则直接调用原来 SSDT 中的 NtTerminateProcess 来结束进程。

SSDT Hook实现进程保护

有了上面的理论基础之后,接下来可以谈谈SSDT Hook实现进程保护的具体实现了。
实现进程保护,可以Hook NtTermianteProcess,另外也可以Hook NtOpenProcess,这里,我是Hook NtTermianteProcess。
SSDT Hook原理一节中已经说过,SSDT Hook原理的本质是:自定义一个函数(HookNtTerminateProcess),让系统服务NtTermianteProcess的当前地址指向我们自定义函数地址。
这一步工作是在驱动入口函数中执行的。当驱动加载的时候,将自定义函数的地址写入SSDT表中NtTermianteProcess服务的当前地址:
复制代码
// 实现 Hook 的安装,主要是在 SSDT 中用 newService 来替换掉 oldService
NTSTATUS InstallHook(ULONG oldService, ULONG newService)
{__try{ULONG uOldAttr = 0;        EnableWrite();    //去掉页面保护    KdPrint(("伪造NtTerminateProcess地址: %x\n",(int)newService));//KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[SYSCALL_INDEX(oldService)] = newService;SYSCALL_FUNCTION(oldService) = newService;//
        DisableWrite();    //恢复页面保护return STATUS_SUCCESS;}__except(1){KdPrint(("安装Hook失败!"));}
}
复制代码
这里需要注意的是:在Hook前,需要去掉内存的页面保护属性,Hook后,需要回复内存的页面保护属性。
HookNtTerminateProcess函数的代码如下:
复制代码
//************************************
// 函数名称 : HookNtTerminateProcess
// 描    述 : 自定义的 NtOpenProcess,用来实现 Hook Kernel API
// 日    期 : 2013/06/28
// 参    数 : ProcessHandle:进程句柄 ExitStatus:
// 返 回 值 : 
//************************************
NTSTATUS HookNtTerminateProcess(__in_opt HANDLE ProcessHandle,__in NTSTATUS ExitStatus)
{ULONG uPID;NTSTATUS rtStatus;PCHAR pStrProcName;PEPROCESS pEProcess;ANSI_STRING strProcName;// 通过进程句柄来获得该进程所对应的 FileObject 对象,由于这里是进程对象,自然获得的是 EPROCESS 对象rtStatus = ObReferenceObjectByHandle(ProcessHandle, FILE_READ_DATA, NULL, KernelMode, (PVOID*)&pEProcess, NULL);if (!NT_SUCCESS(rtStatus)){return rtStatus;}// 保存 SSDT 中原来的 NtTerminateProcess 地址pOldNtTerminateProcess = (NTTERMINATEPROCESS)oldSysServiceAddr[SYSCALL_INDEX(ZwTerminateProcess)];// 通过该函数可以获取到进程名称和进程 ID,该函数在内核中实质是导出的(在 WRK 中可以看到)// 但是 ntddk.h 中并没有到处,所以需要自己声明才能使用uPID = (ULONG)PsGetProcessId(pEProcess);pStrProcName = _strupr((TCHAR *)PsGetProcessImageFileName(pEProcess));//使用微软未公开的PsGetProcessImageFileName函数获取进程名// 通过进程名来初始化一个 ASCII 字符串RtlInitAnsiString(&strProcName, pStrProcName);if (ValidateProcessNeedProtect(uPID) != -1){// 确保调用者进程能够结束(这里主要是指 taskmgr.exe)if (uPID != (ULONG)PsGetProcessId(PsGetCurrentProcess())){// 如果该进程是所保护的的进程的话,则返回权限不够的异常即可return STATUS_ACCESS_DENIED;}}// 对于非保护的进程可以直接调用原来 SSDT 中的 NtTerminateProcess 来结束进程rtStatus = pOldNtTerminateProcess(ProcessHandle, ExitStatus);return rtStatus;
}
复制代码

Ring3与Ring0的通信

请看考:张帆《Windows驱动开发技术详解》一书第7章:派遣函数

这篇关于SSDT Hook实现内核级的进程保护的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/682017

相关文章

Flutter实现文字镂空效果的详细步骤

《Flutter实现文字镂空效果的详细步骤》:本文主要介绍如何使用Flutter实现文字镂空效果,包括创建基础应用结构、实现自定义绘制器、构建UI界面以及实现颜色选择按钮等步骤,并详细解析了混合模... 目录引言实现原理开始实现步骤1:创建基础应用结构步骤2:创建主屏幕步骤3:实现自定义绘制器步骤4:构建U

SpringBoot中四种AOP实战应用场景及代码实现

《SpringBoot中四种AOP实战应用场景及代码实现》面向切面编程(AOP)是Spring框架的核心功能之一,它通过预编译和运行期动态代理实现程序功能的统一维护,在SpringBoot应用中,AO... 目录引言场景一:日志记录与性能监控业务需求实现方案使用示例扩展:MDC实现请求跟踪场景二:权限控制与

Android实现定时任务的几种方式汇总(附源码)

《Android实现定时任务的几种方式汇总(附源码)》在Android应用中,定时任务(ScheduledTask)的需求几乎无处不在:从定时刷新数据、定时备份、定时推送通知,到夜间静默下载、循环执行... 目录一、项目介绍1. 背景与意义二、相关基础知识与系统约束三、方案一:Handler.postDel

使用Python实现IP地址和端口状态检测与监控

《使用Python实现IP地址和端口状态检测与监控》在网络运维和服务器管理中,IP地址和端口的可用性监控是保障业务连续性的基础需求,本文将带你用Python从零打造一个高可用IP监控系统,感兴趣的小伙... 目录概述:为什么需要IP监控系统使用步骤说明1. 环境准备2. 系统部署3. 核心功能配置系统效果展

Python实现微信自动锁定工具

《Python实现微信自动锁定工具》在数字化办公时代,微信已成为职场沟通的重要工具,但临时离开时忘记锁屏可能导致敏感信息泄露,下面我们就来看看如何使用Python打造一个微信自动锁定工具吧... 目录引言:当微信隐私遇到自动化守护效果展示核心功能全景图技术亮点深度解析1. 无操作检测引擎2. 微信路径智能获

Java并发编程之如何优雅关闭钩子Shutdown Hook

《Java并发编程之如何优雅关闭钩子ShutdownHook》这篇文章主要为大家详细介绍了Java如何实现优雅关闭钩子ShutdownHook,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起... 目录关闭钩子简介关闭钩子应用场景数据库连接实战演示使用关闭钩子的注意事项开源框架中的关闭钩子机制1.

Python中pywin32 常用窗口操作的实现

《Python中pywin32常用窗口操作的实现》本文主要介绍了Python中pywin32常用窗口操作的实现,pywin32主要的作用是供Python开发者快速调用WindowsAPI的一个... 目录获取窗口句柄获取最前端窗口句柄获取指定坐标处的窗口根据窗口的完整标题匹配获取句柄根据窗口的类别匹配获取句

在 Spring Boot 中实现异常处理最佳实践

《在SpringBoot中实现异常处理最佳实践》本文介绍如何在SpringBoot中实现异常处理,涵盖核心概念、实现方法、与先前查询的集成、性能分析、常见问题和最佳实践,感兴趣的朋友一起看看吧... 目录一、Spring Boot 异常处理的背景与核心概念1.1 为什么需要异常处理?1.2 Spring B

Python位移操作和位运算的实现示例

《Python位移操作和位运算的实现示例》本文主要介绍了Python位移操作和位运算的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录1. 位移操作1.1 左移操作 (<<)1.2 右移操作 (>>)注意事项:2. 位运算2.1

如何在 Spring Boot 中实现 FreeMarker 模板

《如何在SpringBoot中实现FreeMarker模板》FreeMarker是一种功能强大、轻量级的模板引擎,用于在Java应用中生成动态文本输出(如HTML、XML、邮件内容等),本文... 目录什么是 FreeMarker 模板?在 Spring Boot 中实现 FreeMarker 模板1. 环