其实 SSDT Hook 的原理是很简单的,我们可以知道在 SSDT 这个数组中呢,保存了系统服务的地址,比如对于 Ring0 下的 NtQuerySystemInformation 这个系统服务的地址,就保存在 KeServiceDescriptorTable[105h] 中(计算公式 address = SSDT首地址+服务号*4)
lkd> u 84647e3e
nt!NtQuerySystemInformation:
84647e3e 8bff mov edi,edi
84647e40 55 push ebp
84647e41 8bec mov ebp,esp
84647e43 8b5508 mov edx,dword ptr [ebp+8]
既然是 Hook 的话,我们就可以将这个 KeServiceDescriptorTable[105h] 下保存的服务地址替换掉,将我们自己的 Hook 处理函数的地址来替换掉原来的地址,
Mov eax,84496170h
Mov [eax],MyHookFunction
这样当每次调用 KeServiceDescriptorTable[105h]时就会调用我们自己的这个 Hook 处理函数了,就执行了我们的代码,达到hook原理。
我给大家画个图便以理解:
所有我们只要修改 KeServiceDescriptorTable[105h] 即可达到目的。
所以这就是我们在平时所讲的SSDT Hook原理。
大家都知道在SSDT Hook都时候都看到如下代码:
修改内存属性
__asm
{
cli
mov eax, cr0
and eax, not 10000h
mov cr0, eax
}
恢复内存属性
__asm
{
mov eax, cr0
or eax, 10000h
mov cr0, eax
sti
}
这是为什么呢?原因在于Windows系统对部分内存起用了写保护,IAT 内存属性,来防止内存页被修改,SSDT表的内存属性只是只读。如果你试图对一个只读属性的表进行写入操作,那么等待你的就是无情的蓝屏。因此,要修改ssdt表的话,首先要让表变为可写属性。
注:
修改系统分页内存属性的方法也可以参考:
http://blog.csdn.net/qq1084283172/article/details/40987347这里提供三种修改分页内存的方法。
有两种方法可以绕过写保护,一种是修改控制寄存器的CR0中的写保护位(WP)来绕过,另一种是利用MDL(Memory Descriptor List)来绕过写保护.
第一种方法比较简单,也就是把CR0重的WP(写保护)位设置为0,就可以禁止内存保护了。
比如大家看到看雪上的大部分代码,都是通过修改cr0的wp来禁止内存保护:
http://bbs.pediy.com/showthread.php?t=168061
http://bbs.pediy.com/showthread.php?t=148831
第二种是根据MDL。我们可以根据Mdl分配一段虚拟地址映射到SSDT所在的物理地址,同时因为我们映射的MDL内存属性却是可写,所以就可以修改SSDT,这样就替代了cr0方式。
MDL有很多标志:
1 //MDL Flags
2 #define MDL_MAPPED_TO_SYSTEM_VA 0x0001
3 #define MDL_PAGES_LOCKED 0x0002
4 #define MDL_SOURCE_IS_NONPAGED_POOL 0x0004
5 #define MDL_ALLOCATED_FIXED_SIZE 0x0008
6 #define MDL_PARTIAL 0x0010
7 #define MDL_PARTIAL_HAS_BEEN_MAPPED 0x0020
8 #define MDL_IO_PAGE_READ 0x0040
9 #define MDL_WRITE_OPERATION 0x0080
10 #define MDL_PARENT_MAPPED_SYSTEM_VA 0x0100
11 #define MDL_LOCK_HELD 0x0200
12 #define MDL_PHYSICAL_VIEW 0x0400
13 #define MDL_IO_SPACE 0x0800
14 #define MDL_NETWORK_HEADER 0x1000
15 #define MDL_MAPPING_CAN_FAIL 0x2000
16 #define MDL_ALLOCATED_MUST_SUCCEED 0x4000
我们在SSDTHook过程中,只关心MDL_MAPPED_TO_SYSTEM_VA这个标志位,因为他表示我们申请的MDL内存页池是可读可写的。
下面是采用Cr0和Mdl的方式进行SSDT表的函数Hook的示例:
SSDT.h头文件
//SSDT.h #ifndef _SSDT_H_ #define _SSDT_H_ #include <ntifs.h> //SSDT表的结构体 #pragma pack(1) typedef struct _SERVICE_DESCRIPTOR_TABLE { //System Service Dispatch Table的基地址 PULONG ServiceTable; //SSDT中每个服务被调用次数的计数器。 PULONG CounterTable; //由 ServiceTableBase 描述的服务数目。 ULONG TableSize; //每个系统服务参数字节数表的基地址-系统服务参数表SSPT PUCHAR ArgumentTable; } SERVICE_DESCRIPTOR_TABLE, *PSERVICE_DESCRIPTOR_TABLE; #pragma pack() //进行ssdt表KeServiceDescriptorTable的导出声明 extern PSERVICE_DESCRIPTOR_TABLE KeServiceDescriptorTable; /* _Hook为自定义用于Hook SSDT中函数的函数地址 _Function为SSDT表中被Hook函数原始地址 pdword_mapped_table申请的非分页内存的地址 */ //内存描述符列表 PMDL pmdl_system_call; PVOID *pdword_mapped_table; //获取SSDT表中函数的调用序号Index #define SYSCALL_INDEX(_Function) *(PULONG)((PUCHAR)_Function+1) //Mdl方式的SSDT表函数Hook #define HOOK_SYSCALL(_Function, _Hook, _Orig ) \ _Orig = (PVOID) InterlockedExchange( (PLONG) &pdword_mapped_table[SYSCALL_INDEX(_Function)], (LONG) _Hook) //移除Mdl方式的SSDT表函数Hook #define UNHOOK_SYSCALL(_Function, _Hook, _Orig ) \ InterlockedExchange( (PLONG) &pdword_mapped_table[SYSCALL_INDEX(_Function)], (LONG) _Hook) //开启内存写保护 VOID WPON(); //关闭内存写保护 VOID WPOFF(); //采用Cr0方式SSDT表函数Hook VOID Cr0SSDTHook(int *Index, ULONG_PTR *ul_save_real_addrsss, ULONG_PTR ul_hook_address); //移除采用Cr0方式SSDT表函数Hook VOID Cr0RemoveSSDTHook(int *Index, ULONG_PTR ul_save_real_addrsss); //采用Mdl方式SSDT表函数Hook NTSTATUS MdlSSDTHook(ULONG_PTR ul_real_function, ULONG_PTR hook_function_addr, ULONG_PTR *ul_save_real_function_addr); //移除采用Mdl方式SSDT表函数Hook NTSTATUS MdlRemoveSSDTHook(ULONG_PTR ul_real_function, ULONG_PTR hook_function_addr, ULONG_PTR *ul_save_real_function_addr); /* 驱动中输出提示信息推荐使用 KdPrint函数 : 驱动编程学习中,往往需要通过DbgPrint或者KdPrint来输出调试信息, 对于Check版本,KdPrint只是DbgPrint的一个宏定义,而对于Free版本,KdPrint将被优化掉。 */ /* LONG InterlockedExchange( IN OUT PLONG Target, IN LONG Value); InterlockedExchange(a,b)能以原子操作的方式交换俩个参数a, b,并返回a以前的值; 因为InterlockedExchange 是原子函数,不会要求中止中断,所以交换指针的方式是安全的。 */ /* #define SYSTEMSERVICE(_Function) KeServiceDescriptorTable.ServiceTableBase[ *(PULONG)((PUCHAR)_Function+1)] #define SYSCALL_INDEX(_Function) *(PULONG)((PUCHAR)_Function+1) #define HOOK_SYSCALL(_Function, _Hook, _Orig ) _Orig = (PVOID) InterlockedExchange( (PLONG) &MappedSystemCallTable [SYSCALL_INDEX(_Function)], (LONG) _Hook) #define UNHOOK_SYSCALL(_Function, _Hook, _Orig) InterlockedExchange((PLONG) &MappedSystemCallTable[SYSCALL_INDEX(_Function)], (LONG) _Hook) 而SYSTEMSERVICE宏是采用由ntoskrnl.exe导出的Zw*函数地址,并返回对应的Nt*函数在SSDT中的地址,SYSCALL_INDEX采用Zw*函数地址并返回它在SSDT中相应的索引号。 由于ZwCreateThread没有被ntoskrnl.exe导出,所以这时我们就无法直接使用上述的宏. */ /* MDL的全称是Memory Descriptor List,即内存描述符表。我们可以通过MDL描述一块内存区域, 在MDL中包含了该内存区域的起始地址、拥有者进程、字节数量、标记等信息,如下所示: typedef struct _MDL { struct _MDL *Next; CSHORT Size; CSHORT MdlFlags; struct _EPROCESS *Process; PVOID MappedSystemVa; PVOID StartVa; ULONG ByteCount; ULONG ByteOffset; } MDL, *PMDL; */ /* #define SYSTEMSERVICE(_function) KeServiceDescriptorTable.ServiceTableBase[ *(PULONG)((PUCHAR)_function+1)] */ #endif
//SSDTHook.h #ifndef _SSDT_HOOK_H_ #define _SSDT_HOOK_H_ #include "SSDT.h" //自定义的ZwOpenProcess函数 NTSTATUS NewZwOpenProcess(OUT PHANDLE ProcessHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes, IN PCLIENT_ID ClientId ); //声明ZwOpenProcess函数指针类型 typedef NTSTATUS (__stdcall *ZWOPENPROCESS)(OUT PHANDLE ProcessHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes, IN PCLIENT_ID ClientId ); ZWOPENPROCESS RealZwOpenProcess; //决定我们使用哪种hook BOOLEAN bool_hook_type = TRUE; //设置SSDT表Hook函数的Index int int_hook_index = 190; //保存ZwOpenProcess函数的真实值 ULONG_PTR ul_ZwOpenProcess; UNICODE_STRING unicode_string; #endif
//SSDT.c #include "SSDT.h" //关闭内存写保护 VOID WPOFF() { __asm { cli //屏蔽中断 mov eax, cr0 and eax, not 10000h mov cr0, eax } } //开启内存写保护 VOID WPON() { __asm { mov eax, cr0 or eax, 10000h mov cr0, eax sti //回复中断 } } /* 第一个参数Index就是服务号, 第二个参数ul_save_real_addrsss就是保存函数原始地址。因为我们要在过滤函数中调用原始地址 第三个参数ul_hook_address就是我们的hook代码的函数 */ //Cr0方式SSDT表的函数Hook VOID Cr0SSDTHook(int *Index, ULONG_PTR *ul_save_real_addrsss, ULONG_PTR ul_hook_address) { ULONG_PTR ul_real_service_address; ULONG_PTR num = *Index; //设置断点 //__asm int 3 //根据计算公式 //ul_real_service_address = 0x84495d5c + 105*4; //ul_real_service_address = 0x84496170; //获取被Hook函数的原始地址 ul_real_service_address = (ULONG)KeServiceDescriptorTable->ServiceTable + num*4; if (ul_real_service_address) { //0x84647e3e /* nt!NtQuerySystemInformation: 84647e3e 8bff mov edi,edi 84647e40 55 push ebp 84647e41 8bec mov ebp,esp 84647e43 8b5508 mov edx,dword ptr [ebp+8] 84647e46 83fa53 cmp edx,53h */ //保存SSDT表中原始函数地址 *ul_save_real_addrsss = *((ULONG*)ul_real_service_address); //关闭wp写保护 WPOFF(); //修改SSDT表中的Hook函数的地址为自定函数的地址 *((ULONG*)ul_real_service_address) = ul_hook_address; //开启wp写保护 WPON(); } } //移除Cr0方式SSDT表的函数Hook即恢复SSDT中被Hook的函数 VOID Cr0RemoveSSDTHook(int *Index, ULONG_PTR ul_save_real_addrsss) { ULONG_PTR ul_real_service_address; ULONG_PTR num = *Index; //关闭wp写保护 WPOFF(); //根据计算公式 ul_real_service_address = (ULONG)KeServiceDescriptorTable->ServiceTable + num*4; if (ul_real_service_address) { //恢复SSDT表中被Hook函数的地址 *((ULONG*)ul_real_service_address) = ul_save_real_addrsss; } //开启wp写保护 WPON(); } //****************************************************************************************** //采用比较安全的方法修改ssdt表 //因为SSDT的虚拟地址分页属性是只读的,我们不能够直接修改它,否则会产生蓝屏 //我们借助Mdl分配一段虚拟地址映射到SSDT所在的物理地址, //同时因为我们映射的MDL内存属性却可以是可写,所以就可以修改ssdt,这样就替代了cr0方式。 //****************************************************************************************** //采用Mdl方式的SSDT表函数Hook NTSTATUS MdlSSDTHook(ULONG_PTR ul_real_function, ULONG_PTR hook_function_addr, ULONG_PTR *ul_save_real_function_addr) { //设置断点 //__asm int 3 //创建内存描述符列表Mdl,映射到SSDT表所在的内存物理地址 pmdl_system_call = MmCreateMdl(NULL, KeServiceDescriptorTable->ServiceTable, KeServiceDescriptorTable->TableSize*sizeof(ULONG_PTR)); //判断内存描述符列表Mdl if(!pmdl_system_call) { return STATUS_UNSUCCESSFUL; } //构建非分页内存,建立虚拟地址与物理地址的映射关系 MmBuildMdlForNonPagedPool(pmdl_system_call); //改变MDL的标志,设置为MDL_MAPPED_TO_SYSTEM_VA标志,让这块内存变可写 pmdl_system_call->MdlFlags = pmdl_system_call->MdlFlags | MDL_MAPPED_TO_SYSTEM_VA; //锁定非分页内存 pdword_mapped_table = MmMapLockedPages(pmdl_system_call, KernelMode); if (pdword_mapped_table) { //开始Mdl方式的SSDT表函数Hook HOOK_SYSCALL(ul_real_function, hook_function_addr, *ul_save_real_function_addr); } return STATUS_SUCCESS; } //移除采用Mdl方式的SSDT表函数Hook NTSTATUS MdlRemoveSSDTHook(ULONG_PTR ul_real_function, ULONG_PTR hook_function_addr, ULONG_PTR *ul_save_real_function_addr) { //恢复SSDT表中被Hook的函数 UNHOOK_SYSCALL(ul_real_function, *ul_save_real_function_addr, hook_function_addr); if(pmdl_system_call) { //解锁非分页内存 MmUnmapLockedPages(pdword_mapped_table, pmdl_system_call); //释放申请的非分页内存 IoFreeMdl(pmdl_system_call); return STATUS_SUCCESS; } return STATUS_UNSUCCESSFUL; }
//SSDTHook.c #include "SSDTHook.h" //卸载函数 VOID DriverUnload(IN PDRIVER_OBJECT DriverObject) { if (bool_hook_type) { //移除采用Cr0方式的SSDT表函数Hook Cr0RemoveSSDTHook(&int_hook_index, RealZwOpenProcess); DbgPrint("ZwOpenProcess Remove Cr0RemoveSSDTHook success\r\n"); } else { //移除采用Mdl方式的SSDT表函数Hookook if (MdlRemoveSSDTHook((ULONG_PTR)ul_ZwOpenProcess, NewZwOpenProcess, &RealZwOpenProcess) == STATUS_SUCCESS) { DbgPrint("ZwOpenProcess Remove MdlRemoveSSDTHook success\r\n"); } } DbgPrint("卸载完成!\n"); } //因为这节只是阐述SSDTHOOK原理,所以只是简单的弄一个过滤函数 /* lkd> u 8a114070 l 30 8a114070 8bff mov edi,edi 8a114072 55 push ebp 8a114073 8bec mov ebp,esp 8a114075 8b4514 mov eax,dword ptr [ebp+14h] 8a114078 50 push eax 8a114079 8b4d10 mov ecx,dword ptr [ebp+10h] 8a11407c 51 push ecx 8a11407d 8b550c mov edx,dword ptr [ebp+0Ch] 8a114080 52 push edx 8a114081 8b4508 mov eax,dword ptr [ebp+8] 8a114084 50 push eax 8a114085 ff151c60118a call dword ptr ds:[8A11601Ch] 8a11408b 5d pop ebp 8a11408c c21000 ret 10h */ /* HOOK,SSDT表的内容: lkd> dd 84495d5c+be*4 84496054 8a114070 我们unhook: lkd> dd 84495d5c+be*4 84496054 84629ad4 */ //自定义的ZwOpenProcess函数NewZwOpenProcess NTSTATUS NewZwOpenProcess(OUT PHANDLE ProcessHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes, IN PCLIENT_ID ClientId ) { //Your OEM code DbgPrint("You Have Hooked Me\r\n"); //在过滤函数当中,要调用,所以我们在hook的时候要保存好 return RealZwOpenProcess(ProcessHandle, DesiredAccess, ObjectAttributes, ClientId ); } //**************************************************************************************************************** //驱动入口函数DriverEntry NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { //设置卸载例程函数 DriverObject->DriverUnload = DriverUnload; //定义一个BOOLEAN if (bool_hook_type) { //Cr0方式SSDT表函数Hook Cr0SSDTHook(&int_hook_index, &RealZwOpenProcess, (ULONG_PTR)NewZwOpenProcess); DbgPrint("ZwOpenProcess start Cr0RemoveSSDTHook success\r\n"); } else { DbgPrint("ZwOpenProcess start MdlRemoveSSDTHook success\r\n"); RtlInitUnicodeString(&unicode_string, L"ZwOpenProcess"); //获取函数ZwOpenProcess的原始调用地址 ul_ZwOpenProcess = (ULONG_PTR)MmGetSystemRoutineAddress(&unicode_string); if (ul_ZwOpenProcess) { //Mdl方式SSDT表函数Hook if (MdlSSDTHook(ul_ZwOpenProcess, (ULONG_PTR)NewZwOpenProcess, &RealZwOpenProcess) == STATUS_SUCCESS) { DbgPrint("ZwOpenProcess MdlSSDTHook success\r\n"); } } } return STATUS_SUCCESS; }
makefile文件
# # DO NOT EDIT THIS FILE!!! Edit .\sources. if you want to add a new source # file to this component. This file merely indirects to the real make file # that is shared by all the driver components of the Windows NT DDK # !INCLUDE $(NTMAKEENV)\makefile.def
TARGETNAME=ssdtHook TARGETTYPE=DRIVER TARGETPATH=obj INCLUDE=.\ SOURCES = SSDTHook.c\ SSDT.c \
文档和代码的下载地址:http://download.csdn.net/detail/qq1084283172/8838535
AGP讲课资料的整理和修改。