Следующая новость
Предыдущая новость

Антиотладка. Теория и практика защиты приложений от дебага

17.01.2018 13:06

Содержание статьи

  • IsDebuggerPresent() и структура PEB
  • Process Environment Block
  • NtGlobalFlag
  • Flags и ForceFlags
  • CheckRemoteDebuggerPresent() и NtQueryInformationProcess
  • Тонкости NtQueryInfoProcess
  • DebugObject
  • ProcessDebugFlags
  • Проверка родительского процесса
  • TLS Callbacks
  • Отладочные регистры
  • NtSetInformationThread
  • NtCreateThreadEx
  • SeDebugPrivilege
  • SetHandleInformation
  • Заключение

К методам детектирования отладки прибегают многие программисты: одни хотели бы уберечь свои продукты от конкурентов, другие противостоят вирусным аналитикам или автоматическим системам распознавания малвари. Мы в подробностях рассмотрим разные методы борьбы с дебагом — от простых до довольно нетривиальных.

Поскольку сейчас популярна не только архитектура x86, но и x86-64, многие старые средства обнаружения отладчиков устарели. Другие требуют корректировки, потому что жестко завязаны на смещения в архитектуре x86. В этой статье я расскажу о нескольких методах детекта отладчика и покажу код, который будет работать и на x64, и на x86.

IsDebuggerPresent() и структура PEB

Начинать говорить об антиотладке и не упомянуть о функции IsDebuggerPresent() было бы неправильно. Она универсальна, работает на разных архитектурах и очень проста в использовании. Чтобы определить отладку, достаточно одной строки кода: if (IsDebuggerPresent()).

Что представляет собой WinAPI IsDebuggerPresent? Эта функция обращается к структуре PEB.

Process Environment Block

Блок окружения процесса (PEB) заполняется загрузчиком операционной системы, находится в адресном пространстве процесса и может быть модифицирован из режима usermode. Он содержит много полей: например, отсюда можно узнать информацию о текущем модуле, окружении и загруженных модулях. Получить структуру PEB можно, обратившись к ней напрямую по адресу fs:[30h] для x86 и gs:[60h] для x64.

Соответственно, если загрузить в отладчик функцию IsDebuggerPresent(), на x86-системе мы увидим:

mov     eax,dword ptr fs:[30h] movzx   eax,byte ptr [eax+2] ret 

А на x64 код будет таким:

mov   rax,qword ptr gs:[60h] movzx eax,byte ptr [rax+2] ret 

Что значит byte ptr [rax+2]? По этому смещению находится поле BeingDebugged в структуре PEB, которое и сигнализирует нам о факте отладки. Как еще можно использовать PEB для обнаружения отладки?

NtGlobalFlag

Во время отладки система выставляет флаги FLG_HEAP_VALIDATE_PARAMETERS, FLG_HEAP_ENABLE_TAIL_CHECK, FLG_HEAP_ENABLE_FREE_CHECK, в поле NtGlobalFlag, которое находится в структуре PEB. Отладчик использует эти флаги для контроля разрушения кучи посредством переполнения. Битовая маска флагов — 0x70. Смещение NtGlobalFlag в PEB для x86 составляет 0x68, для x64 — 0xBC. Чтобы показать пример кода детекта отладчика по NtGlobalFlag, воспользуемся функциями intrinsics, а чтобы код был более универсальным, используем директивы препроцессора:

#ifdef _WIN64  DWORD pNtGlobalFlag = NULL; PPEB pPeb = (PPEB)__readgsqword(0x60); pNtGlobalFlag = *(PDWORD)((PBYTE)pPeb + 0xBC);  #else  DWORD pNtGlobalFlag = NULL; PPEB pPeb = (PPEB)__readfsdword(0x30); pNtGlobalFlag = *(PDWORD)((PBYTE)pPeb + 0x68);  #endif  if ((pNtGlobalFlag & 0x70) != 0) std::cout << "Debugger detected!n"; 

Flags и ForceFlags

PEB также содержит указатель на структуру _HEAP, в которой есть поля Flags и ForceFlags. Когда отладчик подсоединен к приложению, поля Flags и ForceFlags содержат признаки отладки. ForceFlags при отладке не должно быть равно нулю, полеFlagsне должно быть равно0x00000002`:

#ifdef _WIN64  PINT64 pProcHeap = (PINT64)(__readgsqword(0x60) + 0x30);    \ Получаем структуру _HEAP через PEB PUINT32 pFlags = (PUINT32)(*pProcHeap + 0x70);      \ Получаем Flags внутри _HEAP PUINT32 pForceFlags = (PUINT32)(*pProcHeap + 0x74);     \ Получаем ForceFlags внутри _HEAP  #else  PPEB pPeb = (PPEB)(__readfsdword(0x30) + 0x18); PUINT32 pFlags = (PUINT32)(*pProcessHeap + 0x40); PUINT32 pForceFlags = (PUINT32)(*pProcessHeap + 0x44);  #endif  if (*pFlags & ~HEAP_GROWABLE || *pForceFlags != 0)  std::cout << "Debugger detected!n"; 

Продолжение статьи доступно только подписчикам

Cтатьи из последних выпусков журнала можно покупать отдельно только через два месяца после публикации. Чтобы читать эту статью, необходимо купить подписку.

Подпишись на журнал «Хакер» по выгодной цене!

Подписка позволит тебе в течение указанного срока читать ВСЕ платные материалы сайта, включая эту статью. Мы принимаем оплату банковскими картами, электронными деньгами и переводами со счетов мобильных операторов. Подробнее о подписке

1 год

5690 р.

Экономия 1400 рублей!

1 месяц

720 р.

25-30 статей в месяц

Уже подписан?

Источник

Последние новости