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

Для начала давай запом­ним нес­коль­ко тер­минов — ско­ро они нам при­годят­ся. Кон­текст поль­зовате­ля (user context), он же кон­текст безопас­ности (security context), — набор уни­каль­ных отли­читель­ных приз­наков поль­зовате­ля, слу­жащий для кон­тро­ля дос­тупа. Сис­тема хра­нит све­дения о кон­тек­сте в токене (его так­же называ­ют мар­кером дос­тупа). Рас­смот­рим их чуть более под­робно.

 

SID и токены

При вхо­де в сис­тему любой поль­зователь вво­дит свой логин и пароль. Затем, если под­клю­чена домен­ная учет­ная запись, эти дан­ные све­ряют­ся с хра­нили­щем учет­ных записей Active Directory на кон­трол­лере домена, которое называ­ется ntds.dit, либо с базой дан­ных локаль­ного компь­юте­ра — SAM.

Ес­ли пароль вер­ный, сис­тема начина­ет собирать све­дения об учет­ной записи. В слу­чае Active Directory так­же собира­ется информа­ция уров­ня домена (нап­ример, домен­ные груп­пы). И незави­симо от типа УЗ находят­ся све­дения, отно­сящи­еся к локаль­ной сис­теме, в том чис­ле перечень локаль­ных групп, в которых сос­тоит поль­зователь. Все эти дан­ные помеща­ются в спе­циаль­ную струк­туру, хра­нящу­юся в объ­екте ядра, который называ­ется токеном дос­тупа.

В сис­темах Windows у мно­гих объ­ектов — груп­пы, домена, поль­зовате­ля — сущес­тву­ет спе­циаль­ный иден­тифика­тор безопас­ности, SID (Security Identifier). Он име­ет вот такой фор­мат:

S-R-I-S-S

В этой записи:

  • S озна­чает, что пос­ледова­тель­ность чисел пред­став­ляет собой иден­тифика­тор безопас­ности;
  • R — номер вер­сии SID;
  • I — чис­ло, пред­став­ляющее упол­номочен­ный орган (authority), который соз­дал или выдал SID;
  • S — чис­ло, пред­став­ляющее вто­рой упол­номочен­ный орган (subauthority). Так­же содер­жит внут­ри себя RID (Relative Identifier) — допол­нитель­ный иден­тифика­тор, который исполь­зует­ся, что­бы отли­чить одно­го поль­зовате­ля от дру­гого;
  • S — еще один упол­номочен­ный орган. SID может содер­жать внут­ри себя любое количес­тво упол­номочен­ных орга­нов.
Как выглядит SID
Как выг­лядит SID

При этом сущес­тву­ют и некото­рые стан­дар­тные SID. Они перечис­лены в до­кумен­тации Microsoft. Такие иден­тифика­торы называ­ются хорошо извес­тны­ми (well known).

То­кен же хра­нит внут­ри себя мно­жес­тво раз­личных SID, сре­ди которых мож­но выделить основные:

  • SID поль­зовате­ля — иден­тифици­рует учет­ную запись, для которой был соз­дан токен;
  • SID групп — иден­тифика­торы групп, в которые вхо­дит поль­зователь;
  • SID регис­тра­ции — уни­каль­ный SID, соз­данный в момент аутен­тифика­ции. Поз­воля­ет отли­чить один сеанс от дру­гого (если поль­зователь нес­коль­ко раз заходил в сис­тему, то сис­тема каж­дый раз соз­дает уни­каль­ный SID регис­тра­ции).

Дос­таточ­но слож­но, прав­да ведь? Но мож­но про­вес­ти прос­тую ана­логию. Токен — кар­точка сот­рудни­ка ком­пании. SID поль­зовате­ля — имя на этой кар­точке, SID груп­пы — напеча­тан­ная дол­жность. Сис­тема смот­рит на эту кар­точку каж­дый раз, ког­да мы начина­ем с ней вза­имо­дей­ство­вать.

 

Токен и процесс

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

У про­цес­са есть токен. Чаще все­го исполь­зует­ся токен поль­зовате­ля, запус­тивше­го про­цесс. Ког­да про­цесс порож­дает дру­гие про­цес­сы, все они исполь­зуют этот же токен.

Дочерние процессы имеют тот же токен
До­чер­ние про­цес­сы име­ют тот же токен

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

 

Приступаем к работе

 

Получаем токен

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

Ва­риант 1: получить токен опре­делен­ного про­цес­са.

BOOL OpenProcessToken(
[in] HANDLE ProcessHandle,
[in] DWORD DesiredAccess,
[out] PHANDLE TokenHandle
);

Ва­риант 2: получить токен опре­делен­ного потока.

BOOL OpenThreadToken(
[in] HANDLE ThreadHandle,
[in] DWORD DesiredAccess,
[in] BOOL OpenAsSelf,
[out] PHANDLE TokenHandle
);

Пе­репи­сывать MSDN и объ­яснять каж­дый параметр как‑то неп­равиль­но. Если вдруг ты нез­наком с WinAPI, то можешь написать мне, ски­ну матери­ал для изу­чения. Пред­лагаю обра­тить вни­мание лишь на вто­рой параметр — DesiredAccess.

Здесь ты дол­жен ука­зать, какой тип дос­тупа к токену хочешь получить. Это зна­чение пре­обра­зует­ся в мас­ку дос­тупа, на осно­ве которой Windows опре­деля­ет, мож­но выдавать токен или нель­зя. WinAPI пре­дос­тавля­ет для такой мас­ки некото­рые стан­дар­тные зна­чения.

Об­рати вни­мание, что прос­то так засунуть TOKEN_ALL_ACCESS нель­зя: сис­тема баналь­но не выдаст токен, так как в эту мас­ку вхо­дит и TOKEN_ADJUST_SESSIONID, который тре­бует наличие при­виле­гии SeTcbPrivilege. Такой при­виле­гией обла­дает лишь сис­тема.

При этом дан­ную ошиб­ку допус­кают очень час­то. Нап­ример, лишь в вер­сии 4.7 инс­тру­мен­та Cobalt Strike был исправ­лен этот недочет.

Ча­ще все­го для наших задач мы будем ука­зывать при­виле­гию TOKEN_DUPLICATE, что­бы исполь­зовать фун­кцию DuplicateTokenEx(), которую мы раз­берем поз­же.

Ва­риант 3: зап­росить токен поль­зовате­ля, если мы зна­ем его логин и пароль.

BOOL LogonUserA(
[in] LPCSTR lpszUsername,
[in, optional] LPCSTR lpszDomain,
[in, optional] LPCSTR lpszPassword,
[in] DWORD dwLogonType,
[in] DWORD dwLogonProvider,
[out] PHANDLE phToken
);
 

Проверка наличия привилегии в токене

То­кен так­же содер­жит информа­цию о при­виле­гиях поль­зовате­ля. У самих при­виле­гий в Windows есть два пред­став­ления:

  • дру­жес­твен­ное имя — имя, которое отоб­ража­ется в интерфей­се Windows, нап­ример Act as part of the operating system;
  • прог­рам­мное имя — имя, которое исполь­зуют при­ложе­ния, нап­ример SE_TCB_NAME.

Для про­вер­ки мож­но исполь­зовать сле­дующую фун­кцию:

BOOL PrivilegeCheck(
[in] HANDLE ClientToken,
[in, out] PPRIVILEGE_SET RequiredPrivileges,
[out] LPBOOL pfResult
);

Сам код может быть при­мер­но сле­дующий (при­нима­ет токен, в котором надо про­верить наличие при­виле­гии, и ее имя. Допус­тим, SE_DEBUG_NAME):

bool IsPrivilegeEnabled(HANDLE hToken, PCWSTR name) {
PRIVILEGE_SET set{};
set.PrivilegeCount = 1;
if (!::LookupPrivilegeValue(nullptr, name, &set.Privilege[0].Luid)) return false;
BOOL result;
return ::PrivilegeCheck(hToken, &set, &result) && result;
}
 

Изменение информации токена

До­пус­тимые изме­нения делят­ся на две груп­пы:

  • све­дения, которые мож­но изме­нить;
  • све­дения, которые мож­но задать.

Для боль­шинс­тва ситу­аций мож­но вос­поль­зовать­ся этой фун­кци­ей:

BOOL SetTokenInformation(
[in] HANDLE TokenHandle,
[in] TOKEN_INFORMATION_CLASS TokenInformationClass,
[in] LPVOID TokenInformation,
[in] DWORD TokenInformationLength
);

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

Что можно изменить
Что мож­но изме­нить

Нап­ример, что­бы вклю­чить вир­туали­зацию UAC, исполь­зуй сле­дующий код:

// hProcess имеет PROCESS_QUERY_INFORMATION
HANDLE hToken;
OpenProcessToken(hProcess, TOKEN_ADJUST_DEFAULT, &hToken);
ULONG enable = 1;
SetTokenInformation(hToken, TokenVirtualizationEnabled,&enable, sizeof(enable));

При этом мы можем изме­нить и при­виле­гии, содер­жащи­еся в токене! Но тре­бует­ся знать, как получить из прог­рам­мно­го име­ни при­виле­гии ее LUID. Это поз­воля­ет сде­лать сле­дующая фун­кция:

BOOL LookupPrivilegeValueA(
[in, optional] LPCSTR lpSystemName,
[in] LPCSTR lpName,
[out] PLUID lpLuid
);

Сле­дующим шагом мы дол­жны выз­вать AdjustTokenPrivilege():

BOOL AdjustTokenPrivileges(
[in] HANDLE TokenHandle,
[in] BOOL DisableAllPrivileges,
[in, optional] PTOKEN_PRIVILEGES NewState,
[in] DWORD BufferLength,
[out, optional] PTOKEN_PRIVILEGES PreviousState,
[out, optional] PDWORD ReturnLength
);

Эта фун­кция может как вклю­чить при­виле­гии, так и отклю­чить их. Не знаю, почему Microsoft не реали­зова­ла что‑нибудь подоб­ное:

BOOL EnableTokenPrivilege(HANDLE hToken, LPTSTR szPriv, BOOL bEnabled) {
TOKEN_PRIVILEGES tp;
LUID luid;
BOOL bRet = FALSE;
__try {
// Ищем уникальный для системы LUID привилегии
if (!LookupPrivilegeValue(NULL, szPriv /*SE_DEBUG_NAME*/, &luid)) {
// Если имя фиктивное
__leave;
}
// Создаем массив привилегий нашего маркера (в данном случае массив из одного элемента)
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = bEnabled ? SE_PRIVILEGE_ENABLED : 0;
// Изменяем состояние привилегий маркера, включая или отключая привилегии из нашего массива
if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) {
__leave;
}
bRet = TRUE;
}
__finally {};
return(bRet);
}

Этой фун­кции тре­бует­ся передать токен, прог­рам­мное имя при­виле­гии и булево зна­чение, TRUE или FALSE, то есть вклю­чить при­виле­гию или вык­лючить ее. При этом токен дол­жен иметь мас­ку TOKEN_ADJUST_PRIVILEGES.

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

Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».

Присоединяйся к сообществу «Xakep.ru»!

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

Подписаться
Уведомить о
4 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии