讲解如何写Hook过滤函数,比如NewZwOpenProcess。打开进程。很多游戏保护都会对这个函数进行Hook。由于我们没有游戏保护的代码,无法得知游戏公司是如何编写这个过滤函数。
我看到很多奇形怪状的Hook过滤函数的写法。看得蛋痛无比。比如:
http://bbs.pediy.com/showthread.php?t=126802
http://bbs.pediy.com/showthread.php?t=126077
第一个bug:
调用这个函数
status = PsLookupProcessByProcessId(
ClientId->UniqueProcess,
&process
);
这个函数我们要注意的地方。没有看到清除调用引数ObDereferenceObject(process);
第二个bug:
参数 PCLIENT_ID ClientId
如果这个参数是NULL,那么 ClientId-->UniqueProcess
就会产生蓝屏即直接对一个NULL变量进行读取。
http://bbs.pediy.com/showthread.php?t=105418
http://bbs.pediy.com/showthread.php?t=82548
http://bbs.pediy.com/showthread.php?t=82043
注:如果你用naked(裸函数)这种形式,就不应该有参数,如果你用__stdcall这种调用规则,就应该要有参数。
我觉得这个还算是标准(至少可读性很高):
http://bbs.pediy.com/showthread.php?t=176477
要注重细节,这就是代码稳定性的编写过程。
随便找一个之前的hook过滤函数的代码,对照WRK,对使用的各种参数进行效验,写一个稳定的过滤函数。
稳定的SSDT Hook代码示例
SSDT.h 头文件的编码:
#ifndef _SSDT_H_
#define _SSDT_H_
#include
//内核导出的SSDT表的结构
typedef struct _SERVICE_DESCRIPTOR_TABLE {
/*
* Table containing cServices elements of pointers to service handler
* functions, indexed by service ID.
*/
PULONG ServiceTable;
/*
* Table that counts how many times each service is used. This table
* is only updated in checked builds.
*/
PULONG CounterTable;
/*
* Number of services contained in this table.
*/
ULONG TableSize;
/*
* Table containing the number of bytes of parameters the handler
* function takes.
*/
PUCHAR ArgumentTable;
} SERVICE_DESCRIPTOR_TABLE, *PSERVICE_DESCRIPTOR_TABLE;
//全局变量
PMDL pmdl_system_call;
PVOID *pdword_mapped_table;
//******************** SSDT Hook宏(固定的) ****************************************************************
//获取Hook函数的Index
#define SYSCALL_INDEX(_Function) *(PULONG)((PUCHAR)_Function+1)
//实现SSDT表的Hook
#define HOOK_SYSCALL(_Function, _Hook, _Orig ) \
_Orig = (PVOID) InterlockedExchange( (PLONG) &pdword_mapped_table[SYSCALL_INDEX(_Function)], (LONG) _Hook)
//恢复SSDT表的Hook
#define UNHOOK_SYSCALL(_Function, _Hook, _Orig ) \
InterlockedExchange( (PLONG) &pdword_mapped_table[SYSCALL_INDEX(_Function)], (LONG) _Hook)
//声明SSDT的导出
extern PSERVICE_DESCRIPTOR_TABLE KeServiceDescriptorTable;
#endif
#ifndef _SSDT_HOOK_H_
#define _SSDT_HOOK_H_
#include
#include "SSDT.h"
//声明没有文档化的函数PsGetProcessImageFileName
UCHAR* PsGetProcessImageFileName(
__in PEPROCESS Process
);
//*************************************************************************************************************************
//定义原函数的指针类型
typedef NTSTATUS (__stdcall *REALZWOPENPROCESS)(OUT PHANDLE ProcessHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN PCLIENT_ID ClientId
);
//定义该函数指针
REALZWOPENPROCESS RealZwOpenProcess;
//保存要Hook函数的真实地址
ULONG_PTR ul_ZwOpenProcess;
//保存要Hook函数的名称
UNICODE_STRING unicode_string;
//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);
#endif
SSDT.c 文件的编写
#include "SSDT.h"
//******************************************************************************************
//采用比较安全的方法修改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)
{
//构建内存描述符MDL
pmdl_system_call = MmCreateMdl(NULL, KeServiceDescriptorTable->ServiceTable, KeServiceDescriptorTable->TableSize*sizeof(ULONG_PTR));
if(!pmdl_system_call)
{
return STATUS_UNSUCCESSFUL;
}
//根据MDL申请分配内存
MmBuildMdlForNonPagedPool(pmdl_system_call);
//设置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)
{
//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;
}
#include "SSDTHook.h"
//深度的字符串效验
BOOLEAN ValidateUnicodeString(PUNICODE_STRING usStr)
{
ULONG i;
__try
{
//判断字符串的内存是否可访问
if (!MmIsAddressValid(usStr))
{
return FALSE;
}
//判断是否为NULL
if (usStr->Buffer == NULL || usStr->Length == 0)
{
return FALSE;
}
//每一个字节都要检查
for (i = 0; i < usStr->Length; i++)
{
if (!MmIsAddressValid((PUCHAR)usStr->Buffer + i))
{
return FALSE;
}
}
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
//触发异常
return FALSE;
}
return TRUE;
}
//自定义的ZwOpenProcess函数(NewZwOpenProcess)
NTSTATUS __stdcall NewZwOpenProcess(OUT PHANDLE ProcessHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN PCLIENT_ID ClientId)
{
NTSTATUS status;
ULONG PID;
HANDLE handle_process_handle;
PEPROCESS eprocess_process_object;
KPROCESSOR_MODE PreMode;
//获取当前的系统模式MODE
PreMode = ExGetPreviousMode();
//*******************************如果非内核模式,就要开始检查IN的这些参数都否可读****************************
//每Hook一个函数之前,你都要先对照WRK:
if(PreMode != KernelMode)
{
__try
{
//这里用ProbeForRead来对参数ClientId进行测试,然后加try捕获
//检查用户模式地址的可读性必须在ring0调用
ProbeForRead(ClientId, sizeof(CLIENT_ID), sizeof(ULONG));
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
//返回异常代码
return GetExceptionCode();
}
}
//执行到这里说明改参数是可以访问,那我们还要效验ClientId是否为NULL
if(ClientId != NULL && MmIsAddressValid(ClientId))
{
//更安全的访问
PID = (ULONG)ClientId->UniqueProcess;
DbgPrint("OpenProcess %d by %s[0x%08X]\r\n", PID, PsGetProcessImageFileName(PsGetCurrentProcess()), PsGetCurrentProcess());
}
/*
typedef struct _OBJECT_ATTRIBUTES {
ULONG Length;
HANDLE RootDirectory;
PUNICODE_STRING ObjectName; //Buffer
ULONG Attributes;
PVOID SecurityDescriptor;
PVOID SecurityQualityOfService;
} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;
*/
if (ObjectAttributes != NULL && MmIsAddressValid(ObjectAttributes))
{
//这个成员是一个指针 ObjectName。效验指针是否为空,接着是否可以访问。这是第二层的效验
if (ObjectAttributes->ObjectName != NULL && MmIsAddressValid(ObjectAttributes->ObjectName))
{
//深度校验检查
if (ObjectAttributes->ObjectName->Buffer != NULL && ValidateUnicodeString(ObjectAttributes->ObjectName->Buffer))
{
//现在才可以安全不蓝屏的访问这个Buffer
DbgPrint("OpenObjectName %ws\r\n", ObjectAttributes->ObjectName->Buffer);
}
}
}
//如果我们要取ProcessHandle怎么办?
status = RealZwOpenProcess(ProcessHandle,
DesiredAccess,
ObjectAttributes,
ClientId
);
if (NT_SUCCESS(status))
{
//为什么我们这里不用效验ProcessHandle?
//因为函数调用成功了
handle_process_handle = *ProcessHandle;
//然后我们还可以通过handle,得到eprocess,即 handle->eprocess,还有一堆的转换,比如eprocess->handle, handle->fileobject
status = ObReferenceObjectByHandle(handle_process_handle,
GENERIC_READ,
*PsProcessType,
KernelMode,
(PVOID*)&eprocess_process_object,
0);
if(NT_SUCCESS(status))
{
DbgPrint("@@ OpenProcess %s by %s\r\n", PsGetProcessImageFileName(eprocess_process_object), PsGetProcessImageFileName(PsGetCurrentProcess()));
//这里很重要,消除引用计数
ObDereferenceObject(eprocess_process_object);
}
//只要RealZwOpenProcess调用成功,无论如何一定要返回成功
status = STATUS_SUCCESS;
}
return status;
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//驱动卸载的例程函数
VOID DriverUnload(IN PDRIVER_OBJECT DriverObject)
{
//卸载Hook
if (ul_ZwOpenProcess)
{
//移除SSDT表的Hook
if (MdlRemoveSSDTHook((ULONG_PTR)ul_ZwOpenProcess, NewZwOpenProcess, &RealZwOpenProcess) == STATUS_SUCCESS)
{
DbgPrint("ZwOpenProcess Remove hook success\r\n");
}
}
DbgPrint("DriverUnload\r\n");
}
//驱动入口例程函数
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,PUNICODE_STRING RegistryPath)
{
//设置驱动的卸载例程函数
DriverObject->DriverUnload = DriverUnload;
RtlInitUnicodeString(&unicode_string, L"ZwOpenProcess");
//获取要Hook函数的真实函数地址
ul_ZwOpenProcess = (ULONG_PTR)MmGetSystemRoutineAddress(&unicode_string);
if (ul_ZwOpenProcess)
{
if (MdlSSDTHook((ULONG_PTR)ul_ZwOpenProcess, NewZwOpenProcess, &RealZwOpenProcess) == STATUS_SUCCESS)
{
DbgPrint("ZwZwOpenProcess hook 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
# $Id$
TARGETNAME=SSDTHook
TARGETPATH=obj
TARGETTYPE=DRIVER
# Create browse info
#BROWSER_INFO=1
#BROWSERFILE=
# Additional defines for the C/C++ preprocessor
C_DEFINES=$(C_DEFINES)
SOURCES=SSDTHook.c\
SSDT.c\
AGP讲课资料的修改和整理。