ObRegisterCallbacks Bypass(1)

[0x00] Overview

먼저 ObRegisterCallbacks Bypass 에 대해 알아보겠습니다. 해당 내용은 굉장히 많은 포인트에서 사용 가능합니다. 우회를 하기 위해 먼저 실제 ObRegisterCallbacks 함수가 어떻게 동작하는지, 구조 등을 분석해보겠습니다.

[0x01] ObRegisterCallbacks Debugging

먼저 이전 챕터에서 ObRegisterCallback 을 사용하기 위해 몇 가지 구조체를 설명했었습니다.

현재 제가 만든 드라이버는 우회라고 말하기도 민망할 정도로 쉽습니다. PreCallback 함수를 찾아 프로세스 이름만 변조해줘도 탐지 할 수 없습니다. 우리의 목적은 notepad.exe 프로세스를 x64dbg를 이용하여 디버깅 하는 것입니다.

눈에는 눈, 이에는 이 라는 말과 같이 커널 드라이버는 커널 드라이버로 무력화 시키는 것이 가장 좋습니다. 불필요하게 메모리를 바꾸기 위해 수작업을 할 필요도 없습니다. 저는 이러한 방법 중에 ObUnRegisterCallbacks 함수를 이용하는 방법을 택했습니다. 바로 등록 된 콜백 루틴을 해제하는 함수입니다.

그러기 위해선 RegistrationHandle 을 추적해야 합니다. (콜백 루틴 식별) 어떻게 찾을 수 있을지 분석해보겠습니다.

[-] Analysis

IDA를 이용하여 ntoskrnl.exe 파일을 열어 ObRegisterCallbacks 함수를 살펴보겠습니다.

ObRegisterCallbacks 함수와 관련된 구조체들은 MS 심볼 서버에 존재하지 않습니다. 그렇기 때문에 최소한의 구조체만 아래와 같이 정의하였습니다.

위와 같이 정의하고 Hexray 기능을 이용해 디컴파일하면 아래와 같은 의사코드를 볼 수 있습니다.

signed __int64 __fastcall ObRegisterCallbacks(OB_CALLBACK_REGISTRATION *CallbackRegistration, PVOID *RegistrationHandle)
{
  OB_CALLBACK_REGISTRATION *vCallbackRegistration; // r15
  unsigned int v3; // edi
  PVOID *vRegistrationHandle; // r12
  signed int v5; // ebx
  unsigned __int16 v6; // ax
  unsigned int v7; // ebp
  unsigned __int16 *v8; // rax
  unsigned __int16 *v9; // rsi
  size_t v10; // r8
  char *v11; // rcx
  unsigned int v12; // ebp
  __int64 **v13; // r14
  __int64 *v14; // rcx
  __int64 *v15; // rcx
  unsigned __int64 v16; // rbx
  __int64 v17; // rcx
  __int64 v18; // rax
  unsigned __int16 *v20; // r14
  struct _KTHREAD *v21; // rax
  __int64 v22; // rcx
  unsigned __int16 **v23; // rax

  vCallbackRegistration = CallbackRegistration;
  v3 = 0;
  vRegistrationHandle = RegistrationHandle;
  v5 = 0;
  if ( (CallbackRegistration->Version & 0xFF00) != 0x100 )
    return 0xC000000Di64;
  v6 = CallbackRegistration->OperationRegistrationCount;
  if ( !v6 )
    return 0xC000000Di64;
  v7 = (v6 << 6) + *(&CallbackRegistration->Altitude.MaximumLength + 1) + 0x20;
  v8 = (unsigned __int16 *)ExAllocatePoolWithTag(PagedPool, v7, 'lFbO');
  v9 = v8;
  if ( !v8 )
    return 0xC000009Ai64;
  memset(v8, 0, v7);
  *v9 = 0x100;
  *((_QWORD *)v9 + 1) = *(PVOID *)((char *)&vCallbackRegistration->RegistrationContext + 4);
  v10 = *(&vCallbackRegistration->Altitude.MaximumLength + 1);
  v9[9] = v10;
  v9[8] = v10;
  v11 = (char *)v9 + v7 - (unsigned int)v10;
  *((_QWORD *)v9 + 3) = v11;
  memmove(v11, *(const void **)((char *)&vCallbackRegistration->Altitude.Buffer + 4), v10);
  v12 = 0;
  if ( vCallbackRegistration->OperationRegistrationCount > 0u )
  {
    while ( 1 )
    {
      v13 = (__int64 **)(*(char **)((char *)&vCallbackRegistration->OperationRegistration + 4) + 0x20 * v12);
      if ( !*((_DWORD *)v13 + 2) || !(*(_BYTE *)(**v13 + 0x42) & 0x40) )
        break;
      v14 = v13[2];
      if ( v14 )
      {
        if ( !(unsigned int)MmVerifyCallbackFunctionCheckFlags(v14, 0x20i64) )
          goto LABEL_23;
      }
      else if ( !v13[3] )
      {
        break;
      }
      v15 = v13[3];
      if ( v15 && !(unsigned int)MmVerifyCallbackFunctionCheckFlags(v15, 0x20i64) )
      {
LABEL_23:
        v5 = 0xC0000022;
        goto LABEL_24;
      }
      v16 = (unsigned __int64)&v9[0x20 * (unsigned __int64)v12 + 0x10];
      *(_QWORD *)(v16 + 8) = v16;
      *(_QWORD *)v16 = v16;
      ExInitializePushLock((PKSPIN_LOCK)(v16 + 0x38));
      *(_DWORD *)(v16 + 0x10) = *((_DWORD *)v13 + 2);
      *(_QWORD *)(v16 + 0x18) = v9;
      v17 = **v13;
      *(_QWORD *)(v16 + 0x20) = v17;
      *(_QWORD *)(v16 + 0x28) = v13[2];
      *(_QWORD *)(v16 + 0x30) = v13[3];
      v5 = ObpInsertCallbackByAltitude(v17, v16);
      if ( v5 >= 0 )
      {
        ++v9[1];
        if ( ++v12 < (unsigned __int16)vCallbackRegistration->OperationRegistrationCount )
          continue;
      }
      goto LABEL_12;
    }
    v5 = 0xC000000D;
  }
LABEL_12:
  if ( v5 < 0 )
  {
LABEL_24:
    if ( v9[1] > 0u )
    {
      do
      {
        v20 = &v9[0x20 * (unsigned __int64)v3 + 0x10];
        v21 = KeGetCurrentThread();
        --v21->SpecialApcDisable;
        ExAcquirePushLockExclusiveEx(*((_QWORD *)v20 + 4) + 0xB8i64, 0i64);
        v22 = *(_QWORD *)v20;
        if ( *(unsigned __int16 **)(*(_QWORD *)v20 + 8i64) != v20
          || (v23 = (unsigned __int16 **)*((_QWORD *)v20 + 1), *v23 != v20) )
        {
          __fastfail(3u);
        }
        *v23 = (unsigned __int16 *)v22;
        *(_QWORD *)(v22 + 8) = v23;
        ExReleasePushLockEx(*((_QWORD *)v20 + 4) + 0xB8i64, 0i64);
        KiLeaveGuardedRegionUnsafe(KeGetCurrentThread());
        ++v3;
      }
      while ( v3 < v9[1] );
    }
    ExFreePoolWithTag(v9, 'lFbO');
  }
  else
  {
    if ( v9[1] > 0u )
    {
      do
      {
        v18 = v3++;
        *(_DWORD *)&v9[0x20 * v18 + 0x1A] |= 1u;
      }
      while ( v3 < v9[1] );
    }
    *vRegistrationHandle = v9;
  }
  return (unsigned int)v5;
}

OB_CALLBACK_REGISTRATION 에 있는 멤버들에 대한 검증절차가 존재하고, 콜백 루틴에 대한 유효성 검증이 존재합니다. 모든 내용을 분석할 필요 없습니다. 실제 우리가 필요한 부분은 RegistrationHandle에 대한 내용입니다. 모든 검증과정을 거쳐 유효한 콜백 루틴의 경우 RegistrationHandle 값에 v9 라는 변수의 값을 저장하는 것을 볼 수 있습니다.

windbg를 이용하여 실제 이전 챕터에서 만든 드라이버를 가지고 분석해보겠습니다. 아래와 같이 해당 드라이버가 로드 될 때 예외가 발생하도록 명령을 입력합니다.

Break instruction exception - code 80000003 (first chance)
*******************************************************************************
*                                                                             *
*   You are seeing this message because you pressed either                    *
*       CTRL+C (if you run console kernel debugger) or,                       *
*       CTRL+BREAK (if you run GUI kernel debugger),                          *
*   on your debugger machine's keyboard.                                      *
*                                                                             *
*                   THIS IS NOT A BUG OR A SYSTEM CRASH                       *
*                                                                             *
* If you did not intend to break into the debugger, press the "g" key, then   *
* press the "Enter" key now.  This message might immediately reappear.  If it *
* does, press "g" and "Enter" again.                                          *
*                                                                             *
*******************************************************************************
nt!DbgBreakPointWithStatus:
fffff806`24474600 cc              int     3
0: kd> sxe ld antikerneldebugging.sys
0: kd> g

이제 드라이버를 로드하면 아래와 같이 예외가 발생하고 디버깅이 가능합니다.

nt!DebugService2+0x5:
fffff806`24474655 cc              int     3
5: kd> u antikerneldebugging!obcallbackreg
AntiKernelDebugging!ObCallbackReg [D:\Shh0ya\ProcessProtection\AntiKernelDebugging\callbacks.h @ 131]:
fffff806`235a1538 4057            push    rdi
fffff806`235a153a 4883ec70        sub     rsp,70h
fffff806`235a153e 488d442440      lea     rax,[rsp+40h]
fffff806`235a1543 488bf8          mov     rdi,rax
fffff806`235a1546 33c0            xor     eax,eax
fffff806`235a1548 b928000000      mov     ecx,28h
fffff806`235a154d f3aa            rep stos byte ptr [rdi]
fffff806`235a154f 488d442420      lea     rax,[rsp+20h]

아래와 같이 ObRegisterCallbacks를 호출하는 주소에 브레이크 포인트를 설치합니다.

AntiKernelDebugging!ObCallbackReg:
fffff806`235a1538 4057            push    rdi
fffff806`235a153a 4883ec70        sub     rsp,70h
fffff806`235a153e 488d442440      lea     rax,[rsp+40h]
fffff806`235a1543 488bf8          mov     rdi,rax
fffff806`235a1546 33c0            xor     eax,eax
fffff806`235a1548 b928000000      mov     ecx,28h
fffff806`235a154d f3aa            rep stos byte ptr [rdi]
fffff806`235a154f 488d442420      lea     rax,[rsp+20h]
fffff806`235a1554 488bf8          mov     rdi,rax
fffff806`235a1557 33c0            xor     eax,eax
fffff806`235a1559 b920000000      mov     ecx,20h
fffff806`235a155e f3aa            rep stos byte ptr [rdi]
fffff806`235a1560 ff15e20a0000    call    qword ptr [AntiKernelDebugging!_imp_ObGetFilterVersion (fffff806`235a2048)]
fffff806`235a1566 6689442440      mov     word ptr [rsp+40h],ax
fffff806`235a156b b801000000      mov     eax,1
fffff806`235a1570 6689442442      mov     word ptr [rsp+42h],ax
fffff806`235a1575 488d1564040000  lea     rdx,[AntiKernelDebugging! ?? ::FNODOBFM::`string' (fffff806`235a19e0)]
fffff806`235a157c 488d4c2448      lea     rcx,[rsp+48h]
fffff806`235a1581 ff15910a0000    call    qword ptr [AntiKernelDebugging!_imp_RtlInitUnicodeString (fffff806`235a2018)]
fffff806`235a1587 48c744245800000000 mov   qword ptr [rsp+58h],0
fffff806`235a1590 488b05210b0000  mov     rax,qword ptr [AntiKernelDebugging!PsProcessType (fffff806`235a20b8)]
fffff806`235a1597 4889442420      mov     qword ptr [rsp+20h],rax
fffff806`235a159c c744242801000000 mov     dword ptr [rsp+28h],1
fffff806`235a15a4 488d0545000000  lea     rax,[AntiKernelDebugging!PreCallback (fffff806`235a15f0)]
fffff806`235a15ab 4889442430      mov     qword ptr [rsp+30h],rax
fffff806`235a15b0 488d0529000000  lea     rax,[AntiKernelDebugging!PostCallback (fffff806`235a15e0)]
fffff806`235a15b7 4889442438      mov     qword ptr [rsp+38h],rax
fffff806`235a15bc 488d442420      lea     rax,[rsp+20h]
fffff806`235a15c1 4889442460      mov     qword ptr [rsp+60h],rax
fffff806`235a15c6 488d15431a0000  lea     rdx,[AntiKernelDebugging!hRegistration (fffff806`235a3010)]
fffff806`235a15cd 488d4c2440      lea     rcx,[rsp+40h]
fffff806`235a15d2 ff15600a0000    call    qword ptr [AntiKernelDebugging!_imp_ObRegisterCallbacks (fffff806`235a2038)]
fffff806`235a15d8 4883c470        add     rsp,70h
fffff806`235a15dc 5f              pop     rdi
fffff806`235a15dd c3              ret

5: kd> bp fffff806`235a15d2
5: kd> g

이제 해당 위치에서의 파라미터 값을 주목해야 합니다. ObRegisterCallbacks 함수를 호출하기 전에 rdx 레지스터의 값(RegistrationHandle)을 확인하고, Step Over로 호출 후에 값을 확인합니다.

5: kd> dp @rdx
fffff806`235a3010  00000000`00000000 00000000`00000000
fffff806`235a3020  fffff806`24310c60 00000000`00000000
fffff806`235a3030  000002e8`000002e0 000003f8`00000450
fffff806`235a3040  00000000`00000000 00000000`00000000
fffff806`235a3050  00000000`00000000 00000000`00000000
fffff806`235a3060  00000000`00000000 00000000`00000000
fffff806`235a3070  00000000`00000000 00000000`00000000
fffff806`235a3080  00000000`00000000 00000000`00000000
5: kd> p
AntiKernelDebugging!ObCallbackReg+0xa0:
fffff806`235a15d8 4883c470        add     rsp,70h
5: kd> dp fffff806`235a3010
fffff806`235a3010  ffff948c`8b306110 00000000`00000000
fffff806`235a3020  fffff806`24310c60 00000000`00000000
fffff806`235a3030  000002e8`000002e0 000003f8`00000450
fffff806`235a3040  00000000`00000000 00000000`00000000
fffff806`235a3050  00000000`00000000 00000000`00000000
fffff806`235a3060  00000000`00000000 00000000`00000000
fffff806`235a3070  00000000`00000000 00000000`00000000
fffff806`235a3080  00000000`00000000 00000000`00000000
5: kd> dp ffff948c`8b306110
ffff948c`8b306110  00000000`00010100 00000000`00000000
ffff948c`8b306120  00000000`000c000c ffff948c`8b306170
ffff948c`8b306130  ffffc207`eb2ae448 ffffc207`eb2ae448
ffff948c`8b306140  00000001`00000001 ffff948c`8b306110
ffff948c`8b306150  ffffc207`eb2ae380 fffff806`235a15f0
ffff948c`8b306160  fffff806`235a15e0 00000000`00000000
ffff948c`8b306170  00300030`00300033 00000000`00300030
ffff948c`8b306180  7346744e`03080000 00000000`00005128

알 수 없는 주소 값이 할당되었고 내부에는 어떤 값들이 채워져 있습니다. 이제 이 값들의 정체를 알아내고, 연관성 있는 무언가를 찾아 접근하는 방법을 찾아야 합니다.

[-] OBJECT_TYPE Structure

현재 운영체제에 존재하는 콜백 루틴들은 목록화되어 어딘가에 보관하고 있습니다. 그 내용을 가지고 있는 구조체가 바로 OBJECT_TYPE 입니다. 문서화되어 있지 않으므로 windbg에서 직접 찾아볼 수 있습니다.

5: kd> dt_OBJECT_TYPE
nt!_OBJECT_TYPE
   +0x000 TypeList         : _LIST_ENTRY
   +0x010 Name             : _UNICODE_STRING
   +0x020 DefaultObject    : Ptr64 Void
   +0x028 Index            : UChar
   +0x02c TotalNumberOfObjects : Uint4B
   +0x030 TotalNumberOfHandles : Uint4B
   +0x034 HighWaterNumberOfObjects : Uint4B
   +0x038 HighWaterNumberOfHandles : Uint4B
   +0x040 TypeInfo         : _OBJECT_TYPE_INITIALIZER
   +0x0b8 TypeLock         : _EX_PUSH_LOCK
   +0x0c0 Key              : Uint4B
   +0x0c8 CallbackList     : _LIST_ENTRY

위 결과를 토대로 구조체를 정의하면 아래와 같습니다.

typedef struct _OBJECT_TYPE
{
    LIST_ENTRY                 TypeList;
    UNICODE_STRING             Name;
    PVOID                      DefaultObject;
    ULONG                      Index;
    ULONG                      TotalNumberOfObjects;
    ULONG                      TotalNumberOfHandles;
    ULONG                      HighWaterNumberOfObjects;
    ULONG                      HighWaterNumberOfHandles;
    OBJECT_TYPE_INITIALIZER    TypeInfo;
    EX_PUSH_LOCK               TypeLock;
    ULONG                      Key;
    LIST_ENTRY                 CallbackList;
}OBJECT_TYPE,*POBJECT_TYPE;

맨 마지막 멤버에 CallbackList가 존재하는 것을 볼 수 있습니다. 콜백 루틴을 등록할 때 OB_OPERATION_REGISTRATIONObjectType을 설정했던 것을 기억해야 합니다.

이 때 PsProcessType 이라는 커널 내 전역변수를 사용했습니다. 이제 windbg에서 다시 한번 구조체를 확인해보겠습니다.

4: kd> dt_OBJECT_TYPE poi(nt!PsProcessType)
nt!_OBJECT_TYPE
   +0x000 TypeList         : _LIST_ENTRY [ 0xffffc207`eb2ae380 - 0xffffc207`eb2ae380 ]
   +0x010 Name             : _UNICODE_STRING "Process"
   +0x020 DefaultObject    : (null) 
   +0x028 Index            : 0x7 ''
   +0x02c TotalNumberOfObjects : 0x79
   +0x030 TotalNumberOfHandles : 0x3d0
   +0x034 HighWaterNumberOfObjects : 0x8a
   +0x038 HighWaterNumberOfHandles : 0x440
   +0x040 TypeInfo         : _OBJECT_TYPE_INITIALIZER
   +0x0b8 TypeLock         : _EX_PUSH_LOCK
   +0x0c0 Key              : 0x636f7250
   +0x0c8 CallbackList     : _LIST_ENTRY [ 0xffff948c`8b306130 - 0xffff948c`8b306130 ]
4: kd> dp 0xffff948c8b306130 
ffff948c`8b306130  ffffc207`eb2ae448 ffffc207`eb2ae448
ffff948c`8b306140  00000001`00000001 ffff948c`8b306110
ffff948c`8b306150  ffffc207`eb2ae380 fffff806`235a15f0
ffff948c`8b306160  fffff806`235a15e0 00000000`00000000
ffff948c`8b306170  00300030`00300033 00000000`00300030
ffff948c`8b306180  7346744e`03080000 00000000`00005128
ffff948c`8b306190  00010000`00000c6b 00520054`00530049
ffff948c`8b3061a0  0041004d`005c0059 004e0049`00480043

이제 LIST_ENTRY 로 이루어진 CallbackList에 접근하는 방법에 대해 알았습니다.

[-] CALLBACK_ENTRY_ITEM

typedef struct _CALLBACK_ENTRY_ITEM {
    LIST_ENTRY EntryItemList;
    OB_OPERATION Operations1;
    OB_OPERATION Operations2;
    CALLBACK_ENTRY* CallbackEntry;
    POBJECT_TYPE ObjectType;
    POB_PRE_OPERATION_CALLBACK PreOperation;
    POB_POST_OPERATION_CALLBACK PostOperation;
}CALLBACK_ENTRY_ITEM, *PCALLBACK_ENTRY_ITEM;
typedef struct _CALLBACK_ENTRY{
     INT16 Version;
     unsigned char unknown[6];
     POB_OPERATION_REGISTRATION RegistrationContext;
     UNICODE_STRING Altitude;
     CALLBACK_ENTRY_ITEM Items;
}CALLBACK_ENTRY, *PCALLBACK_ENTRY;

위와 같이 문서화되지 않은 구조체가 존재한다. 그리고 위에서 구한 OBJECT_TYPECallbackList의 링크 값은 CALLBACK_ENTRY_ITEM 에 대한 포인터입니다.

[-] Find Register Callback routine

이제 하나씩 디버깅하며 위의 내용들에 대해 증명해보겠습니다.

먼저 OBJECT_TYPECallbackList 멤버를 찾습니다.

4: kd> dt_OBJECT_TYPE poi(nt!PsProcessType)
nt!_OBJECT_TYPE
   +0x000 TypeList         : _LIST_ENTRY [ 0xffffc207`eb2ae380 - 0xffffc207`eb2ae380 ]
   +0x010 Name             : _UNICODE_STRING "Process"
   +0x020 DefaultObject    : (null) 
   +0x028 Index            : 0x7 ''
   +0x02c TotalNumberOfObjects : 0x79
   +0x030 TotalNumberOfHandles : 0x3d0
   +0x034 HighWaterNumberOfObjects : 0x8a
   +0x038 HighWaterNumberOfHandles : 0x440
   +0x040 TypeInfo         : _OBJECT_TYPE_INITIALIZER
   +0x0b8 TypeLock         : _EX_PUSH_LOCK
   +0x0c0 Key              : 0x636f7250
   +0x0c8 CallbackList     : _LIST_ENTRY [ 0xffff948c`8b306130 - 0xffff948c`8b306130 ]

콜백 리스트의 값을 확인합니다.

4: kd> dp 0xffff948c`8b306130
ffff948c`8b306130  ffffc207`eb2ae448 ffffc207`eb2ae448
ffff948c`8b306140  00000001`00000001 ffff948c`8b306110
ffff948c`8b306150  ffffc207`eb2ae380 fffff806`235a15f0
ffff948c`8b306160  fffff806`235a15e0 00000000`00000000
ffff948c`8b306170  00300030`00300033 00000000`00300030
ffff948c`8b306180  7346744e`03080000 00000000`00005128
ffff948c`8b306190  00010000`00000c6b 00520054`00530049
ffff948c`8b3061a0  0041004d`005c0059 004e0049`00480043

CALLBACK_ENTRY_ITEM 구조에 맞게 만들어보겠습니다.

ffff948c`8b306130 ; 0x000 EntryItemList    : _LIST_ENTRY [ ffffc207`eb2ae448 - ffffc207`eb2ae448]
ffff948c`8b306140 ; 0x010 Operation1       : 0x1
ffff948c`8b306144 ; 0x014 Operation2       : 0x1
ffff948c`8b306148 ; 0x018 CallbackEntry    : CALLBACK_ENTRY* (ffff948c`8b306110)
ffff948c`8b306150 ; 0x020 ObjectType       : OBJECT_TYPE* (ffffc207`eb2ae380)
ffff948c`8b306158 ; 0x028 PreOperation     : OB_PRE_OPERATION_CALLBACK* (fffff806`235a15f0)
ffff948c`8b306160 ; 0x030 PostOperation    : OB_POST_OPERATION_CALLBACK* (fffff806`235a15e0)

ObjectTypePreOperation, PostOperation 멤버를 통해 구조체가 일치하는지 확인합니다.

4: kd> dt_OBJECT_TYPE ffffc207`eb2ae380
nt!_OBJECT_TYPE
   +0x000 TypeList         : _LIST_ENTRY [ 0xffffc207`eb2ae380 - 0xffffc207`eb2ae380 ]
   +0x010 Name             : _UNICODE_STRING "Process"
   +0x020 DefaultObject    : (null) 
   +0x028 Index            : 0x7 ''
   +0x02c TotalNumberOfObjects : 0x79
   +0x030 TotalNumberOfHandles : 0x3d0
   +0x034 HighWaterNumberOfObjects : 0x8a
   +0x038 HighWaterNumberOfHandles : 0x440
   +0x040 TypeInfo         : _OBJECT_TYPE_INITIALIZER
   +0x0b8 TypeLock         : _EX_PUSH_LOCK
   +0x0c0 Key              : 0x636f7250
   +0x0c8 CallbackList     : _LIST_ENTRY [ 0xffff948c`8b306130 - 0xffff948c`8b306130 ]

4: kd> u fffff806`235a15f0 l5
AntiKernelDebugging!PreCallback [D:\Shh0ya\ProcessProtection\AntiKernelDebugging\callbacks.h @ 56]:
fffff806`235a15f0 4889542410      mov     qword ptr [rsp+10h],rdx
fffff806`235a15f5 48894c2408      mov     qword ptr [rsp+8],rcx
fffff806`235a15fa 57              push    rdi
fffff806`235a15fb 4883ec40        sub     rsp,40h
fffff806`235a15ff 488b05fa190000  mov     rax,qword ptr [AntiKernelDebugging!__security_cookie (fffff806`235a3000)]

4: kd> u fffff806`235a15e0 l5
AntiKernelDebugging!PostCallback [D:\Shh0ya\ProcessProtection\AntiKernelDebugging\callbacks.h @ 81]:
fffff806`235a15e0 4889542410      mov     qword ptr [rsp+10h],rdx
fffff806`235a15e5 48894c2408      mov     qword ptr [rsp+8],rcx
fffff806`235a15ea c3              ret
fffff806`235a15eb cc              int     3
fffff806`235a15ec cc              int     3

정확하게 일치하는 것을 볼 수 있습니다. 하지만 아직 가장 중요한 RegistrationHandle에 관한 내용이 없습니다. CALLBACK_ENTRY_ITEM 내에 CallbackEntry 내용을 살펴보겠습니다.

4: kd> dp ffff948c`8b306110
ffff948c`8b306110  00000000`00010100 00000000`00000000
ffff948c`8b306120  00000000`000c000c ffff948c`8b306170
ffff948c`8b306130  ffffc207`eb2ae448 ffffc207`eb2ae448
ffff948c`8b306140  00000001`00000001 ffff948c`8b306110
ffff948c`8b306150  ffffc207`eb2ae380 fffff806`235a15f0
ffff948c`8b306160  fffff806`235a15e0 00000000`00000000
ffff948c`8b306170  00300030`00300033 00000000`00300030
ffff948c`8b306180  7346744e`03080000 00000000`00005128

CALLBACK_ENTRY 구조체에 맞게 만들어보면 아래와 같습니다.

ffff948c`8b306110 ; 0x000 Version          : 0x100
ffff948c`8b306112 ; 0x002 Unknown[6]       : 0x00000000`0001
ffff948c`8b306118 ; 0x008 RegistrationContext       : OB_OPERATION_REGISTRATION* (0x0)
ffff948c`8b306120 ; 0x010 Altitude         : UNICODE_STRING
ffff948c`8b306130 ; 0x020 Items            : CALLBACK_ENTRY_ITEM

자 지금 CALLBACK_ENTRY 구조로 되어있는 0xffff948c'8b306110 은 무언가와 매우 닮아있습니다. 바로 이 위치가 RegistrationHandle 입니다. 즉 CALLBACK_ENTRY_ITEMCallbackEntry 포인터의 값이 RegistrationHandle 입니다.

이제 이 값을 이용해 ObUnRegisterCallbacks 함수를 호출할 수 있습니다.

[0x02] Conclusion

ObUnRegisterCallbacks 함수를 이용하여 콜백 루틴을 해제하는 방법에 대해 알아봤습니다. 이제 다음 챕터에서 실제로 해제를 하는 드라이버 코드를 작성하고 테스트해보겠습니다.