Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Система безопасности Windows состоит из многих инструментов, один из которых — токены аутентификации. В этой статье мы научимся работать с токенами и привилегиями и проводить имперсонацию пользователей Windows.
Для начала давай запомним несколько терминов — скоро они нам пригодятся. Контекст пользователя (user context), он же контекст безопасности (security context), — набор уникальных отличительных признаков пользователя, служащий для контроля доступа. Система хранит сведения о контексте в токене (его также называют маркером доступа). Рассмотрим их чуть более подробно.
При входе в систему любой пользователь вводит свой логин и пароль. Затем, если подключена доменная учетная запись, эти данные сверяются с хранилищем учетных записей 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. Они перечислены в документации Microsoft. Такие идентификаторы называются хорошо известными (well known).
Токен же хранит внутри себя множество различных 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 есть два представления:
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
.
Источник: xakep.ru