TP保护的研究和学习-用户态下调试附加篇
引言:
本篇系列旨在研究学习腾讯保护系统的保护方案实现,文中尽量以“发现问题然后尝试解决问题”的模式来处理遇到的问题,此前网上有太多相关的资料都只是给出了一个结论式答案或者方法,对于想要深入学习的人太不友好。 我相信有很多人和我一样在尝试去深入分析的时候遇到了各种问题,我的处理方式就是遇到实际问题实际分析的方式来推进学习;
计算机理论到现在为止已经非常完善了, 它是一门科学;我们要相信科学,那么一切的问题和现象都是有据可依的,耐心肯定能找到原因的。
调试目标是DNF,囊括了老版本的70版本,90版本,以及最新的95版本;也希望给同样对操作系统内核和保护机制感兴趣的人做抛砖引玉之用。
目的
在这里我们的目的是为了能够使用OD成功附加游戏,并观测和学习在这个过程中TP防护的处理方案。
用户态调试的建立和探索
-
附加游戏开始测试
尝试用OD进行附加调试DNF.exe时,发生了以下的现象:
-
**观测现象一:**发现OD会附加失败,并弹出错误。
-
观测现象二: 然后测试使用OD对其他的非游戏进程进行附加测试,发现仍然会附加失败。
-
观测现象三: 当使用OD通过打开文件的方式建立调试关系时会打开失败。
-
现象分析
很明显,出现以上三种情况,那么说明在我们尝试附加或者打开程序的时候发生了什么事情,以至于我们的操作都没有达到我们的预期。既然我们现在正在尝试调试,那么此时我们就需要学习一下进程调试的相关知识;
Windows下进程建立调试的方式有两种:
-
第一种是通过
DebugActiveProcess
传入一个目标进程的句柄来进行调试; -
第二种是通过调用API
CreateProcess
并传入DEBUG_PROCESS或DEBUG_ONLY_THIS_PROCESS创建标识来建立调试关系;
因为游戏是先运行的,所以OD附加将会使用DebugActiveProcess
来建立调试关系。
我们写下测试代码来进行观测:
#include <iostream>
#include <windows.h>
int DebugProcess(DWORD ProcessId)
{
// 1. Establish debug connection
BOOL ret = DebugActiveProcess(ProcessId);
if (!ret)
{
std::cout << "Failed to attach process. Last error = "
<< GetLastError() << "." << std::endl;
return 0;
}
std::cout << "Process(" << ProcessId << ") is being debugged." << std::endl;
// 2. Run debug loop.
// 3. Detach process.
std::cout << "Press any key to detach debugee from debugger." << std::endl;
system("pause");
DebugActiveProcessStop(ProcessId);
std::cout << "Detached." << std::endl;
return 1;
}
int main()
{
DWORD targetProcessId = 1;
while (targetProcessId)
{
std::cout << "Please enter Process ID to debug(decimal number, enter 0 to exit):";
std::cin >> targetProcessId;
if (targetProcessId == 0)
break;
DebugProcess(targetProcessId);
system("pause");
}
}
使用以上代码在虚拟机中附加游戏进行测试,结果如下:

果然和上面OD的测试的结果基本吻合,都是附加失败了。该结果的产生是因为`DebugActiveProcess`函数调用失败导致。并且得到的错误码是5(ACCESS DENIED),
在测试过程中,测试进程的是以管理员权限运行的,目标进程的账户权限不高于调试器进程,因此正常情况下可以建立调试关系。但此时建立调试失败,那么怀疑是这个函数的执行流程破坏了。
调试流程的摸索
既然如此,那么此处咱们借助WRK,ReactOS和实际调试来重温一下建立调试的流程,当一个进程DebugActiveProcess
附加进程的时候经历了什么事情呢,这里来盘一盘,以ReactOS代码为例(代码请参考相应的文件):
-
第一步:调用
DbgUiConnectToDbg
,为当前的进程创建一个调试对象,该函数内部调用ZwCreateDebugObject
创建调试句柄,并指定AccessMask
为DEBUG_OBJECT_ALL_ACCESS(0x1F000F);并将调试对象句柄写入到当前线程的TEB->DbgSsReserved
; -
第二步:调用
ProcessIdToHandle
打开目标进程;并取得的操作权限, 该函数内部调用NtOpenProcess
,并传入进程访问权限为:PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ | PROCESS_SUSPEND_RESUME | PROCESS_QUERY_INFORMATION
,(0xC3A); -
第三步:调用
DbgUiDebugActiveProcess
建立实际的调试连接,该步骤为建立调试用户调试连接的最后一步,在该函数内调用了NtDebugActiveProcess
来激活调试关系,传入了第二步里面得到的进程句柄。
代码如下(代码摘自ReactOS
):
/*
* @implemented
*/
BOOL
WINAPI
DebugActiveProcess(IN DWORD dwProcessId)
{
NTSTATUS Status, Status1;
HANDLE Handle;
/* Connect to the debugger */
Status = DbgUiConnectToDbg();
if (!NT_SUCCESS(Status))
{
BaseSetLastNTError(Status);
return FALSE;
}
/* Get the process handle */
Handle = ProcessIdToHandle(dwProcessId);
if (!Handle) return FALSE;
/* Now debug the process */
Status = DbgUiDebugActiveProcess(Handle);
/* Close the handle since we're done */
Status1 = NtClose(Handle);
ASSERT(NT_SUCCESS(Status1));
/* Check if debugging worked */
if (!NT_SUCCESS(Status))
{
/* Fail */
BaseSetLastNTError(Status);
return FALSE;
}
/* Success */
return TRUE;
}
// 更多代码参考ReactOS
通过以上代码我们观察到, 这三步流程都有失败的可能性。 我们上面的代码里面,出现的ACCESS_DENIED,还不得知这个错误是由谁产生的。
既然如此,那我们现在对这三步流程逐一测试和排查,我们在DebugActiveProcess
函数下一个执行断点,然后单步跟踪每一步的执行结果。
-
开始第一步测试:
DebugActiveProcess
断下之后, 我们单步执行经过DbgUiConnectToDbg
,然后观察流程。通过上面第一步的流程介绍我们得知:如果创建调试句柄成功, 那么最后将会将调试对象的句柄放在当前线程TEB->DbgSsReserved
中, 由此,**我们首先观察这个函数的执行结果,并且观察TEB->DbgSsReserved
中存放的调试句柄说不能得知第一步是否正常。**好,那我们开始:; 检查DbgUiConnectToDbg的执行结果,该函数返回的是NTSTATUS, 2: kd> r Last set context: eax=00000000 ebx=76f5f176 ecx=001bfb24 edx=770d64f4 esi=00000b30 edi=76f9618c eip=76f96198 esp=001bfb5c ebp=001bfb5c iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 kernel32!DebugActiveProcess+0xc: 001b:76f96198 7d0a jge kernel32!DebugActiveProcess+0x18 (76f961a4) [br=1] ;该函数的返回值为0,即STATUS_SUCCESS, 由此可以看出该函数的执行是正常的, ; ;那么接下来我们观察一下调试对象的属性。 ; 首先我们需要找到DebugObject的句柄属性,当DbgUiConnectToDbg成功调用之后,该函数将会将调试对象的句柄放在TEB->DbgSsReserved里面,因此需要首先找到TEB->DbgSsReserved. 2: kd> !process PROCESS 87d52030 SessionId: 1 Cid: 15a8 Peb: 7ffd6000 ParentCid: 075c DirBase: be5c0e00 ObjectTable: aafb2a98 HandleCount: 8. Image: Observing_DebugActiveProcesss.exe VadRoot 880225c8 Vads 17 Clone 0 Private 70. Modified 0. Locked 0. DeviceMap 8f5e0c10 Token cdfdec88 ElapsedTime 91 Days 18:57:45.807 UserTime 00:00:00.000 KernelTime 00:00:00.000 QuotaPoolUsage[PagedPool] 13092 QuotaPoolUsage[NonPagedPool] 1020 Working Set Sizes (now,min,max) (338, 50, 345) (1352KB, 200KB, 1380KB) PeakWorkingSetSize 338 VirtualSize 6 Mb PeakVirtualSize 6 Mb PageFaultCount 334 MemoryPriority FOREGROUND BasePriority 8 CommitCharge 75 Job 87de9de0 THREAD 86b6fd48 Cid 15a8.112c Teb: 7ffdf000 Win32Thread: 00000000 RUNNING on processor 2 2: kd> .thread /p 86b6fd48; !teb 7ffdf000 Implicit thread is now 86b6fd48 Implicit process is now 87d52030 .cache forcedecodeuser done TEB at 7ffdf000 ExceptionList: 001bfbb8 StackBase: 001c0000 StackLimit: 001be000 SubSystemTib: 00000000 FiberData: 00001e00 ArbitraryUserPointer: 00000000 Self: 7ffdf000 EnvironmentPointer: 00000000 ClientId: 000015a8 . 0000112c RpcHandle: 00000000 Tls Storage: 7ffdf02c PEB Address: 7ffd6000 LastErrorValue: 87 LastStatusValue: c000000d Count Owned Locks: 0 HardErrorMode: 0 2: kd> dt _TEB 7ffdf000 ntdll!_TEB +0x000 NtTib : _NT_TIB +0x01c EnvironmentPointer : (null) +0x020 ClientId : _CLIENT_ID +0x028 ActiveRpcHandle : (null) +0x02c ThreadLocalStoragePointer : 0x7ffdf02c Void +0x030 ProcessEnvironmentBlock : 0x7ffd6000 _PEB +0x034 LastErrorValue : 0x57 +0x038 CountOfOwnedCriticalSections : 0 +0x03c CsrClientThread : (null) +0x040 Win32ThreadInfo : (null) +0x044 User32Reserved : [26] 0 +0x0ac UserReserved : [5] 0 +0x0c0 WOW32Reserved : (null) +0x0c4 CurrentLocale : 0x804 +0x0c8 FpSoftwareStatusRegister : 0 +0x0cc SystemReserved1 : [54] (null) +0x1a4 ExceptionCode : 0n0 +0x1a8 ActivationContextStackPointer : 0x002d07d0 _ACTIVATION_CONTEXT_STACK +0x1ac SpareBytes : [36] "" +0x1d0 TxFsContext : 0xfffe +0x1d4 GdiTebBatch : _GDI_TEB_BATCH +0x6b4 RealClientId : _CLIENT_ID +0x6bc GdiCachedProcessHandle : (null) +0x6c0 GdiClientPID : 0 +0x6c4 GdiClientTID : 0 +0x6c8 GdiThreadLocalInfo : (null) +0x6cc Win32ClientInfo : [62] 0 +0x7c4 glDispatchTable : [233] (null) +0xb68 glReserved1 : [29] 0 +0xbdc glReserved2 : (null) +0xbe0 glSectionInfo : (null) +0xbe4 glSection : (null) +0xbe8 glTable : (null) +0xbec glCurrentRC : (null) +0xbf0 glContext : (null) +0xbf4 LastStatusValue : 0xc000000d +0xbf8 StaticUnicodeString : _UNICODE_STRING "" +0xc00 StaticUnicodeBuffer : [261] "" +0xe0c DeallocationStack : 0x000c0000 Void +0xe10 TlsSlots : [64] (null) +0xf10 TlsLinks : _LIST_ENTRY [ 0x0 - 0x0 ] +0xf18 Vdm : (null) +0xf1c ReservedForNtRpc : (null) +0xf20 DbgSsReserved : [2] (null) +0xf28 HardErrorMode : 0 +0xf2c Instrumentation : [9] (null) +0xf50 ActivityId : _GUID {00000000-0000-0000-0000-000000000000} +0xf60 SubProcessTag : (null) +0xf64 EtwLocalData : (null) +0xf68 EtwTraceData : (null) +0xf6c WinSockData : (null) +0xf70 GdiBatchCount : 0 +0xf74 CurrentIdealProcessor : _PROCESSOR_NUMBER +0xf74 IdealProcessorValue : 0 +0xf74 ReservedPad0 : 0 '' +0xf75 ReservedPad1 : 0 '' +0xf76 ReservedPad2 : 0 '' +0xf77 IdealProcessor : 0 '' +0xf78 GuaranteedStackBytes : 0 +0xf7c ReservedForPerf : (null) +0xf80 ReservedForOle : (null) +0xf84 WaitingOnLoaderLock : 0 +0xf88 SavedPriorityState : (null) +0xf8c SoftPatchPtr1 : 0 +0xf90 ThreadPoolData : (null) +0xf94 TlsExpansionSlots : (null) +0xf98 MuiGeneration : 0 +0xf9c IsImpersonating : 0 +0xfa0 NlsCache : (null) +0xfa4 pShimData : (null) +0xfa8 HeapVirtualAffinity : 0 +0xfac CurrentTransactionHandle : (null) +0xfb0 ActiveFrame : (null) +0xfb4 FlsData : 0x002d27d0 Void +0xfb8 PreferredLanguages : (null) +0xfbc UserPrefLanguages : (null) +0xfc0 MergedPrefLanguages : (null) +0xfc4 MuiImpersonation : 0 +0xfc8 CrossTebFlags : 0 +0xfc8 SpareCrossTebBits : 0y0000000000000000 (0) +0xfca SameTebFlags : 0x420 +0xfca SafeThunkCall : 0y0 +0xfca InDebugPrint : 0y0 +0xfca HasFiberData : 0y0 +0xfca SkipThreadAttach : 0y0 +0xfca WerInShipAssertCode : 0y0 +0xfca RanProcessInit : 0y1 +0xfca ClonedThread : 0y0 +0xfca SuppressDebugMsg : 0y0 +0xfca DisableUserStackWalk : 0y0 +0xfca RtlExceptionAttached : 0y0 +0xfca InitialThread : 0y1 +0xfca SpareSameTebBits : 0y00000 (0) +0xfcc TxnScopeEnterCallback : (null) +0xfd0 TxnScopeExitCallback : (null) +0xfd4 TxnScopeContext : (null) +0xfd8 LockCount : 0 +0xfdc SpareUlong0 : 0 +0xfe0 ResourceRetValue : (null) ;找到TEB->DbgSsReserved,并得到DebugObject的值 2: kd> dx -id 0,0,ffffffff87d52030 -r1 (*((ntdll!void * (*)[2])0x7ffdff20)) (*((ntdll!void * (*)[2])0x7ffdff20)) [Type: void * [2]] [0] : 0x0 [Type: void *] [1] : 0x20 [Type: void *] 2: kd> !handle 20 PROCESS 87d52030 SessionId: 1 Cid: 15a8 Peb: 7ffd6000 ParentCid: 075c DirBase: be5c0e00 ObjectTable: aafb2a98 HandleCount: 8. Image: Observing_DebugActiveProcesss.exe Handle table at aafb2a98 with 8 entries in use ;; 0x20所对应句柄的权限,注意观察GrantedAccess的值,此处为0,那么此处就意味着当前创建的调试句柄是有问题的, 因为如果创建的对象没有任何的访问权限,那么这个句柄将毫无意义。 0020: Object: 8809f4e0 GrantedAccess: 00000000 Entry: b5469040 Object: 8809f4e0 Type: (867e65a0) DebugObject ObjectHeader: 8809f4c8 (new version) HandleCount: 1 PointerCount: 1
通过以上的测试我们发现虽然第一步的执行是成功了, 但是我们最终得到的调试对象句柄的
AccessMask
为0,AccessMask
用于什么地方呢? 这非常重要,理解AccessMask
的作用对调试机制的理解有很大的帮助。DEBUG_OBJECT_ALL_ACCESS(0x1F000F)宏由以下字段组成:Field(8) Desc DELETE(0x00010000L) 删除权限 READ_CONTROL(0x00020000L) READ,WRITE,EXECUTE WRITE_DAC(0x00040000L) 修改DACL WRITE_OWNER(0x00080000L) 修改拥有者 SYNCHRONIZE(0x00100000L) 用来同步 DEBUG_OBJECT_WAIT_STATE_CHANGE(0x0001) 修改调试事件等待状态 DEBUG_OBJECT_ADD_REMOVE_PROCESS(0x0002) 添加或者移除一个被调试进程 DEBUG_OBJECT_SET_INFORMATION(0x0004) 设置句柄信息 当我们创建的调试对象的句柄拥有以上对应的权限时, 才能对调试句柄做相应的操作; 如果当执行一个操作时,而该句柄没有相应的权限,那么都会返回拒绝访问(STATUS_ACCESS_DENIED);
-
第一步测试结论
通过以上的测试,我们已经意识到了我们在创建调试对象的时候发生了某种我们不知道的事情,以至于我们第一步看似成功,结果我们得到的句柄的
AccessMask
的值都为0,我们作为对象的创建者都不具备权限,那么这肯定是不合理的。那么接下来看起来我们需要继续跟踪创建的流程才有可能知道在创建对象的时候到底发生了什么导致这个结果的事情。
===========================================================================================
-
第一步继续深入跟踪测试
经过了上面的初步测试,我们已经观察到了创建对象的
AccessMask
存在标志位问题,那么此处我们就跟踪一下更详细的创建流程,看看我们的对象在什么地方出现的问题。此处参考WRK或者ReactOS
里面的代码观测一下创建的流程:(以下的流程如果想要完全看懂的话, 需要事先学习句柄相关的知识。)-
调用
nt!ObCreateObject
函数,并将nt!DbgkDebugObjectType
作为参数创建一个_DEBUG_OBJECT
对象;-
nt!DbgkDebugObjectType
是一个由nt!ObCreateObjectType
创建的一个基础类型对象,其相应的结构体为OBJECT_TYPE
, 怎样理解这个对象?这个对象用来干嘛的? Windows定义了很多种内核对象,然后由nt!ObCreateObjectType
创建相应的基础类型对象,方便在实际创建对应的对象时,直接使用该基础对象的数据而不用重新创建;比如线程对象,进程对象等等。(文中描述只为理解其大意,不得作为实际应用参考) -
nt!DbgkDebugObjectType
中包含了丰富的数据,其中包含了这个对象的名称,有效访问掩码(ValidAccessMask
),对象打开/关闭等回调函数等等;那么我们来看一下这个结构体里面有什么数据:
3: kd> dd nt!DbgkDebugObjectType 84150d2c 867e65a0 00000000 8d46baa8 00000003 84150d3c 00000012 0000002a 0000009d 00000024 84150d4c 8c472b3c 01010000 00000002 96247cb7 84150d5c 840e8920 00000000 00000000 00000000 84150d6c 00000003 8401a550 87b396a8 00000000 84150d7c 00000000 00000000 00000000 00000003 84150d8c 00000001 00000001 00000001 00000001 84150d9c 97c07408 97c07006 97c01006 97c072fa 3: kd> dt _OBJECT_TYPE 867e65a0 ntdll!_OBJECT_TYPE +0x000 TypeList : _LIST_ENTRY [ 0x867e65a0 - 0x867e65a0 ] +0x008 Name : _UNICODE_STRING "DebugObject" +0x010 DefaultObject : (null) +0x014 Index : 0xb '' +0x018 TotalNumberOfObjects : 1 +0x01c TotalNumberOfHandles : 1 +0x020 HighWaterNumberOfObjects : 1 +0x024 HighWaterNumberOfHandles : 1 +0x028 TypeInfo : _OBJECT_TYPE_INITIALIZER +0x078 TypeLock : _EX_PUSH_LOCK +0x07c Key : 0x75626544 +0x080 CallbackList : _LIST_ENTRY [ 0x867e6620 - 0x867e6620 ] 3: kd> dx -id 0,0,ffffffff87d52030 -r1 (*((ntdll!_OBJECT_TYPE_INITIALIZER *)0xffffffff867e65c8)) (*((ntdll!_OBJECT_TYPE_INITIALIZER *)0xffffffff867e65c8)) [Type: _OBJECT_TYPE_INITIALIZER] [+0x000] Length : 0x50 [Type: unsigned short] [+0x002] ObjectTypeFlags : 0x8 [Type: unsigned char] [+0x002 ( 0: 0)] CaseInsensitive : 0x0 [Type: unsigned char] [+0x002 ( 1: 1)] UnnamedObjectsOnly : 0x0 [Type: unsigned char] [+0x002 ( 2: 2)] UseDefaultObject : 0x0 [Type: unsigned char] [+0x002 ( 3: 3)] SecurityRequired : 0x1 [Type: unsigned char] [+0x002 ( 4: 4)] MaintainHandleCount : 0x0 [Type: unsigned char] [+0x002 ( 5: 5)] MaintainTypeList : 0x0 [Type: unsigned char] [+0x002 ( 6: 6)] SupportsObjectCallbacks : 0x0 [Type: unsigned char] [+0x004] ObjectTypeCode : 0x0 [Type: unsigned long] [+0x008] InvalidAttributes : 0x0 [Type: unsigned long] [+0x00c] GenericMapping [Type: _GENERIC_MAPPING] ;====================================================================> ;;;;;; 注意此处, 当前这个对象的有效访问掩码为0 [+0x01c] ValidAccessMask : 0x0 [Type: unsigned long] ;====================================================================> [+0x020] RetainAccess : 0x0 [Type: unsigned long] [+0x024] PoolType : NonPagedPool (0) [Type: _POOL_TYPE] [+0x028] DefaultPagedPoolCharge : 0x0 [Type: unsigned long] [+0x02c] DefaultNonPagedPoolCharge : 0x30 [Type: unsigned long] [+0x030] DumpProcedure : 0x0 [Type: void (*)(void *,_OBJECT_DUMP_CONTROL *)] [+0x034] OpenProcedure : 0x0 [Type: long (*)(_OB_OPEN_REASON,char,_EPROCESS *,void *,unsigned long *,unsigned long)] [+0x038] CloseProcedure : 0x842c8fa9 [Type: void (*)(_EPROCESS *,void *,unsigned long,unsigned long)] [+0x03c] DeleteProcedure : 0x842a0873 [Type: void (*)(void *)] [+0x040] ParseProcedure : 0x0 [Type: long (*)(void *,void *,_ACCESS_STATE *,char,unsigned long,_UNICODE_STRING *,_UNICODE_STRING *,void *,_SECURITY_QUALITY_OF_SERVICE *,void * *)] [+0x044] SecurityProcedure : 0x8428dd13 [Type: long (*)(void *,_SECURITY_OPERATION_CODE,unsigned long *,void *,unsigned long *,void * *,_POOL_TYPE,_GENERIC_MAPPING *,char)] [+0x048] QueryNameProcedure : 0x0 [Type: long (*)(void *,unsigned char,_OBJECT_NAME_INFORMATION *,unsigned long,unsigned long *,char)] [+0x04c] OkayToCloseProcedure : 0x0 [Type: unsigned char (*)(_EPROCESS *,void *,void *,char)]
-
-
调用
nt!ObInsertObject
函数将上一步创建的对象,插入到当前进程的句柄表中;并且在调用时传递了由应用层指定的DesiredAccessMask
(DEBUG_OBJECT_ALL_ACCESS);该函数调用成功之后将会通过最后一个参数返回该对象在句柄表中的句柄索引;(该步骤实际比较复杂,请参见实际情况,如果阅读困难,请原谅我,理解大概意思然后自己去深入研究吧!)-
对传入的
DesiredAccessMask
与nt!DbgkDebugObjectType->TypeInfo->ValidAccessMask
与运算获取最大权限,然后进行权限检查; -
将上一步计算最后得到值作为
GrantedAccess
,写入到句柄表项中的高32位中,具体字段分配,参见_HANDLE_TABLE_ENTRY
-
进程句柄表存放在进程对象
_EPROCESS::ObjectTable
中,该项是一个结构体(_HANDLE_TABLE),有关于该结构体的详细介绍这里不详述,在这个结构体中,_HANDLE_TABLE::TableCode
指向了实际的句柄表数组的起始位置。- 每一个句柄项目由8个字节构体,结构请参见:
_HANDLE_TABLE_ENTRY
。
- 每一个句柄项目由8个字节构体,结构请参见:
-
怎样索引到句柄所在的位置呢? 公式为:
_HANDLE_TABLE::TableCode + HandleValue / 4 * 8
,低三位为属性位。3: kd> dt _HANDLE_TABLE_ENTRY b5469000+20/4*8 ntdll!_HANDLE_TABLE_ENTRY +0x000 Object : 0x8809f4c9 Void +0x000 ObAttributes : 0x8809f4c9 +0x000 InfoTable : 0x8809f4c9 _HANDLE_TABLE_ENTRY_INFO +0x000 Value : 0x8809f4c9 +0x004 GrantedAccess : 0 ; 注意此处为0 +0x004 GrantedAccessIndex : 0 +0x006 CreatorBackTraceIndex : 0 +0x004 NextFreeTableEntry : 0 ; 此处得到Object的值为0x8809f4c9,该值的低三位为属性位,相关说明参见WRK,因此需要清除低三位才是真正的对象头的基地址, 注意:此处获取的是_OBJECT_HEADER,实际对象是该结构体的Body成员。 3: kd> dt _OBJECT_HEADER 0x8809f4c8 nt!_OBJECT_HEADER +0x000 PointerCount : 0n1 +0x004 HandleCount : 0n1 +0x004 NextToFree : 0x00000001 Void +0x008 Lock : _EX_PUSH_LOCK +0x00c TypeIndex : 0xb '' +0x00d TraceFlags : 0 '' +0x00e InfoMask : 0x8 '' +0x00f Flags : 0 '' +0x010 ObjectCreateInfo : 0x88bfdd00 _OBJECT_CREATE_INFORMATION +0x010 QuotaBlockCharged : 0x88bfdd00 Void +0x014 SecurityDescriptor : 0xb369501f Void +0x018 Body : _QUAD
-
大致理解了以上的流程之后, 我们现在知道我们创建的调试对象的权限是放在什么地方的,以及他的访问掩码是怎样来的,现在得到以下的结论:
- 调试类型对象的ValidAccessMask为0, 他为创建的调试对象提供了能赋予的最大的权限,如果该值为0,那么调试对象将不能被分配到任何权限;
- 我们的调试对象的访问权限存放在 _HANDLE_TABLE_ENTRY中,根据以上的观测结果,我们得知这个值为0;该值为0的情况下,在我们的应用程序中, 我们将不用使用调试对象做任何事情,所有有关于调试的行为都将被拒绝;
-
-
针对以上结果的恢复测试
通过以上的测试, 我们已经非常清楚造成此问题的根本原因是什么,就是因为我们创建的对象没有权限导致的。那么我们现在来尝试处理一下以上的问题看看能不能让我们创建的对象重新拥有权限:
-
恢复
ValidAccessMask
:0: kd> dt _OBJECT_TYPE DbgkDebugObjectType nt!_OBJECT_TYPE Cannot find specified field members. 0: kd> dt _OBJECT_TYPE 867e65a0 nt!_OBJECT_TYPE +0x000 TypeList : _LIST_ENTRY [ 0x867e65a0 - 0x867e65a0 ] +0x008 Name : _UNICODE_STRING "DebugObject" +0x010 DefaultObject : (null) +0x014 Index : 0xb '' +0x018 TotalNumberOfObjects : 0 +0x01c TotalNumberOfHandles : 0 +0x020 HighWaterNumberOfObjects : 1 +0x024 HighWaterNumberOfHandles : 1 +0x028 TypeInfo : _OBJECT_TYPE_INITIALIZER +0x078 TypeLock : _EX_PUSH_LOCK +0x07c Key : 0x75626544 +0x080 CallbackList : _LIST_ENTRY [ 0x867e6620 - 0x867e6620 ] 0: kd> dx -id 0,0,ffffffff8850bbe0 -r1 (*((ntkrpamp!_OBJECT_TYPE_INITIALIZER *)0xffffffff867e65c8)) (*((ntkrpamp!_OBJECT_TYPE_INITIALIZER *)0xffffffff867e65c8)) [Type: _OBJECT_TYPE_INITIALIZER] [+0x000] Length : 0x50 [Type: unsigned short] [+0x002] ObjectTypeFlags : 0x8 [Type: unsigned char] [+0x002 ( 0: 0)] CaseInsensitive : 0x0 [Type: unsigned char] [+0x002 ( 1: 1)] UnnamedObjectsOnly : 0x0 [Type: unsigned char] [+0x002 ( 2: 2)] UseDefaultObject : 0x0 [Type: unsigned char] [+0x002 ( 3: 3)] SecurityRequired : 0x1 [Type: unsigned char] [+0x002 ( 4: 4)] MaintainHandleCount : 0x0 [Type: unsigned char] [+0x002 ( 5: 5)] MaintainTypeList : 0x0 [Type: unsigned char] [+0x002 ( 6: 6)] SupportsObjectCallbacks : 0x0 [Type: unsigned char] [+0x004] ObjectTypeCode : 0x0 [Type: unsigned long] [+0x008] InvalidAttributes : 0x0 [Type: unsigned long] [+0x00c] GenericMapping [Type: _GENERIC_MAPPING] [+0x01c] ValidAccessMask : 0x0 [Type: unsigned long] [+0x020] RetainAccess : 0x0 [Type: unsigned long] [+0x024] PoolType : NonPagedPool (0) [Type: _POOL_TYPE] [+0x028] DefaultPagedPoolCharge : 0x0 [Type: unsigned long] [+0x02c] DefaultNonPagedPoolCharge : 0x30 [Type: unsigned long] [+0x030] DumpProcedure : 0x0 [Type: void (*)(void *,_OBJECT_DUMP_CONTROL *)] [+0x034] OpenProcedure : 0x0 [Type: long (*)(_OB_OPEN_REASON,char,_EPROCESS *,void *,unsigned long *,unsigned long)] [+0x038] CloseProcedure : 0x842c8fa9 [Type: void (*)(_EPROCESS *,void *,unsigned long,unsigned long)] [+0x03c] DeleteProcedure : 0x842a0873 [Type: void (*)(void *)] [+0x040] ParseProcedure : 0x0 [Type: long (*)(void *,void *,_ACCESS_STATE *,char,unsigned long,_UNICODE_STRING *,_UNICODE_STRING *,void *,_SECURITY_QUALITY_OF_SERVICE *,void * *)] [+0x044] SecurityProcedure : 0x8428dd13 [Type: long (*)(void *,_SECURITY_OPERATION_CODE,unsigned long *,void *,unsigned long *,void * *,_POOL_TYPE,_GENERIC_MAPPING *,char)] [+0x048] QueryNameProcedure : 0x0 [Type: long (*)(void *,unsigned char,_OBJECT_NAME_INFORMATION *,unsigned long,unsigned long *,char)] [+0x04c] OkayToCloseProcedure : 0x0 [Type: unsigned char (*)(_EPROCESS *,void *,void *,char)] ; 为该类型对象设置最大访问权限, 即:DEBUG_OBJECT_ALL_ACCESS 0: kd> ed 867e65a0+0x28+1c 0x1F000F
-
重启测试程序,让进程重新创建调试对象:
我们发现附加仍然是失败, 看起来和最开始的测试结果一模一样,我们现在再看一下我们的调试对象的权限是否正常的。
3: kd> !process 0 0 Observing_DebugActiveProcesss.exe PROCESS 87ceb990 SessionId: 1 Cid: 1604 Peb: 7ffdf000 ParentCid: 075c DirBase: bfd67040 ObjectTable: a9e34dd8 HandleCount: 16. Image: Observing_DebugActiveProcesss.exe 3: kd> .process 87ceb990 Implicit process is now 87ceb990 WARNING: .cache forcedecodeuser is not enabled 3: kd> !handle 20 PROCESS 87ceb990 SessionId: 1 Cid: 1604 Peb: 7ffdf000 ParentCid: 075c DirBase: bfd67040 ObjectTable: a9e34dd8 HandleCount: 16. Image: Observing_DebugActiveProcesss.exe Handle table at a9e34dd8 with 16 entries in use ; 注意此处,我们观察到调试权限已经恢复正常了。那么肯定还有其他的问题导致了这个现象。 0020: Object: 888e8318 GrantedAccess: 001f000f Entry: b5468040 Object: 888e8318 Type: (867e65a0) DebugObject ObjectHeader: 888e8300 (new version) HandleCount: 1 PointerCount: 1
-
既然以上的测试失败了, 那么我们现在换一个进程来附加,看看我们上面的关于调试权限的修复是否完成,如果能够附加成功,那么说明我们以上的修复是有用的,如果其他的进程仍然附加失败,那么说明我们以上的修复就失败了。(因为在最开始的测试中, 所有的进程调试附加都是失败的)
我们此处找一个其他的进程来测试:
我们注意到,第二次附加其他的进程成功了。那就说明我们对于第一步的修复已经成功了,关于为什么DNF.exe附加失败那就只能说明还有其他的处理阻止了正常的附加。
第一步测试结论
在这一步骤中, 我们知道了在调用附加函数时的详细逻辑,也知道了TP针对nt!DbgkDebugObjectType->TypeInfo.ValidAccessMask
进行了清零操作,该操作影响了所有的进程,以至于当任何的进程想要创建调试对象时,都无法获取正常的权限,通过恢复该字段之后,进程创建的调试对象就能正常的附加了。但是目标游戏的附加测试仍然是失败的,因此还需要再继续分析第更多步骤来确定是否存在的其他的反调试方案。
========================================================================================
第二步的测试
未完待续