• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Аппаратные брейкпоинты и исключения в Windows

Azrv3l

win32kfull
Эксперт
Регистрация
30.03.2019
Сообщения
215
Реакции
539
Аппаратные брейкпоинты в основном используются для отладки. В отличие от обычных точек останова, они не требуют модификации кода и более универсальны. Из-за этого они часто используются при отладке целей, которые используют тактику защиты от отладки. В этой статье подробно описывается внутренняя работа аппаратных брейкпоинтов в Windows, а также рассматриваются некоторые общие способы использования и методы обнаружения.

0.0 Предисловие
Исследование в этом блоге проводилось на 64-битной Windows 10 20H1. Возможно, что некоторые методы могут быть похожи на 32-битную Windows, однако это не является основной темой этого сообщения. Более того, эти методы, вероятно, будут сильно отличаться в других операционных системах, таких как Linux и OSX, из-за архитектурных различий.

1.0 Краткое руководство по регистрам отладки
Читатели, знакомые с регистрами отладки, могут сразу перейти к разделу 2.0.

Аппаратные брейкпоинты доступны как на x86, так и на x64. Они реализованы с помощью 8 регистров отладки, с именами от DR0 до DR7. Эти регистры имеют длину 32 и 64 бита на x86 и x64 соответственно. Расположение регистров на архитектуре x64 можно увидеть на рисунке ниже. Не волнуйтесь, если рисунок кажется запутанным, мы рассмотрим каждый регистр более подробно. Если вы хотите узнать больше о более тонких деталях регистров отладки, Intel SDM и AMD APM - отличные ресурсы.

1.jpg

1.1 DR0 - DR3

От DR0 до DR3 упоминаются как «Регистры адреса отладки» или «Регистры адреса точки останова». Они очень просты, поскольку содержат только линейный адрес точки останова. Когда этот адрес совпадает с инструкцией или ссылкой на данные, возникает остановка. Регистр отладки DR7 можно использовать для более детального управления условиями каждой точки останова. Поскольку регистры должны быть заполнены линейным адресом, они будут работать, даже если пейджинг отключён. В этом случае линейный адрес будет таким же, как физический адрес.

1.2 DR4 - DR5

DR4 и DR5 и «Зарезервированные регистры отладки». Несмотря на то, что можно предположить из их названия, они не всегда зарезервированы и могут использоваться. Их функциональность зависит от значения поля DE в регистре управления CR4. Когда этот бит включен, точки останова ввода-вывода включены, и попытка доступа к одному из регистров приводит к генерации исключения #UD. Однако, когда бит DE не активирован, регистры отладки DR4 и DR5 отображаются в DR6 и DR7 соответственно. Это сделано для совместимости с ПО для старых процессоров.

1.3 DR6
Когда срабатывает аппаратная точка останова, статус отладки сохраняется в регистре отладки DR6. Вот почему этот регистр называется «Регистром состояния отладки». Он содержит биты для быстрой проверки, были ли запущены определенные события.

Биты с 0 по 3 устанавливаются в зависимости от того, какая аппаратная точка останова запускается. Это используется для быстрой проверки сработавшей точки останова.

Бит 13 называется BD и устанавливается, если текущее исключение запускается из-за доступа к регистру отладки. Бит GD должен быть активирован в DR7 для запуска этого типа исключения.

Бит 14 называется BS и устанавливается, если текущее исключение запускается из-за одиночного шага. Флаг TF должен быть включен в регистре EFLAGS для запуска этого типа исключения.

Бит 15 называется TS и устанавливается, если текущее исключение запускается из-за того, что текущая задача переключилась на задачу, для которой включен флаг отладочной ловушки.

1.4 DR7

DR7 называется «Регистром управления отладкой» и позволяет детально контролировать каждую точку останова. Первые 8 битов определяют, включена ли конкретная аппаратная точка останова. Четные биты (0, 2, 4 и 6), называемые L0 - L3, включают точку останова локально, то есть она срабатывает только при обнаружении исключения точки останова в текущей задаче. Нечетные биты (1, 3, 5, 7), называемые G0 - G3, активируют точку останова глобально, то есть она срабатывает при обнаружении исключения точки останова в любой задаче. Когда точка останова включена локально, соответствующие биты удаляются при переключении аппаратной задачи, чтобы избежать нежелательных точек останова в новой задаче. Биты не сбрасываются, когда он включен глобально.

Биты 8 и 9 называются LE и GE и представляют собой устаревшие функции, которые ничего не делают в современных процессорах. Эти биты использовались для указания процессору определить точную инструкцию, на которой возникла точка останова. Все условия точки останова на современных процессорах точны. Для совместимости со старым оборудованием рекомендуется всегда устанавливать оба бита в 1.

Бит 13 называется GD и очень интересен. Если этот бит включен, исключение отладки будет генерироваться всякий раз, когда инструкция пытается получить доступ к регистру отладки. Чтобы отличить этот тип исключения от обычного исключения аппаратной точки останова, в регистре отладки DR6 включен флаг BD. Этот бит обычно используется для предотвращения вмешательства программ в регистры отладки. Важно помнить, что исключение происходит до выполнения инструкции, и этот флаг автоматически удаляется процессором при вводе обработчика исключения отладки. Однако это решение не идеально, поскольку оно работает только с использованием инструкций MOV для доступа к регистру отладки. Они недоступны в пользовательском режиме, и, судя по моему тестированию, функции GetThreadContext и SetThreadContext не запускают это событие. Это делает невозможным использование этого обнаружения в пользовательском режиме.

Биты с 16 по 31 используются для управления условиями и размером каждой аппаратной точки останова. Каждый регистр имеет 4 бита, которые разделены на 2 2-битных поля. Первые 2 бита используются для определения типа аппаратной точки останова. Исключение отладки можно генерировать только при выполнении инструкции, записи данных, чтении и записи ввода-вывода, чтении и записи данных. Чтение и запись ввода-вывода разрешены только в том случае, если включено поле DE в регистре управления CR4, в противном случае это условие является неопределенным поведением. Размером можно управлять, используя последние 2 бита, и он используется для указания размера ячейки памяти по указанному адресу. Доступные размеры: 1 байт, 2 байта, 4 байта и 8 байтов.

1.5 Использование
Использование регистров отладки довольно просто. Существуют специальные инструкции для перемещения содержимого из регистра общего назначения в регистр отладки или наоборот. Однако эти инструкции могут быть выполнены только на уровне привилегий 0, в противном случае будет сгенерировано исключение #GP (0). Чтобы разрешить приложениям пользовательского режима изменять регистр отладки, Windows добавила поддержку изменения этих регистров с помощью SetThreadContext и GetThreadContext API. Пример использования этих функций показан в следующем фрагменте кода.
C++:
/* Инициализация контекстной структуры  */
CONTEXT context = { 0 };
context.ContextFlags = CONTEXT_ALL;

/* Заполнить контекстную структуру, контекстом текущего потока */
GetThreadContext(GetCurrentThread(), &context);

/* Установить локальную 1-байтовую аппаратную точку останова на test_func */
context.Dr0 = (DWORD64)&test_func;
context.Dr7 = 1 << 0;
context.ContextFlags = CONTEXT_DEBUG_REGISTERS;

/* Установить контекст */
SetThreadContext(GetCurrentThread(), &context);

2.0 Windows и исключения
Теперь, когда мы знаем, как использовать аппаратные точки останова, пора посмотреть, как Windows справляется с ними.

Когда срабатывает аппаратная точка останова, независимо от причины запускается исключение #DB. Это соответствует прерыванию №1, что означает, что выполнение будет перенаправлено обработчику прерывания 1. Для получения дополнительной информации о том, как обрабатываются исключения, я рекомендую прочитать это сообщение, написанное Daax.

2.png


В Windows каждый обработчик прерывания инициализируется во время загрузки. Как именно это будет сделано, пока не важно. Каждый обработчик прерывания можно найти в таблице KiInterruptInitTable в ntoskrnl.exe. Это показывает нам, что KiDebugTrapOrFault является обработчиком прерывания для прерывания №1. Вторую функцию каждой записи пока можно игнорировать, она связана с смягчением последствий Meltdown, которое было добавлено в Windows.

KiDebugTrapOrFault начинает с выполнения некоторых проверок работоспособности, чтобы убедиться в правильности GS. Эти проверки были добавлены для смягчения последствий CVE-2018-88974. Если все верно, вызывается KxDebugTrapOrFault. Эта функция эквивалентна KiDebugTrapOrFault до добавления защиты. Функция начинается с сохранения определенных регистров в TrapFrame. Остальная часть функции не очень полезна для нас, но она проверяет некоторые вещи, такие как SMAP. В конце функции вызывается KiExceptionDispatch.

KiExceptionDispatch немного интереснее предыдущих функций. Он начинается с выделения ExceptionFrame в стеке и его заполнения. После этого он сохраняет несколько энергонезависимых регистров. Как только это будет сделано, функция создаст ExceptionRecord и заполнит его информацией о текущем исключении. После этого вызывается KiDispatchException.

Код:
.text:00000001403EF940     KiExceptionDispatch proc near
.text:00000001403EF940
.text:00000001403EF940     ExceptionFrame  = _KEXCEPTION_FRAME ptr -1D8h
.text:00000001403EF940     ExceptionRecord = _EXCEPTION_RECORD ptr -98h
.text:00000001403EF940
.text:00000001403EF940                 sub     rsp, 1D8h
.text:00000001403EF947                 lea     rax, [rsp+1D8h+ExceptionFrame._Rbx]
.text:00000001403EF94F                 movaps  xmmword ptr [rsp+1D8h+ExceptionFrame._Xmm6.Low], xmm6
.text:00000001403EF954                 movaps  xmmword ptr [rsp+1D8h+ExceptionFrame._Xmm7.Low], xmm7
.text:00000001403EF959                 movaps  xmmword ptr [rsp+1D8h+ExceptionFrame._Xmm8.Low], xmm8
.text:00000001403EF95F                 movaps  xmmword ptr [rsp+1D8h+ExceptionFrame._Xmm9.Low], xmm9
.text:00000001403EF965                 movaps  xmmword ptr [rsp+1D8h+ExceptionFrame._Xmm10.Low], xmm10
.text:00000001403EF96B                 movaps  xmmword ptr [rax-80h], xmm11
.text:00000001403EF970                 movaps  xmmword ptr [rax-70h], xmm12
.text:00000001403EF975                 movaps  xmmword ptr [rax-60h], xmm13
.text:00000001403EF97A                 movaps  xmmword ptr [rax-50h], xmm14
.text:00000001403EF97F                 movaps  xmmword ptr [rax-40h], xmm15
.text:00000001403EF984                 mov     [rax], rbx
.text:00000001403EF987                 mov     [rax+8], rdi
.text:00000001403EF98B                 mov     [rax+10h], rsi
.text:00000001403EF98F                 mov     [rax+18h], r12
.text:00000001403EF993                 mov     [rax+20h], r13
.text:00000001403EF997                 mov     [rax+28h], r14
.text:00000001403EF99B                 mov     [rax+30h], r15

[...]

.text:00000001403EF9BD                 lea     rax, [rsp+1D8h+ExceptionFrame.Return]
.text:00000001403EF9C5                 mov     [rax], ecx
.text:00000001403EF9C7                 xor     ecx, ecx
.text:00000001403EF9C9                 mov     [rax+4], ecx
.text:00000001403EF9CC                 mov     [rax+8], rcx
.text:00000001403EF9D0                 mov     [rax+10h], r8
.text:00000001403EF9D4                 mov     [rax+18h], edx
.text:00000001403EF9D7                 mov     [rax+20h], r9
.text:00000001403EF9DB                 mov     [rax+28h], r10
.text:00000001403EF9DF                 mov     [rax+30h], r11
.text:00000001403EF9E3                 mov     r9b, [rbp+0F0h]
.text:00000001403EF9EA                 and     r9b, 1          ; PreviousMode
.text:00000001403EF9EE                 mov     byte ptr [rsp+1D8h+ExceptionFrame.P5], 1 ; FirstChance
.text:00000001403EF9F3                 lea     r8, [rbp-80h]   ; TrapFrame
.text:00000001403EF9F7                 mov     rdx, rsp        ; ExceptionFrame
.text:00000001403EF9FA                 mov     rcx, rax        ; ExceptionRecord

[...]

.text:00000001403EFA67 SkipExceptionStack:
.text:00000001403EFA67                 call    KiDispatchException

KiDispatchException - это довольно длинная функция, в которой исключение наконец отправляется обработчику исключений. Ну, почти. Короче говоря, эта функция применит некоторые преобразования к коду исключения, объединит TrapFrame и ExceptionFrame в ContextRecord и предварительно обработает исключение, вызвав KiPreprocessFault. Что происходит здесь, зависит от того, произошло ли исключение из режима пользователя или режима ядра. В обоих случаях это позволит отладчику обработать это как первый и второй шанс.

Если исключение пришло из режима ядра, будет вызвано исключение RtlDispatchException, которое будет искать любые обработчики SEH и вызывать их. Если он не может найти обработчик SEH или если исключение не обрабатывается правильно, система выполнит проверку ошибок, вызвав KeBugCheckEx. Если исключение возникло из пользовательского режима, некоторые поля в TrapFrame будут исправлены, например указатель стека. Наконец, указатель инструкции в TrapFrame будет перезаписан адресом KeUserExceptionDispatcher. Мы скоро узнаем, что делает эта функция. ExceptionRecord и ContextRecord копируются в стек пользователя, и функция вернется.

Вернувшись в KiExceptionDispatch, мы просто очистим стек, восстановим изменчивое состояние, которое мы сохранили ранее, и вернемся в пользовательский режим с помощью iretq. Поскольку мы ранее перезаписали стек пользователя, поток выполнения возобновляется из KeUserExceptionDispatcher.

Код:
.text:00000001403EFA6C                 lea     rcx, [rsp+1D8h+ExceptionFrame._Rbx] ; rcx = _KTRAP_FRAME
.text:00000001403EFA74                 movaps  xmm6, xmmword ptr [rsp+1D8h+ExceptionFrame._Xmm6.Low]
.text:00000001403EFA79                 movaps  xmm7, xmmword ptr [rsp+1D8h+ExceptionFrame._Xmm7.Low]
.text:00000001403EFA7E                 movaps  xmm8, xmmword ptr [rsp+1D8h+ExceptionFrame._Xmm8.Low]
.text:00000001403EFA84                 movaps  xmm9, xmmword ptr [rsp+1D8h+ExceptionFrame._Xmm9.Low]
.text:00000001403EFA8A                 movaps  xmm10, xmmword ptr [rsp+1D8h+ExceptionFrame._Xmm10.Low]
.text:00000001403EFA90                 movaps  xmm11, xmmword ptr [rcx-80h]
.text:00000001403EFA95                 movaps  xmm12, xmmword ptr [rcx-70h]
.text:00000001403EFA9A                 movaps  xmm13, xmmword ptr [rcx-60h]
.text:00000001403EFA9F                 movaps  xmm14, xmmword ptr [rcx-50h]
.text:00000001403EFAA4                 movaps  xmm15, xmmword ptr [rcx-40h]
.text:00000001403EFAA9                 mov     rbx, [rcx]
.text:00000001403EFAAC                 mov     rdi, [rcx+8]
.text:00000001403EFAB0                 mov     rsi, [rcx+10h]
.text:00000001403EFAB4                 mov     r12, [rcx+18h]
.text:00000001403EFAB8                 mov     r13, [rcx+20h]
.text:00000001403EFABC                 mov     r14, [rcx+28h]
.text:00000001403EFAC0                 mov     r15, [rcx+30h]

[...]

.text:00000001403EFBEC                 mov     rdx, [rbp-40h]
.text:00000001403EFBF0                 mov     rcx, [rbp-48h]
.text:00000001403EFBF4                 mov     rax, [rbp-50h]
.text:00000001403EFBF8                 mov     rsp, rbp
.text:00000001403EFBFB                 mov     rbp, [rbp+0D8h]
.text:00000001403EFC02                 add     rsp, 0E8h

[...]

.text:00000001403EFC17                 swapgs
.text:00000001403EFC1A                 iretq

Помните тот адрес KeUserExceptionDispatcher, который мы установили ранее? На самом деле это KiUserExceptionDispatcher, который находится в ntdll.dll. Эта функция отвечает за обработку исключений в пользовательском режиме. Он получит ExceptionRecord и Context из исключения и передаст выполнение в RtlDispatchException. Я не буду вдаваться в подробности здесь, но в конечном итоге он проверит обработчики исключений SEH и VEH и вызовет их, если они есть.

Код:
.text:000000018009EBF0 KiUserExceptionDispatcher proc near
.text:000000018009EBF0                 cld
.text:000000018009EBF1                 mov     rax, cs:Wow64PrepareForException
.text:000000018009EBF8                 test    rax, rax
.text:000000018009EBFB                 jz      short loc_18009EC0C
.text:000000018009EBFD                 mov     rcx, rsp
.text:000000018009EC00                 add     rcx, 4F0h
.text:000000018009EC07                 mov     rdx, rsp
.text:000000018009EC0A                 call    rax ; Wow64PrepareForException
.text:000000018009EC0C
.text:000000018009EC0C loc_18009EC0C:
.text:000000018009EC0C                 mov     rcx, rsp
.text:000000018009EC0F                 add     rcx, 4F0h
.text:000000018009EC16                 mov     rdx, rsp
.text:000000018009EC19                 call    RtlDispatchException
.text:000000018009EC1E                 test    al, al
.text:000000018009EC20                 jz      short loc_18009EC2E
.text:000000018009EC22                 mov     rcx, rsp
.text:000000018009EC25                 xor     edx, edx
.text:000000018009EC27                 call    RtlGuardRestoreContext
.text:000000018009EC2C                 jmp     short loc_18009EC43
.text:000000018009EC2E ; ---------------------------------------------------------------------------
.text:000000018009EC2E
.text:000000018009EC2E loc_18009EC2E:
.text:000000018009EC2E                 mov     rcx, rsp
.text:000000018009EC31                 add     rcx, 4F0h
.text:000000018009EC38                 mov     rdx, rsp
.text:000000018009EC3B                 xor     r8b, r8b
.text:000000018009EC3E                 call    ZwRaiseException
.text:000000018009EC43
.text:000000018009EC43 loc_18009EC43:
.text:000000018009EC43                 mov     ecx, eax
.text:000000018009EC45                 call    RtlRaiseStatus
.text:000000018009EC45 KiUserExceptionDispatcher endp

3.0 (Вредоносное) Использование

3.1 Отладка

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

3.2 Malware

Благодаря скрытому использованию и встроенным средствам управления безопасностью (см. DR7, бит 13) они также являются любимым инструментом авторов вредоносных программ, особенно руткитов. Они позволяют вредоносному ПО незаметно перехватить функцию. Это можно использовать для перехвата важных системных процедур, таких как KiSystemCall64 в Windows или do_debug в Linux5.

3.3 Читинг

Конечно, эти методы также используются читами, которые хотят оставаться скрытыми от античитов. Регистры отладки можно использовать для перехвата важных игровых функций и реализации пользовательской логики. Хорошим примером этого является ловушка Outlines VEH, выпущенная EBFE для Overwatch. Регистр отладки помещается в функцию, отвечающую за рисование контуров проигрывателя, а обработчик исключений регистрируется с помощью AddVectoredExceptionHandler. Когда игра вызывает функцию outlines, аппаратная точка останова срабатывает и перенаправляет поток управления зарегистрированному обработчику исключений. Здесь он проверяет, исходит ли исключение из функции контуров, и редактирует некоторые данные, чтобы игра рисовала контур для всех игроков. Похоже, эта техника довольно хороша, поскольку Blizzard, похоже, не может ее обнаружить.

4.0 Общие векторы обнаружения
В последнем разделе мы рассмотрим некоторые общие векторы обнаружения аппаратных точек останова. Для простоты к примерам не будут применяться какие-либо методы запутывания, и это оставлено в качестве упражнения для читателя. Вы можете быть настолько безумным, насколько хотите.

4.1 GetThreadContext

Один из простейших способов обнаружения аппаратных точек останова - использование WinAPI GetThreadContext. Эта функция просто возвращает структуру CONTEXT для данного потока. Эта структура включает значение каждого регистра отладки, что позволяет нам легко проверить, заполнен ли какой-либо из регистров.

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

C++:
/* Подготовка контекстной структуры  */
CONTEXT context = { 0 };
/* CONTEXT_ALL заполнит все поля в структуре, это можно изменить в зависимости от ваших потребностей.  */
context.ContextFlags = CONTEXT_ALL;

/* Вызов GetThreadContext с текущим потоком  */
BOOL result = GetThreadContext(GetCurrentThread(), &context);
if (!result)
{
    /* GetThreadContext failed, use GetLastError to find out why */
    return;
}

/* Проверка каждого поля регистра отладки  */
if (context.Dr0 != 0 /* ... */)
{
    /* Debug register detected */
}

4.2 Обработчик исключений
Альтернативный способ получения структуры CONTEXT, включая регистры отладки, - это регистрация обработчика исключений. Первый и единственный аргумент в обработчике исключений VEH - это указатель на структуру EXCEPTION_POINTERS. Эта структура содержит информацию о текущем исключении, а также указатель на структуру CONTEXT. Оттуда мы можем легко проверить, заполнен ли какой-либо из регистров отладки. Есть несколько способов реализовать это обнаружение, самый простой - использовать AddVectoredExceptionHandler и RaiseException.

C++:
/* Наш обработчик исключений */
long debug_veh(struct _EXCEPTION_POINTERS* ExceptionInfo)
{
    /* Проверка исключения */
    if (ExceptionInfo->ExceptionRecord->ExceptionCode == 0x1337)
    {
        /* Проверьте каждое поле регистра отладки  */
        if (ExceptionInfo->ContextRecord->Dr0 != 0 /* ... */)
        {
            /* Обнаружен регистр отладки  */
        }

        /* Исправляет ошибку деления на ноль (см. Ниже). Второй аргумент должен быть сохранен в rcx, просто измените его на 100/10, прежде чем продолжить.  */
        /* ExceptionInfo->ContextRecord->Rcx = 10; */

        /* Исключение обработано, мы можем продолжить нормальное выполнение  */
        return EXCEPTION_CONTINUE_EXECUTION;
    }
    
    /* Попробуйте следующий обработчик исключения, если это не то исключение  */
    return EXCEPTION_CONTINUE_SEARCH;
}

[...]

/* Где-нибудь в функции инициализации зарегистрируйте наш обработчик исключений  */
AddVectoredExceptionHandler(1, debug_veh);

[...]

/* Обнаружение может быть запущено, когда вы захотите, вызвав исключение  */
RaiseException(0x1337, 0, 0, nullptr);

/* В качестве альтернативы, если вышеуказанное не работает должным образом, просто активируйте ошибку деления на ноль.
   Обязательно измените код исключения и исправьте ошибку (см. Выше)  */
volatile int b = 0;
volatile int a = 100 / b;

4.3 MOV DRx инструкции

Это обнаружение возможно только при выполнении в режиме ядра, так как используемые инструкции MOV нигде не доступны. Используя __readdr и __writedr, можно напрямую управлять содержимым регистров отладки. Мы можем использовать эти встроенные функции, чтобы проверить, установлен ли какой-либо из регистров отладки. Важно помнить, что злоумышленник мог включить общий бит обнаружения в DR7. Это приводит к генерации исключения #DB каждый раз при доступе к регистру отладки. Это можно использовать для быстрой очистки регистров, когда вы пытаетесь их проверить.

C++:
/* Проверьте каждое поле регистра отладки  */
if (__readdr(0) != 0)
{
    /* Обнаружен регистр отладки */
}

4.4 Проверка DR6

Когда срабатывает аппаратная точка останова, DR6 заполняется информацией о событии. Это можно использовать для принятия более обоснованных решений в текущей ситуации. Важно отметить, что DR6 не очищается автоматически после обработки аппаратной точки останова. Следующий абзац Intel SDM описывает это более подробно.

Определенные исключения отладки могут очищать биты 0–3. Оставшееся содержимое регистра DR6 никогда не очищается процессором. Чтобы избежать путаницы при идентификации исключений отладки, обработчики отладки должны очистить регистр (кроме бита 16, который они должны установить) перед возвратом к прерванной задаче.

Если вы уверены, что программа не использует аппаратные точки останова, можно проверить значение DR6, используя любой из ранее упомянутых методов, поскольку злоумышленник мог не очистить регистр.

4.5 Использование всех регистров отладки

Один из самых простых способов - просто использовать все доступные регистры отладки для себя. Этот метод ограничен только вашим творчеством и позволяет как обнаруживать, так и давать сбой при использовании оборудования. Простая реализация этого метода - установить все аппаратные точки останова на важных функциях. После вызова точки останова вы манипулируете некоторыми данными, прежде чем вернуться к исходной функции. Если злоумышленник перезапишет любой из регистров отладки, манипуляции с данными не произойдет, и программа выйдет из строя. Пример ниже изменяет сборку и восстанавливает ее прямо перед выполнением. Это можно повторить для всех 4 регистров отладки, поэтому удаление одного из них приведет к сбою программы.

C++:
/* Измените права доступа к странице на RWX, чтобы мы могли изменить ассемблер */
DWORD old_protect = 0;
BOOL result = VirtualProtect((void*)test_func, 0x1000, PAGE_EXECUTE_READWRITE, &old_protect);
if (!result)
{
    /* Ошибка VirtualProtect, вызовите GetLastError, чтобы узнать, почему */
    return;
}


/* Заменяем ассемблер мусором */
*(byte*)test_func ^= 0x42;


/* Зарегистрируйте наш VEH  */
AddVectoredExceptionHandler(1, debug_veh);


/* Установите аппаратную точку останова для нашей функции */
CONTEXT context = { 0 };
context.ContextFlags = CONTEXT_ALL;

GetThreadContext(GetCurrentThread(), &context);

context.Dr0 = (DWORD64)test_func;
context.Dr7 = 1 << 0;
context.ContextFlags = CONTEXT_DEBUG_REGISTERS;

SetThreadContext(GetCurrentThread(), &context);


[...]


long debug_veh(struct _EXCEPTION_POINTERS* ExceptionInfo)
{
    /* Проверьте, исходит ли исключение от нас */
    if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP)
    {
        /* Восстановите ассемблер перед его выполнением. 
           Мы не превращаем его обратно в мусор, поэтому при последующих вызовах произойдет сбой.
           Это может быть достигнуто во второй аппаратной точке останова. */
        *(byte*)test_func ^= 0x42;

        /* Установите флаг возобновления (RF), чтобы мы не застряли в бесконечном цикле  */
        ExceptionInfo->ContextRecord->EFlags |= 0x10000;

        return EXCEPTION_CONTINUE_EXECUTION;
    }

    return EXCEPTION_CONTINUE_SEARCH;
}

Заключение
Некоторые люди, чьи предыдущие исследования мне очень помогли, перечислены в произвольном порядке.
Ссылки
Intel Software Developer Manual
AMD Architecture Programmer’s Manual
Applied Reverse Engineering: Exceptions and Interrupts
Detecting debuggers by abusing a bad assumption within Windows
ByePg: Defeating Patchguard using Exception-hooking

От ТС
Давно хотел перевести эту статью, но собрался только сейчас.
В последнее время я много работаю (потому что хочется кушать), и много времени уделять статьям и переводам не получается.
По возможности буду делать столько, сколько успеваю.

Спасибо всем, кто хвалит мои статьи и переводы. Отдельное спасибо команде форума, и администратору.

Перевод:
Azrv3l cпециально для xss.pro
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх