Содержание статьи
- SID и токены
- Токен и процесс
- Приступаем к работе
- Получаем токен
- Проверка наличия привилегии в токене
- Изменение информации токена
- Выполнение кода с использованием токена
- Создание процесса
- Применение к потоку
- Заимствование прав подключенного пользователя
- Без установления соединения
- Именованные каналы
- Сокеты или другой механизм взаимодействия
- Начало работы
- Роль клиента
- Роль сервера
- Использование полного контекста
- Выводы
Для начала давай запомним несколько терминов — скоро они нам пригодятся. Контекст пользователя (user context), он же контекст безопасности (security context), — набор уникальных отличительных признаков пользователя, служащий для контроля доступа. Система хранит сведения о контексте в токене (его также называют маркером доступа). Рассмотрим их чуть более подробно.
SID и токены
При входе в систему любой пользователь вводит свой логин и пароль. Затем, если подключена доменная учетная запись, эти данные сверяются с хранилищем учетных записей Active Directory на контроллере домена, которое называется ntds.
, либо с базой данных локального компьютера — 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. Они перечислены в документации 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_INFORMATIONHANDLE 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
.
Продолжение доступно только участникам
Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
Вариант 2. Открой один материал
Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.
Я уже участник «Xakep.ru»