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進行了清零操作,該操作影響了所有的進程,以至於當任何的進程想要創建調試對象時,都無法獲取正常的權限,通過恢復該字段之後,進程創建的調試對象就能正常的附加了。但是目標遊戲的附加測試仍然是失敗的,因此還需要再繼續分析第更多步驟來確定是否存在的其他的反調試方案。

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

第二步的測試

​ 未完待續

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