Возможность успешного выполнения некоторых операций в Windows NT зависит от того, имеет ли вызывающий пользователь некоторую привилегию или набор привилегий. Например, чтобы завершить работу системы, необходимо иметь привилегию SeShutdownPrivilege. Хотя это обязанность операционной системы - проверять наличие необходимых привилегий, приложение может делать это, чтобы более тонко настроить свой пользовательский интерфейс. Например, оно может запретить команду завершения работы системы, если у пользователя нет соответствующей привилегии, вместо того, чтобы сообщать ему об этом, когда он попытается воспользоваться этой командой.
Список привилегий, предоставленных пользователю, хранится в системной базе данных учетных записей. При входе пользователя в систему, этот список заносится в токен пользователя, откуда его легко получить с помощью функции GetTokenInformation:
BOOL GetTokenInformation( HANDLE TokenHandle, // описатель токена TOKEN_INFORMATION_CLASS InformationClass, // тип информации PVOID TokenInformation, // выходной буфер DWORD TokenInformationLength, // размер буфера PDWORD ReturnLength // требуемый размер буфера ); |
Эта функция позволяет получить различную информацию о токене пользователя, параметр InformationClass указывает требуемый вид информации. Нас интересует список привилегий, поэтому мы будем указывать в этом параметре значение TokenPrivileges. Функция IsPrivilege, приведенная в листинге 1, определяет наличие указанной привилегии с использованием GetTokenInformation.
Листинг 1. Использование функции GetTokenInformationBOOL IsPrivilege( IN PCTSTR pszPrivilegeName // имя привилегии ) { _ASSERTE(pszPrivilegeName != NULL); LUID Luid; HANDLE hToken; DWORD cbNeeded; PTOKEN_PRIVILEGES pPriv; // получаем идентификатор привилегии if (!LookupPrivilegeValue(NULL, pszPrivilegeName, &Luid)) return FALSE; // получаем токен текущего потока if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, FALSE, &hToken)) { if (GetLastError() != ERROR_NO_TOKEN) return FALSE; // получаем токен процесса if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) return FALSE; } // определяем размер буфера, необходимый для получения // всех привилегий if (!GetTokenInformation(hToken, TokenPrivileges, NULL, 0, &cbNeeded)) { if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { DWORD dwError = GetLastError(); CloseHandle(hToken); return SetLastError(dwError), FALSE; } } // выделяем память для выходного буфера pPriv = (PTOKEN_PRIVILEGES)_alloca(cbNeeded); _ASSERTE(pPriv != NULL); // получаем список привилегий if (!GetTokenInformation(hToken, TokenPrivileges, pPriv, cbNeeded, &cbNeeded)) { DWORD dwError = GetLastError(); CloseHandle(hToken); return SetLastError(dwError), FALSE; } CloseHandle(hToken); // проходим по списку привилегий и проверяем, есть ли в нем // указанная привилегия for (UINT i = 0; i < pPriv->PrivilegeCount; i++) { if (pPriv->Privileges[i].Luid.LowPart == Luid.LowPart && pPriv->Privileges[i].Luid.HighPart == Luid.HighPart) return TRUE; } SetLastError(ERROR_SUCCESS); return FALSE; } |
Своим первым действием, функция IsPrivilege получает идентификатор привилегии по ее имени. Хотя привилегии идентифицируются текстовыми строками, при загрузке системы каждой привилегии сопоставляется 64-битный идентификатор, используемый в дальнейшем для сравнения привилегий. Функция LookupPrivilegeValue позволяет получить по имени привилегии ее идентификатор.
Затем функция открывает описатель токена, представляющего текущего пользователя, учитывая при этом возможность имперсонации. Механизм имперсонации в Windows NT позволяет некоторому потоку процесса временно выполнять действия от лица другого пользователя, нежели пользователя изначально создавшего процесс. Поэтому функция сначала пытается открыть токен, связанный с текущим потоком, так как в случае имперсонации этот токен будет представлять имперсонируемого пользователя. Если же поток не имеет ассоциированного с ним токена, функция использует OpenProcessToken, чтобы получить токен, назначенный процессу.
После того, как описатель токена получен, функция вызывает GetTokenInformation дважды: первый раз для того, чтобы определить требуемый размер буфера, и второй раз - чтобы заполнить буфер списком привилегий. В заключение, функция проходит по списку в поиске идентификатора привилегии, указанной в качестве параметра функции.
Как известно, даже если привилегия присутствует в токене пользователя, она может находится в двух состояниях: включенном (enabled) или выключенном (disabled). Некоторые функции Win32 API требуют, чтобы привилегия была явно включена приложением с помощью AdjustTokenPrivileges, некоторые же самостоятельно включают необходимые привилегии. Если необходимо определить, включена ли некоторая привилегия в токене пользователя, для этого можно воспользоваться функцией PrivilegeCheck, что иллюстрируется функцией IsPrivilegeEnabled, приведенной в листинге 2.
Листинг 2. Использование функции PrivilegeCheckBOOL IsPrivilegeEnabled( IN PCTSTR pszPrivilegeName ) { _ASSERTE(pszPrivilegeName != NULL); HANDLE hToken; PRIVILEGE_SET PrivSet; BOOL bEnabled = FALSE; PrivSet.PrivilegeCount = 1; PrivSet.Control = 0; PrivSet.Privilege[0].Attributes = 0; // получаем идентификатор привилегии if (!LookupPrivilegeValue(NULL, pszPrivilegeName, &PrivSet.Privilege[0].Luid)) return FALSE; // получаем токен текущего потока if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, FALSE, &hToken)) { if (GetLastError() != ERROR_NO_TOKEN) return FALSE; // получаем токен процесса if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) return FALSE; } // проверяем статус привилегии if (!PrivilegeCheck(hToken, &PrivSet, &bEnabled)) { DWORD dwError = GetLastError(); CloseHandle(hToken); return SetLastError(dwError), FALSE; } CloseHandle(hToken); SetLastError(0); return bEnabled; } |
Применение описанных в этой статье методов демонстрирует тестовое приложение PrivChk. Это консольное приложение, которое, будучи запущенным без параметров, выдает список привилегий текущего пользователя. Если же в качестве параметра указано имя конкретной привилегии, программа отображает ее состояние, используя функции IsPrivilege и IsPrivilegeEnabled.