Точки останова используются для контроля выполнения программ и, естественно, для их остановки в нужный момент. Есть два типа брейк‑пойнтов (точек останова): программные точки останова и аппаратные точки останова. В статье поговорим про установку hardware breakpoint.
Еще по теме: Как хакеры обходят проверку AMSI
Hardware breakpoint (хардверный брейкпоинт) — это специальный механизм, встроенный в аппаратное обеспечение компьютера или микроконтроллера, который позволяет автоматически приостанавливать выполнение программы, когда определенное условие или адрес в памяти программы достигнуты.
Установка hardware breakpoint
Установить hardware breakpoint (HWBP) проще простого — достаточно лишь занести в нужный регистр адрес. Для большей абстракции я написал функцию SetHWBP(), куда нужно передать адрес, по которому следует установить точку останова, булево значение ( TRUE — установить, FALSE — снять), а также номер регистра.
Согласно документации, адрес может быть указан в Dr0, Dr1 и так далее, но у меня почему‑то работало только с Dr0.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
// address — адрес, по которому ставить функцию // setBP — FALSE — снять бряк, TRUE — установить // regnumer — номер регистра, который инициализировать адресом VOID SetHWBP(LPVOID address, BOOL setBP, int regnumber) { // Здесь передаем 0 CONTEXT context = { 0 }; context.ContextFlags = CONTEXT_DEBUG_REGISTERS; GetThreadContext(GetCurrentThread(), &context); // Почему-то бряк, если адрес записывать в регистры, отличные от Dr0, не срабатывает std::string registerNames[] = { "Dr0", "Dr1", "Dr2", "Dr3" }; if (setBP) { DWORD64* registers[] = { &context.Dr0, &context.Dr1, &context.Dr2, &context.Dr3 }; if (regnumber >= 0 && regnumber < 4) { *registers[regnumber] = (DWORD64)address; std::cout << "Writing Address to " << registerNames[regnumber] << std::endl; } else { std::wcout << L"Invalid Registry Number" << std::endl; exit(-1); } // Установка бита 0 в DR7 для активации DR0 context.Dr7 |= 1; // Установка битов 16–17 в DR7 для типа точки останова (Execute) context.Dr7 |= (0b00 << 16); // Установка битов 18–19 в DR7 для длины точки останова (1 byte) context.Dr7 |= (0b00 << 18); } else { context.Dr0 = 0; context.Dr1 = 0; context.Dr2 = 0; context.Dr3 = 0; context.Dr7 &= ~(1 << 0); } context.ContextFlags = CONTEXT_DEBUG_REGISTERS; SetThreadContext(GetCurrentThread(), &context); } |
Для получения значения регистров текущего потока используется функция GetThreadContext(), а для установки измененных значений используется SetThreadContext().
Причем, если мы хотим обрабатывать только исключения, сработавшие из‑за hardware breakpoint, в нашей функции — обработчике следует предусмотреть проверку на наличие в структуре EXCEPTION_POINTERS элемента ExceptionCode, равного STATUS_SINGLE_STEP. Это значение свидетельствует о том, что возникло событие, когда одна инструкция завершается и следующая инструкция готова к выполнению.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
LONG WINAPI Handler(PEXCEPTION_POINTERS exceptionInfo) { if (exceptionInfo->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP) { std::cout << "Unhandled exception occurred!" << std::endl; // Проверка, что реально наш бряк сработал if (exceptionInfo->ContextRecord->Dr0 == exceptionInfo->ContextRecord->Rip || exceptionInfo->ContextRecord->Dr1 == exceptionInfo->ContextRecord->Rip || exceptionInfo->ContextRecord->Dr2 == exceptionInfo->ContextRecord->Rip || exceptionInfo->ContextRecord->Dr3 == exceptionInfo->ContextRecord->Rip) { std::cout << "[-] Breakpoint triggered 0x" << std::hex << exceptionInfo->ExceptionRecord->ExceptionAddress << std::endl; std::cout << "[!] Exception Code 0x" << std::hex << exceptionInfo->ExceptionRecord->ExceptionCode << std::endl; std::cout << "[!] RIP 0x" << std::hex << exceptionInfo->ContextRecord->Rip << std::endl; std::cout << "[!] RAX 0x" << std::hex << exceptionInfo->ContextRecord->Rax << std::endl; std::cout << "[!] RCX 0x" << std::hex << exceptionInfo->ContextRecord->Rcx << std::endl; std::cout << "[!] RDX 0x" << std::hex << exceptionInfo->ContextRecord->Rdx << std::endl; } exceptionInfo->ContextRecord->EFlags |= (1 << 16); return EXCEPTION_CONTINUE_EXECUTION; } // Вернем EXCEPTION_CONTINUE_SEARCH, чтобы передать исключение дальше return EXCEPTION_CONTINUE_SEARCH; } |
Использовать в своей программе этот код проще простого.
Вот пример.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
#include #include LONG WINAPI Handler(PEXCEPTION_POINTERS exceptionInfo) { if (exceptionInfo->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP) { std::cout << "Unhandled exception occurred!" << std::endl; // Проверка того, что наш бряк реально сработал if (exceptionInfo->ContextRecord->Dr0 == exceptionInfo->ContextRecord->Rip || exceptionInfo->ContextRecord->Dr1 == exceptionInfo->ContextRecord->Rip || exceptionInfo->ContextRecord->Dr2 == exceptionInfo->ContextRecord->Rip || exceptionInfo->ContextRecord->Dr3 == exceptionInfo->ContextRecord->Rip) { std::cout << "[-] Breakppint triggered " << std::hex << exceptionInfo->ExceptionRecord->ExceptionAddress << std::endl; std::cout << "[!] Exception Code " << std::hex << exceptionInfo->ExceptionRecord->ExceptionCode << std::endl; std::cout << "[!] RIP " << std::hex << exceptionInfo->ContextRecord->Rip << std::endl; std::cout << "[!] RAX " << std::hex << exceptionInfo->ContextRecord->Rax << std::endl; std::cout << "[!] RCX " << std::hex << exceptionInfo->ContextRecord->Rcx << std::endl; std::cout << "[!] RDX " << std::hex << exceptionInfo->ContextRecord->Rdx << std::endl; std::cout << "[!] R8 " << std::hex << exceptionInfo->ContextRecord->R8 << std::endl; std::cout << "[!] R9 " << std::hex << exceptionInfo->ContextRecord->R9 << std::endl; std::cout << "[!] RSP " << std::hex << exceptionInfo->ContextRecord->Rsp << std::endl; std::cout << "[!] Dr0 " << std::hex << exceptionInfo->ContextRecord->Dr0 << std::endl; } exceptionInfo->ContextRecord->EFlags |= (1 << 16); return EXCEPTION_CONTINUE_EXECUTION; } // Вернем EXCEPTION_CONTINUE_SEARCH, чтобы передать исключение дальше return EXCEPTION_CONTINUE_SEARCH; } VOID SetHWBP(LPVOID address, BOOL setBP, int regnumber) { CONTEXT context = { 0 }; context.ContextFlags = CONTEXT_DEBUG_REGISTERS; GetThreadContext(GetCurrentThread(), &context); // Почему-то бряк, если адрес записывать в регистры, отличные от Dr0, не срабатывает std::string registerNames[] = { "Dr0", "Dr1", "Dr2", "Dr3" }; if (setBP) { DWORD64* registers[] = { &context.Dr0, &context.Dr1, &context.Dr2, &context.Dr3 }; if (regnumber >= 0 && regnumber < 4) { *registers[regnumber] = (DWORD64)address; std::cout << "Writing Address to " << registerNames[regnumber] << std::endl; } else { std::wcout << L"Invalid Registry Number" << std::endl; exit(-1); } // Установка бита 1 в DR7 для активации DR0 context.Dr7 |= 1; // Установка битов 16–17 в DR7 для типа точки останова (Execute) context.Dr7 |= (0b00 << 16); // Установка битов 18–19 в DR7 для длины точки останова (1 byte) context.Dr7 |= (0b00 << 18); } else { context.Dr0 = 0; context.Dr1 = 0; context.Dr2 = 0; context.Dr3 = 0; context.Dr7 &= ~(1 << 0); } context.ContextFlags = CONTEXT_DEBUG_REGISTERS; SetThreadContext(GetCurrentThread(), &context); } int main() { if (AddVectoredExceptionHandler(1, Handler) == nullptr) { std::cout << "Failed to set the vectored exception filter!" << std::endl; return 1; } // Адрес функции printf void* targetAddress = (void*)printf; SetHWBP(targetAddress, TRUE, 0); // Генерация сигнала точки останова printf("Hello, world!"); return 0; } |
Здесь был установлен hardware breakpoint по адресу функции printf(). Как только система дошла до вызова этой функции, сработало исключение, вызвался обработчик исключений, вывел содержимое регистров, а затем вернул управление на функцию, что привело к появлению на консоли Hello, world!.
Итак, мы научились ставить hardware breakpoint. О том, как их использовать для пентестерских целей, я расскажу в статье «Обход AMSI».
ПОЛЕЗНЫЕ ССЫЛКИ: