Skip to content
KitploitKITPLOIT
ИнструментыЭксплойтыБлог
Log in
Отправить
ИнструментыЭксплойтыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

ЛентыКонтактыКонфиденциальность© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
hwbp4mw — Движок перехвата на аппаратных точках останова для Windows, который использует отладочные регистры для перехвата функций, обхода ETW/AMSI и уклонения от мониторинга EDR на уровне пользовательского режима. | Kitploit
Инструменты/GitHubGitHub/rad9800/hwbp4mw
Обход IDS/IPSОтладчикиПост-эксплуатацияRed TeamingСостязательная Атака
GitHubrad9800/hwbp4mw

hwbp4mw

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

Репозиторий
27354283 лет назадПроверено Kitploit

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

Эта статья изначально была предназначена для VX-Underground Black Mass Halloween Edition 2022.

Механизмы перехвата:

  • Многопоточный безопасный x86/x64 hwbp-движок перехвата на c
  • Библиотека точек останова PAGE_GUARD/hwbp c++20
  • Библиотека hwbp (пример DLL) c++20

Универсальная техника обхода для пользовательского режима x64 с использованием регистров отладки:

  • TamperingSyscalls2 на c
  • TamperingSyscalls2 на c++20

Доступны примеры хуков ETW/AMSI:

  • rad9800/misc

Аппаратные точки останова для вредоносного ПО v 1.0

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

Мы не можем рассчитывать на применение IAT-хуков, так как они вызываются не всегда и потому ненадёжны. Инлайн-хукинг — мощная техника; однако она требует, чтобы мы пропатчили память, где находится код. Это мощная техника, но такие инструменты, как PE-Sieve и Moneta, могут отличить резидентную в памяти копию модуля от его копии на диске и пометить это. Это оставляет нам идеальный инструмент для данной задачи: регистры отладки, хотя они весьма недооценены авторами вредоносного ПО!

В Windows, если рассматривать на высоком уровне, процесс — это, по сути, инкапсуляция потоков, и каждый из этих потоков поддерживает контекст, который является состоянием потока: регистры, стек и т.д. Регистры отладки — привилегированный ресурс, как и их установка; однако Windows предоставляет различные системные вызовы, которые позволяют нам запросить у ядра выполнение привилегированного действия от нашего имени; это включает установку регистров отладки, что идеально для нас. NtSetThreadContext и NtGetThreadContext предоставляют функциональность для изменения любого контекста потока, к которому мы можем открыть дескриптор с необходимыми привилегиями. Мы видим, как установить регистры отладки с помощью Win32 API.```c CONTEXT context = { .ContextFlags = CONTEXT_DEBUG_REGISTERS }; GetThreadContext(thd, &context);

// set our debug information in the Dr registers

SetThreadContext(thd, &context);
Существует 8 регистров отладки, от Dr0 до Dr7. Из них нас интересуют только
Dr0-3, в которые мы сохраняем адреса, где хотим установить точки останова, а Dr6 — это просто
статус отладки. Наиболее важен Dr7, который описывает условия точек останова, при
которых процессор выбрасывает исключение. При использовании регистров отладки есть
различные ограничения, например, ограниченное количество (4) и неприменимость ко всем
потокам/вновь создаваемым потокам. Мы постараемся устранить некоторые из этих ограничений!

Когда выбрасывается исключение, оно ищет обработчик исключений, который мы можем
определить и зарегистрировать в нашей программе [1]. В нашем определённом обработчике исключений
мы хотим, чтобы наш соответствующий код (различные ветви выполнения) выполнялся при срабатывании соответствующей точки останова.```c
LONG WINAPI ExceptionHandler(PEXCEPTION_POINTERS ExceptionInfo)
{
	if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP)
	{
		// Look for our associated code flow relative to our RIP 
		if (HWBP_ADDRESS_MAP.contains(ExceptionInfo->ContextRecord->Rip)) {
			HWBP_ADDRESS_MAP.at(ExceptionInfo->ContextRecord->Rip).func(ExceptionInfo);
			return EXCEPTION_CONTINUE_EXECUTION;
		}
	}
	return EXCEPTION_CONTINUE_SEARCH;
}

Это достигается с помощью конструктора, который устанавливает соответствие между лямбда-функцией "callback" и адресом. using EXCEPTION_FUNC = std::function <void(PEXCEPTION_POINTERS)>;```c typedef struct { UINT pos; EXCEPTION_FUNC func; } HWBP_CALLBACK;

// Global std::unordered_map<uintptr_t, HWBP_CALLBACK> HWBP_ADDRESS_MAP{ 0 };

// Create our mapping HWBP_ADDRESS_MAP[address].func = function; HWBP_ADDRESS_MAP[address].pos = pos;

Мы должны перебрать все потоки нашего процесса и задать соответствующие корректировки
контекста для них. Это можно сделать с помощью вспомогательных функций ToolHelp32:
CreateToolhelp32Snapshot и Thread32Next. Ничего особенного, но это решает одно
из наших ограничений — невозможность прикрепления ко всем потокам.```c
VOID SetHWBPS(const uintptr_t address, const UINT pos, const bool init = true)
{
	DWORD pid{ GetCurrentProcessId() };
	HANDLE h{ CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0) };
	if (h != INVALID_HANDLE_VALUE) {
		THREADENTRY32 te{ .dwSize = sizeof(THREADENTRY32) };
		if (Thread32First(h, &te)) {
			do {
				if ((te.dwSize >= FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) +
					sizeof(te.th32OwnerProcessID)) && te.th32OwnerProcessID == pid) {

					HANDLE thd = OpenThread(THREAD_ALL_ACCESS, FALSE, te.th32ThreadID);
					if (thd != INVALID_HANDLE_VALUE) {
						SetHWBP(thd, address, pos, init);
						CloseHandle(thd);
					}
				}
				te.dwSize = sizeof(te);
			} while (Thread32Next(h, &te));
		}
		CloseHandle(h);
	}
}

Наличие установленных аппаратных точек останова можно считать подозрительным, поскольку они могут указывать на вредоносную активность (хотя, насколько мне известно, ни один EDR активно их не сканирует). Они могут быть использованы против нас как потенциальный IoC, поэтому после завершения работы с ними мы должны удалить их следы.

Мы можем реализовать это в нашей функции-деструкторе!! Она будет перебирать все потоки и проверять, указывает ли регистр (&context.Dr0)[pos] на адрес, по которому мы изначально установили аппаратную точку останова (pos — это просто индекс % 4, дающий нам доступ к context.Dr0-Dr3). Мы также можем удалить условия, необходимые в регистре Dr7. Мы также должны не забыть удалить нашу запись отображения. Таким образом, наша аппаратная точка останова будет присутствовать только в течение требуемого времени!```c SetHWBPS(address, pos, false); HWBP_ADDRESS_MAP.erase(address);

Примером аппаратной точки останова может быть Sleep, где мы просто заменяем продолжительность сна
на 0.```c
HWBP HWBPSleep{ (uintptr_t)&Sleep, 0,	// Set Dr 0 
	([&](PEXCEPTION_POINTERS ExceptionInfo) {
		ExceptionInfo->ContextRecord->Rcx = 0;
		ExceptionInfo->ContextRecord->EFlags |= (1 << 16);	// continue execution
}) };

Нам известно, что нужно установить RCX согласно соглашению о вызове x64 Windows с четырьмя регистрами (fastcall)[1]. Первый аргумент конструктора — адрес, на котором нужно остановиться, второй — какой из регистров Dr0-3 использовать для хранения (учтите, что одновременно может быть только 4 адреса для остановки), а третий — лямбда-функция, которая захватывает по ссылке PEXCEPTION_POINTERS — информацию, получаемую обработчиком исключений. В конечном счёте это позволит нам по-разному управлять потоком программы в зависимости от того, какая точка останова была активирована.

При создании нового потока он не наследует связанный набор регистров отладки, если только нам не удастся каким-то образом перехватить создание нового потока! Один из удобных приёмов — захватить фактический стартовый адрес и перенаправить новый поток на создание нашего собственного потока. Большинство новых потоков в конечном итоге вызывают NtCreateThreadEx.```c // Global Variable PVOID START_THREAD{ 0 };

Скачать инструмент