Ломаем дескрипторы! Как злоупотреблять хендлами в Windows

Содержание статьи

  • Интересные хендлы
  • Изучение хендлов процесса
  • Handle Duplicating
  • Leaked Handle
  • Handle Hijacking
  • Выводы

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

Хен­длы (они же дес­крип­торы) в Windows — это один из важ­ных кам­ней в фун­дамен­те сис­темы. На хен­длах пос­тро­ено и осно­выва­ется все вза­имо­дей­ствие из прог­рам­мно­го кода с ком­понен­тами Windows. Имен­но бла­года­ря дес­крип­торам ядро понима­ет, к какому объ­екту мы хотим обра­тить­ся и с каким уров­нем дос­тупа. Хендл обыч­но мож­но получить с помощью стан­дар­тных фун­кций. На файл — через CreateFile(), на LSA — через LsaOpenPolicy(). В Windows очень мно­го раз­ных объ­ектов, с которы­ми мож­но вза­имо­дей­ство­вать. Ты можешь открыть спи­сок WinAPI, ткнуть на любой инте­ресу­ющий ком­понент и убе­дить­ся, что самой основной и важ­ной апи­хой будет API для получе­ния хен­дла на нуж­ный объ­ект. Не получил хендл — не смог вза­имо­дей­ство­вать с сис­темой.

Зна­ешь ли ты, что есть ата­ки и на сами хен­длы?

warning

Статья име­ет озна­коми­тель­ный харак­тер и пред­назна­чена для спе­циалис­тов по безопас­ности, про­водя­щих тес­тирова­ние в рам­ках кон­трак­та. Автор и редак­ция не несут ответс­твен­ности за любой вред, при­чинен­ный с при­мене­нием изло­жен­ной информа­ции. Рас­простра­нение вре­донос­ных прог­рамм, наруше­ние работы сис­тем и наруше­ние тай­ны перепис­ки прес­леду­ются по закону.

 

Интересные хендлы

Во‑пер­вых, сле­дует опре­делить­ся с тем, что мы ищем. Нас инте­ресу­ют далеко не все хен­длы. Конеч­но, при­ори­тет­нее все­го про­цес­сы и потоки. Вза­имо­дей­ствие с ними поз­волит запус­тить шелл‑код. Впро­чем, тут опять не все так глад­ко. Получен­ный хендл дол­жен иметь хотя бы пра­во PROCESS_VM_WRITE (для про­цес­са) или THREAD_SET_CONTEXT (для потока). Кста­ти, нам подой­дут и хен­длы на сек­ции памяти и — в исклю­читель­ных слу­чаях — на кон­крет­ные фай­лы или клю­чи реес­тра.

 

Изучение хендлов процесса

Пе­ред тем как прис­тупить, можем гля­нуть хен­длы кон­крет­ных про­цес­сов в сис­теме. Это поз­волит наметить даль­нейший век­тор раз­вития ата­ки. Удоб­нее все­го, как мне кажет­ся, исполь­зовать ути­литу Process Hacker.

Изу­чение хен­длов про­цес­са

Есть так­же System Explorer и Sysinternals, но я ими поль­зовал­ся не очень мно­го.

Вы­вод инс­тру­мен­та Handle из пакета Sysinternals 

Handle Duplicating

Это самая пер­вая ата­ка на хен­длы. Осно­вана она по боль­шей час­ти на фун­кции DuplicateHandle() и ее более низ­коуров­невых ана­логах вро­де NtDuplicateObject().

Взгля­нем на про­тотип фун­кции.

BOOL DuplicateHandle( [in] HANDLE hSourceProcessHandle, [in] HANDLE hSourceHandle, [in] HANDLE hTargetProcessHandle, [out] LPHANDLE lpTargetHandle, [in] DWORD dwDesiredAccess, [in] BOOL bInheritHandle, [in] DWORD dwOptions);

  • hSourceProcessHandle — хендл про­цес­са, из которо­го тре­бует­ся ско­пиро­вать хендл. Дол­жен иметь мас­ку дос­тупа PROCESS_DUP_HANDLE;
  • hSourceHandle — хендл, который нуж­но ско­пиро­вать;
  • hTargetProcessHandle — про­цесс, в который нуж­но ско­пиро­вать хендл;
  • lpTargetHandle — куда класть ско­пиро­ван­ный хендл;
  • dwDesiredAccess — фла­ги дос­тупа;
  • bInheritHandle — нас­леду­емый ли дес­крип­тор, то есть мож­но ли его переда­вать в дочер­ние про­цес­сы;
  • dwOptions — допол­нитель­ные парамет­ры.

Под­робнее каж­дый аргу­мент мож­но изу­чить в офи­циаль­ной докумен­тации.

Итак, вся ата­ка зак­люча­ется в том, что мы находим про­цес­сы, на которые можем зап­росить хендл с мас­кой PROCESS_DUP_HANDLE, затем копиру­ем их хен­длы, изу­чаем, на что они ука­зыва­ют, пос­ле чего экс­плу­ати­руем! Нап­ример, на про­цесс victim.exe из нашего про­цес­са target.exe мож­но успешно получить хендл с нуж­ной мас­кой. Пос­ле копиро­вания дес­крип­торов из целево­го про­цес­са обна­ружи­ваем, что у нас есть дес­крип­тор про­цес­са lsass.exe! Как следс­твие, успешно дам­пим про­цесс.

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

Здесь нам поможет фун­кция NtQueryObject() и переда­ча струк­туры PUBLIC_OBJECT_BASIC_INFORMATION.

#include <iostream>#include <windows.h>#include <winternl.h>#pragma comment(lib, "ntdll.lib")void ParseAccessMask(ACCESS_MASK accessMask) { if ((accessMask & GENERIC_READ) == GENERIC_READ) std::cout << "GENERIC_READ "; if ((accessMask & GENERIC_WRITE) == GENERIC_WRITE) std::cout << "GENERIC_WRITE "; if ((accessMask & GENERIC_EXECUTE) == GENERIC_EXECUTE) std::cout << "GENERIC_EXECUTE "; if ((accessMask & GENERIC_ALL) == GENERIC_ALL) std::cout << "GENERIC_ALL "; if ((accessMask & PROCESS_CREATE_PROCESS) == PROCESS_CREATE_PROCESS) std::cout << "PROCESS_CREATE_PROCESS "; if ((accessMask & PROCESS_TERMINATE) == PROCESS_TERMINATE) std::cout << "PROCESS_TERMINATE "; if ((accessMask & PROCESS_SUSPEND_RESUME) == PROCESS_SUSPEND_RESUME) std::cout << "PROCESS_SUSPEND_RESUME "; if ((accessMask & PROCESS_QUERY_INFORMATION) == PROCESS_QUERY_INFORMATION) std::cout << "PROCESS_QUERY_INFORMATION "; if ((accessMask & PROCESS_SET_INFORMATION) == PROCESS_SET_INFORMATION) std::cout << "PROCESS_SET_INFORMATION "; std::cout << std::endl;}int main() { HANDLE hProcess = GetCurrentProcess(); ULONG returnLength = 0; PUBLIC_OBJECT_BASIC_INFORMATION obi; NTSTATUS status = NtQueryObject( hProcess, ObjectBasicInformation, &obi, sizeof(obi), &returnLength); if (NT_SUCCESS(status)) { std::cout << "Granted Access: " << std::hex << obi.GrantedAccess << std::endl; ParseAccessMask(obi.GrantedAccess); } else { std::cerr << "NtQueryObject failed with status: " << std::hex << status << std::endl; } CloseHandle(hProcess); return 0;}

При­мер вывода

Есть проб­лема: мы пар­сим мас­ки, совер­шенно забывая о том, что мас­ки дос­тупа могут иметь перек­рыва­ющиеся зна­чения. Нап­ример, у про­цес­са есть PROCESS_VM_READ, у потока — THREAD_SET_CONTEXT, а эти оба зна­чения ука­зыва­ют на 0x0010, поэто­му нашу фун­кцию пар­синга мас­ки дос­тупа нуж­но модифи­циро­вать, научив рас­позна­вать передан­ный хендл.

Да­леко ходить не надо — исполь­зуем ту же фун­кцию NtQueryObject(), толь­ко будем переда­вать ей струк­туру PUBLIC_OBJECT_TYPE_INFORMATION.

typedef struct __PUBLIC_OBJECT_TYPE_INFORMATION { UNICODE_STRING TypeName; ULONG Reserved[22];} PUBLIC_OBJECT_TYPE_INFORMATION, *PPUBLIC_OBJECT_TYPE_INFORMATION;

Здесь будем отталки­вать­ся толь­ко от поля TypeName. Имен­но там содер­жится тип объ­екта, на который ука­зыва­ет хендл.

Источник: xakep.ru

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *