Содержание статьи
В основном Windows использует два формата исполняемых файлов — .exe и DLL. Первые представляют собой привычные для нас программы, а DLL — это библиотеки с дополнительными функциями, подгружаемые исполняемым файлом. Файлы DLL могут быть загружены в процесс и сразу при запуске (так называемая статическая линковка), и при выполнении (динамическая линковка), то есть когда процесс уже вовсю работает и вдруг что‑то его подталкивает к решению загрузить библиотеку.
Ни для кого не секрет, что процессы в Windows имеют виртуальное адресное пространство. Пространство представляет собой кусок памяти, который принадлежит только текущему процессу. В этой части памяти будут данные только текущего процесса. При подгрузке DLL оказывается в том же самом адресном пространстве и, как следствие, получает доступ ко всем данным процесса. Такое поведение приводит к тому, что злостные хакеры так и желают, так и ждут подгрузки своей вредоносной библиотеки в наш процесс, чтобы установить хуки, сдампить память и всячески изменить поведение программы. С этим можно бороться! Давай посмотрим как.
UpdateProcThreadAttribute
Предлагаю начать с наиболее распространенных способов предотвращения подгрузки DLL-библиотек в процесс. Первый вариант основан на функции UpdateProcThreadAttribute(). Для его реализации устанавливается специальное значение:
PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY
Единственная проблема — в нашу программу все‑таки смогут внедряться чужие либы, но только если они подписаны в Microsoft. Также стоит отметить, что предотвращение подгрузки DLL будет применяться к процессу, который мы запустим через CreateProcess(
, к текущему процессу применить эту функцию не получится.
Тем не менее есть возможность обойти это ограничение. Наш текущий процесс может проверять, был ли он запущен с параметром, ограничивающим подгрузку DLL. Если нет, то он запускает копию самого себя, но уже с помощью атрибутов, которые устанавливаются в функции UpdateProcThreadAttribute(
.
Итак, сначала напишем функцию CreateProcessWithBlockDllPolicy(
. Для этого нам потребуется лишь путь до исполняемого файла, который нужно запустить. Функция будет возвращать PID запущенного процесса, его хендл, а также хендл потока процесса.
BOOL CreateProcessWithBlockDllPolicy(IN LPSTR lpProcessPath, OUT DWORD* dwProcessId, OUT HANDLE* hProcess, OUT HANDLE* hThread) { STARTUPINFOEXA SiEx = { 0 }; PROCESS_INFORMATION Pi = { 0 }; SIZE_T sAttrSize = NULL; if (lpProcessPath == NULL) return FALSE; RtlSecureZeroMemory(&SiEx, sizeof(STARTUPINFOEXA)); RtlSecureZeroMemory(&Pi, sizeof(PROCESS_INFORMATION)); SiEx.StartupInfo.cb = sizeof(STARTUPINFOEXA); SiEx.StartupInfo.dwFlags = EXTENDED_STARTUPINFO_PRESENT; InitializeProcThreadAttributeList(NULL, 1, NULL, &sAttrSize); LPPROC_THREAD_ATTRIBUTE_LIST pAttrBuf = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sAttrSize); if (!InitializeProcThreadAttributeList(pAttrBuf, 1, NULL, &sAttrSize)) { printf("[!] InitializeProcThreadAttributeList Failed With Error : %d \n", GetLastError()); return FALSE; } DWORD64 dwPolicy = PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON; if (!UpdateProcThreadAttribute(pAttrBuf, NULL, PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY, &dwPolicy, sizeof(DWORD64), NULL, NULL)) { printf("[!] UpdateProcThreadAttribute Failed With Error : %d \n", GetLastError()); return FALSE; } SiEx.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)pAttrBuf; if (!CreateProcessA( NULL, lpProcessPath, NULL, NULL, FALSE, EXTENDED_STARTUPINFO_PRESENT, NULL, NULL, &SiEx.StartupInfo, &Pi)) { printf("[!] CreateProcessA Failed With Error : %d \n", GetLastError()); return FALSE; } *dwProcessId = Pi.dwProcessId; *hProcess = Pi.hProcess; *hThread = Pi.hThread; DeleteProcThreadAttributeList(pAttrBuf); HeapFree(GetProcessHeap(), 0, pAttrBuf); if (*dwProcessId != NULL && *hProcess != NULL && *hThread != NULL) return TRUE; else return FALSE;}
Сначала мы очищаем структуры, а затем инициализируем структуры, необходимые для запуска процесса. Дополнительно устанавливаем флаг:
EXTENDED_STARTUPINFO_PRESENT
Это даст возможность запскать процессы с нужной митигацией.
Далее с помощью функции InitializeProcThreadAttributeList() готовим структуру LPPROC_THREAD_ATTRIBUTE_LIST
. Ее будем указывать в функцию UpdateProcAttributeList(). Двойной вызов функции стандартен для Windows — первый вызов служит для получения нужного размера, а второй для инициализации элементов структуры.
Затем с помощью функции UpdateProcAttributeList(
заносим необходимые атрибуты в нашу структуру со списком атрибутов, после чего применяем ее в функции CreateProcessA(
, что позволяет запустить процесс с защитой от подгрузки левых библиотек.
Как же запустить текущий процесс, применив эту функцию? Здесь будем использовать аргументы командной строки. Наш процесс может породить дочерний такой же процесс с митигацией, а самого себя уничтожить. Для этого нам нужно создать специальный STOP_ARG
. Если вдруг в аргументах командной строки текущего процесса нет STOP_ARG
, то он должен перезапуститься с защитой от подгрузки DLL.
Наша функция main(
должна выглядеть вот так:
#define LOCAL_BLOCKDLLPOLICY#ifdef LOCAL_BLOCKDLLPOLICY#define STOP_ARG "xakep"#endifint main(int argc, char* argv[]) { DWORD dwProcessId = NULL; HANDLE hProcess = NULL, hThread = NULL;#ifdef LOCAL_BLOCKDLLPOLICY if (argc == 2 && (strcmp(argv[1], STOP_ARG) == 0)) { printf("[+] Process Is Now Protected With The Block Dll Policy \n"); WaitForSingleObject((HANDLE)-1, INFINITE); } else { printf("[!] Local Process Is Not Protected With The Block Dll Policy \n"); CHAR pcFilename[MAX_PATH * 2]; if (!GetModuleFileNameA(NULL, (LPSTR)&pcFilename, MAX_PATH * 2)) { printf("[!] GetModuleFileNameA Failed With Error : %d \n", GetLastError()); return -1; } DWORD dwBufferSize = (DWORD)(lstrlenA(pcFilename) + lstrlenA(STOP_ARG) + 0xFF); CHAR* pcBuffer = (CHAR*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwBufferSize); if (!pcBuffer) return FALSE; sprintf_s(pcBuffer, dwBufferSize, "%s %s", pcFilename, STOP_ARG); if (!CreateProcessWithBlockDllPolicy(pcBuffer, &dwProcessId, &hProcess, &hThread)) { return -1; } HeapFree(GetProcessHeap(), 0, pcBuffer); printf("[i] Process Created With Pid %d \n", dwProcessId); }#endif#ifndef LOCAL_BLOCKDLLPOLICY if (!CreateProcessWithBlockDllPolicy((LPSTR)"C:\\Windows\\System32\\RuntimeBroker.exe", &dwProcessId, &hProcess, &hThread)) { return -1; } printf("[i] Process Created With Pid %d \n", dwProcessId);#endif return 0;}
Сначала объявляются некоторые директивы, позволяющие управлять тем, что мы хотим получить от кода. Если хотим защитить собственную программу, то оставляем define
. Если хотим запустить иной процесс с защитой от подгрузки DLL, то убираем это поле.
Продолжение доступно только участникам
Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
Вариант 2. Открой один материал
Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.
Я уже участник «Xakep.ru»