Апну кодом. Чет внатуре приуныл дебаггер Код (ASM): .686 .model flat, stdcall option casemap :none include \masm32\include\windows.inc include \masm32\include\kernel32.inc include \masm32\include\user32.inc includelib \masm32\lib\kernel32.lib includelib \masm32\lib\user32.lib .data RaceBufferSize equ 1024 RaceBuffer dd 0 .code ; Функция для выделения буфера в одном потоке DoAllocateRaceBuffer proc invoke LocalAlloc, LPTR, RaceBufferSize mov RaceBuffer, eax ret DoAllocateRaceBuffer endp ; Функция для освобождения буфера в другом потоке DoFreeRaceBuffer proc mov eax, RaceBuffer cmp eax, 0 je @f invoke LocalFree, eax mov RaceBuffer, 0 @@: ret DoFreeRaceBuffer endp ; Функция, которая вызывается из другого модуля DoRace proc ; Бесконечный цикл выделения и освобождения буфера DoRaceLoop: invoke DoAllocateRaceBuffer invoke DoFreeRaceBuffer jmp DoRaceLoop ret DoRace endp ; Точка входа в модуль Entry proc ; Создаем два потока для выделения и освобождения буфера invoke CreateThread, NULL, 0, offset DoRace, NULL, 0, NULL invoke CreateThread, NULL, 0, offset DoRace, NULL, 0, NULL ; Ожидаем нажатия клавиши для завершения программы push 'Q' call GetAsyncKeyState test eax, eax jz Entry ret Entry endp end Entry --- Сообщение объединено, 25 сен 2024 --- Эта версия получше, exception не ловится, а висим долго Код (ASM): .686 .model flat, stdcall option casemap :none include \masm32\include\windows.inc include \masm32\include\kernel32.inc include \masm32\include\user32.inc includelib \masm32\lib\kernel32.lib includelib \masm32\lib\user32.lib .data RaceBufferSize equ 1024 RaceBuffer dd 0 RaceBufferMutex HANDLE ? RaceBufferEvent HANDLE ? .code ; Функция для выделения буфера в одном потоке DoAllocateRaceBuffer proc invoke CreateMutex, NULL, FALSE, NULL mov RaceBufferMutex, eax invoke CreateEvent, NULL, FALSE, FALSE, NULL mov RaceBufferEvent, eax invoke LocalAlloc, LPTR, RaceBufferSize mov RaceBuffer, eax invoke ReleaseMutex, RaceBufferMutex ret DoAllocateRaceBuffer endp ; Функция для освобождения буфера в другом потоке DoFreeRaceBuffer proc invoke WaitForSingleObject, RaceBufferMutex, INFINITE mov eax, RaceBuffer cmp eax, 0 je @f invoke LocalFree, eax mov RaceBuffer, 0 invoke SetEvent, RaceBufferEvent @@: invoke ReleaseMutex, RaceBufferMutex ret DoFreeRaceBuffer endp ; Функция, которая вызывается из другого модуля DoRace proc ; Бесконечный цикл выделения и освобождения буфера DoRaceLoop: invoke DoAllocateRaceBuffer invoke DoFreeRaceBuffer jmp DoRaceLoop ret DoRace endp ; Точка входа в модуль Entry proc ; Создаем два потока для выделения и освобождения буфера invoke CreateThread, NULL, 0, offset DoRace, NULL, 0, NULL invoke CreateThread, NULL, 0, offset DoRace, NULL, 0, NULL ; Ожидаем нажатия клавиши для завершения программы push 'Q' call GetAsyncKeyState test eax, eax jz Entry ret Entry endp end Entry --- Сообщение объединено, 25 сен 2024 --- v3 Код (ASM): .686 .model flat, stdcall option casemap :none include \masm32\include\windows.inc include \masm32\include\kernel32.inc include \masm32\include\user32.inc includelib \masm32\lib\kernel32.lib includelib \masm32\lib\user32.lib .data RaceBufferSize equ 1024 RaceBuffer dd 0 RaceBufferMutex HANDLE ? RaceBufferEvent HANDLE ? RaceBufferCounter dd 0 RaceBufferOverflowFlag dd 0 RaceBufferDelay equ 2000 ; Задержка в миллисекундах .code ; Функция для выделения буфера в одном потоке DoAllocateRaceBuffer proc ; Захватываем мьютекс invoke WaitForSingleObject, RaceBufferMutex, INFINITE ; Проверяем счетчик cmp RaceBufferCounter, 0 jne @f ; Выделяем буфер и увеличиваем счетчик invoke LocalAlloc, LPTR, RaceBufferSize mov RaceBuffer, eax inc RaceBufferCounter ; Устанавливаем событие invoke SetEvent, RaceBufferEvent @@: ; Освобождаем мьютекс invoke ReleaseMutex, RaceBufferMutex ; Если счетчик больше 1, устанавливаем флаг переполнения cmp RaceBufferCounter, 1 jle @f mov RaceBufferOverflowFlag, 1 @@: ; Задержка перед освобождением буфера invoke Sleep, RaceBufferDelay ret DoAllocateRaceBuffer endp ; Функция для освобождения буфера в другом потоке DoFreeRaceBuffer proc ; Захватываем мьютекс invoke WaitForSingleObject, RaceBufferMutex, INFINITE ; Проверяем, что буфер выделен и счетчик больше 0 cmp RaceBufferCounter, 0 je @f ; Уменьшаем счетчик и освобождаем буфер dec RaceBufferCounter mov eax, RaceBuffer invoke LocalFree, eax mov RaceBuffer, 0 ; Если счетчик стал 0, сбрасываем событие cmp RaceBufferCounter, 0 jne @f invoke ResetEvent, RaceBufferEvent @@: ; Освобождаем мьютекс invoke ReleaseMutex, RaceBufferMutex ; Если был флаг переполнения, обрабатываем его cmp RaceBufferOverflowFlag, 1 jne @f ; Выделяем дополнительный буфер invoke DoAllocateRaceBuffer @@: ; Задержка перед выделением нового буфера invoke Sleep, RaceBufferDelay ret DoFreeRaceBuffer endp ; Функция, которая вызывается из другого модуля DoRace proc ; Бесконечный цикл выделения и освобождения буфера DoRaceLoop: invoke DoAllocateRaceBuffer invoke DoFreeRaceBuffer ; Ждем события invoke WaitForSingleObject, RaceBufferEvent, INFINITE jmp DoRaceLoop ret DoRace endp ; Точка входа в модуль Entry proc ; Создаем мьютекс и событие invoke CreateMutex, NULL, FALSE, NULL mov RaceBufferMutex, eax invoke CreateEvent, NULL, TRUE, FALSE, NULL mov RaceBufferEvent, eax ; Создаем два потока для выделения и освобождения буфера invoke CreateThread, NULL, 0, offset DoRace, NULL, 0, NULL invoke CreateThread, NULL, 0, offset DoRace, NULL, 0, NULL ; Ожидаем нажатия клавиши для завершения программы push 'Q' call GetAsyncKeyState test eax, eax jz Entry ; Очищаем ресурсы invoke CloseHandle, RaceBufferMutex invoke CloseHandle, RaceBufferEvent ret Entry endp end Entry ну и ловкий код без хендлов Код (ASM): .686 .model flat, stdcall option casemap :none include \masm32\include\windows.inc include \masm32\include\kernel32.inc include \masm32\include\user32.inc includelib \masm32\lib\kernel32.lib includelib \masm32\lib\user32.lib .data RaceBufferSize equ 1024 RaceBuffer dd 0 RaceBufferCounter dd 0 RaceBufferOverflowFlag dd 0 RaceBufferDelay equ 50 ; Задержка в миллисекундах .code ; Функция для выделения буфера в одном потоке DoAllocateRaceBuffer proc ; Захватываем критическую секцию push eax push ecx xor ecx, ecx mov eax, RaceBufferCounter lock xadd [RaceBufferCounter], ecx cmp ecx, 0 jne @f ; Выделяем буфер и увеличиваем счетчик invoke LocalAlloc, LPTR, RaceBufferSize mov RaceBuffer, eax inc RaceBufferCounter @@: ; Освобождаем критическую секцию pop ecx pop eax ; Если счетчик больше 1, устанавливаем флаг переполнения cmp ecx, 1 jle @f mov RaceBufferOverflowFlag, 1 @@: ; Задержка перед освобождением буфера invoke Sleep, RaceBufferDelay ret DoAllocateRaceBuffer endp ; Функция для освобождения буфера в другом потоке DoFreeRaceBuffer proc ; Захватываем критическую секцию push eax push ecx xor ecx, ecx mov eax, RaceBufferCounter lock xadd [RaceBufferCounter], ecx cmp ecx, 1 jne @f ; Уменьшаем счетчик и освобождаем буфер dec RaceBufferCounter mov eax, RaceBuffer invoke LocalFree, eax mov RaceBuffer, 0 @@: ; Освобождаем критическую секцию pop ecx pop eax ; Если был флаг переполнения, обрабатываем его cmp RaceBufferOverflowFlag, 1 jne @f ; Выделяем дополнительный буфер invoke DoAllocateRaceBuffer @@: ; Задержка перед выделением нового буфера invoke Sleep, RaceBufferDelay ret DoFreeRaceBuffer endp ; Функция, которая вызывается из другого модуля DoRace proc ; Бесконечный цикл выделения и освобождения буфера DoRaceLoop: invoke DoAllocateRaceBuffer invoke DoFreeRaceBuffer jmp DoRaceLoop ret DoRace endp ; Точка входа в модуль Entry proc ; Создаем два потока для выделения и освобождения буфера invoke CreateThread, NULL, 0, offset DoRace, NULL, 0, NULL invoke CreateThread, NULL, 0, offset DoRace, NULL, 0, NULL ; Ожидаем нажатия клавиши для завершения программы push 'Q' call GetAsyncKeyState test eax, eax jz Entry ret Entry endp end Entry --- Сообщение объединено, 25 сен 2024 --- Для x64 Но требует улучшения Код (ASM): option casemap :none include \masm64\include64\masm64rt.inc include \masm64\include64\kernel32.inc include \masm64\include64\user32.inc includelib \masm64\lib64\kernel32.lib includelib \masm64\lib64\user32.lib .data counter QWORD 0 num_threads QWORD 4 str_format db "%lld", 0Ah, 0 str_result db "Final counter value: ", 0 .code printf PROTO ; Thread procedure ThreadProc PROC @loop: ; Небезопасный инкремент счетчика mov rax, [counter] inc rax mov [counter], rax jmp @loop ; Бесконечный цикл ThreadProc ENDP Entry PROC ; Создание потоков xor rbx, rbx ; Thread counter lea r12, [num_threads] lea r13, [ThreadProc] create_thread_loop: xor rcx, rcx ; lpThreadAttributes xor rdx, rdx ; dwStackSize mov r8, r13 ; lpStartAddress (ThreadProc) xor r9, r9 ; lpParameter push 0 ; lpThreadId push 0 ; dwCreationFlags sub rsp, 32 call CreateThread add rsp, 48 ; Закрываем дескриптор сразу после создания mov rcx, rax call CloseHandle inc rbx cmp rbx, r12 jl create_thread_loop ; Небольшая задержка для работы потоков mov rcx, 5000 ; 5000 миллисекунд = 5 секунд call Sleep ; Вывод результата lea rcx, [str_result] sub rsp, 32 call printf add rsp, 32 lea rcx, [str_format] mov rdx, [counter] sub rsp, 32 call printf add rsp, 32 ; Выход из программы xor rcx, rcx call ExitProcess Entry ENDP END
мб кому пригодится Код (C++): #include <Windows.h> #include <iostream> #include <TlHelp32.h> #include <algorithm> const int MAX_THREADS = 1024; int DummyVariable; void DummyFunction1() {} void DummyFunction2() {} void DummyFunction3() {} void DummyFunction4() {} void DummyFunction() {} bool HasTitanHide() { const auto dummy_breakpoint = reinterpret_cast<DWORD64>(&DummyFunction); const DWORD64 breakpoint_mask = 1; // Mask to set the breakpoint HANDLE current_thread = GetCurrentThread(); // Store the current thread handle CONTEXT ctx = {}; ctx.Dr0 = dummy_breakpoint; ctx.Dr7 = breakpoint_mask; // Set the breakpoint ctx.ContextFlags = 0x10; // Context flags if (!SetThreadContext(current_thread, &ctx)) return false; if (!GetThreadContext(current_thread, &ctx)) return false; return ctx.Dr0 != dummy_breakpoint; // Check if the breakpoint was removed } bool HasTitanHideRandomBreakpoints() { const auto dummy_breakpoint1 = reinterpret_cast<DWORD64>(&DummyFunction1); const auto dummy_breakpoint2 = reinterpret_cast<DWORD64>(&DummyFunction2); const auto dummy_breakpoint3 = reinterpret_cast<DWORD64>(&DummyFunction3); const DWORD64 breakpoint_mask = 7; // Mask to set 3 breakpoints HANDLE current_thread = GetCurrentThread(); CONTEXT ctx = {}; ctx.ContextFlags = 0x10; // Set breakpoints in random order int order[3] = { 0, 1, 2 }; std::random_shuffle(order, order + 3); ctx.Dr7 = breakpoint_mask; (&ctx.Dr0)[order[0]] = dummy_breakpoint1; (&ctx.Dr0)[order[1]] = dummy_breakpoint2; (&ctx.Dr0)[order[2]] = dummy_breakpoint3; if (!SetThreadContext(current_thread, &ctx)) return false; if (!GetThreadContext(current_thread, &ctx)) return false; return (&ctx.Dr0)[order[0]] != dummy_breakpoint1 || (&ctx.Dr0)[order[1]] != dummy_breakpoint2 || (&ctx.Dr0)[order[2]] != dummy_breakpoint3; } bool HasTitanHideDr7Flags() { const auto dummy_breakpoint = reinterpret_cast<DWORD64>(&DummyFunction); const DWORD64 breakpoint_mask = 1; const DWORD64 dr7_flags_mask = 0x155; // Mask for DR7 flags (bits 0, 2, 4, 6) HANDLE current_thread = GetCurrentThread(); CONTEXT ctx = {}; ctx.Dr0 = dummy_breakpoint; ctx.Dr7 = breakpoint_mask; ctx.ContextFlags = 0x10; if (!SetThreadContext(current_thread, &ctx)) return false; if (!GetThreadContext(current_thread, &ctx)) return false; return (ctx.Dr7 & dr7_flags_mask) != breakpoint_mask; } DWORD GetThreadIds(HANDLE hProcess, DWORD* thread_ids) { DWORD count = 0; HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); if (hSnapshot != INVALID_HANDLE_VALUE) { THREADENTRY32 te; te.dwSize = sizeof(THREADENTRY32); if (Thread32First(hSnapshot, &te)) { do { if (te.th32OwnerProcessID == GetProcessId(hProcess)) { thread_ids[count++] = te.th32ThreadID; } } while (Thread32Next(hSnapshot, &te)); } CloseHandle(hSnapshot); } return count; } bool HasTitanHideMultipleThreadBreakpoints() { const auto dummy_breakpoint = reinterpret_cast<DWORD64>(&DummyFunction); const DWORD64 breakpoint_mask = 1; HANDLE current_process = GetCurrentProcess(); CONTEXT ctx = {}; ctx.Dr0 = dummy_breakpoint; ctx.Dr7 = breakpoint_mask; ctx.ContextFlags = 0x10; DWORD thread_ids[MAX_THREADS]; DWORD thread_count = GetThreadIds(current_process, thread_ids); for (DWORD i = 0; i < thread_count; ++i) { HANDLE thread = OpenThread(THREAD_ALL_ACCESS, FALSE, thread_ids[i]); if (thread != NULL) { if (!SetThreadContext(thread, &ctx) || !GetThreadContext(thread, &ctx)) { CloseHandle(thread); return false; } if (ctx.Dr0 != dummy_breakpoint) { CloseHandle(thread); return true; } CloseHandle(thread); } } return false; } bool HasTitanHideExecuteBreakpoint() { const auto dummy_breakpoint = reinterpret_cast<DWORD64>(&DummyFunction); const DWORD64 breakpoint_mask = 1 << 0; // Mask to set execute breakpoint HANDLE current_thread = GetCurrentThread(); CONTEXT ctx = {}; ctx.Dr0 = dummy_breakpoint; ctx.Dr7 = breakpoint_mask; ctx.ContextFlags = 0x10; if (!SetThreadContext(current_thread, &ctx)) return false; if (!GetThreadContext(current_thread, &ctx)) return false; return ctx.Dr0 != dummy_breakpoint; } bool HasTitanHideMemoryBreakpoint() { const auto dummy_address = reinterpret_cast<DWORD64>(&DummyVariable); const DWORD64 breakpoint_mask = 1 << 2; // Mask to set memory breakpoint HANDLE current_thread = GetCurrentThread(); CONTEXT ctx = {}; ctx.Dr0 = dummy_address; ctx.Dr7 = breakpoint_mask; ctx.ContextFlags = 0x10; if (!SetThreadContext(current_thread, &ctx)) return false; if (!GetThreadContext(current_thread, &ctx)) return false; return ctx.Dr0 != dummy_address; } bool HasTitanHideMultipleBreakpoints() { const auto dummy_breakpoint1 = reinterpret_cast<DWORD64>(&DummyFunction1); const auto dummy_breakpoint2 = reinterpret_cast<DWORD64>(&DummyFunction2); const auto dummy_breakpoint3 = reinterpret_cast<DWORD64>(&DummyFunction3); const auto dummy_breakpoint4 = reinterpret_cast<DWORD64>(&DummyFunction4); const DWORD64 breakpoint_mask = 0xF; // Mask to set all 4 breakpoints HANDLE current_thread = GetCurrentThread(); CONTEXT ctx = {}; ctx.Dr0 = dummy_breakpoint1; ctx.Dr1 = dummy_breakpoint2; ctx.Dr2 = dummy_breakpoint3; ctx.Dr3 = dummy_breakpoint4; ctx.Dr7 = breakpoint_mask; ctx.ContextFlags = 0x10; if (!SetThreadContext(current_thread, &ctx)) return false; if (!GetThreadContext(current_thread, &ctx)) return false; return ctx.Dr0 != dummy_breakpoint1 || ctx.Dr1 != dummy_breakpoint2 || ctx.Dr2 != dummy_breakpoint3 || ctx.Dr3 != dummy_breakpoint4; } int main() { std::cout << "TitanHide Detection Results:" << std::endl; // Первоначальная функция обнаружения if (HasTitanHide()) std::cout << " - TitanHide detected (basic detection)" << std::endl; else std::cout << " - TitanHide not detected (basic detection)" << std::endl; // Обнаружение через установку нескольких аппаратных точек останова if (HasTitanHideMultipleBreakpoints()) std::cout << " - TitanHide detected (multiple breakpoints)" << std::endl; else std::cout << " - TitanHide not detected (multiple breakpoints)" << std::endl; // Обнаружение через установку точки останова на доступ к памяти if (HasTitanHideMemoryBreakpoint()) std::cout << " - TitanHide detected (memory breakpoint)" << std::endl; else std::cout << " - TitanHide not detected (memory breakpoint)" << std::endl; // Обнаружение через установку точки останова на выполнение if (HasTitanHideExecuteBreakpoint()) std::cout << " - TitanHide detected (execute breakpoint)" << std::endl; else std::cout << " - TitanHide not detected (execute breakpoint)" << std::endl; // Обнаружение через установку точек останова на несколько потоков if (HasTitanHideMultipleThreadBreakpoints()) std::cout << " - TitanHide detected (multiple thread breakpoints)" << std::endl; else std::cout << " - TitanHide not detected (multiple thread breakpoints)" << std::endl; // Обнаружение через проверку флагов регистра Dr7 if (HasTitanHideDr7Flags()) std::cout << " - TitanHide detected (DR7 flags)" << std::endl; else std::cout << " - TitanHide not detected (DR7 flags)" << std::endl; // Обнаружение через установку точек останова в случайном порядке if (HasTitanHideRandomBreakpoints()) std::cout << " - TitanHide detected (random breakpoints)" << std::endl; else std::cout << " - TitanHide not detected (random breakpoints)" << std::endl; return 0; } под масморито32бито Код (ASM): .data dummy_breakpoint dd 0 TitanHideDetectedMsg db "TitanHide detected", 0 TitanHideNotDetectedMsg db "TitanHide not detected", 0 TitanHideTitle db "HasTitanHide", 0 ctx CONTEXT <> ; Объявление переменной для структуры CONTEXT .code ; DummyFunction DummyFunction proc ret DummyFunction endp HasTitanHide proc ; Save registers push ebx push ecx push edx push esi push edi %DBG "HasTitanHide: Начало выполнения" ; Set breakpoint on dummy_breakpoint mov eax, offset DummyFunction mov [ctx.iDr0], eax mov dword ptr [ctx.iDr7], 1 %DBG "HasTitanHide: Установлена точка останова на DummyFunction (0x%X)", eax ; Set CONTEXT.ContextFlags mov dword ptr [ctx.ContextFlags], CONTEXT_DEBUG_REGISTERS %DBG "HasTitanHide: Установлены флаги контекста CONTEXT_DEBUG_REGISTERS" ; Call SetThreadContext invoke GetCurrentThread invoke SetThreadContext, eax, addr ctx %DBG "HasTitanHide: Вызван SetThreadContext, результат: 0x%X", eax ; Call GetThreadContext invoke GetCurrentThread invoke GetThreadContext, eax, addr ctx %DBG "HasTitanHide: Вызван GetThreadContext, результат: 0x%X", eax ; Check if breakpoint is still set mov eax, offset DummyFunction cmp [ctx.iDr0], eax jne @F %DBG "HasTitanHide: Проверка точки останова, результат: все еще установлена" jmp TitanHideNotDetected @@: %DBG "HasTitanHide: Проверка точки останова, результат: снята" jmp TitanHideDetected TitanHideNotDetected: ; TitanHide not detected xor eax, eax %DBG "HasTitanHide: TitanHide не обнаружен" jmp Exit TitanHideDetected: mov eax, 1 %DBG "HasTitanHide: Обнаружен TitanHide!" Exit: ; Restore registers pop edi pop esi pop edx pop ecx pop ebx %DBG "HasTitanHide: Завершение выполнения, результат: 0x%X", eax ret HasTitanHide endp
Как я понял здесь всё крутится вокруг регистров отладки DRx что ли (хард-бряки)? Так плагин "ScyllaHide" для x64dbg перехватывает-же обращения к ним не только под юзером Get/SetThreadContext(), но и ниже в нативной NtGet/SetContextThread(). Можете сбросить сюда свой ConsoleApp3.exe? ..посмотрим на реакцию данного плага на него.
вот. чекайте лог в .log файле, а не с cmd сообщения если это будет установлено в scyllahide то тоже будет детект по сути main.zip детектит и титанку и скиляку
смотри в лог. там в cmd еще не до конца сделано --- Сообщение объединено, 29 сен 2024 --- TrIsDebug и Setup.StkTrap самые важные 0xC0000353 это без дебагера 0x0 детект дебагера