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

t=82548">http://bbs.pediy.com/showthread.php?t=82548

http://bbs.pediy.com/showthread.php?t=82043

注:假设你用naked(裸函数)这样的形式,就不应该有參数,假设你用__stdcall这样的调用规则。就应该要有參数。

我认为这个还算是标准(至少可读性非常高):

t=176477">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]\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

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讲课资料的改动和整理。

时间: 2024-10-17 09:13:49

SSDTHook实例--编写稳定的Hook过滤函数的相关文章

4.5 HOOK分发函数

4.5 HOOK分发函数 本节开始深入的探讨键盘的过滤与反过滤.有趣的是,无论是过滤还是反过 滤,其原理都是进行过滤.取胜的关键在于:谁将第一个得到信息. 黑客可能会通过修改一个已经存在的驱动对象(比如前面的KbdClass)的分 发函数指针来实现过滤所有请求的目的.黑客将这些函数指针替换成自己的黑客 驱动中的函数,这样请求将被黑客的程序首先截获.然后通过调用原来被替换过 的旧的函数指针来让Windows的击键过程正常的运作下去. 4.5.1 获得类驱动对象 当然,首先要获得键盘类驱动对象,才能

php filter 安全过滤函数

转自:http://www.blags.org/archives/741.html php 利用filter 扩展编写的参数处理静态类,欢迎使用.希望大家看得开心,用得放心. <?php /** * @参数验证函数 * @method: * @license http://www.blags.org/ * @created:2011年07月02日 11时00分 * @copyright 1997-2011 The Martin Group * @author Martin <[email pr

360HOOK表,Hook过滤架构搭建

分析了一下360的HOOK,通过直接hook KiFastCallEntry实现以所有系统调用的过滤.我分析的版本如下:主程序版本: 6.0.1.1003HookPort.sys版本: 1, 0, 0, 1005HookPort.sys的TimeStamp: 4A8D4AB8 简单说明:360把所有被hook的系统服务的过滤函数放在了一个表里,索引即对应的系统服务在该过滤函数表中的索引.所有列出来的函数都会被hook掉的,是否处理指某个系统服务有没有相应的过滤函数进行处理,拒绝还是放行就是在过滤

如何编写高质量的 JS 函数(3) --函数式编程[理论篇]

本文首发于 vivo互联网技术 微信公众号 链接:https://mp.weixin.qq.com/s/EWSqZuujHIRyx8Eb2SSidQ作者:杨昆 [编写高质量函数系列]中, <如何编写高质量的 JS 函数(1) -- 敲山震虎篇>介绍了函数的执行机制,此篇将会从函数的命名.注释和鲁棒性方面,阐述如何通过 JavaScript 编写高质量的函数. <如何编写高质量的 JS 函数(2)-- 命名/注释/鲁棒篇>从函数的命名.注释和鲁棒性方面,阐述如何通过 JavaScri

PHP反序列化中过滤函数使用不当导致的对象注入

1.漏洞产生的原因 ####  正常的反序列化语句是这样的 $a='a:2:{s:8:"username";s:7:"dimpl3s";s:8:"password";s:6:"abcdef";}'; 但是如果写成这样 $b='a:2:{s:8:"username";s:7:"dimpl3s";s:8:"password";s:6:"123456";

面试题之java 编写一个截取字符串的函数,输入为一个字符串和字节数,输出为按字节截取的字符串。 要求不能出现截半的情况

题目:10. 编写一个截取字符串的函数,输入为一个字符串和字节数,输出为按字节截取的字符串. 但是要保证汉字不被截半个,如“我ABC”4,应该截为“我AB”,输入“我ABC汉DEF”,6,应该输出为“我ABC”而不是“我ABC+汉的半个”. 一.需要分析 1.输入为一个字符串和字节数,输出为按字节截取的字符串-------------->按照字节[byte]截取操作字符串,先将String转换成byte类型 .2.汉字不可以截半----------------------------------

数据库编程1 Oracle 过滤 函数 分组 外连接 自连接

[本文谢绝转载原文来自http://990487026.blog.51cto.com] <大纲> 数据库编程1 Oracle 过滤 函数 分组 外连接 自连接 本文实验基于的数据表: winsows安装好Oracle11g之后,开始实验 SQLplus 登陆 ORacle sqlplus 退出的方式 查看用户之下有什么表 查看表的所有记录,不区分大小写 设置SQLplus行宽,页宽,列宽: 清屏命令 select as 语法 1,as别名的使用 2,没有引号带有空格的别名,无法识别: 3,带有

数据库编程2 Oracle 过滤 函数 分组 外连接 自连接

[本文谢绝转载原文来自http://990487026.blog.51cto.com] 续:数据库编程1 Oracle 过滤 函数 分组 外连接 自连接 where like模糊查询,查询员工姓名是4个字母 SQL> select * from emp where ename like '____';      EMPNO ENAME                JOB                       MGR HIREDATE          SAL       COMM    

c语言:编写一个输出链表的函数print

编写一个输出链表的函数print. 解:程序: #include<stdio.h> #include<stdlib.h> #define LEN sizeof(struct Student) struct Student { long num; float score; struct Student *next; }; int n; struct Student *creat()//建立链表的函数 { struct Student *head; struct Student *p1