Гайд Anti-debug под прицелом

Today is the day I live
Пользователь
Статус
Оффлайн
Регистрация
2 Июл 2020
Сообщения
125
Реакции[?]
237
Поинты[?]
66K
Мне стало интересно, какие anti-debug трюки часто используют.
Здесь будет рассмотрены популярные anti-debug трюки,как они работают в ядре и в добавок посмотрим фишки протекторов(VMP,Themida/WinLicense ).
Для наглядности здесь будут сами примеры anti-отладки.
Детекты через FindWindows/поиск процесса - в данной статье не будут разбираться.


Так же в конце будут файлы, чтобы вы могли поиграться с ними, если захотите.

Отказ от ответственности.
Данная статья публикуется только в образовательных целях.
Автор не пытается никого оскорбить, а только лишь хочет поднять некоторые моменты, чтобы новичкам их было легче понять.
Заранее извиняюсь, что беру псевдокод из иды, но постараюсь объяснить некоторые моменты, чтобы вам было более понятно.



Оглавление:
-UM
-kernel32/kernelbase + примеры
-ntdll + примеры
-KM
-как начинается отладка
-как происходи отсоединение дебаггера
-анализ как работают трюки в KM
-Маленький анализ протекторов(VMP/Themida)
-Печальная темида
-VMP
-Идея автора VMP и небольшой пример обхода anti-debug VMP(x64).


- Начало анализа


C++:
BOOL __stdcall IsDebuggerPresent()
{
  return NtCurrentPeb()->BeingDebugged;
}

Мы просто стучим в PEB и проверяем BeingDebugged(смещение - 0x2).
Здесь ничего интересного, поскольку можно просто перезаписать значение на 0.
Дальше будет объяснено, откуда появляется запись в BeingDebugged при начале отладки.

P.S для новичков.
Для получения адреса к PEB вы можете вызвать:
__readgsqword(0x60)(x64)/__readfsdword(0x30)(x32)

C++:
BOOL __stdcall CheckRemoteDebuggerPresent(HANDLE hProcess, PBOOL pbDebuggerPresent)
{

  WINBOOL IsDebugPortExist; // ebx
  NTSTATUS nt_status; // eax
  BOOL result; // eax
  __int64 IsDebugPort; // [rsp+40h] [rbp+8h] BYREF ( __int32   if x32 and work under wow64)

  IsDebugPortExist = FALSE;

  if ( hProcess && pbDebuggerPresent )
  {
    nt_status = NtQueryInformationProcess(hProcess, ProcessDebugPort, &IsDebugPort, sizeof(IsDebugPort), (PULONG)FALSE);

    if (NT_SUCCESS(nt_status))
    {
      result = TRUE;

      LOBYTE(IsDebugPortExist) = IsDebugPort != FALSE;

      *pbDebuggerPresent = IsDebugPortExist;

      return result;
    }

    BaseSetLastNTError(nt_status);
  }
  else
  {
    RtlSetLastWin32Error(ERROR_INVALID_PARAMETER);
  }

  return FALSE;
}

Здесь идёт вызов NtQueryInformationProcess с ProcessDebugPort.
Отладчик просто так не может работать DebugPort'а(далее будет объяснено почему).
Этот WinApi используют часто,поскольку он задокументирован(хотя лучше вызывать NTAPI напрямую).

C++:
BOOL __stdcall CloseHandle(HANDLE hObject)
{

  HANDLE hObject_1; // rbx
  void (*best_function)(void); // rax
  NTSTATUS nt_status; // eax
  void *phPrevValue; // [rsp+30h] [rbp+8h] BYREF

  hObject_1 = hObject;

  if ( (unsigned int)hObject >= 0xFFFFFFF4
    && (unsigned int)hObject <= 0xFFFFFFF6
    && SetStdHandleEx((DWORD)hObject, NULL, &phPrevValue) )
  {
    hObject_1 = phPrevValue;
  }

  best_function = (void (*)(void))SbSelectProcedure(0xABABABABi64, TRUE, "kLsE");

  if ( best_function )
    best_function();

  nt_status = NtClose(hObject_1);

  if (NT_SUCCESS(nt_status))
    return TRUE;
  BaseSetLastNTError((unsigned int)nt_status);
  return FALSE;
}

CloseHandle вызывает NtClose и если закрыть невалидный HANDLE,то ничего не произойдёт, но вот в случае дебагинга процесса будет вызвано исключение!
(ну или если применить один трюк, но об этом позже :D ).
Маленький пример обнаружения дебаггера:
C++:
auto is_close_invalid_handle() -> bool
{
    __try
    {
        CloseHandle((HANDLE)0xDEADBEEF);
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        return TRUE;
    }
    return FALSE;

}



Однако можно сделать дескриптор защищённым и если попытаться закрыть HANDLE под дебаггером,то будет вызвано исключение.
Давайте посмотрим, код происходит вызов SetHandleInformation.

C++:
BOOL __stdcall SetHandleInformation(HANDLE hObject, DWORD dwMask, DWORD dwFlags)
{

  char dwFlags_2; // si
  char dwMask_2; // bp
  HANDLE hObject_2; // rbx
  NTSTATUS nt_status; // eax
  BOOL result; // edi
  OBJECT_HANDLE_FLAG_INFORMATION ObjectInformation; // [rsp+40h] [rbp+8h] BYREF

  dwFlags_2 = dwFlags;
  dwMask_2 = dwMask;
  hObject_2 = hObject;

  switch ( (_DWORD)hObject )
  {
    case 0xFFFFFFF4:
     hObject_2 = NtCurrentPeb()->ProcessParameters->StandardError;
      break;

    case 0xFFFFFFF5:
      hObject_2 = NtCurrentPeb()->ProcessParameters->StandardOutput;
      break;

    case 0xFFFFFFF6:
      hObject_2 = NtCurrentPeb()->ProcessParameters->StandardInput;
      break;

  }

  nt_status = NtQueryObject(hObject_2, ObjectHandleFlagInformation, &ObjectInformation, sizeof(OBJECT_HANDLE_FLAG_INFORMATION ), NULL);

  if (!NT_SUCCESS(nt_status))
    goto BAD_STATUS;

  result = TRUE;

  if ( (dwMask_2 & HANDLE_FLAG_INHERIT) != NULL )
    ObjectInformation.Inherit  = dwFlags_2 & HANDLE_FLAG_INHERIT;

  if ( (dwMask_2 & HANDLE_FLAG_PROTECT_FROM_CLOSE) != NULL )
    ObjectInformation.ProtectFromClose  = (dwFlags_2 & HANDLE_FLAG_PROTECT_FROM_CLOSE) != NULL;

  nt_status = NtSetInformationObject(hObject_2, ObjectHandleFlagInformation, &ObjectInformation, sizeof(OBJECT_HANDLE_FLAG_INFORMATION ));
  if ( !NT_SUCCESS(nt_status))
  {
BAD_STATUS:
    BaseSetLastNTError((unsigned int)nt_status);
    result = FALSE;
  }
  return result;

}

Можно установить у дескриптора HANDLE_FLAG_PROTECT_FROM_CLOSE и при закрытие HANDLE'а система выбросит исключение из-за присутствия отладчика или трюка.
Потом мы посмотрим, как это работает в ядре.
Пример детекта:
C++:
auto is_close_protected_handle() -> bool
{

    auto thread_handle = CreateThread(NULL, NULL, NULL, NULL, CREATE_SUSPENDED, NULL);

    if (!SetHandleInformation(thread_handle, HANDLE_FLAG_PROTECT_FROM_CLOSE, HANDLE_FLAG_PROTECT_FROM_CLOSE) || !thread_handle)
        return FALSE;
 
    __try
    {
        CloseHandle(thread_handle);
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        return TRUE;
    }

    return FALSE;

}

Второй вариант - DuplicateHandle т.к внутри себя он вызывает NtClose,если передать аргумент - DUPLICATE_CLOSE_SOURCE.
Об этом мы поговорим позже.
По факту - это попытка обойти просто хук NtClose.

C++:
BOOL __stdcall DuplicateHandle(HANDLE hSourceProcessHandle, HANDLE hSourceHandle, HANDLE hTargetProcessHandle, LPHANDLE lpTargetHandle, DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwOptions)
{

  NTSTATUS nt_status; // eax

  switch ( (_DWORD)hSourceHandle )
  {
    case 0xFFFFFFF4:
      hSourceHandle = NtCurrentPeb()->ProcessParameters->StandardError;
      break;

    case 0xFFFFFFF5:
      hSourceHandle = NtCurrentPeb()->ProcessParameters->StandardOutput;
      break;

    case 0xFFFFFFF6:
      hSourceHandle = NtCurrentPeb()->ProcessParameters->StandardInput;
      break;

  }

  nt_status = NtDuplicateObject(
                hSourceProcessHandle,
                hSourceHandle,
                hTargetProcessHandle,
                lpTargetHandle,
                dwDesiredAccess,
                bInheritHandle ? 2 : NULL,
                dwOptions);

  if (NT_SUCCESS(nt_status))
    return TRUE;

  BaseSetLastNTError(nt_status);

  return FALSE;

}

Сам пример:

C++:
auto is_close_protect_handle_ex() -> bool
{
    HANDLE dublicate_handle = NULL;
    __try
    {
        DuplicateHandle(NtCurrentProcess, NtCurrentProcess, NtCurrentProcess, &dublicate_handle, NULL, FALSE, NULL);
        SetHandleInformation(dublicate_handle, HANDLE_FLAG_PROTECT_FROM_CLOSE, HANDLE_FLAG_PROTECT_FROM_CLOSE);
        DuplicateHandle(NtCurrentProcess, dublicate_handle, NtCurrentProcess, &dublicate_handle, NULL, FALSE, DUPLICATE_CLOSE_SOURCE);
    }

    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        return TRUE;
    }
    return FALSE;

}

Сейчас пойдут мемы связанные с OutputDebugString:

В Windows 7~ всё так:

C++:
VOID __stdcall OutputDebugStringW(LPCWSTR lpOutputString)

{

  NTSTATUS nt_status; // ebx
  const CHAR *output_string; // rcx
  struct STRING AnsiString; // [rsp+20h] [rbp-28h] BYREF
  _UNICODE_STRING DestinationString; // [rsp+30h] [rbp-18h] BYREF

  AnsiString.MaximumLength = NULL;
  AnsiString.Buffer = 0;
  AnsiString.Length = 0;

  nt_status = RtlInitUnicodeStringEx(&DestinationString, lpOutputString);

  if ( !NT_SUCCESS(nt_status)|| (nt_status = RtlUnicodeStringToAnsiString(&AnsiString, &DestinationString, TRUE), !NT_SUCCESS(nt_status)) )
  {
    output_string = OutputString;//Global variable
    AnsiString.Buffer = (PCHAR)OutputString;
  }
  else
  {
    output_string = AnsiString.Buffer;
  }

  OutputDebugStringA(output_string);

  if (NT_SUCCESS(nt_status))
    RtlFreeAnsiString(&AnsiString);

}

и это вызывает ->

C++:
/*
typedef struct _DBG_PRINTEXCEPTION_C_INFO
{
    ULONG max_lenght_str;
    WCHAR* buffer;
} DBG_PRINTEXCEPTION_C_INFO, * PDBG_PRINTEXCEPTION_C_INFO;
*/

void __stdcall OutputDebugStringA(LPCSTR lpOutputString)
{
  const char *lpOutputString_1; // rdx
  DBG_PRINTEXCEPTION_C_INFO dbg_print_excepthion_info;
  lpOutputString_1 = lpOutputString;

  if ( !lpOutputString )
    lpOutputString_1 = OutputString;

  dbg_print_excepthion_info.max_lenght_str = strlen(lpOutputString_1) + 1;
  dbg_print_excepthion_info.buffer = lpOutputString_1;
  RaiseException(DBG_PRINTEXCEPTION_C, 0, 2u, (ULONG_PTR)dbg_print_excepthion_info);

}


Как по мне это самый худший anti-debug трюк, который существует на сегодняшний день.
Причина в том, что этот трюк работает ниже windows vista и попросту бесполезен на сегодняшний день.
Забавно, но с помощью него можно попытаться крашнуть OllyDbg ( версию 1.1 и ниже).
C++:
OutputDebugStringW(L"%s")

Сюрприз-сюрприз!
В Windows 10 реализовали вызов исключения с LPCWSTR!(Unicode)
C++:
VOID __stdcall OutputDebugStringW(LPCWSTR lpOutputString)
{
/*
    typedef struct _DBG_PRINTEXCEPTION_WIDE_INFO_WIN_10
    {
        ULONG max_lenght_str;
        WCHAR* buffer_1;
        ULONG max_lenght_str_2;
        WCHAR* buffer_2;
    } DBG_PRINTEXCEPTION_WIDE_INFO_WIN_10, * PDBG_PRINTEXCEPTION_WIDE_INFO_WIN_10;
*/

  const WCHAR *buffer; // rsi
  NTSTATUS nt_status; // edi
  __int64 lenght_str_2; // rax
  __int64 lenght_str; // rcx
   _STRING DestinationString; // [rsp+20h] [rbp-48h] BYREF
  UNICODE_STRING SourceString; // [rsp+30h] [rbp-38h] BYREF
  DBG_PRINTEXCEPTION_WIDE_INFO_WIN_10 print_exception_info; // [rsp+40h] [rbp-28h] BYREF

  DestinationString = NULL;
  buffer = &word_1801C45C0;
  if ( lpOutputString )
    buffer = lpOutputString;

  nt_status = RtlInitUnicodeStringEx(&SourceString, buffer);

  if ( NT_SUCCESS(nt_status) )
    nt_status = RtlUnicodeStringToAnsiString(&DestinationString, &SourceString, 1u);

  if ( !NT_SUCCESS(nt_status) )
    RtlInitAnsiString(&DestinationString, pszBase);

  lenght_str_2 = -1i64;
  lenght_str = -1i64;

  do
    ++lenght_str;
  while ( buffer[lenght_str] );

  print_exception_info.max_lenght_str = lenght_str + 1;
  print_exception_info.buffer = buffer;

  do
    ++lenght_str_2;
  while ( DestinationString.Buffer[lenght_str_2] );

  print_exception_info.max_lenght_str_2 = lenght_str_2 + 1;
  print_exception_info.buffer_2 = DestinationString.Buffer;
  RaiseException(DBG_PRINTEXCEPTION_WIDE_C, 0, 4u, (ULONG_PTR)print_exception_info);

  if (NT_SUCCESS(nt_status) )
    RtlFreeAnsiString(&DestinationString);

}

Цепочка:RtlRaiseException -> ZwRaiseException & RtlpCaptureContext2(&ContextRecord);
Можно вызывать исключение и проверять какое исключение было вызвано, но как по мне этот трюк бесполезен.
Наверное, вам уже надоело читать код, поэтому ещё пару примеров и перейдём к ядру.

SetThreadInformation вызывает NtSetInformationThread и я не вижу смысла показывать дизассемблированный код,но вот пример anti-debug трюка:

C++:
auto is_hide_thread() -> bool
{
    NT_SUCCESS(NtSetInformationThread(NtCurrentThread, ThreadHideFromDebugger, NULL, NULL));
}

Сам поток будет спрятан от дебаггера и у вас не получится дебажить программу.
Дальше будет объяснено в чём смысл данного трюка и принцип его работы в ядре.
Это выглядит ~ так:

Сейчас будет один мем, связанный с присоединением дебаггера.
При присоединении к процессу вызывается DbgUiDebugActiveProcess(если использовать DebugApi) :

C++:
NTSTATUS __fastcall DbgUiDebugActiveProcess(HANDLE proc)
{

  NTSTATUS nt_status; // ebx
  nt_status = NtDebugActiveProcess(proc, NtCurrentTeb()->DbgSsReserved[1]);

  if (NT_SUCCESS(nt_status))
  {
    nt_status = DbgUiIssueRemoteBreakin(proc);
    if (!NT_SUCCESS(nt_status) )
      ZwRemoveProcessDebug(proc, NtCurrentTeb()->DbgSsReserved[1]);
  }
  return nt_status;
}

и это вызывает

C++:
NTSTATUS __fastcall DbgUiIssueRemoteBreakin(HANDLE proc)
{
  int nt_status; // ebx
  void *v3; // [rsp+30h] [rbp-48h]
  _CLIENT_ID Handle; // [rsp+88h] [rbp+10h] BYREF

  nt_status = RtlpCreateUserThreadEx(proc, NULL, 2u, NULL, NULL, 0x4000ui64, v3, DbgUiRemoteBreakin, NULL, &Handle);
  if (NT_SUCCESS(nt_status))
    NtClose(Handle.UniqueProcess);
  return nt_status;
}

И наконец:

C++:
VOID __noreturn DbgUiRemoteBreakin()
{
  if ( (NtCurrentPeb()->BeingDebugged || (KUSER_SHARED_DATA->KdDebuggerEnabled  & KdDebuggerNotPresent) != NULL) && (NtCurrentTeb()->SomeTebFlags & DbgRanProcessInit) == NULL )//0x2 - KdDebuggerNotPresent ,0x20 - DbgRanProcessInit
  {                                 
    if ( UseWOW64 )
    {
      if ( g_LdrpWow64PrepareForDebuggerAttach )
        g_LdrpWow64PrepareForDebuggerAttach();
    }
    DbgBreakPoint();
  }
  RtlExitUserThread(NULL);
}


Но как добиться anti-debug эффекта?
Идея состоит в том, чтобы пропатчить DbgUiRemoteBreakin или DbgBreakPoint.
Например, это делает Themida/VMP.
Однако, разработчики x64dbg просто
Пожалуйста, авторизуйтесь для просмотра ссылки.
,чтобы обойти данный трюк.

Гениально и просто :D

Так же можно пройтись по процессорам и проверить записано ли в NtCurrentTeb()->DbgSsReserved[1] процессе объект отладки(частично забавный аналог ObjectTypesInformation).
Так же можно пройтись по всех HANDLE'ам и если процесс указывает имеет хэндл с правами R/W и присутствует DbgSsReserved[1] ,то,скорее всего,вас дебажут.


Теперь мы вернёмся к NtQueryInformationProcess:
Про ProcessDebugPort было уже сказано и мы вернёмся к нему скоро.

ProcessDebugObjectHandle на самом деле является доступ к DebugPort и мы дальше разберём что представляет из себя DebugPort на самом деле.
В случае отсутствие DebugPort'а функция вернёт STATUS_PORT_NOT_SET(если аргументы переданы правильно).

C++:
auto is_debug_object_present() -> bool
{
    HANDLE debug_object = NULL;
    auto nt_status = NtQueryInformationProcess(NtCurrentProcess, ProcessDebugObjectHandle, &debug_object, sizeof(debug_object), NULL);

    return debug_object != NULL || nt_status != STATUS_PORT_NOT_SET;
}

ProcessDebugFlag: - это на самом деле это небольшой забавный флаг, который вы можете установить/снять с помощью функции NtSetInformationProcess.
Сам пример обнаружения дебаггера:
C++:
auto is_debug_flag_present() -> bool
{
    ULONG debug_flag = NULL;
    if(NT_SUCCESS(NtQueryInformationProcess(NtCurrentProcess, ProcessDebugFlags, &debug_flag, sizeof (debug_flag), NULL )))
        return debug_flag == NULL;
    return FALSE;
}

Про NtQueryObject -
Эту функцию можно использовать для проверки присутствия DebugObject(ObjectTypesInformation) или существует ли у процессора DebugObject(ObjectTypeInformation) + можно проверить флаги у HANDLE'а(ObjectHandleFlagInformation).
На мой взгляд не имеет смысл проверять DebugObject т.к просто искать в системе отладчик - глупо.
Забавно, но все anti-anti-debug tool's до написания статьи
просто устанавливают значение на 0,если вызывается с ObjectTypesInformation, благодаря чему они могут быть легко пойманы.


Hello,ring0 via heroin!

Первоначально мы разберём, как работает NtCreateDebugObject и NtDebugActiveProcess т.к эти функции отвечают за создание объекта отладки и прикрепление объекта отладки к процессу.
C++:
NTSTATUS __fastcall NtCreateDebugObject(PHANDLE DebugObjectHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, ULONG Flags)
{

  char Flags_1; // si
  unsigned __int8 PreviousMode; // r10
  __int64 v8; // rcx
  NTSTATUS nt_status; // rax
  PDEBUG_OBJECT DebugObject_1; // rbx
  _EWOW64PROCESS *WoW64Process; // rax
  unsigned __int16 Machine; // ax
  __int64 unk; // [rsp+20h] [rbp-68h]
  PDEBUG_OBJECT DebugObject; // [rsp+58h] [rbp-30h] BYREF
  HANDLE Handle; // [rsp+60h] [rbp-28h] OVERLAPPED BYREF

  Flags_1 = Flags;
  Handle = NULL;
  DebugObject = NULL;

  PreviousMode = KeGetCurrentThread()->PreviousMode;

  if ( PreviousMode != KernelMode )               
  {
    __try
    {
        ProbeForWrite(DebugObjectHandle, 4, 1);
    }
    __except (ExSystemExceptionFilter ())
    {
        return GetExceptionCode ();
    }

  }
  *DebugObjectHandle = NULL;

  if ( (Flags & ~DEBUG_KILL_ON_CLOSE) != 0 )              //  & ~DEBUG_KILL_ON_CLOSE
    return STATUS_INVALID_PARAMETER;

  nt_status = ObCreateObjectEx(
                PreviousMode,
                DbgkDebugObjectType,
                ObjectAttributes,
                PreviousMode,
                unk,
                sizeof(unk),               
                NULL,
                NULL,
                &DebugObject,
                NULL);

  if (NT_SUCCESS(nt_status) )
  {
    DebugObject_1 = DebugObject;
    DebugObject->Mutex.Count = 1;
    DebugObject_1->Mutex.Owner = NULL;
    DebugObject_1->Mutex.Contention = NULL;
    KeInitializeEvent(&DebugObject_1->Mutex.Event, SynchronizationEvent, 0);
    DebugObject_1->EventList.Blink = &DebugObject_1->EventList;
    DebugObject_1->EventList.Flink =1 &DebugObject_1->EventList;
    KeInitializeEvent(&DebugObject_1->EventsPresent, NotificationEvent, 0);
    if ( (Flags_1 & DEBUG_OBJECT_KILL_ON_CLOSE     ) == NULL )                // DEBUG_OBJECT_KILL_ON_CLOSE
      DebugObject_1->Flags = NULL;
    else
      DebugObject_1->Flags = DEBUG_OBJECT_KILL_ON_CLOSE;                 // DEBUG_OBJECT_KILL_ON_CLOSE

    WoW64Process = PsGetCurrentProcess()->WoW64Process
    if ( WoW64Process )
    {
      Machine = WoW64Process->Machine;

      if ( Machine == IMAGE_FILE_MACHINE_I386 || Machine == IMAGE_FILE_MACHINE_ARMNT )
        DebugObject_1->Flags |= 4u;
    }

    nt_status = ObInsertObjectEx(DebugObject, NULL, DesiredAccess, NULL, NULL, NULL, &Handle);

    if (NT_SUCCESS(nt_status) )
    {
      *DebugObjectHandle = Handle;
    }
  }
  return nt_status;

}

Здесь идёт инициализация объекта отладки и после этого выделяется память для объекта.
Этот объекта будет использован в NtDebugActiveProcess(2 аргумент) для присоединения DebugPort'а к процессу.

C++:
NTSTATUS __fastcall NtDebugActiveProcess(HANDLE ProcessHandle, HANDLE DebugObjectHandle)
{

  char AccessMode; // bp
  NTSTATUS nt_status_1; // eax
  __int64 AccessMode_2; // rcx
  struct _KTHREAD *v6; // rax
  struct _EX_RUNDOWN_REF *proc_1; // rdi
  _KPROCESS *current_thread; // rsi
  int nt_status; // ebx
  _EWOW64PROCESS IsWoW64; // rax
  __int16 v11; // cx
  unsigned __int64 v12; // rax
  __int16 v13; // cx
  BOOLEAN IsPortectOk; // al
  struct _KEVENT *DebugObject; // rsi
  int nt_status_2; // eax
  int LastThread; // [rsp+40h] [rbp-28h] OVERLAPPED BYREF
  PVOID proc; // [rsp+80h] [rbp+18h] BYREF
  PVOID proc_2; // [rsp+88h] [rbp+20h] BYREF

  proc = NULL;
  AccessMode = KeGetCurrentThread()->PreviousMode;
  nt_status_1 = ObReferenceObjectByHandleWithTag(
             ProcessHandle,
             PROCESS_SET_PORT,                 
             (POBJECT_TYPE)PsProcessType,
             AccessMode,
             'OgbD',
             &proc,
             NULL);

  if ( NT_SUCCESS(nt_status )
  {
    current_thread = KeGetCurrentThread();
    proc_1 = proc;
    current_thread = current_thread->ApcState.Process;      // PsGetCurrentProcess()
    if ( proc == current_thread || proc == PsInitialSystemProcess ) //Try self-debugging or debug system process
    {
      nt_status = STATUS_ACCESS_DENIED;
    }
    else
    {
      LOBYTE(AccessMode_2) = AccessMode;
      if ( PsTestProtectedProcessIncompatibility(AccessMode_2, PsGetCurrentProcess()->ApcState.Process, proc) )
      {
        nt_status = STATUS_PROCESS_IS_PROTECTED;
      }
      else if ( (proc_1.SecureHandle & 1) == 0 || (nt_status = PsRequestDebugSecureProcess(proc_1, 1u), NT_SUCCESS(nt_status)) )
      {
        IsWoW64 = current_thread.WoW64Process;
        if ( !IsWoW64
          || (Machine = IsWoW64.Machine, Machine !=  IMAGE_FILE_MACHINE_I386 ) && Machine != IMAGE_FILE_MACHINE_ARMNT
          || (IsWoW64 != 0 ) && ((Machine_2 = IsWoW64.Machine, Machine_2 ==  IMAGE_FILE_MACHINE_I386 ) || Machine_2 == IMAGE_FILE_MACHINE_ARMNT) )
          {
          proc_2 = NULL;
          nt_status = ObReferenceObjectByHandle(
                        DebugObjectHandle,
                        DEBUG_OBJECT_ADD_REMOVE_PROCESS ,
                        DbgkDebugObjectType,
                        AccessMode,
                        &proc_2,
                        NULL);
          if (NT_SUCCESS(nt_status))
          {
            IsPortectOk = ExAcquireRundownProtection_0(proc_1->RundownProtect);
            DebugObject = proc_2;
            if ( IsPortectOk )
            {
              nt_status_2 = DbgkpPostFakeProcessCreateMessages(proc_1,proc_2,&LastThread);
              nt_status = DbgkpSetProcessDebugObject(proc_1, DebugObject, nt_status_2, LastThread);
              ExReleaseRundownProtection_0(proc_1 + 0x8B);
            }
            else
            {
              nt_status = STATUS_PROCESS_IS_TERMINATING;
            }
            ObfDereferenceObjectWithTag(DebugObject, 'tlfD');
          }

        }
        else
        {
          nt_status = STATUS_NOT_SUPPORTED;
        }

      }
    }
    ObfDereferenceObjectWithTag(proc_1, 'OgbD');
    nt_status_1 = nt_status;
  }
  return nt_status_1;

}

Во-первых,вы не можете дебажить свой процесс или системный процесс,поэтому функция в данном случае вернёт STATUS_ACCESS_DENIED.
Во-вторых, вы не можете подключиться к защищённому процессу(proc->Protection) и у вас не получится открыть HANDLE к процессу(если вы пытаетесь открыть HANDLE,
да можно изменить права у дескриптора(например с PROCESS_QUERY_LIMITED_INFORMATION до PROCESS_ALL_ACCESS ),
но вот для подключения всё-равно нужно патчить снимать временно защиту).

В-третьих,у вас не получится дебажить процесс,если он работает под WoW64 и архитектура неожиданно станет неизвестной (можно подделать для x64 установив в proc->WoW64Process правильные значение)
и значение Machine не равно 452(IMAGE_FILE_MACHINE_ARMNT) или 332(IMAGE_FILE_MACHINE_I386).
Однако, если вы захотите попробовать данных трюк,чтобы добиться anti-debug эффекта,то есть побочный эффект - некоторые функции будут возвращать не тот результат.
В-четвёртых,если функция ObReferenceObjectByHandle закончится неудачно,то у вас так же не получится дебажить процесс(мы поговорим позже как можно добиться этого эффекта без хуков и триггера PG).

C++:
NTSTATUS __stdcall DbgkpSetProcessDebugObject(PEPROCESS Process, DEBUG_OBJECT *DebugObject, NTSTATUS MsgStatus, PETHREAD LastThread)
{
   _LIST_ENTRY *CurrentThread; // r13
  NTSTATUS nt_status; // edi
  PETHREAD LastThread_2; // rbx
  __int64 ethread_1; // r14
  LIST_ENTRY *list_entry; // r14
  DEBUG_EVENT *debug_event; // rbx
  ULONG flag_debug_event; // eax
  __int64 ethread; // r13
  _LIST_ENTRY *v14; // rcx
  _LIST_ENTRY *v15; // rax
  __int64 v16; // rax
  _LIST_ENTRY *v17; // rax
  ULONG Flag; // eax
  struct _DEBUG_EVENT *DebugEvent; // rcx
  _LIST_ENTRY *v20; // rax
  PVOID Object; // [rsp+30h] [rbp-30h] BYREF
  _LIST_ENTRY *CurrentThread_1; // [rsp+38h] [rbp-28h]
  PKGUARDED_MUTEX Mutex; // [rsp+40h] [rbp-20h]
  PDEBUG_EVENT DebugEvent_1; // [rsp+48h] [rbp-18h] BYREF
  DEBUG_EVENT *v26; // [rsp+50h] [rbp-10h]
  char First; // [rsp+A8h] [rbp+48h]
  char GlobalHeld; // [rsp+B0h] [rbp+50h]
  PETHREAD LastThread_1; // [rsp+B8h] [rbp+58h] BYREF


  LastThread_1 = LastThread;
  CurrentThread = KeGetCurrentThread();
  Object = NULL;
  v26 = (DEBUG_EVENT *)&P;
  P = &P;
  nt_status = MsgStatus;
  CurrentThread_1 = CurrentThread;
  First = TRUE;
  GlobalHeld = 0;

  if ( MsgStatus >= 0 )
  {
    LastThread_2 = LastThread_1;
    nt_status = STATUS_SUCCESS;
  }
  else
  {
    LastThread_2 = NULL;
    LastThread_1 = NULL;
  }

  if (NT_SUCCESS(nt_status) )
  {
    ExAcquireFastMutex(&DbgkpProcessDebugPortMutex);
    while ( 1 )                      
    {
      if (Process-> DebugPort )
      {
        nt_status = STATUS_PORT_ALREADY_SET; //Мы не можем подключить отладчик к процессу,который дебажут/есть DebugPort
        GlobalHeld = TRUE;
        goto LABEL_11;
      }
      Process-> DebugPort = DebugObject;// EPROCESS-> DebugPort
      ObfReferenceObjectWithTag(LastThread_2, 'OgbD');
      GlobalHeld = TRUE;
      ethread_1 = PsGetNextProcessThread(Process, LastThread_2);

      if ( !ethread_1 )
        goto LABEL_11;

      Process-> DebugPort = NULL;      // EPROCESS-> DebugPort
      KeReleaseGuardedMutex(&DbgkpProcessDebugPortMutex);
      GlobalHeld = 0;
      ObfDereferenceObjectWithTag(LastThread_2, 'OgbD');
      nt_status = DbgkpPostFakeThreadMessages(Process, DebugObject, ethread_1, &Object, &LastThread_1);

      if (!NT_SUCCESS(nt_status))
        break;

      ObfDereferenceObjectWithTag(Object, 'OgbD');
      ExAcquireFastMutex(&DbgkpProcessDebugPortMutex);
      LastThread_2 = LastThread_1;
    }
    LastThread_2 = NULL;
    LastThread_1 = NULL;

  }

LABEL_11:

  Mutex = &DebugObject->Mutex;
  ExAcquireFastMutex(&DebugObject->Mutex);

  if (NT_SUCCESS(nt_status))
  {
    if ( (DebugObject->Flags & DebuggerInactive) != 0 )     // DebugObject -> DebuggerInactive
    {
      Process-> DebugPort = NULL;      // EPROCESS-> DebugPort
      nt_status = STATUS_DEBUGGER_INACTIVE;
    }
    else
    {
      _InterlockedOr(Process->Flags,  PS_PROCESS_FLAGS_NO_DEBUG_INHERIT|PS_PROCESS_FLAGS_CREATE_REPORTED);// Set DebugFlag
      ObfReferenceObject(DebugObject);
      LastThread_2 = LastThread_1;
    }
  }

  list_entry = DebugObject->EventList.Flink;

  if ( list_entry == &DebugObject->EventList )
    goto LABEL_37;

  do
  {

    debug_event = (DEBUG_EVENT *)list_entry;
    list_entry = list_entry->Flink;
    flag_debug_event = debug_event->Flags;

    if ( (flag_debug_event & DEBUG_EVENT_INACTIVE) == 0 || debug_event->BackoutThread != CurrentThread )// DEBUG_EVENT_INACTIVE
      continue;
    ethread = debug_event->Thread;

    if (!NT_SUCCESS(nt_status) )
    {
      if ( (DEBUG_EVENT *)list_entry->Blink != debug_event
        || (v16 = (__int64)debug_event->EventList.Blink, *(DEBUG_EVENT **)v16 != debug_event) )
      {
LABEL_45:
        __fastfail(ERROR_PATH_NOT_FOUND);       // ???
      }

      *(_QWORD *)v16 = list_entry;
      list_entry->Blink = (_LIST_ENTRY *)v16;
      goto LABEL_30;

    }

    if ( (flag_debug_event & DEBUG_EVENT_PROTECT_FAILED) != 0 )       // DEBUG_EVENT_PROTECT_FAILED
    {
      _InterlockedOr((volatile signed __int32 *)(ethread->CrossThreadFlags ), SkipCreationMsg);// Thread->CrossThreadFlags ->SkipCreationMsg
      v14 = debug_event->EventList.Flink;
      if ( (DEBUG_EVENT *)debug_event->EventList.Flink->Blink != debug_event )
        goto LABEL_45;

      v15 = debug_event->EventList.Blink;

      if ( (DEBUG_EVENT *)v15->Flink != debug_event )
        goto LABEL_45;

      v15->Flink = v14;
      v14->Blink = v15;

LABEL_30:

      v17 = &v26->EventList;

      if ( (PVOID *)v26->EventList.Flink != &P )
        goto LABEL_45;
 
      debug_event->EventList.Flink = (_LIST_ENTRY *)&P;
      debug_event->EventList.Blink = v17;
      v17->Flink = &debug_event->EventList;
      v26 = debug_event;
      goto LABEL_32;

    }
    if ( First )
    {
      debug_event->Flags &= ~DEBUG_EVENT_INACTIVE;//  &= ~DEBUG_EVENT_INACTIVE
      KeSetEvent(&DebugObject->EventsPresent, NULL, NULL);
      First = FALSE;

    }

    debug_event->BackoutThread = NULL;

    _InterlockedOr((volatile signed __int32 *)(ethread->CrossThreadFlags), 0x40);// ?    Thread->CrossThreadFlags->BreakOnTermination

LABEL_32:

    Flag = debug_event->Flags;
    if ( (Flag & DEBUG_EVENT_RELEASE) != NULL )                      // DEBUG_EVENT_RELEASE
    {
      debug_event->Flags  &= ~DEBUG_EVENT_RELEASE;   //  &= ~DEBUG_EVENT_RELEASE  
      ExReleaseRundownProtection_0((PEX_RUNDOWN_REF)(ethread->RundownProtect));// ETHREAD -> RundownProtect
    }
    CurrentThread = CurrentThread_1;
  }
  while ( list_entry != &DebugObject->EventList );

  LastThread_2 = LastThread_1;

LABEL_37:

  KeReleaseGuardedMutex(Mutex);
  if ( GlobalHeld )
    KeReleaseGuardedMutex(&DbgkpProcessDebugPortMutex);

  if ( LastThread_2 )
    ObfDereferenceObjectWithTag(LastThread_2, 'OgbD');

  while ( 1 )
  {

   DebugEvent = DebugEvent_1;

    if ( DebugEvent_1 == (PDEBUG_EVENT)&DebugEvent_1 )
      break;

    if ( (PDEBUG_EVENT *)DebugEvent_1->EventList.Blink != &DebugEvent_1 )
      goto LABEL_45;

    v20 = DebugEvent_1->EventList.Flink;

    if ( (PDEBUG_EVENT)DebugEvent_1->EventList.Flink->Blink != DebugEvent_1 )
      goto LABEL_45;

    DebugEvent_1 = (PDEBUG_EVENT)DebugEvent_1->EventList.Flink;
    v20->Blink = (_LIST_ENTRY *)&DebugEvent_1;
    DbgkpWakeTarget(DebugEvent);
  }

  if (NT_SUCCESS(nt_status))
    DbgkpMarkProcessPeb((PEPROCESS)Process);

  return nt_status;

}



Вот мы и добрались до главной функции,которая устанавливает DebugPort.
Сюрприз-сюрприз!
DebugPort в ялре - это на самом деле PDEBUG_OBJECT.
Во-вторых, вы не можете подключить дебаггер к процессу, который уже дебажут/есть DebugPort.
Можно создать 2 процесс, который будет дебажить основной(вы можете это наблюдать данную технику в данном crackme )
На мой взгляд это самый мощный anti-debug из юзермода, поскольку можно сделать свою логику для дебаггера и для отладки такого приложения потребуется больше времени/усилий!
В-третьих, тут идёт установка в Flags флага NoDebugInherit(DebugFlag). Этот флаг можно перезаписать из UM(NtSetInformationProcess)
и некоторые anti-anti-debug plugin до сих пор не могут справиться с этим(печальный TitanHide и SharpOD)

C++:
void __fastcall DbgkpMarkProcessPeb(PEPROCESS Process)
{
  struct _EX_RUNDOWN_REF *RundownProtect; // rdi
  _EWOW64PROCESS *WoW64Process; // rax
  unsigned __int16 Machine; // cx
  PRKAPC_STATE ApcState; // [rsp+28h] [rbp-40h] OVERLAPPED BYREF

  ApcState = NULL;
  RundownProtect = Process -> RundownProtect;
  if ( ExAcquireRundownProtection_0(RundownProtect )
  {
    if (Process ->PEB )
    {
      KiStackAttachProcess(Process, FLAG_CHANGE_STACK_COUNT, &ApcState);
      ExAcquireFastMutex(&DbgkpProcessDebugPortMutex);
      Process ->PEB ->BeingDebugged = Process -> DebugPort != 0
      WoW64Process = Process -> WoW64Process;

      if ( WoW64Process )
      {
        Machine = WoW64Process->Machine;
        if ( Machine == IMAGE_FILE_MACHINE_I386 || Machine == IMAGE_FILE_MACHINE_ARMNT )
        {

          if ( WoW64Process->Peb )
            Process ->PEB ->BeingDebugged = Process -> DebugPort
        }

      }
      KeReleaseGuardedMutex(&DbgkpProcessDebugPortMutex);
      KiUnstackDetachProcess(&ApcState, NULL);
    }
    ExReleaseRundownProtection_0(RundownProtect);
  }
}

Ага! Тут и идёт установка BeingDebugged в PEB.
Мы изучили 3 важных ntapi,поэтому вернёмся к анализу некоторых функций.
Погнали изучать другие моменты дальше!

Но как происходи отсоединение дебаггера?
Ну, привет NtRemoveProcessDebug.

C++:
NTSTATUS __fastcall NtRemoveProcessDebug(HANDLE ProcessHandle, HANDLE DebugObjectHandle)
{

  char PreviousMode; // si
  __int64 PreviousMode_1; // rcx
  PEPROCESS Process_1; // rdi
  NTSTATUS nt_status; // ebx
  __int64 SecureHandle; // rbx
  PVOID DebugObject; // [rsp+40h] [rbp-A8h] BYREF
  PEPROCESS Process; // [rsp+48h] [rbp-A0h] BYREF
  __int64 buffer[14]; // [rsp+50h] [rbp-98h] BYREF


  PreviousMode = KeGetCurrentThrad()->PreviousMode;
  Process = NULL;

  nt_status = ObpReferenceObjectByHandleWithTag(
                  ProcessHandle,
                  PROCESS_SET_PORT,                        // PROCESS_SET_PORT
                  PsProcessType,
                  PreviousMode,
                  'OgbD',
                  &Process,
                  NULL,
                  NULL);

  if ( NT_SUCCESS(nt_status))
  {
    LOBYTE(PreviousMode_1) = PreviousMode;
    Process_1 = Process;
    if ( PsTestProtectedProcessIncompatibility(
           PreviousMode_1,
           KeGetCurrentThread()->ApcState.Process,
           (Process) )
    {
      nt_status = STATUS_PROCESS_IS_PROTECTED;
    }
    else

    {

      SecureHandle = Process_1->SecureState.SecureHandle;

      if ( (SecureHandle & 1) == NULL           // Unused ??
        || (memset(buffer, NULL, 104ui64),
            buffer[2] = NULL,
            buffer[1] = SecureHandle,
            nt_status = VslpEnterIumSecureMode(2u, 12, 0, (__int64)buffer),
            nt_status >= 0) )
      {
        DebugObject = NULL;
        nt_status = ObReferenceObjectByHandle(
                      DebugObjectHandle,
                      DEBUG_PROCESS_ASSIGN,                       // DEBUG_PROCESS_ASSIGN
                      DbgkDebugObjectType,
                      PreviousMode,
                      &DebugObject,
                      NULL);
        if ( NT_SUCCESS(nt_status) )
        {
          nt_status = DbgkClearProcessDebugObject(Process_1, DebugObject);
          ObfDereferenceObjectWithTag(DebugObject, 'tlfD');
        }
      }
    }

    ObfDereferenceObjectWithTag(Process_1, 'OgbD');

  }

  return nt_status;

}

и это вызывает

C++:
NTSTATUS __fastcall DbgkClearProcessDebugObject(PEPROCESS eprocess, PDEBUG_OBJECT SourceDebugObject)
{
  PDEBUG_OBJECT DebugObject; // rbx
  NTSTATUS nt_status; // edi
  _LIST_ENTRY *NextEntry; // rdx
  _LIST_ENTRY *curr; // rax
  _LIST_ENTRY *v9; // rcx
  _LIST_ENTRY *v10; // r9
  PDEBUG_EVENT DebugEvent; // rcx
  _LIST_ENTRY *Link; // rax
  PDEBUG_EVENT DebugEvent_1; // [rsp+20h] [rbp-10h] BYREF
  DEBUG_EVENT var8; // [rsp+28h] [rbp-8h] OVERLAPPED
  ExAcquireFastMutex(&DbgkpProcessDebugPortMutex);
  DebugObject =   eprocess->DebugPort ;// DebugPort

  if ( DebugObject && (DebugObject == SourceDebugObject || !SourceDebugObject) )

  {
    eprocess-> DebugPort = NULL;      // eprocess -> DebugPort = NULL
    nt_status = STATUS_SUCCESS;
  }
  else
  {
    DebugObject = NULL;
    nt_status = STATUS_PORT_NOT_SET;
  }

  KeReleaseGuardedMutex(&DbgkpProcessDebugPortMutex);

  if ( nt_status >= 0 )
    DbgkpMarkProcessPeb(eprocess);

  if ( DebugObject )
  {
    var8.EventList.Flink = (_LIST_ENTRY *)&DebugEvent_1;
    DebugEvent_1 = (PDEBUG_EVENT)&DebugEvent_1;
    ExAcquireFastMutex(&DebugObject->Mutex);
    NextEntry = DebugObject->EventList.Flink;

    while ( NextEntry != &DebugObject->EventList )
    {
      curr = NextEntry;
      NextEntry = NextEntry->Flink;

      if ( (PEPROCESS)curr[3].Blink == eprocess )
      {
        if ( NextEntry->Blink != curr
          || (v9 = curr->Blink, v9->Flink != curr)
          || (v9->Flink = NextEntry,
              NextEntry->Blink = v9,
              v10 = var8.EventList.Flink,
              var8.EventList.Flink->Flink != (_LIST_ENTRY *)&DebugEvent_1) )
        {

fail:
          __fastfail(ERROR_PATH_NOT_FOUND);
        }
        curr->Blink = var8.EventList.Flink;
        curr->Flink = (_LIST_ENTRY *)&DebugEvent_1;
        v10->Flink = curr;
        var8.EventList.Flink = curr;
      }
    }

    KeReleaseGuardedMutex(&DebugObject->Mutex);
    HalPutDmaAdapter((PADAPTER_OBJECT)DebugObject);

    while ( 1 )
    {
      DebugEvent = DebugEvent_1;
      if ( DebugEvent_1 == (PDEBUG_EVENT)&DebugEvent_1 )
        break;
      if ( (PDEBUG_EVENT *)DebugEvent_1->EventList.Blink != &DebugEvent_1 )
        goto fail;
      Link = DebugEvent_1->EventList.Flink;
      if ( (PDEBUG_EVENT)DebugEvent_1->EventList.Flink->Blink != DebugEvent_1 )
        goto fail;
      DebugEvent_1 = (PDEBUG_EVENT)DebugEvent_1->EventList.Flink;
      Link->Blink = (_LIST_ENTRY *)&DebugEvent_1;
      DebugEvent->Status = STATUS_DEBUGGER_INACTIVE;
      DbgkpWakeTarget(DebugEvent);
    }
  }
  return nt_status;

}



Данная функция просто убирает DebugPort с процесса и идёт работа с ивентами.
Забавно,но DebugFlag не изменяется в случае отсоединения.
Так же,если ProcessDebugObjectHandle вернул HANDLE,то можно просто убрать DebugPort с нашего процесса(с помощью NtRemoveProcessDebug),поэтому отладка просто будет невозможной.
Самые важные моменты изучили, поэтому идём изучать сами anti-debug трюки в ядре!
Ядро первоначально проверяет на запись буффер, перед тем как что-то начать с ним делать(вот пример с NtQueryInformationProcess):

C++:
auto PreviousMode = KeGetPreviousMode();
if (PreviousMode != KernelMode)
{
    try
    {
        ProbeForRead (ProcessInformation,
                      ProcessInformationLength,
                      sizeof(ULONG));

        if (ARGUMENT_PRESENT (ReturnLength))
        {
            ProbeForWrite(ReturnLength, 4, 1);
        }
    } except(EXCEPTION_EXECUTE_HANDLER)

    {
        return GetExceptionCode();
    }
}
Если передать неправильный адрес/не UM адрес , то функции вернёт STATUS_ACCESS_VIOLATION или STATUS_DATATYPE_MISALIGNMENT,если не выровнен адрес.
C++:
case ProcessDebugPort:
  if (ProcessInformationLength_1 != sizeof (HANDLE) )
    goto status_lenght_mismatch;

  nt_status = ObReferenceObjectByHandleWithTag(
                ProcessHandle,
                PROCESS_QUERY_INFORMATION,
                (POBJECT_TYPE)PsProcessType,
                PreviousMode,
                'yQsP',
                &proc,
                NULL);

  if (!NT_SUCCESS(nt_status))
    return nt_status;

  Handle = proc->DebugPort;
  ObfDereferenceObjectWithTag(proc, 'yQsP');

  __try
  {
  *(_QWORD *)ProcessInformation_1 = Handle != 0 ? -1:0;

  if ( ReturnLength )
    *ReturnLength = sizeof (HANDLE);
  }
  __except(EXCEPTION_EXECUTE_HANDLER)
  {
    return GetExceptionCode();
  }
  return STATUS_SUCCESS;

Во-первых,идёт проверка правильно ли передана длинна входного буфера.
Во-вторых, проверяется есть ли у HANDLE'а нужные права.
В-третьих, если существует DebugPort в PEPROCESS,то записывает в буфер -1.

C++:
case ProcessDebugFlags:

  if ( (_DWORD)ProcessInformationLength_1 != sizeof (ULONG) )
    goto status_lenght_mismatch;

  nt_status = ObReferenceObjectByHandleWithTag(
                ProcessHandle,
                PROCESS_QUERY_INFORMATION,
                PsProcessType,
                PreviousMode,
                'yQsP',
                &proc,
                NULL);
         
  if (!NT_SUCCESS(nt_status))
    return nt_status;

  __try
  {
     *(ULONG *)ProcessInformation_1 =  proc->Flags&PS_PROCESS_FLAGS_NO_DEBUG_INHERIT != NULL;
     if(ReturnLength)
        *ReturnLength =  sizeof(ULONG);
  }

  __except(EXCEPTION_EXECUTE_HANDLER)
  {
       return GetExceptionCode();
  }
   ObfDereferenceObjectWithTag(proc, 'yQsP');
   return nt_status;

Аналогично то же самое,что и с ProcessDebugPort почти,но идёт проверка флага в eprocess.
Если существует ProcessDebugFlags,то записывает в буфер 0,а в противном случае 1.

Теперь фаворит VMP:
C++:
case ProcessDebugObjectHandle:
   if ( (_DWORD)ProcessInformationLength_1 != sizeof (HANDLE) )
     goto status_lenght_mismatch;

   nt_status = ObReferenceObjectByHandleWithTag(
                 ProcessHandle,
                 PROCESS_QUERY_INFORMATION,
                 (POBJECT_TYPE)PsProcessType,
                 PreviousMode,
                 'yQsP',
                 &proc,
                 NULL);

   if (!NT_SUCCESS(nt_status))
     return nt_status;

   nt_status = DbgkOpenProcessDebugPort(proc, PreviousMode, &DebugPort);

   if (!NT_SUCCESS(nt_status))
        DebugPort = NULL;
 
   ObfDereferenceObjectWithTag(proc, 'yQsP');
    __try
    {
        *(_QWORD *)ProcessInformation_1 = DebugPort;
        if ( ReturnLength )
            *ReturnLength = sizeof (HANDLE);
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
       return GetExceptionCode();
    }
    return nt_status;



Во-первых,она связанная с одним anti-debug трюком из VMP 3.1.x и выше.
Да, атака на ReturnLength,но как понимаю эта любимая функция автора VMP :D.
Во-вторых, вызывется функция DbgkOpenProcessDebugPort,которая записывает в буфер хэндл для работы с DebugPort в случае успеха.
При написании anti-debug плагина ни в коем случае нельзя вызывать оригинальную функцию с DbgkOpenProcessDebugPort т.к можно перезаписать ReturnLength,благодаря чему произойдёт утечка HANDLE'a

P.S почему же это фаворит VMP?
Ну,вот ответы:
1)
Пожалуйста, авторизуйтесь для просмотра ссылки.

2)
Пожалуйста, авторизуйтесь для просмотра ссылки.

3)и 3 детект будет в статье

C++:
NTSTATUS __fastcall DbgkOpenProcessDebugPort(PEPROCESS proc, KPROCESSOR_MODE PreviousMode, PHANDLE pHandle)
{
  NTSTATUS nt_status; // edi
  struct _DMA_ADAPTER *debug_port; // rbx
  __int64 PreviousMode_1; // rcx

  nt_status = STATUS_PORT_NOT_SET;

  if ( proc->DebugPort)             // proc->DebugPort
  {
    ExAcquireFastMutex(&DbgkpProcessDebugPortMutex);
    debug_port = proc->DebugPort;// proc->DebugPort

    if ( debug_port )
      ObfReferenceObject(proc->DebugPort]);// proc->DebugPort

    KeReleaseGuardedMutex(&DbgkpProcessDebugPortMutex);
    if ( debug_port )
    {
      LOBYTE(PreviousMode_1) = PreviousMode;
      if ( PsTestProtectedProcessIncompatibility(
             PreviousMode_1,
             KeGetCurrentThread()->ApcState.Process,
             proc) )
      {
        nt_status = STATUS_PROCESS_IS_PROTECTED;
        HalPutDmaAdapter(debug_port);
        return nt_status;
      }

      nt_status = ObOpenObjectByPointer(
                    debug_port,
                    PreviousMode == KernelMode ?  OBJ_KERNEL_HANDLE : NULL,
                    NULL,
                    MAXIMUM_ALLOWED,        
                    DbgkDebugObjectType,
                    PreviousMode,
                    pHandle);
             
      if (!NT_SUCCESS(nt_status))
      {
        HalPutDmaAdapter(debug_port);
        return nt_status;
      }
    }
  }
  return nt_status;

}

Функция должна вернуть STATUS_PORT_NOT_SET,если отсутствует DebugPort + идёт проверка после этого на защищённый процесс.
В конце открывается доступ к DebugPort'у с правами MAXIMUM_ALLOWED и функция вернёт STATUS_SUCCESS в случае успеха ObOpenObjectByPointer.
Мы затронули ThreadHideFromDebugger,но как он работает на самом деле? Наш дебаггер взрывается или что с ним вообще происходит?

C++:
case ThreadHideFromDebugger:
   if ( ThreadInformationLength )
     return STATUS_INFO_LENGTH_MISMATCH;

   nt_status = ObReferenceObjectByHandleWithTag(
                 ThreadHandle_1,
                 THREAD_SET_INFORMATION,
                 PsThreadType,
                 PreviousMode,
                 'yQsP',
                 &thread,
                 NULL);
   if (!NT_SUCCESS(nt_status) )
     return nt_status;
   _InterlockedOr(thread->CrossThreadFlags, PS_CROSS_THREAD_FLAGS_HIDEFROMDBG);// lock or dword ptr [rax+510h], 4
   goto derederence_object;

Так же не забудем о NtCreateThreadEx(с THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER и с THREAD_CREATE_FLAGS_BYPASS_PROCESS_FREEZE(начиная с windows 10 19H1) ):

Цепочка:
NtCreateThreadEx -> PspCreateThread -> PspMapThreadCreationFlags.
Именно в PspMapThreadCreationFlags идёт установка флагов и при создании можно сделать поток скрытым:

C++:
void __fastcall PspMapThreadCreationFlags(char ThreadFlags, int *flag_result)
{

  int res; // eax

  res = NULL;

  *flag_result = NULL;
  if ( (ThreadFlags & THREAD_CREATE_FLAGS_CREATE_SUSPENDED ) != NULL )
  {
    *flag_result = THREAD_CREATE_FLAGS_CREATE_SUSPENDED ;
    res = THREAD_CREATE_FLAGS_CREATE_SUSPENDED;
  }
  if ( (ThreadFlags & THREAD_CREATE_FLAGS_SKIP_THREAD_ATTACH ) != NULL )
  {
    res |= THREAD_CREATE_FLAGS_SKIP_THREAD_ATTACH ;
    *flag_result = res;
  }
  if ( (ThreadFlags & THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER ) != NULL )
  {
    res |= THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER ;
    *flag_result = res;
  }
  if ( (ThreadFlags & THREAD_CREATE_FLAGS_HAS_SECURITY_DESCRIPTOR ) != NULL )
  {
    res |= 0x80u;
    *flag_result = res;
  }

  if ( (ThreadFlags & THREAD_CREATE_FLAGS_ACCESS_CHECK_IN_TARGET ) != NULL )
  {
    res |= 0x100u;
    *flag_result = res;
  }
  if ( (ThreadFlags & THREAD_CREATE_FLAGS_BYPASS_PROCESS_FREEZE) != NULL )

    *flag_result = res | 0x200;
}
и в PspAllocateThread идёт установка в сам поток флагов.

THREAD_CREATE_FLAGS_BYPASS_PROCESS_FREEZE работает следующим образом:

C++:
NTSTATUS __fastcall PsSuspendProcess(PEPROCESS proc)
{
  struct _KTHREAD *current_thread; // rbp
  struct _EX_RUNDOWN_REF *RundownProtect; // r14
  __int64 next_thread; // rax
  NTSTATUS nt_status; // ebx
  __int64 current_thread_1; // rdi

  current_thread = KeGetCurrentThread();
  --current_thread->KernelApcDisable;
  RundownProtect = proc->RundownProtect;

  if ( ExAcquireRundownProtection_0(proc->RundownProtect) == 1 )
  {
    next_thread = PsGetNextProcessThread(proc, NULL);
    nt_status = STATUS_SUCCESS;

    while ( 1 )
    {
      current_thread_1 = next_thread;
      if ( !next_thread )
        break;
      if ( (next_thread->MiscFlags & THREAD_CREATE_FLAGS_BYPASS_PROCESS_FREEZE) == NULL )
        PsSuspendThread(next_thread, NULL);
      next_thread = PsGetNextProcessThread(proc, current_thread_1);
    }
    ExReleaseRundownProtection_0(RundownProtect);
  }
  else
  {
    nt_status = STATUS_PROCESS_IS_TERMINATING;
  }
  if ( proc-> Flags3 & EnableThreadSuspendResumeLogging) != 0 )// EnableThreadSuspendResumeLogging
    EtwTiLogSuspendResumeProcess(nt_status, current_thread, proc, NULL);
  KeLeaveCriticalRegionThread(current_thread);
  return nt_status;
}
Поток просто не будет приостановлен и это просто забавный трюк.

А теперь вернёмся к HideFromDebugger.
Установился флаг и установился! А дальше что?

Ну,тут ответ:DbgkForwardException
На самом деле проверяется бит ещё в следующих функциях:
Не будет доставлено уведомление через DbgkpSendApiMessage о создании/завершении потока или завершении процесса:DbgkExitThread,DbgkExitProcess,DbgkCreateThread.
Не будет доставлено уведомление через DbgkpSendApiMessage о мапе/анмапе секций:DbgkMapViewOfSection,DbgkUnMapViewOfSection.
Про это уже было сказано здесь.
Сама функция выглядит так~:

C++:
bool __fastcall DbgkForwardException(PEXCEPTION_RECORD ExceptionRecord, BOOLEAN DebugException, BOOLEAN SecondChance)

{
  struct _KTHREAD *current_thread; // rax
  struct _KPROCESS *current_process; // rsi
  struct _DMA_ADAPTER *port; // rbx
  char LpcPort; // r14
  int nt_status; // eax
  __int64 v14; // [rsp+20h] [rbp-E0h] BYREF
  DBGKM_APIMSG m; // [rsp+30h] [rbp-D0h] BYREF
  v14 = NULL;
  memset(m, 0, sizeof(m));

  if ( SecondChance )
  {
    LODWORD(v14) = 1;
    PsSetProcessFaultInformation(KeGetCurrentThread()->ApcState.Process, &v14);
  }

  m[10] = 0;
  m.u1.Length = 0xD000A8;
  m.u2.ZeroInit = 8;

  current_thread = KeGetCurrentThread();
  current_process = current_thread->ApcState.Process;

  if ( DebugException )
  {
    if ( (KeGetCurrentThread()->CrossThreadFlags) & PS_CROSS_THREAD_FLAGS_HIDEFROMDBG) != NULL ) //Thread is hide
      port = NULL;
    else
      port = current_process->DebugPort;
    LpcPort = FALSE;
  }
  else
  {

    port = PsCaptureExceptionPort(current_thread->ApcState.Process);
    m.h.u2.ZeroInit = LPC_EXCEPTION;
    LpcPort = TRUE;
  }

  if ( !port && DebugException )
    return FALSE;

  KeCopyExceptionRecord(&m[12], ExceptionRecord);
  m[50] = SecondChance == NULL;
  if ( LpcPort )
  {
    if ( port )
    {
      nt_status = DbgkpSendApiMessageLpc(m, port, DebugException);
      HalPutDmaAdapter(port);
    }
    else
    {
      nt_status = STATUS_SUCCESS;
      m.ReturnedStatus  = DBG_EXCEPTION_NOT_HANDLED;
    }
  }
  else
  {
    nt_status = DbgkpSendApiMessage(current_process, DebugException != NULL, m);
  }
  if (!NT_SUCCESS(nt_status))
    return FALSE;

  nt_status = m.ReturnedStatus ;

  if ( m.ReturnedStatus  == DBG_EXCEPTION_NOT_HANDLED )
  {
    if ( DebugException )
      return 0;
    nt_status = DbgkpSendErrorMessage(ExceptionRecord, 2, m);
  }
  return NT_SUCCESS(nt_status);

}

По названию понятно,что она связанна с исключениями.
Если поток скрыт и процесс дебажут,то исключение не будет передано на DebugPort при скрытом потоке,поэтому процесс рухнет.
На мой взгляд это мощный anti-debug трюк т.к сами Microsoft добавили данный трюк.
Правда,данный трюк легко обойти(особенно в ядре) т.к можно хукнуть саму функцию DbgkForwardException или просто очистить PS_CROSS_THREAD_FLAGS_HIDEFROMDBG у потока.


Теперь посмотрим NtClose:
C++:
NTSTATUS __stdcall NtClose(HANDLE Handle)
{
  EXHANDLE Handle_1; // rbx
  char PreviousMode; // r14
  struct _KTHREAD *current_thread; // rdi
  bool is_safe; // r15
  _KPROCESS *current_process; // r13
  PEPROCESS current_process_1; // r12
  _HANDLE_TABLE *kernel_handle_table; // rbp
  _HANDLE_TABLE *handle_table_entry; // rax
  _HANDLE_TABLE *handle_table_entry_1; // rsi
  NTSTATUS nt_status; // edi
  ULONG_PTR v12; // rcx
  struct _HANDLE_TRACE_DEBUG_INFO *debug_trace; // [rsp+70h] [rbp+8h] BYREF
  __int64 is_debug_info_or_flag_exception; // [rsp+78h] [rbp+10h] BYREF

  Handle_1.Value = (ULONG_PTR)Handle;

  PreviousMode = KeGetCurrentThread()->PreviousMode;

  if ( (MmVerifierData & 0x100) != NULL && !PreviousMode && !ObpIsKernelHandle(Handle, NULL) )
    VfCheckUserHandle(v12);

  current_thread = KeGetCurrentThread();
  is_safe = 0;
  current_process = current_thread->ApcState.Process;
  LOBYTE(debug_trace) = NULL;

  if ( ObpIsKernelHandle(Handle_1.GenericHandleOverlay, PreviousMode) )
  {

    kernel_handle_table = ObpKernelHandleTable;
    Handle_1.Value ^= 0xFFFFFFFF80000000ui64;
    current_process_1 = PsInitialSystemProcess;

  }
  else
  {
    current_process_1 = current_process;
    if ( KeGetCurrentThread()->ApcStateIndex != 1 )
    {
      kernel_handle_table = current_process ->ObjectTable;// current_process -> ObjectTable

      if ( kernel_handle_table != ObpKernelHandleTable )
        goto check_handle;
      return STATUS_INVALID_HANDLE;

    }
    kernel_handle_table = ObReferenceProcessHandleTable(current_process);

    if ( !kernel_handle_table )
      return STATUS_INVALID_HANDLE;
    is_safe = TRUE;

  }

check_handle:

  --current_thread->KernelApcDisable;

  if ( (*(_WORD *)&Handle_1.u & 0x3FC) != NULL )
  {

    handle_table_entry = ExpLookupHandleTableEntry(kernel_handle_table, Handle_1.GenericHandleOverlay);
    handle_table_entry_1 = handle_table_entry;
    if ( handle_table_entry )
    {
      if ( ExLockHandleTableEntry(kernel_handle_table, handle_table_entry) )
      {
         //Тут идёт проверка установлена ли защита от закрытия защищённого хэндла и просиходи само закрытие
        nt_status = ObCloseHandleTableEntry(
                      kernel_handle_table,
                      handle_table_entry_1,
                      current_process_1,
                      Handle_1.GenericHandleOverlay,
                      PreviousMode,
                      NULL);
        goto exit;
      }
    }
  }

  KeLeaveCriticalRegionThread(current_thread);

  if ( Handle_1.Value >= 0xFFFFFFFFFFFFFFFAui64 || Handle_1.Value == NULL )
    goto LABEL_14;

  ExQueryHandleExceptionsPermanency(kernel_handle_table, &is_debug_info_or_flag_exception, &debug_trace);

  if (kernel_handle_table->Flags & RaiseUMExceptionOnInvalidHandleClose) != NULL && (_BYTE)debug_trace )// Duplicated or RaiseUMExceptionOnInvalidHandleClose
    ExHandleLogBadReference(kernel_handle_table, Handle_1.GenericHandleOverlay);

  if ( !PreviousMode )
  {
    if ( current_thread->Terminated == NULL// current_thread->Terminated != NULL
      && current_process->Peb  // current_process->Peb
      && (_BYTE)KdDebuggerEnabled )
    {
      KeBugCheckEx(INVALID_KERNEL_HANDLE, Handle_1.Value, TRUE, NULL, NULL);
    }

    goto LABEL_14;

  }

  if ( (NtGlobalFlag & FLG_ENABLE_CLOSE_EXCEPTIONS) == NULL && !current_process ->DebugPort && !kernel_handle_table->DebugInfo )// current_process ->DebugPort
  {
    goto LABEL_14;
  }

  if ( KeGetCurrentThread()->ApcStateIndex == 1 )
        nt_status = STATUS_INVALID_HANDLE;
  else
    nt_status = KeRaiseUserException(STATUS_INVALID_HANDLE);  //Доставляем исключение в UM процесс

     goto exit;

LABEL_14:

    nt_status = STATUS_INVALID_HANDLE;

    if ( Handle_1.Value + 6 <= 5 )
        nt_status = STATUS_SUCCESS;
    goto exit;
exit:
  if ( is_safe )
    ExReleaseRundownProtection_0(current_process_1->RundownProtect);// current_process_1 ->RundownProtect
  return nt_status;

}



На мой взгляд это альтернативный вариант проверки DebugPort т.к если закрыть invalid handle и
при этом вы дебажите процесс,то исключение для приложения будет выброшено и
в случае с защищённым handle'ом так же или если установлен системой FLG_ENABLE_CLOSE_EXCEPTIONS(NtGlobalFlag).

При закрытии handle'а (ObCloseHandleTableEntry) идёт проверка защищён ли он от закрытия:
C++:
//boring code
if ( (GrantedAccess & OBJ_PROTECT_CLOSE) == 0 || IgnoreHandleProtection )
{
    //boring code
}
if ( !PreviousMode )
  KeBugCheckEx(INVALID_KERNEL_HANDLE, (ULONG_PTR)Handle_1, NULL, NULL, NULL);
_InterlockedExchangeAdd64(handle_table_entry_1, 1ui64);
_InterlockedOr(v50, 0);

if ( kernel_handle_table->HandleContentionEvent.Value )
  ExfUnblockPushLock(&kernel_handle_table->HandleContentionEvent, NULL, v11);
KeLeaveCriticalRegion();
if ( is_attached )
  KiUnstackDetachProcess(&ApcState, NULL);
if ( KeGetCurrentThread()->ApcStateIndex == 1
  || (NtGlobalFlag & FLG_ENABLE_CLOSE_EXCEPTIONS) == 0
  && !KeGetCurrentThread()->ApcState.Process->DebugPort
  && !kernel_handle_table->DebugInfo )
{
  return STATUS_HANDLE_NOT_CLOSABLE;  //Я ядреный, как кабан, я имею свой баян 🤘
}
return KeRaiseUserException(STATUS_HANDLE_NOT_CLOSABLE);


Тут происходит вызов исключения,если процесс дебажут и HANDLE при этом защищён от закрытия/включена трассировка или установлен FLG_ENABLE_CLOSE_EXCEPTIONS в NtGlobalFlag .
Опять же, альтернатива проверки DebugPort,если не делать лишних действия.
Предлагаю вернуться к NtDuplicateObject.

C++:
NTSTATUS __fastcall NtDuplicateObject(HANDLE SourceProcessHandle, HANDLE SourceHandle, HANDLE TargetProcessHandle, PHANDLE TargetHandle, ACCESS_MASK DesiredAccess, ULONG HandleAttributes, ULONG Options)
{
  PHANDLE TargetHandle_1; // rbx
  struct _KPROCESS *TargetProcess_1; // rdi
  char PreviousMode; // si
  NTSTATUS nt_status; // eax
  NTSTATUS nt_status_1; // er14
  HANDLE SourceHandle_1; // rdx
  PVOID SourceProcess_1; // r15
  NTSTATUS nt_status_2; // esi
  __int64 v17; // rdx
  PVOID TargetProcess; // [rsp+48h] [rbp-40h] BYREF
  PVOID SourceProcess; // [rsp+50h] [rbp-38h] BYREF
  HANDLE NewHandle; // [rsp+58h] [rbp-30h] OVERLAPPED BYREF



  TargetHandle_1 = TargetHandle;
  TargetProcess_1 = NULL;
  NewHandle = NULL;
  SourceProcess = NULL;
  TargetProcess = NULL;

  PreviousMode = KeGetCurrentThread()->PreviousMode;
  if ( TargetHandle && PreviousMode )
  {
     __try
     {
        ProbeForWrite(TargetHandle, 4, 1);
     }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
         return GetExceptionCode();
    }

  }

  nt_status = ObReferenceObjectByHandleWithTag(
                SourceProcessHandle,
                PROCESS_DUP_HANDLE,                          // PROCESS_DUP_HANDLE
                (POBJECT_TYPE)PsProcessType,
                PreviousMode,
                'uDbO',
                &SourceProcess,
                NULL);
  if ( NT_SUCCESS(nt_status))
  {
    if ( TargetProcessHandle )
    {
      nt_status_1 = ObReferenceObjectByHandleWithTag(

                      TargetProcessHandle,
                      PROCESS_DUP_HANDLE,                    // PROCESS_DUP_HANDLE
                      (POBJECT_TYPE)PsProcessType,
                      PreviousMode,
                      'uDbO',
                      &TargetProcess,
                      NULL);
      if (!NT_SUCCESS(nt_status_1))
      {
        TargetProcess = NULL;
        goto LABEL_7;
      }
    }
    else
    {
      nt_status_1 = STATUS_SUCCESS;
    }

    TargetProcess_1 = TargetProcess;

LABEL_7:

    SourceHandle_1 = SourceHandle;
    SourceProcess_1 = SourceProcess;
    nt_status_2 = ObDuplicateObject(
                    SourceProcess,
                    SourceHandle_1,
                    TargetProcess_1,
                    &NewHandle,
                    DesiredAccess,
                    HandleAttributes,
                    Options,
                    PreviousMode);
    if ( TargetHandle_1 )
      *TargetHandle_1 = NewHandle;
    ObfDereferenceObjectWithTag(SourceProcess_1, 'uDbO');
    if ( TargetProcess_1 )
      ObfDereferenceObjectWithTag(TargetProcess_1, 'uDbO');
    if ( !NT_SUCCESS(nt_status_1) )
      nt_status_2 = nt_status_1;
    nt_status = nt_status_2;
  }
  return nt_status;

}

в ObDuplicateObject вызывается NtClose,если передан флаг DUPLICATE_CLOSE_SOURCE:

C++:
//boring code
if ((Options & DUPLICATE_CLOSE_SOURCE) != 0 )
{
  KeStackAttachProcess(SourceProcess_1, &ApcState);
  NtClose(SourceHandle_1);
  KeUnstackDetachProcess(&ApcState);
}

Эту функцию можно использовать для замены NClose в качестве anti-debug трюка т.к с параметром DUPLICATE_CLOSE_SOURCE функция вызывает NtClose!
Благодаря этому можно достичь anti-debug эффекта!

Вернёмся к NtQueryObject.
Поскольку код не слишком изменился с Windows XP,то я возьму функцию из WRK и уберу некоторые моменты:
C++:
NTSTATUS
NtQueryObject (
    __in HANDLE Handle,
    __in OBJECT_INFORMATION_CLASS ObjectInformationClass,
    __out_bcount_opt(ObjectInformationLength) PVOID ObjectInformation,
    __in ULONG ObjectInformationLength,
    __out_opt PULONG ReturnLength
    )
/*++

Routine description:

    This routine is used to query information about a given object

Arguments:

    Handle - Supplies a handle to the object being queried.  This value

        is ignored if the requested information class is for type

        information.

    ObjectInformationClass - Specifies the type of information to return

    ObjectInformation - Supplies an output buffer for the information being

        returned

    ObjectInformationLength - Specifies, in bytes, the length of the

        preceding object information buffer

    ReturnLength - Optionally receives the length, in bytes, used to store

        the object information

Return Value:

    An appropriate status value

--*/

{

    KPROCESSOR_MODE PreviousMode;
    NTSTATUS Status;
    PVOID Object;
    POBJECT_HEADER ObjectHeader;
    POBJECT_HEADER_QUOTA_INFO QuotaInfo;
    POBJECT_HEADER_NAME_INFO NameInfo;
    POBJECT_TYPE ObjectType;
    POBJECT_HEADER ObjectDirectoryHeader;
    POBJECT_DIRECTORY ObjectDirectory;
    ACCESS_MASK GrantedAccess;
    POBJECT_HANDLE_FLAG_INFORMATION HandleFlags;
    OBJECT_HANDLE_INFORMATION HandleInformation = {0};
    ULONG NameInfoSize;
    ULONG SecurityDescriptorSize;
    ULONG TempReturnLength;
    OBJECT_BASIC_INFORMATION ObjectBasicInfo;
    POBJECT_TYPES_INFORMATION TypesInformation;
    POBJECT_TYPE_INFORMATION TypeInfo;
    ULONG i;

    PAGED_CODE();

    //
    //  Initialize our local variables
    //

    TempReturnLength = 0;

    //
    //  Get previous processor mode and probe output argument if necessary.
    //

    PreviousMode = KeGetPreviousMode();

    if (PreviousMode != KernelMode) {

        try {

            if (ObjectInformationClass != ObjectHandleFlagInformation) {

                ProbeForWrite( ObjectInformation,
                               ObjectInformationLength,
                               sizeof( ULONG ));

            } else {
                ProbeForWrite( ObjectInformation,
                               ObjectInformationLength,
                               1 );
            }

            //
            //  We'll use a local temp return length variable to pass
            //  through to the later ob query calls which will increment
            //  its value.  We can't pass the users return length directly
            //  because the user might also be altering its value behind
            //  our back.
            //

            if (ARGUMENT_PRESENT( ReturnLength )) {
                ProbeForWriteUlong( ReturnLength );
            }

        } except( EXCEPTION_EXECUTE_HANDLER ) {

            return( GetExceptionCode() );

        }

    }

    //
    //  If the query is not for types information then we
    //  will have to get the object in question. Otherwise
    //  for types information there really isn't an object
    //  to grab.
    //

    if (ObjectInformationClass != ObjectTypesInformation)
    {

        Status = ObReferenceObjectByHandle( Handle,
                                            0,
                                            NULL,
                                            PreviousMode,
                                            &Object,
                                            &HandleInformation );
        if (!NT_SUCCESS( Status ))
        {
            return( Status );
        }

        GrantedAccess = HandleInformation.GrantedAccess;
        ObjectHeader = OBJECT_TO_OBJECT_HEADER( Object );
        ObjectType = ObjectHeader->Type;

    }
    else
    {
        GrantedAccess = 0;
        Object = NULL;
        ObjectHeader = NULL;
        ObjectType = NULL;
        Status = STATUS_SUCCESS;
    }

    //  Now process the particular information class being
    //  requested

    switch( ObjectInformationClass )
    {
    case ObjectTypeInformation:
        //
        //  Call a local worker routine
        //

        Status = ObQueryTypeInfo( ObjectType,
                                  (POBJECT_TYPE_INFORMATION)ObjectInformation,
                                  ObjectInformationLength,
                                  &TempReturnLength );
        break;

    case ObjectTypesInformation:

        try {

            //
            //  The first thing we do is set the return length to cover the
            //  types info record.  Later in each call to query type info
            //  this value will be updated as necessary
            //

            TempReturnLength = sizeof( OBJECT_TYPES_INFORMATION );
            //
            //  Make sure there is enough room to hold the types info record
            //  and if so then compute the number of defined types there are
            //
            TypesInformation = (POBJECT_TYPES_INFORMATION)ObjectInformation;

            if (ObjectInformationLength < sizeof( OBJECT_TYPES_INFORMATION ) ) {
     
                Status = STATUS_INFO_LENGTH_MISMATCH;

            } else {

                TypesInformation->NumberOfTypes = 0;

                for (i=0; i<OBP_MAX_DEFINED_OBJECT_TYPES; i++) {
         
                    ObjectType = ObpObjectTypes[ i ];

                    if (ObjectType == NULL) {
                        break;
                    }
                    TypesInformation->NumberOfTypes += 1;
                }

            }
            //
            //  For each defined type we will query the type info for the
            //  object type and adjust the TypeInfo pointer to the next
            //  free spot
            //
            TypeInfo = (POBJECT_TYPE_INFORMATION)(((PUCHAR)TypesInformation) + ALIGN_UP( sizeof(*TypesInformation), ULONG_PTR ));

            for (i=0; i<OBP_MAX_DEFINED_OBJECT_TYPES; i++) {
                ObjectType = ObpObjectTypes[ i ];
                if (ObjectType == NULL) {
                    break;
                }

                Status = ObQueryTypeInfo( ObjectType,
                                          TypeInfo,
                                          ObjectInformationLength,
                                          &TempReturnLength );

                if (NT_SUCCESS( Status )) {
                    TypeInfo = (POBJECT_TYPE_INFORMATION)
                        ((PCHAR)(TypeInfo+1) + ALIGN_UP( TypeInfo->TypeName.MaximumLength, ULONG_PTR ));
                }

            }

        } except( EXCEPTION_EXECUTE_HANDLER ) {
            Status = GetExceptionCode();
        }
        break;

    case ObjectHandleFlagInformation:
        try {
            //
            //  Set the amount of data we are going to return
            //

            TempReturnLength = sizeof(OBJECT_HANDLE_FLAG_INFORMATION);
 
            HandleFlags = (POBJECT_HANDLE_FLAG_INFORMATION)ObjectInformation;

            //  Make sure we have enough room for the query, and if so we'll
            //  set the output based on the flags stored in the handle


            if (ObjectInformationLength < sizeof( OBJECT_HANDLE_FLAG_INFORMATION))
            {
                Status = STATUS_INFO_LENGTH_MISMATCH;
            }
            else
            {

                HandleFlags->Inherit = FALSE;
                if (HandleInformation.HandleAttributes & OBJ_INHERIT)
                {
                    HandleFlags->Inherit = TRUE;
                }
                HandleFlags->ProtectFromClose = FALSE;
         
                if (HandleInformation.HandleAttributes & OBJ_PROTECT_CLOSE)
                {
                    HandleFlags->ProtectFromClose = TRUE;
                }
            }
        } except( EXCEPTION_EXECUTE_HANDLER )
        {
            Status = GetExceptionCode();
        }
        break;

    default:

        //  To get to this point we must have had an object and the
        //  information class is not defined, so we should dereference the
        //  object and return to our user the bad status

        ObDereferenceObject( Object );
        return( STATUS_INVALID_INFO_CLASS );
    }

    //  Now if the caller asked for a return length we'll set it from
    //  our local copy

    try
    {
        if (ARGUMENT_PRESENT( ReturnLength ) )
        {
            ReturnLength = TempReturnLength;
        }
    } except( EXCEPTION_EXECUTE_HANDLER )
    {
        //  Fall through, since we cannot undo what we have done.
    }

    //
    //  In the end we can free the object if there was one and return
    //  to our caller
    //

    if (Object != NULL)
    {
        ObDereferenceObject( Object );
    }
    return( Status );

}

ObQueryTypeInfo просто переносит аргументы из ObjectType(POBJECT_TYPE) в ObjectTypeInfo(POBJECT_TYPE_INFORMATION):

C++:
NTSTATUS __fastcall ObQueryTypeInfo(POBJECT_TYPE ObjectType, POBJECT_TYPE_INFORMATION ObjectTypeInfo, ULONG Length, PULONG ReturnLength)
{
  NTSTATUS nt_status; // ebx

  nt_status = STATUS_INFO_LENGTH_MISMATCH;

  *ReturnLength += sizeof( *ObjectTypeInfo ) + ALIGN_UP( ObjectType->Name.MaximumLength, ULONG );
  if ( Length >= *ReturnLength )
  {
    ObjectTypeInfo->TotalNumberOfObjects = ObjectType->TotalNumberOfObjects;
    ObjectTypeInfo->TotalNumberOfHandles = ObjectType->TotalNumberOfHandles;
    ObjectTypeInfo->HighWaterNumberOfObjects = ObjectType->HighWaterNumberOfObjects;
    ObjectTypeInfo->HighWaterNumberOfHandles = ObjectType->HighWaterNumberOfHandles;
    ObjectTypeInfo->InvalidAttributes = ObjectType->TypeInfo.InvalidAttributes;
    ObjectTypeInfo->GenericMapping = ObjectType->TypeInfo.GenericMapping;
    ObjectTypeInfo->ValidAccessMask = ObjectType->TypeInfo.ValidAccessMask;
    ObjectTypeInfo->SecurityRequired = (ObjectType->TypeInfo.ObjectTypeFlags & OB_FLAG_EXCLUSIVE_OBJECT ) != 0;
    ObjectTypeInfo->MaintainHandleCount = (ObjectType->TypeInfo.ObjectTypeFlags & OB_FLAG_PERMANENT_OBJECT) != 0;
    ObjectTypeInfo->PoolType = ObjectType->TypeInfo.PoolType;
    ObjectTypeInfo->DefaultPagedPoolCharge = ObjectType->TypeInfo.DefaultPagedPoolCharge;
    ObjectTypeInfo->DefaultNonPagedPoolCharge = ObjectType->TypeInfo.DefaultNonPagedPoolCharge;
    ObjectTypeInfo->TypeIndex = ObjectType->Index;
    nt_status = STATUS_SUCCESS;
    ObjectTypeInfo->ReservedByte = STATUS_SUCCESS;
    ObjectTypeInfo->TypeName.Buffer = &ObjectTypeInfo[1].TypeName.Length;
    ObjectTypeInfo->TypeName.Length = ObjectType->Name.Length;
    ObjectTypeInfo->TypeName.MaximumLength = ObjectType->Name.Length + 2;
    memmove(&ObjectTypeInfo[1], ObjectType->Name.Buffer, ObjectType->Name.Length);
    ((PWSTR)(ObjectTypeInfo+1))[ ObjectType->Name.Length/sizeof(WCHAR) ] = UNICODE_NULL;
  }
  return nt_status;

}

Не вижу смысла описывать полностью т.к это исходный код windows и тут есть комментарии, поэтому перейдём к тому,что нас интересует!
Во-первых,для получения ObjectTypeInformation(количество объектов только для нашего процесса!) вызывается ObReferenceObjectByHandle и после этого конвертируется в POBJECT_TYPE.
Во-вторых,для получения ObjectTypesInformation мы получаем все объекты из глобальной переменной ObpObjectTypes,которая хранит информацию об объектах(POBJECT_TYPE )
и после вызывается ObQueryTypeInfo для передачи параметров в входной буфер.
В-третьих,можно проверить действительно ли установлен флаг защиты от закрытия HANDLE'а.


Теперь вернёмся к ObReferenceObjectByHandle via лечим инфекцию пилой.
Нет возможности дебажить - нет проблем для нас.
Если вы использовали SharpOD,то,возможно,видели возможность исправить какой-то ValidAccessMask.
Но что из себя представляет ValidAccessMask?

Ну,вот ответ:
C++:
void DbgkpInitializePhase0()
{
  __int64 nt_status; // rax
  __int64 some_lenght; // rbx
  union _RTL_RUN_ONCE *call_back; // rcx
  union _RTL_RUN_ONCE *v3; // rcx
  UNICODE_STRING object_name; // [rsp+20h] [rbp-39h] BYREF
  _OBJECT_TYPE_INITIALIZER object_type; // [rsp+30h] [rbp-29h] BYREF

  object_name.Length = 0x180016i64;
  object_name.Buffer = L"DebugObject";
  memset(&object_type, NULL, sizeof(object_type));
  DbgkpProcessDebugPortMutex.Owner = NULL;
  DbgkpProcessDebugPortMutex.Contention = NULL;
  DbgkpProcessDebugPortMutex.Event.Header.SignalState = NULL;
  DbgkpProcessDebugPortMutex.Event.Header.WaitListHead.Blink = &DbgkpProcessDebugPortMutex.Event.Header.WaitListHead;
  DbgkpProcessDebugPortMutex.Event.Header.WaitListHead.Flink = &DbgkpProcessDebugPortMutex.Event.Header.WaitListHead;
  DbgkpProcessDebugPortMutex.Count = 1;        //Я на ем панк-рок пистоню, не найти во мне изъян
  LOWORD(DbgkpProcessDebugPortMutex.Event.Header.Lock) = 1;
  DbgkpProcessDebugPortMutex.Event.Header.Size = 6;
  nt_status = DbgkpGetServerSiloState();

  if (NT_SUCCESS(DbgkpInitializePhase0SiloState(nt_status)))

  {
    object_type.InvalidAttributes = 0;
    object_type.DefaultPagedPoolCharge = 0;
    object_type.DefaultNonPagedPoolCharge = 0;
    object_type.DeleteProcedure = (void (__fastcall *)(void *))AlpcMessageDeleteProcedure;
    object_type.Length = sizeof(object_type);
    object_type.CloseProcedure = DbgkpCloseObject;
    some_lenght = sizeof(HANDLE);
    LOBYTE(object_type.ObjectTypeFlags) |= 8u;
    object_type.ValidAccessMask = DEBUG_ALL_ACCESS;     //Первый парень на весь край, на меня все бабки в лай
    object_type.GenericMapping.GenericAll = DEBUG_ALL_ACCESS;
    object_type.PoolType = NonPagedPoolNx;
    object_type.GenericMapping.GenericRead = 0x20001;
    object_type.GenericMapping.GenericWrite = 0x20002;
    object_type.GenericMapping.GenericExecute = 0x120000;

    if ( NT_SUCCESS(ObCreateObjectType(&object_name, &object_type, NULL, &DbgkDebugObjectType)))
    {

      if ( !DbgkpMaxModuleMsgs )
        DbgkpMaxModuleMsgs = 500;
      call_back = &call_back_dbg;
      do
      {
        CmSiRWLockInitialize(call_back);
        call_back = v3 + 2;
        --some_lenght;
      }
      while ( some_lenght );
    }
  }

}

На самом деле это просто переменная в глобальной структуре DbgkDebugObjectType с правами DEBUG_ALL_ACCESS.
Сам anti-debug трюк заключается в перезаписи ValidAccessMask на NULL т.к после этого некоторые функции связанные с отладчиком будут возвращать STATUS_ACCESS_DENIED/STATUS_NOT_SUPPORTED
На мой взгляд это сильный трюк,поскольку если вы не хотите,чтобы присутствовал отладчик в системе,то вы попросто можете сломать попытку присоединения/создания процесса с DebugPort.
Однако,если дебаггер уже подключился к процессу,то данный трюк бесполезен т.к объект отладчи будет создан и дебаггер будет уже подключен к процессу.
Вот небольшая цепочка, обозначающая неудачную попытку создать процесс/присеодиниться к процессу,если ValidAccessMask = 0.

(Create process)NtCreateProcess with DebugPort -> PspCreateProcess -> ObReferenceObjectByHandle with DEBUG_PROCESS_ASSIGN ->SeComputeDeniedAccesses -> STATUS_ACCESS_DENIED
(Attach to process)NtDebugActiveProcess ->( NT_SUCCESS return FALSE)ObReferenceObjectByHandle with DEBUG_OBJECT_ADD_REMOVE_PROCESS ->SeComputeDeniedAccesses -> STATUS_ACCESS_DENIED -> NT_SUCCESS(ObReferenceObjectByHandle) -> STATUS_NOT_SUPPORTED


Баста! Но я хочу ломать вообще все дебаггеры!(включая дебаггеры,которые уже работают)
Я босс системы и никто не имеет права ограничивать меня!

Ну, тогда скажите привет DbgkpProcessDebugPortMutex.

Вы уже видели это:
DbgkpProcessDebugPortMutex.Count = 1; (in DbgkpInitializePhase0)
DbgkpProcessDebugPortMutex используется в этих функциях:
DbgkpSetProcessDebugObject/DbgkCopyProcessDebugPort/DbgkpQueueMessage/DbgkpCloseObject/DbgkpMarkProcessPeb -> KeReleaseGuardedMutex/ExAcquireFastMutex -> ExpReleaseFastMutexContended


В ExAcquireFastMutex вызывается ExpAcquireFastMutexContended.
По идее,именно там и происходит бесконечный цикл,если переписать DbgkpProcessDebugPortMutex->Count на 0.
Вы не сможете дебажить после присоединения/начала дебага процесса,поскольку ваш отладчик тупо зависнет!
На мой взгляд это самый эффективный anti-debug трюк на данный момнт,который не тригерет при этом PG и позволяет затроллить реверсера
(представьте,что вы присоединились к лоадеру и ваш дебаггер просто ничего не может сделать,вы его даже не можете убить...).
Да, можно переписать данное значение на первоначальное, но если его будут лениво проверять через какое-то время - вы проиграли т.к DbgkpProcessDebugPortMutex используется во многих местах во время дебаггинга.



-Протекторы

Теперь перейдём к протекторам.
Первым будет Themida.

Themida/WinLicense:
У меня нет лицензии, поэтому я воспользуюсь взломанной версией(3.0.4.0)

x64
NtQueryInformationProcess:ProcessDebugPort,ProcessDebugObjectHandle
NtGetContextThread
NtSetInformationThread(ThreadHideFromDebugger)

x32
NtSetContextThread/NtGetContextThread
NtQueryInformationProcess:ProcessDebugPort,ProcessDebugObjectHandle
NtSetInformationThread(ThreadHideFromDebugger)

Так же они хукают DbgUiRemoteBreakin & DbgBreakPoint:

hook DbgUiRemoteBreakin -> LdrShutdownProcess
hook DbgBreakPoint -> ret

Так же поиск окон,но это мем и они даже не пытаются обойти в x32 банальный хук Wow64Transition.
Короче, Темида разочаровала сильно автора.

Теперь перейдём к моему фавориту т.е VMP:
На данный момент VMP( версия 3.6. build 1406) использует следующие трюки:
x32:
Manual read BeingDebugged in PEB
NtQueryInformationProcess:ProcessDebugPort,ProcessDebugObjectHandle
NtSetContextThread
NtSetInformationThread(ThreadHideFromDebugger)
CloseHandle(0xDEADC0DE) (not manual syscall)
NtSetInformationProcess with NULL (ProcessInstrumentationCallback) (not manual syscall)
ZwQuerySystemInformation with SystemKernelDebuggerInformation (Detect KM Debugger and not manual syscall)
ZwQuerySystemInformation with SystemModuleInformation (Detect KM Debugger and not manual syscall)

x64
Manual read BeingDebugged in PEB
NtQueryInformationProcess:ProcessDebugPort,ProcessDebugObjectHandle
NtSetInformationThread:ThreadHideFromDebugger
CloseHandle(0xDEADC0DE) (not manual syscall)
NtSetInformationProcess with NULL (ProcessInstrumentationCallback) (not manual syscall)
ZwQuerySystemInformation with SystemKernelDebuggerInformation (Detect KM Debugger and not manual syscall)
ZwQuerySystemInformation with SystemModuleInformation (Detect KM Debugger and not manual syscall)

Так же VMP уходит в manual syscall/sysentry(x32 system),чтобы обойти банальные хуки в ntdll начиная с версии 3.1.x.(если идёт поддерживаемая сборка т.к номера syscall'ов могут поменяться, иначе идёт вызов NTAPI)
Забавно,но для проверки CRC файла используется manual syscall NtClose,в то время для обнаружения отладчика просто используется CloseHandle.
Так же,если использовать anti-debug в GUI версии т.е встроенный в лоадер кода,то там будут идти проверки на CRC syscall(даже,если отключена опция защита памяти),
в то время как при использовании SDK функций такого нет.

Перед тем как скажу про heaven's gate,уточню один момент для новичков.
Для выполнения x32 кода в x64 системе был предуман WoW64.
Одна из многих целей - нормально выполнять syscall'ы и другие моменты в x32 коде.
В x64 системе у вас нет инструкции sysentry для выполнения+
у вас отсутствуют прямой доступ к регистрам r8/r9/r10 в x32 коде и другие моменты,хотя у вас есть syscall для которого эти параметры необходимы.

VMP в x32 code работающий под Wow64 смешивается с x64 кодом(heaven's gate):
jmp far 33:address т.е прыжок в x64 code.
Это делает забавный трюк,поскольку вы не можете нормально дебажить x64 code под WoW64 в x32 дебаггере.

Вот минимальный пример c выполнением x64 code под WoW64 с прямым syscall NtSetInformationThread(HideFromDebugger):
C-like:
jmp far 0x33:$+7    ;hello x64 code(cs under: x64 code - 0x33,x32 code & wow64 - 0x22,just windows x32 - 0x1B
/*
or
push 0x33
call $+5
add dwowd ptr ss:[esp],5
ret far
*/
mov rcx,-2        ;NtCurrentThread via psevdo-handle thread
mov edx,0x11      ;HideFromDebugger
xor r8d,r8d       ;ThreadInformation
xor r9d,r9d       ;ThreadInformationLength
mov r10,rcx
mov eax,0xD       ;syscall number in Windows 10-11
syscall
call $+5          ;in x64 code we can't use jmp far 0x23:$+7
mov dword ptr ss:[esp+4],0x23
add dword ptr ss:[esp],5
ret far
//P.S 100% не работает в arm/arm64 т.к системные вызовы устроены по-другому
Так же идёт вызов одношагового исключения, чтобы проверить присутствие отладчика + идёт проверка на наличие HWBP.
Так же они хукают DbgUiRemoteBreakin и делают ~ следующие:
C++:
//hook DbgUiRemoteBreakin  -> push DEADC0DE -> VM Entry or
TerminateProcess(GetCurrentProcess,0xDEADC0DE);

Так же VMP пытается злоупотреблять логикой ядра,чтобы обнаружить неправильную логику хуков у anti-anti-debug tool.
На данный момент это :
1)перезапись ProcessInformation с помощью ReturnLength(адрес у ProcessInformation и ReturnLength одинаковый).
returnn lenght mem.png
Пример детекта:
C++:
auto is_bad_overwrite_lenght() -> bool
{

    HANDLE debug_object = NULL;
    auto nt_status NtQueryInformationProcess(NtCurrentProcess, ProcessDebugObjectHandle, &debug_object, sizeof(debug_object), reinterpret_cast<PULONG>(&debug_object));
    return debug_object != sizeof(debug_object) || NT_SUCCESS(nt_status);

}

2)Неправильный указатель.
Начиная с версии 3.5.1.x автор писал,что он добавил
Пожалуйста, авторизуйтесь для просмотра ссылки.
.
Они отправляют неправильный указатель в ReturnLength(1),но в TitanHide нет проверки на входный буффер с помощью ProbeForRead /ProbeForWrite перед записью буффера.
bad pointer.png
~детект выглядит как-то так
C++:
auto is_titan_hide() -> bool
{

    HANDLE debug_object = reinterpret_cast<HANDLE>(1);
    auto nt_status = NtQueryInformationProcess(NtCurrentProcess, ProcessDebugObjectHandle, &debug_object, lenght, reinterpret_cast<PULONG>(0x1));
    return debug_object != reinterpret_cast<HANDLE>(1) && STATUS_ACCESS_VIOLATION ==nt_status;

}

Небольшой бонус для новичков ;) :
Dark_Bull писал 2 года назад обход anti-debug'a в VMP 3.5.1213.
Разработчики VMP спустя столько времени не исправили главную ошибку - они не шифруют syscall.
Да, они изменили вход в VM,но никто не запрещает искать по паттерну syscall: 0F 05( в x64 системе)
Если вы используете anti-debug от лоадера, то syscall будет располагаться в самой последней секции VMP,а syscall для SDK функции в самой первой секции VMP.
Да,если включена опция упаковки,то первая секция будет зашифрована,но посмотрите просто как работает распаковка.
Вы можете легко найти syscall и подделать результат(HWBP + немного ловкости рук и у вас всё легко получится).
Вот информация для новичков и RVA/паттерн anti-debug'a + файл(в конце статьи):
1)Для NtQueryInformationProcess вы можете подделать NTSTATUS на отрицательный(например на -1),после выполнения syscall'a(тесты показали,что VMP проверяет NTSTATUS так:NT_SUCCESS(nt_status),а не например nt_status != STATUS_PORT_NOT_SET)
2)Для NtSetInformationThread вы должны подделать класс/номер syscall'a до его выполнения,иначе поток будет спрятан.
3)Для CloseHandle вы должны вернуть именно STATUS_INVALID_HANDLE(если измените класс/номер syscall'a вручную),иначе будет обнаружения дебаггера.

Просто попробуйте и вы поймёте,что это не так сложно + автор добавил RVA/паттерн для файла, чтобы новичкам было легче:
anti-debug bypassed.png


- Идея VMP

Общаясь с Пермяковым, мне в одно время пришла идея:"А в чём проблема просто обнаружить все anti-anti-debug-tool?".
Ну, я написал
Пожалуйста, авторизуйтесь для просмотра ссылки.
и задену эту тему, поскольку это попросту интересно ^^.
Я не буду разбирать все баги, поскольку нет смысла в этом.
Приведу 2 примера с описанием,а дальше все зависит от вас :D.
Начнём с SharpOD/SchyllaHide и хука NtSetInformationThread:
C++:
/*
https://github.com/x64dbg/ScyllaHide/blob/a0e5b8f2b1d90be65022545d25288f389368a94d/HookLibrary/HookedFunctions.cpp#L34
*/

NTSTATUS NTAPI HookedNtSetInformationThread(HANDLE ThreadHandle, THREADINFOCLASS ThreadInformationClass, PVOID ThreadInformation, ULONG ThreadInformationLength)
{
    if (ThreadInformationClass == ThreadHideFromDebugger && ThreadInformationLength == 0) // NB: ThreadInformation is not checked, this is deliberate
    {
        if (ThreadHandle == NtCurrentThread ||
            HandleToULong(NtCurrentTeb()->ClientId.UniqueProcess) == GetProcessIdByThreadHandle(ThreadHandle)) //thread inside this process?
        {
            return STATUS_SUCCESS;
        }
    }
    return HookDllData.dNtSetInformationThread(ThreadHandle, ThreadInformationClass, ThreadInformation, ThreadInformationLength);

}


Если вы подумаете слегка, то увидите минимум 2 ошибки:
Во-первых, идёт проверка является ли HANDLE нашим процессом,
но никто не запрещает временно перезаписать UniqueProcess в TEB,чтобы скрыть поток или можно заставить вернуть STATUS_SUCCESS,
передав в качестве HANDL'а NULL и временно перезаписать UniqueProcess в TEB на NULL
Во-вторых, можно открыть потом(OpenThread) без прав THREAD_QUERY_INFORMATION ,
благодаря чему проверка с помощью GetProcessIdByThreadHandle(NtQueryInformationThread с ThreadBasicInformation) благополучно обосрётся.
Пример:
C++:
auto is_bad_hide_hook() -> bool
{
    HANDLE uniq_process_id = NtCurrentTeb()->ClientId.UniqueProcess;
    NtCurrentTeb()->ClientId.UniqueProcess = reinterpret_cast<HANDLE>(NULL);
    auto nt_status = NtSetInformationThread(NULL, ThreadHideFromDebugger, NULL, NULL);
    NtCurrentTeb()->ClientId.UniqueProcess = reinterpret_cast<HANDLE>(uniq_process_id)
    return nt_status == STATUS_SUCCESS;

}

Дальше мем с которым не могли долгое время справиться почти все anti-anti-debug tool's и речь про NtClose.
Обычно функция перехватывается и просто возвращается ожидаемый NTSTATUS(STATUS_INVALID_HANDLE/STATUS_HANDLE_NOT_CLOSABLE) и не вызывали ошибку.
Однако, ядро вызовет KeRaiseUserException(STATUS_INVALID_HANDLE)/KeRaiseUserException(STATUS_HANDLE_NOT_CLOSABLE) не только при присутствии DebugPort у EPROCESS,но и если
в NtGlobalFlag установлен флаг FLG_ENABLE_CLOSE_EXCEPTIONS или включена трассировка HANDLE'ов.
Вы не можете установить FLG_ENABLE_CLOSE_EXCEPTIONS используя NtSetSystemInformation,поскольку система очищает некоторые флаги,
но можно включить трассировку с помощью ProcessHandleTracing(NtSetInformationProcess ) и
благодаря этому будет вызываться исключение, если закрыть невалидный/защищенный HANDLE!
Сам пример:
C++:
auto is_very_lazy_hook() -> bool
{
    bool is_lul_hook = FALSE;
    PROCESS_HANDLE_TRACING_ENABLE tracing_handle = { 0 };

    auto nt_status = NtSetInformationProcess(NtCurrentProcess, ProcessHandleTracing, &tracing_handle, sizeof(tracing_handle));

    if (!NT_SUCCESS(nt_status))
        return FALSE;
    __try
    {
        NtClose((HANDLE)(0x13333337));
        is_lul_hook = TRUE;
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        is_lul_hook =  FALSE;
    }

    NtSetInformationProcess(NtCurrentProcess, ProcessHandleTracing, &tracing_handle, NULL);
    return is_lul_hook;

}

Дальше я скажу, про что мне лень было говорить:
1)На самом деле можно дебажить процесс без создания DebugPort'а,но вам придётся похукать много функций, но Китайцы сделали мемный
Пожалуйста, авторизуйтесь для просмотра ссылки.
:
2)Для людей,которые не знают как работают Heaven’s Gate,рекомендую прочитать:
Пожалуйста, авторизуйтесь для просмотра ссылки.

Пожалуйста, авторизуйтесь для просмотра ссылки.

3)Я щё не изучил как работает anti-debug VMP в x32 системе полностью, но если окажется что-то неправильно в этой статье, то исправлю это в ближайшее время ;) .
Ну,вот и всё.
Данная забавная статья подошла к концу.
Надеюсь, вам было интересно или хотя бы весело :)

Пожалуйста, авторизуйтесь для просмотра ссылки.


Огромное спасибо colby57 за опыт.

Пожалуйста, авторизуйтесь для просмотра ссылки.
 
Последнее редактирование:
Забаненный
Статус
Оффлайн
Регистрация
18 Июл 2022
Сообщения
63
Реакции[?]
11
Поинты[?]
0
Обратите внимание, пользователь заблокирован на форуме. Не рекомендуется проводить сделки.
ДИРЕКТОР ИНТЕРНЕТА
Пользователь
Статус
Оффлайн
Регистрация
17 Дек 2017
Сообщения
704
Реакции[?]
84
Поинты[?]
12K
нихуя не понял но очень интересно
 
EFI_COMPROMISED_DATA
лучший в мире
Статус
Оффлайн
Регистрация
26 Янв 2018
Сообщения
915
Реакции[?]
1,622
Поинты[?]
63K
на этом сайте нету трюков которые использует вмп 3.6 полагаю
Я и не говорю, что они там есть. Сайт был скинут скорее в дополнение. Один хуй любой разработчик понимает, что все ваши "трюки" это полная туфта, которая не прибавляет твоему приложению каких-то не ебаться плюсов в защите.
 
:-|
Пользователь
Статус
Оффлайн
Регистрация
13 Май 2021
Сообщения
120
Реакции[?]
38
Поинты[?]
2K
Спасибо большое
 
Забаненный
Статус
Оффлайн
Регистрация
26 Июн 2022
Сообщения
90
Реакции[?]
10
Поинты[?]
1K
Обратите внимание, пользователь заблокирован на форуме. Не рекомендуется проводить сделки.
огромный гайд(а главное без тайминг золупы), не представляю сколько времени и сил ушло на его написание.
осталось все в один метод запихнуть и обвернуть протектором xD
 
Today is the day I live
Пользователь
Статус
Оффлайн
Регистрация
2 Июл 2020
Сообщения
125
Реакции[?]
237
Поинты[?]
66K
Ссылку на Google disk не удовлетворяет правилам Google Disk, поэтому обновил её :roflanPominki:

огромный гайд(а главное без тайминг золупы), не представляю сколько времени и сил ушло на его написание.
осталось все в один метод запихнуть и обвернуть протектором xD
На самом деле я хотел добавить в конце примеры обнаружения дебаггера из ядра,но тогда бы статья растянулась бы в 2 раза(а она и без этого большая + в тот момент мне уже всё надоело).
Я как-то не получил удовольствия во время/после написания статьи, но спасибо всем👍
 
Похожие темы
Ответы
826
Просмотры
70K
Сверху Снизу