SSDT Hook实现内核级的过程保护
添加时间:2013-7-1 点击量:
目次
- SSDT Hook结果图
- SSDT简介
- SSDT布局
- SSDT HOOK道理
- Hook前筹办
- 如何获得SSDT中函数的地址呢
- SSDT Hook流程
- SSDT Hook实现过程保护
- Ring3与Ring0的通信
- 如何安装启动停止卸载办事
- 参考文献
- 源码附件
- 版权
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的当前地址会产生变更,此中,变更后的当前地址:0 xF885A110为我们自定义的Hook函数(即:HookNtTerminateProcess)的地址。如许,今后每次履行NtTerminateProcess的时辰,就会按照履行“当前地址”所指向的函数了,这也就是SSDT Hook的道理。
别的,看雪的出错天才写的不错,我直接引用下:
SSDT HOOK 的道理其实很是简单,我们先实际看看KeServiceDescriptorTable是什么样的。
lkd> dd KeServiceDescriptorTable
8055ab80 804e3d20 00000000 0000011c 804d9f48
8055ab90 00000000 00000000 00000000 00000000
8055aba0 00000000 00000000 00000000 00000000
8055abb0 00000000 00000000 00000000 00000000
如上,80587691 805716ef 8057ab71 80581b5c 这些就是体系办事函数的地址了。比如在ring3调用OpenProcess时,进入sysenter的ID是0 x7A(XP SP2),然后体系查KeServiceDescriptorTable,可能是如许KeServiceDescriptorTable.ntoskrnel.ServiceTableBase(804e3d20) + 0 x7A 4 = 804E3F08,然后804E3F08 ->8057559e 这个就是OpenProcess体系办事函数地点,我们再跟踪看看:
lkd> u 8057559e
nt!NtOpenProcess:
8057559e 68c4000000 push 0C4h
805755a3 6860b54e80 push offset nt!ObReferenceObjectByPointer+0 x127 (804eb560)
805755a8 e8e5e4f6ff call nt!InterlockedPushEntrySList+0 x79 (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\DisablePagingutive=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
{
cli
mov eax,cr0
and 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章:调派函数
如何安装、启动、停止、卸载办事
请参考我的另一篇博文:NT式驱动加载器(附带源码)
参考文献
SSDT Hook的妙用-对抗ring0 inline hook:http://bbs.pediy.com/showthread.php?t=40832
过程隐蔽与过程保护(SSDT Hook 实现):http://www.cnblogs.com/BoyXiao/archive/2011/09/03/2164574.html
张帆的《Windows驱动开辟技巧详解》一书
源码附件
SSDT Hook实现内核级的过程保护
版权
版权所有,欢迎转载,但转载请注明: 转载自 曾是土木人
我所有的自负皆来自我的自卑,所有的英雄气概都来自于我的软弱。嘴里振振有词是因为心里满是怀疑,深情是因为痛恨自己无情。这世界没有一件事情是虚空而生的,站在光里,背后就会有阴影,这深夜里一片寂静,是因为你还没有听见声音。—— 马良《坦白书》
目次
- SSDT Hook结果图
- SSDT简介
- SSDT布局
- SSDT HOOK道理
- Hook前筹办
- 如何获得SSDT中函数的地址呢
- SSDT Hook流程
- SSDT Hook实现过程保护
- Ring3与Ring0的通信
- 如何安装启动停止卸载办事
- 参考文献
- 源码附件
- 版权
SSDT Hook结果图
SSDT简介
这个表就是一个把 Ring3 的 Win32 API 和 Ring0 的内核 API 接洽起来。
SSDT 并不仅仅只包含一个重大的地址索引表,它还包含着一些其它有效的信息,诸如地址索引的基地址、办事函数个数等。
经由过程批改此表的函数地址可以对常用 Windows 函数及 API 进行 Hook,从而实现对一些关怀的体系动作进行过滤、监控的目标。
一些 HIPS、防毒软件、体系监控、注册表监控软件往往会采取此接口来实现本身的监控模块。
SSDT布局
// 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;
SSDT HOOK道理
lkd> dd KeServiceDescriptorTable
8055ab80 804e3d20 00000000 0000011c 804d9f48
8055ab90 00000000 00000000 00000000 00000000
8055aba0 00000000 00000000 00000000 00000000
8055abb0 00000000 00000000 00000000 00000000
如上,80587691 805716ef 8057ab71 80581b5c 这些就是体系办事函数的地址了。比如在ring3调用OpenProcess时,进入sysenter的ID是0 x7A(XP SP2),然后体系查KeServiceDescriptorTable,可能是如许KeServiceDescriptorTable.ntoskrnel.ServiceTableBase(804e3d20) + 0 x7A 4 = 804E3F08,然后804E3F08 ->8057559e 这个就是OpenProcess体系办事函数地点,我们再跟踪看看:
lkd> u 8057559e
nt!NtOpenProcess:
8057559e 68c4000000 push 0C4h
805755a3 6860b54e80 push offset nt!ObReferenceObjectByPointer+0 x127 (804eb560)
805755a8 e8e5e4f6ff call nt!InterlockedPushEntrySList+0 x79 (804e3a92)
805755ad 33f6 xor esi,esi
嗯,若是我们把8057559e改为指向我们函数的地址呢?比如 MyNtOpenProcess,那么体系就会直接调用MyNtOpenProcess,而不是本来的NtOpenProcess了。这就是SSDT HOOK 道理地点。
Hook前筹办
//设置为不成写
void DisableWrite()
{
__try
{
_asm
{
mov eax, cr0
or eax, 10000h
mov cr0, eax
sti
}
}
__except(1)
{
DbgPrint(DisableWrite履行失败!);
}
}
// 设置为可写
void EnableWrite()
{
__try
{
_asm
{
cli
mov eax,cr0
and eax,not 10000h //and eax,0FFFEFFFFh
mov cr0,eax
}
}
__except(1)
{
DbgPrint(EnableWrite履行失败!);
}
}
具体做法可以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实现过程保护
// 实现 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失败!));
}
}
//
// 函数名称 : 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;
}