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

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

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

warning

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

 

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

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

 

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

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

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

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

Вывод инструмента Handle из пакета 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. Имен­но там содер­жится тип объ­екта, на который ука­зыва­ет хендл.

Продолжение доступно только участникам

Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

Вариант 2. Открой один материал

Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.


  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии