• 驱动保护进程 句柄降权 杀软自保 游戏破图标技术原理和实现


    实现效果

    效果如图所示:

    无法结束进程:

    在这里插入图片描述

    CE无图标:

    在这里插入图片描述

    内存无法读取

    在这里插入图片描述

    可以看到被保护的进程无法被结束,同时在CE内无法看到图标,并且无法被读取内存。驱动的进程保护,游戏的无图标保护内存和杀软的自我保护,实际上都是一个原理。真是一招鲜吃遍天啊。

    实现原理

    在这里插入图片描述

    所有的实现其实都来自于一个API->ObRegisterCallbacks 。这个函数可以让你设置一个回调函数,当我们对进程或者线程的句柄进程操作时,可以在操作前和操作后拿到进程相关的信息,从而实现句柄降权,这样就可以阻止掉需要使用句柄的相关操作,例如读写内存和结束进程。

    函数原型如下:

    NTSTATUS ObRegisterCallbacks(
      [in]  POB_CALLBACK_REGISTRATION CallbackRegistration,
      [out] PVOID                     *RegistrationHandle
    );
    
    • 1
    • 2
    • 3
    • 4
    1. 第一个参数是一个结构体,里面是要设置的回调的相关信息
    2. 第二个参数是返回给我们的一个句柄,在卸载的时候需要用到

    我们来看下这个关键的结构体

    typedef struct _OB_CALLBACK_REGISTRATION {
      USHORT                    Version;
      USHORT                    OperationRegistrationCount;
      UNICODE_STRING            Altitude;
      PVOID                     RegistrationContext;
      OB_OPERATION_REGISTRATION *OperationRegistration;
    } OB_CALLBACK_REGISTRATION, *POB_CALLBACK_REGISTRATION;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. 第一个是版本号,可以用ObGetFilterVersion()函数获得
    2. 第二个是最后一个成员OperationRegistration的数量
    3. 第三个是一个Unicode 字符串,指定驱动程序的高度。(这个我也没看懂是个啥玩意)
    4. 第四个是回调函数的参数
    5. 最后一个参数比较关键,指向的是一个结构体数组,我们需要在这个结构体里面去指定操作前的回调函数和操作后的回调函数,这个结构可以设置多个。

    来看一下这个结构体的成员

    typedef struct _OB_OPERATION_REGISTRATION {
      POBJECT_TYPE                *ObjectType;
      OB_OPERATION                Operations;
      POB_PRE_OPERATION_CALLBACK  PreOperation;
      POB_POST_OPERATION_CALLBACK PostOperation;
    } OB_OPERATION_REGISTRATION, *POB_OPERATION_REGISTRATION;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. 第一个是回调函数的对象类型指针,要么是PsProcessType,要么是PsThreadType
    2. 第二个是标志位
    3. 第三个是操作前回调
    4. 第四个是操作后回调

    代码实现

    接下来按照这个步骤来写代码

    1. 初始化OB_OPERATION_REGISTRATION结构体
    2. 初始化OB_CALLBACK_REGISTRATION结构体
    3. 调用ObRegisterCallbacks注册回调函数
    4. 编写回调函数

    首先初始化OB_OPERATION_REGISTRATION结构体

    OB_OPERATION_REGISTRATION obOperationRegistrations;
    obOperationRegistrations.ObjectType = PsProcessType;
    obOperationRegistrations.Operations |= OB_OPERATION_HANDLE_CREATE;
    obOperationRegistrations.Operations |= OB_OPERATION_HANDLE_DUPLICATE;
    obOperationRegistrations.PreOperation = PreOperationCallback;
    obOperationRegistrations.PostOperation = NULL;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这个结构可以注册多个,我们只注册一个,处理进程句柄就够了。操作后的回调我们也不需要,直接填NULL

    然后再来初始化第二个结构体OB_CALLBACK_REGISTRATION

    OB_CALLBACK_REGISTRATION obCallbackRegistration = { 0 };
    UNICODE_STRING altitude = { 0 };
    RtlInitUnicodeString(&altitude, L"1000");
    obCallbackRegistration.Version = ObGetFilterVersion();
    obCallbackRegistration.OperationRegistrationCount = 1;
    obCallbackRegistration.RegistrationContext = NULL;
    obCallbackRegistration.Altitude = altitude;
    obCallbackRegistration.OperationRegistration = &obOperationRegistrations;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    最后再来调用API注册回调

    ObRegisterCallbacks(&obCallbackRegistration, &g_RegistrationHandle);
    
    • 1

    这里延伸一下:ObRegisterCallbacks这个函数是有校验的

    在这里插入图片描述

    里面会调用MmVerifyCallbackFunction这个函数进行校验

    在这里插入图片描述

    校验的方法是判断 DriverSection 里面的一个字段是否包含了0x20,所以我们只要把这个字段跟0x20按位与就行了。

    另外这个句柄需要放到全局变量里面,方便卸载的时候用,如果卸载驱动的时候没取消回调的注册,会蓝屏

    PVOID g_RegistrationHandle = NULL;
    
    • 1

    然后再来编写我们的回调函数

    OB_PREOP_CALLBACK_STATUS
    PreOperationCallback(_In_ PVOID RegistrationContext,
    	_Inout_ POB_PRE_OPERATION_INFORMATION PreInfo)
    {
    	//获取操作的进程对象
    	PEPROCESS process = (PEPROCESS)PreInfo->Object;
    	
    	//获取进程名
    	PUCHAR processName = PsGetProcessImageFileName(process);
    
    	if (_stricmp((char*)processName, "Notepad.exe") != 0)
    	{
    		//不是我们关心的进程,直接return
    		return OB_PREOP_SUCCESS;
    	}
    
    	if (PreInfo->Operation == OB_OPERATION_HANDLE_CREATE)
    	{
    		PreInfo->Parameters->CreateHandleInformation.DesiredAccess=0;
    	}
    
    	if (PreInfo->Operation == OB_OPERATION_HANDLE_DUPLICATE)
    	{
    		PreInfo->Parameters->DuplicateHandleInformation.DesiredAccess =0;
    	}
    
    	return OB_PREOP_SUCCESS;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    在回调函数里我们判断是否是我们关心的进程,如果不是,直接把所有权限抹掉。如果要实现游戏的效果,抹掉单个权限,比如禁用掉读写,就可以借助下面这些宏

    #define PROCESS_TERMINATE                  (0x0001)
    #define PROCESS_CREATE_THREAD              (0x0002)
    #define PROCESS_SET_SESSIONID              (0x0004)
    #define PROCESS_VM_OPERATION               (0x0008)
    #define PROCESS_VM_READ                    (0x0010)
    #define PROCESS_VM_WRITE                   (0x0020)
    #define PROCESS_DUP_HANDLE                 (0x0040)
    #define PROCESS_CREATE_PROCESS             (0x0080)
    #define PROCESS_SET_QUOTA                  (0x0100)
    #define PROCESS_SET_INFORMATION            (0x0200)
    #define PROCESS_QUERY_INFORMATION          (0x0400)
    #define PROCESS_SUSPEND_RESUME             (0x0800)
    #define PROCESS_QUERY_LIMITED_INFORMATION  (0x1000)
    #define PROCESS_SET_LIMITED_INFORMATION    (0x2000)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    实现效果

    在这里插入图片描述

    注册完成之后,在PCHunter的Object钩子这一栏,显示挂钩函数->PreOperation,PCHunter直接标红是可以查到的,还能看到挂钩的驱动名

    在这里插入图片描述

    句柄降权对抗(实现破游戏图标和关闭杀软自保)

    既然知道了原理,那想要摘除就很简单了,能防自然也能攻。

    
    OB_PREOP_CALLBACK_STATUS preCallback(
    	_In_ PVOID RegistrationContext,
    	_Inout_ POB_PRE_OPERATION_INFORMATION PreInfo
    )
    {
    	if (PreInfo->Operation == OB_OPERATION_HANDLE_CREATE)
    	{
    		PreInfo->Parameters->CreateHandleInformation.DesiredAccess = PROCESS_ALL_ACCESS;
    		PreInfo->Parameters->CreateHandleInformation.OriginalDesiredAccess = PROCESS_ALL_ACCESS;
    	}
    	else
    	{
    		PreInfo->Parameters->DuplicateHandleInformation.DesiredAccess = PROCESS_ALL_ACCESS;
    		PreInfo->Parameters->DuplicateHandleInformation.OriginalDesiredAccess = PROCESS_ALL_ACCESS;
    	}
    	
    	return OB_PREOP_SUCCESS;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    还是一样的代码,改一下回调,不管什么进程,都把句柄权限开到最大即可。

    关闭杀软:

    在这里插入图片描述

    游戏破图标:

    在这里插入图片描述

    降权对抗延伸

    游戏降权对抗

    既然破图标的操作这么简单粗暴,那么游戏自然不会坐视不管。在下了Object钩子之后,还会增加一层保险,就是剥夺所有线程和进程的游戏操作权限

    VOID StripHandlePermission()
    {
    	__try
    	{
    		CheckDebugPort(g_FlagProcessPid);
    		CheckDebugPort((HANDLE)g_StarterPid);
    		PSYSTEM_HANDLE_INFORMATION_EX HandleInfo = QueryHandleTable();
    		if (HandleInfo) {
    			for (int i = 0; i < HandleInfo->NumberOfHandles; i++)
    			{
    				//7 是 process 属性
    				if (HandleInfo->Information[i].ObjectTypeNumber == 7 || HandleInfo->Information[i].ObjectTypeNumber == OB_TYPE_INDEX_PROCESS || HandleInfo->Information[i].ObjectTypeNumber == OB_TYPE_INDEX_THREAD)
    				{
    					if (g_FlagProcessPid == (HANDLE)-1)
    						break;
    					if (HandleInfo->Information[i].ProcessId == (ULONG)g_FlagProcessPid || HandleInfo->Information[i].ProcessId == 4)
    						continue;
    					bool bCheck = ((HandleInfo->Information[i].GrantedAccess & PROCESS_VM_READ) == PROCESS_VM_READ ||
    						(HandleInfo->Information[i].GrantedAccess & PROCESS_VM_OPERATION) == PROCESS_VM_OPERATION ||
    						(HandleInfo->Information[i].GrantedAccess & PROCESS_VM_WRITE) == PROCESS_VM_WRITE);
    					PEPROCESS pEprocess = (PEPROCESS)HandleInfo->Information[i].Object;
    					if (pEprocess) {
    						HANDLE handle_pid = *(PHANDLE)((PUCHAR)pEprocess + g_OsData.UniqueProcessId);
    						HANDLE handle_pid2 = *(PHANDLE)((PUCHAR)pEprocess + g_OsData.InheritedFromUniqueProcessId);
    						if (bCheck && (handle_pid == g_FlagProcessPid || handle_pid2 == g_FlagProcessPid)) {
    							pEprocess = NULL;
    							NTSTATUS status = PsLookupProcessByProcessId((HANDLE)HandleInfo->Information[i].ProcessId, &pEprocess);
    							if (NT_SUCCESS(status)) {
    								//DebugPrint("Full Acess Handle! pid: %d \n", HandleInfo->Information[i].ProcessId);
    								PHANDLE_TABLE HandleTable = *(PHANDLE_TABLE*)((PUCHAR)pEprocess + g_OsData.ObjTable);
    								if (MmIsAddressValid((void*)HandleTable)) {
    									ExEnumHandleTable(HandleTable, g_isWin7 ? (DWORD64*)&StripHandleCallback_win7 : (DWORD64*)&StripHandleCallback_win10, (PVOID)HandleInfo->Information[i].Handle, NULL);
    								}
    								ObDereferenceObject(pEprocess);
    							}
    						}
    					}
    				}
    			}
    			ExFreePoolWithTag(HandleInfo, POOL_TAG);
    		}
    	}
    	__except (EXCEPTION_EXECUTE_HANDLER)
    	{
    		return;
    	}
    	
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    这段代码实现的功能就是枚举进程列表,然后遍历所有进程的私有句柄表,查看句柄类型为7的进程对象,如果这个进程对象的ID是游戏的ID,那么说明游戏句柄被我正在遍历的这个进程打开了,就把这个句柄的读写权限抹除。这样可以起到保护游戏进程的作用

    游戏完全可以创建一个线程,定时去执行这个操作。这样的话即使我们的回调恢复了句柄完整的属性,也无济无事,还需要把这个部分给处理掉。

    如果把恢复权限的回调代码也定时执行的话就会出现争抢句柄权限的局面,导致游戏的内存一闪一闪,这样做并不能完全解决问题。

    那么如果我们想要防止游戏定时把我们的句柄降权,可以采取的方法如下:

    1.构建EPROCESS结构。将目标进程的EPROCESS结构复制到我们构建的那块内存中。
    2.将新构建的EPROCESS结构中的PID、进程名清空或修改。
    3.将目标进程的第一级页表的内容复制到一块新内存中,并将新构建的EPROCESS结构的DirectoryTableBase改为新内存的物理地址,从而实现CR3的替换。
    4.修改我们自身进程的私有句柄表中的目标进程的句柄结构的Object为新构建的EPROCESS结构。
    5.此时我们再拿着新的句柄操作的进程与目标进程是同一个进程空间,但是指向的进程结构却是不同的。
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这个步骤可能不太好理解,这里我画了个图:

    在这里插入图片描述

    1. 首先把游戏进程的进程结构体复制出来一份,然后把PID跟名字清空
    2. 接着把游戏进程的CR3替换成我们自己的CR3

    以上两步实现了一个EPROCESS结构的替换,因为CR3变了所以不管你怎么操作原来的EPROCESS结构体,那么修改的也不是真正的游戏进程了。

    由于我们的CE还需要去操作游戏内存,所以需要把这一份新的游戏进程的EPROCESS的地址放到CE进程的句柄表内,也就是第4步的操作。

    这样的话就实现了句柄防止降权,也就实现了提权操作。还有一种方法是把私有句柄表的链表给摘掉,这样的话也可以防止降权。

    杀软自保对抗

    杀软的自保也是通过ObRegisterCallbacks实现的,这个操作基本可以防住大部分的R3的病毒。

    但是在一些系统进程中会有完整的读写权限的句柄,比如lsass.execsrss.exe,只要把这个拥有完整权限的句柄复制一份,就可以直接在三环关闭杀软了。相关的代码各位可以百度,网上肯定有现成的。

    隐藏Object钩子回调

    既然我们下的Object回调PCHunter直接标红,那么别人查起来也不难。有这么一个操作可以隐藏自己设置的回调,就是把进程保护回调直接注册到ntoskerl.exe,让操作系统的内核模块帮我们保护进程。这样的话在PCHunter里面就是没有任何异常的,而且别人想查起来也很难。这个操作是之前看某个驱动大佬的课程学习到的,当时直呼牛逼。具体细节就不展开了。

    完整代码

    https://download.csdn.net/download/qq_38474570/87085676?spm=1001.2014.3001.5501

  • 相关阅读:
    【C语言】通讯录系统实现 (保姆级教程,附源码)
    AR人脸道具SDK,打造极致用户体验
    wu-ui-uniapp 多平台快速开发的UI框架
    TV蓝牙无法被搜索问题解决记录:REQUEST_DISCOVERABLE ActivityNotFoundException
    【面试题】JS 中这些继承方式你知道吗?
    2020年MathorCup数学建模B题养老服务床位需求预测与运营模式研究全过程解题程序及多篇论文
    Linux之防火墙
    作为一个普通人学习算法的经验分享
    [C++从入门到精通] 带你彻底了解String类型的相关用法
    开源文档预览项目 kkFileView (9.9k star) ,快速入门
  • 原文地址:https://blog.csdn.net/qq_38474570/article/details/128054947