TP保护的研究和学习-用户态下调试附加篇(二)

TP保护的研究和学习-用户态下调试附加篇

引言:

​ 本篇系列旨在研究学习腾讯保护系统的保护方案实现,文中尽量以“发现问题然后尝试解决问题”的模式来处理遇到的问题,此前网上有太多相关的资料都只是给出了一个结论式答案或者方法,对于想要深入学习的人太不友好。 我相信有很多人和我一样在尝试去深入分析的时候遇到了各种问题,我的处理方式就是遇到实际问题实际分析的方式来推进学习;

​ 计算机理论到现在为止已经非常完善了, 它是一门科学;我们要相信科学,那么一切的问题和现象都是有据可依的,耐心肯定能找到原因的。

​ 调试目标是DNF,囊括了老版本的70版本,90版本,以及最新的95版本;也希望给同样对操作系统内核和保护机制感兴趣的人做抛砖引玉之用。

目的

​ 在这里我们的目的是为了能够使用OD成功附加游戏,并观测和学习在这个过程中TP防护的处理方案。

用户态调试的建立和探索

  • 附加游戏开始测试

    尝试用OD进行附加调试DNF.exe时,发生了以下的现象:

    • **观测现象一:**发现OD会附加失败,并弹出错误。

      在这里插入图片描述

    • 观测现象二: 然后测试使用OD对其他的非游戏进程进行附加测试,发现仍然会附加失败。

      在这里插入图片描述

    • 观测现象三: 当使用OD通过打开文件的方式建立调试关系时会打开失败。

      在这里插入图片描述

现象分析

​ 很明显,出现以上三种情况,那么说明在我们尝试附加或者打开程序的时候发生了什么事情,以至于我们的操作都没有达到我们的预期。既然我们现在正在尝试调试,那么此时我们就需要学习一下进程调试的相关知识;

Windows下进程建立调试的方式有两种:

  • 第一种是通过DebugActiveProcess传入一个目标进程的句柄来进行调试;

  • 第二种是通过调用API CreateProcess并传入DEBUG_PROCESSDEBUG_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");
  }
}

使用以上代码在虚拟机中附加游戏进行测试,结果如下:

![在这里插入图片描述](https://img-blog.csdnimg.cn/20190423115405460.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyMjA4ODI2,size_16,color_FFFFFF,t_70)

果然和上面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里面的代码观测一下创建的流程:(以下的流程如果想要完全看懂的话, 需要事先学习句柄相关的知识。)

    1. 调用nt!ObCreateObject函数,并将nt!DbgkDebugObjectType作为参数创建一个_DEBUG_OBJECT对象;

      1. nt!DbgkDebugObjectType是一个由nt!ObCreateObjectType创建的一个基础类型对象,其相应的结构体为OBJECT_TYPE, 怎样理解这个对象?这个对象用来干嘛的? Windows定义了很多种内核对象,然后由nt!ObCreateObjectType创建相应的基础类型对象,方便在实际创建对应的对象时,直接使用该基础对象的数据而不用重新创建;比如线程对象,进程对象等等。(文中描述只为理解其大意,不得作为实际应用参考)

      2. 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)]
        
        
    2. 调用nt!ObInsertObject函数将上一步创建的对象,插入到当前进程的句柄表中;并且在调用时传递了由应用层指定的DesiredAccessMask(DEBUG_OBJECT_ALL_ACCESS);该函数调用成功之后将会通过最后一个参数返回该对象在句柄表中的句柄索引;(该步骤实际比较复杂,请参见实际情况,如果阅读困难,请原谅我,理解大概意思然后自己去深入研究吧!)

      1. 对传入的DesiredAccessMasknt!DbgkDebugObjectType->TypeInfo->ValidAccessMask 与运算获取最大权限,然后进行权限检查;

      2. 将上一步计算最后得到值作为GrantedAccess,写入到句柄表项中的高32位中,具体字段分配,参见_HANDLE_TABLE_ENTRY

      3. 进程句柄表存放在进程对象_EPROCESS::ObjectTable 中,该项是一个结构体(_HANDLE_TABLE),有关于该结构体的详细介绍这里不详述,在这个结构体中,_HANDLE_TABLE::TableCode指向了实际的句柄表数组的起始位置。

        • 每一个句柄项目由8个字节构体,结构请参见:_HANDLE_TABLE_ENTRY
      4. 怎样索引到句柄所在的位置呢? 公式为: _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
        
        

    ​ 大致理解了以上的流程之后, 我们现在知道我们创建的调试对象的权限是放在什么地方的,以及他的访问掩码是怎样来的,现在得到以下的结论:

    1. 调试类型对象的ValidAccessMask为0, 他为创建的调试对象提供了能赋予的最大的权限,如果该值为0,那么调试对象将不能被分配到任何权限;
    2. 我们的调试对象的访问权限存放在 _HANDLE_TABLE_ENTRY中,根据以上的观测结果,我们得知这个值为0;该值为0的情况下,在我们的应用程序中, 我们将不用使用调试对象做任何事情,所有有关于调试的行为都将被拒绝;
  • 针对以上结果的恢复测试

​ 通过以上的测试, 我们已经非常清楚造成此问题的根本原因是什么,就是因为我们创建的对象没有权限导致的。那么我们现在来尝试处理一下以上的问题看看能不能让我们创建的对象重新拥有权限:

  1. 恢复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
    
    
  2. 重启测试程序,让进程重新创建调试对象:

    在这里插入图片描述

    我们发现附加仍然是失败, 看起来和最开始的测试结果一模一样,我们现在再看一下我们的调试对象的权限是否正常的。

    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
    
    
  3. 既然以上的测试失败了, 那么我们现在换一个进程来附加,看看我们上面的关于调试权限的修复是否完成,如果能够附加成功,那么说明我们以上的修复是有用的,如果其他的进程仍然附加失败,那么说明我们以上的修复就失败了。(因为在最开始的测试中, 所有的进程调试附加都是失败的)

    ​ 我们此处找一个其他的进程来测试:

    在这里插入图片描述

    我们注意到,第二次附加其他的进程成功了。那就说明我们对于第一步的修复已经成功了,关于为什么DNF.exe附加失败那就只能说明还有其他的处理阻止了正常的附加。

第一步测试结论

​ 在这一步骤中, 我们知道了在调用附加函数时的详细逻辑,也知道了TP针对nt!DbgkDebugObjectType->TypeInfo.ValidAccessMask进行了清零操作,该操作影响了所有的进程,以至于当任何的进程想要创建调试对象时,都无法获取正常的权限,通过恢复该字段之后,进程创建的调试对象就能正常的附加了。但是目标游戏的附加测试仍然是失败的,因此还需要再继续分析第更多步骤来确定是否存在的其他的反调试方案。

========================================================================================

第二步的测试

​ 未完待续

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章