zoukankan      html  css  js  c++  java
  • SSDTHook实例--编写稳定的Hook过滤函数

    解说怎样写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 <ntifs.h>
    
    //内核导出的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

    SSDTHook.h 头文件的编码:

    #ifndef _SSDT_HOOK_H_
    #define _SSDT_HOOK_H_
    
    #include <ntifs.h>
    #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;
    }


    SSDTHook.c 文件的编写

    #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]
    ", 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
    ", 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
    ", 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
    ");
    		}
    	}
    
    	DbgPrint("DriverUnload
    ");
    }
    
    
    //驱动入口例程函数
    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
    ");
    		}
    	}
    	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
    

    sources文件的编写:

    # $Id$
    TARGETNAME=SSDTHook
    TARGETPATH=obj
    TARGETTYPE=DRIVER
    
    
    # Create browse info
    #BROWSER_INFO=1
    #BROWSERFILE=<some path>
    
    # Additional defines for the C/C++ preprocessor
    C_DEFINES=$(C_DEFINES)
    
    SOURCES=SSDTHook.c
    	SSDT.c
    
    



    參考资料:

    AGP讲课资料的改动和整理。




  • 相关阅读:
    cmanformat
    mysql-sql语言参考
    jQuery 判断多个 input checkbox 中至少有一个勾选
    Java实现 蓝桥杯 算法提高 计算行列式
    Java实现 蓝桥杯 数独游戏
    Java实现 蓝桥杯 数独游戏
    Java实现 蓝桥杯 数独游戏
    Java实现 蓝桥杯 算法提高 成绩排序2
    Java实现 蓝桥杯 算法提高 成绩排序2
    Java实现 蓝桥杯 算法提高 成绩排序2
  • 原文地址:https://www.cnblogs.com/lcchuguo/p/5095018.html
Copyright © 2011-2022 走看看