We are writing a run-only remote desktop application, that uses SendInput for keyboard (& mouse) interaction. However it cannot interact with UAC prompts.
What permissions/rights does our application require for this?
Background info: The application is spawned by another process duplicating winlogon.exe's Access Token. This enables to run under SYSTEM account with System Integrity Level, is attached to the Physical Console Session and has the same SE privileges as winlogon.exe (https://learn.microsoft.com/en-us/windows/desktop/secauthz/privilege-constants), although not all of them are enabled.
struct MY_TOKEN_PRIVILEGES {
DWORD PrivilegeCount;
LUID_AND_ATTRIBUTES Privileges[2];
};
int RunUnderWinLogon(LPCWSTR executableWithSendInput)
{
DWORD physicalConsoleSessionId = WTSGetActiveConsoleSessionId();
auto winlogonPid = GetWinLogonPid(); // external function
if (!winlogonPid)
{
std::cout << "ERROR getting winlogon pid" << std::endl;
return 0;
}
HANDLE hWinlogonToken, hProcess;
hProcess = OpenProcess(MAXIMUM_ALLOWED, FALSE, winlogonPid);
if (!::OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY
| TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY | TOKEN_ADJUST_SESSIONID
| TOKEN_READ | TOKEN_WRITE, &hWinlogonToken))
{
printf("Process token open Error: %u\n", GetLastError());
}
// Is security descriptor needed for SendInput?
PSECURITY_DESCRIPTOR pSD = NULL;
SECURITY_ATTRIBUTES saToken;
ZeroMemory(&saToken, sizeof(SECURITY_ATTRIBUTES));
saToken.nLength = sizeof (SECURITY_ATTRIBUTES);
saToken.lpSecurityDescriptor = pSD;
saToken.bInheritHandle = FALSE;
HANDLE hWinlogonTokenDup;
if (!DuplicateTokenEx(hWinlogonToken, TOKEN_ALL_ACCESS, &saToken, SecurityImpersonation, TokenPrimary, &hWinlogonTokenDup))
{
printf("DuplicateTokenEx Error: %u\n", GetLastError());
}
if (!SetTokenInformation(hWinlogonTokenDup, TokenSessionId, (void*)physicalConsoleSessionId, sizeof(DWORD)))
{
printf("SetTokenInformation Error: %u\n", GetLastError());
}
//Adjust Token privilege
LUID luidSeDebugName;
if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luidSeDebugName))
{
printf("Lookup Privilege value Error: %u\n", GetLastError());
}
LUID luidSeTcbName;
if (!LookupPrivilegeValue(NULL, SE_TCB_NAME, &luidSeTcbName))
{
printf("Lookup Privilege value Error: %u\n", GetLastError());
}
MY_TOKEN_PRIVILEGES tp;
tp.PrivilegeCount = 2;
tp.Privileges[0].Luid = luidSeDebugName;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
tp.Privileges[1].Luid = luidSeTcbName;
tp.Privileges[1].Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(hWinlogonTokenDup, FALSE, (PTOKEN_PRIVILEGES)&tp, /*BufferLength*/0, /*PreviousState*/(PTOKEN_PRIVILEGES)NULL, NULL))
{
printf("Adjust Privilege value Error: %u\n", GetLastError());
}
if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
{
printf("Token does not have the privilege\n");
}
DWORD creationFlags;
creationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE;
LPVOID pEnv = NULL;
if (CreateEnvironmentBlock(&pEnv, hWinlogonTokenDup, TRUE))
{
std::cout << "CreateEnvironmentBlock() success" << std::endl;
creationFlags |= CREATE_UNICODE_ENVIRONMENT;
}
SECURITY_ATTRIBUTES saProcess, saThread;
ZeroMemory(&saProcess, sizeof(SECURITY_ATTRIBUTES));
ZeroMemory(&saThread, sizeof(SECURITY_ATTRIBUTES));
saProcess.nLength = sizeof (SECURITY_ATTRIBUTES);
saProcess.lpSecurityDescriptor = pSD;
saProcess.bInheritHandle = FALSE;
saThread.nLength = sizeof (SECURITY_ATTRIBUTES);
saThread.lpSecurityDescriptor = pSD;
saThread.bInheritHandle = FALSE;
STARTUPINFO si;
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.lpDesktop = (LPWSTR)L"winsta0\\default";
PROCESS_INFORMATION pi;
ZeroMemory(&pi, sizeof(pi));
BOOL bResult = CreateProcessAsUser(
hWinlogonTokenDup, // client's access token
executableWithSendInput, // file using SendInput
NULL, // command line
&saProcess, // pointer to process SECURITY_ATTRIBUTES
&saThread, // pointer to thread SECURITY_ATTRIBUTES
FALSE, // handles are not inheritable
creationFlags, // creation flags
pEnv, // pointer to new environment block
NULL, // name of current directory
&si, // pointer to STARTUPINFO structure
&pi // receives information about new process
);
}
SendInput, like SendMessage and PostMessage is limited to work between processes in the same login session and within the same desktop as the target process. The UAC prompt is shown in the Winlogon's Secure Desktop (winsta0\Winlogon) so you need poll the current desktop periodically with OpenInputDesktop() then use SetThreadDesktop() to enable the current thread to send messages to the user's desktop / secure desktop, whichever active is.
In case of UAC, you need to run your process under the System Account, to comply with the UIPI Integrity Level check, as you already did.
See also: How to switch a process between default desktop and Winlogon desktop?
It is possible to authorize your application to be able to do these UIAutomation/screen reader tasks.
Create an entry in your assembly manifest that includes:
uiAccess="true"
Then you have to be digitally sign with a valid digital certificate.
And you have to be installed in Program Files.
Being able to automate the UAC dialog is serious business; and you don't get to screw around with that willy-nilly.
Bonus Reading
https://techcommunity.microsoft.com/t5/windows-blog-archive/using-the-uiaccess-attribute-of-requestedexecutionlevel-to/ba-p/228641
Related
I am trying to find a way to allow a single process to be launched as an elevated user but prohibit this process from launching any children with it's token--in a way, sort of "sealing" the token. The reasoning behind this is to prevent a user from launching cmd.exe and gaining full access to the system.
I've looked through the process privilege constants and I'm not seeing anything that could be used to implement this functionality.
I don't think you can seal the token but you can control child process creation with job objects:
static BOOL SpawnProcessAndTerminateGrandchildren(PTSTR Cmdline)
{
HANDLE hJob = CreateJobObject(0, 0);
if (!hJob) return false;
JOBOBJECT_BASIC_LIMIT_INFORMATION jobli;
jobli.LimitFlags = JOB_OBJECT_LIMIT_ACTIVE_PROCESS;
jobli.ActiveProcessLimit = 1;
BOOL retval = SetInformationJobObject(hJob, JobObjectBasicLimitInformation, &jobli, sizeof(jobli));
PROCESS_INFORMATION pi;
if (retval)
{
STARTUPINFO si;
ZeroMemory(&si, sizeof(si)), si.cb = sizeof(si);
retval = CreateProcess(0, Cmdline, 0, 0, false, CREATE_SUSPENDED|CREATE_BREAKAWAY_FROM_JOB|CREATE_NEW_CONSOLE, 0, 0, &si, &pi);
}
if (retval)
{
if (AssignProcessToJobObject(hJob, pi.hProcess)) // This can fail if we are already in a job
{
ResumeThread(pi.hThread);
WaitForSingleObject(pi.hProcess, INFINITE);
}
else
TerminateProcess(pi.hProcess, ERROR_OPERATION_ABORTED);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
CloseHandle(hJob);
return retval;
}
int main(int argc, ...)
{
TCHAR cmd[] = TEXT("cmd.exe /k regedit"); // cmd.exe is our child, regedit is the grandchild spawned by cmd.exe
SpawnProcessAndTerminateGrandchildren(cmd);
return 0;
}
If you want more control you can use JobObjectAssociateCompletionPortInformation so you get the JOB_OBJECT_MSG_NEW_PROCESS message each time a new child process is created.
I have an application that needs to monitor the primary drive for file changes via ReadDirectoryChangesW. However, when UAC is enabled, it doesn't work.
All of the Windows API calls succeed, but I'm not notified of any changes.
I can work around this by individually monitoring each directory in the root, but this is a problem, because it can potentially cause a blue screen if there are too many directories.
Is there an acceptable way to get around UAC and receive file change notifications on the entire primary drive?
The relevant CreateFile and ReadDirectoryChangesW is below. In the case where it doesn't work, directory is C:\. If I monitor any secondary drive (i.e. E:\, F:\, G:\) it works as expected. None of the calls return errors.
HANDLE fileHandle = CreateFileW(directory.c_str(), FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);
BOOL success = ReadDirectoryChangesW(fileHandle, watched.buffer.data(),
watched.buffer.size(), TRUE,
FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE,
NULL, &watched.overlapped, NULL);
Interestingly, the .NET System.IO.FileSystemWatcher does work correctly, and it uses the exact same functions and parameters as I'm using, but it behaves correctly.
First it is best for applications that use the ReadDirectoryChangesW API to run elevated make a manifest file for you app and set requireAdministrator as the requestedExecutionLevel level. Check here for reference.
Try removing FILE_SHARE_WRITE from the CreateFile call if you are using it.
Another option is to make your program run as a service, im not sure how applicable this is to your needs. You could post some code as to how you are getting the file handle and what are you passing to ReadDirectoryChangesW
Here's some working test code, for future reference.
#include <Windows.h>
#include <stdio.h>
int main(int argc, char ** argv)
{
HANDLE filehandle;
BYTE buffer[65536];
DWORD dw;
FILE_NOTIFY_INFORMATION * fni;
OVERLAPPED overlapped = {0};
overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (overlapped.hEvent == NULL)
{
printf("CreateEvent: %u\n", GetLastError());
return 1;
}
filehandle = CreateFile(L"C:\\",
FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
NULL);
if (filehandle == INVALID_HANDLE_VALUE)
{
printf("CreateFile: %u\n", GetLastError());
return 1;
}
for (;;)
{
if (!ReadDirectoryChangesW(filehandle, buffer, sizeof(buffer),
TRUE,
FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE,
NULL, &overlapped, NULL))
{
printf("ReadDirectoryChangesW: %u\n", GetLastError());
return 1;
}
printf("Queued OK.\n");
if (!GetOverlappedResult(filehandle, &overlapped, &dw, TRUE))
{
printf("GetOverlappedResult: %u\n", GetLastError());
return 1;
}
printf("%u bytes read.\n", dw);
fni = (FILE_NOTIFY_INFORMATION *)buffer;
for (;;)
{
printf("Next entry offset = %u\n", fni->NextEntryOffset);
printf("Action = %u\n", fni->Action);
printf("File name = %.*ws\n",
fni->FileNameLength / 2,
fni->FileName);
if (fni->NextEntryOffset == 0) break;
fni = (FILE_NOTIFY_INFORMATION *)
(((BYTE *)fni) + fni->NextEntryOffset);
}
}
printf("All done\n");
return 0;
}
You can adjust the privileges of your process yourself like this:
// enable the required privileges for this process
LPCTSTR arPrivelegeNames[] = { SE_BACKUP_NAME,
SE_RESTORE_NAME,
SE_CHANGE_NOTIFY_NAME
};
for (int i=0; i<(sizeof(arPrivelegeNames)/sizeof(LPCTSTR)); ++i)
{
CAutoGeneralHandle hToken;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, hToken.GetPointer()))
{
TOKEN_PRIVILEGES tp = { 1 };
if (LookupPrivilegeValue(NULL, arPrivelegeNames[i], &tp.Privileges[0].Luid))
{
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
}
}
}
This works also for non-privileged processes (a.k.a. normal user processes).
I am seeking example code:
For a service calls CreateProcessAsUser() I want the process to run in the user's session, not session 0
thus far the created process is only running like a service in session 0
This was stripped from some old code that launched a console app from a service. It worked under NT4 but I haven't tested it with a modern version of Windows so can't guarantee it will work as it did on NT4.
EDIT: No, that's not going to work as-is. You need to add the code found here to create a desktop, set the SID, etc.
if (!LogonUser(userId,
domain,
password,
LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT,
&hUserToken))
{
return GetLastError();
}
if (!ImpersonateLoggedOnUser(hUserToken))
{
DWORD rc = GetLastError();
CloseHandle(hUserToken);
return rc;
}
STARTUPINFO si;
PROCESS_INFORMATION pi;
memset(&si, 0, sizeof(si));
memset(&pi, 0, sizeof(pi));
si.cb = sizeof(si);
rc = CreateProcessAsUser(hUserToken, // user token
0, // app name
"foo.exe", // command line
0, // process attributes
0, // thread attributes
FALSE, // don't inherit handles
DETACHED_PROCESS, // flags
0, // environment block
0, // current dir
&si, // startup info
&pi); // process info gets put here
if (!rc)
{
DWORD rc = GetLastError();
RevertToSelf();
CloseHandle(hUserToken);
return rc;
}
RevertToSelf();
CloseHandle(hUserToken);
return 0;
I know this is an ancient post but I happen to be working on this so here's some code that works for me.
Determine the session ID of the currently logged-on user
DWORD GetCurrentSessionId ()
{
WTS_SESSION_INFO *pSessionInfo;
DWORD n_sessions = 0;
BOOL ok = WTSEnumerateSessions (WTS_CURRENT_SERVER, 0, 1, &pSessionInfo, &n_sessions);
if (!ok)
return 0;
DWORD SessionId = 0;
for (DWORD i = 0; i < n_sessions; ++i)
{
if (pSessionInfo [i].State == WTSActive)
{
SessionId = pSessionInfo [i].SessionId;
break;
}
}
WTSFreeMemory (pSessionInfo);
return SessionId;
}
Launch process as the currently logged-on user
bool LaunchProcess (const char *process_path)
{
DWORD SessionId = GetCurrentSessioId ();
if (SessionId == 0) // no-one logged in
return false;
HANDLE hToken;
BOOL ok = WTSQueryUserToken (SessionId, &hToken);
if (!ok)
return false;
void *environment = NULL;
ok = CreateEnvironmentBlock (&environment, hToken, TRUE);
if (!ok)
{
CloseHandle (hToken);
return false;
}
STARTUPINFO si = { sizeof (si) } ;
PROCESS_INFORMATION pi = { } ;
si.lpDesktop = "winsta0\\default";
// Do NOT want to inherit handles here
DWORD dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_UNICODE_ENVIRONMENT;
ok = CreateProcessAsUser (hToken, process_path, NULL, NULL, NULL, FALSE,
dwCreationFlags, environment, NULL, &si, &pi);
DestroyEnvironmentBlock (environment);
CloseHandle (hToken);
if (!ok)
return false;
CloseHandle (pi.hThread);
CloseHandle (pi.hProcess);
return true;
}
When installing SQL Server 2008, if this privilege is not enabled for the user doing the install, the install fails un-gracefully. So in my app, before installing SQL Server (using its silent install), I'd like to detect whether or not the current running user has the "Debug Programs" privilege set (i.e., SeDebugPrivilege, SE_DEBUG_NAME...)
I don't want to know if the current process has it set (because, apparently, most times it does not, even if the privilege is enabled on the system). I originally thought that the "PrivilegeCheck" API would work, but it does not. If you run this code under the VS debugger, then it tells you the privilege is enabled. If you run it from a command line, it tells you the privilege is disabled. How should I correct this program to actually be able to check whether or not the privilege is available?
HANDLE hToken;
// Get the calling thread's access token.
if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &hToken))
{
if (GetLastError() != ERROR_NO_TOKEN)
{
printf("CAN'T GET THREAD TOKEN!!!\n");
return -1;
}
// Retry against process token if no thread token exists.
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
{
printf("CAN'T GET PROCESS TOKEN!!!\n");
return -1;
}
}
//Find the LUID for the debug privilege token
LUID luidDebugPrivilege;
if ( !LookupPrivilegeValue(
NULL, // lookup privilege on local system
"SeDebugPrivilege", // privilege to lookup
&luidDebugPrivilege ) ) // receives LUID of privilege
{
printf("LookupPrivilegeValue error: %u\n", GetLastError() );
return -1;
}
PRIVILEGE_SET privs;
privs.PrivilegeCount = 1;
privs.Control = PRIVILEGE_SET_ALL_NECESSARY;
privs.Privilege[0].Luid = luidDebugPrivilege;
privs.Privilege[0].Attributes = SE_PRIVILEGE_ENABLED;
BOOL bResult;
::PrivilegeCheck(hToken, &privs, &bResult);
if(bResult)
{
printf("DEBUG ENABLED!\n");
}
else
{
printf("DEBUG NOT ENABLED!\n");
}
OK, we figured this out after posting the original question. What we actually need to do is attempt to set the "debug programs" privilege for the current process. If we can enable that privilege, then that means the current logged-in user has that privilege enabled for them in the local security policy editor (gpedit.msc on XP...)
See below for example code, in case anyone else needs to solve this problem! The important pieces are:
Use LookupPrivilegeValue() to find the LUID for SeDebugPrivilege. (All the APIs for this stuff need LUIDs...)
Use GetTokenInformation() to find out what privileges are enabled on this process already. If the process has the privilege enabled already, that means that the process is most likely being run under a debugger, and that the current logged-in user does have the privilege enabled.
If the process doesn't have the privilege set, use the AdjustTokenPrivileges() to attempt to set the privilege. This is in our method AttemptToAddDebugPrivilegeToProcess() below; we return true if the privilege can be set (meaning the current logged-in user has the "debug programs" privilege enabled) or false if it can't.
#include "stdafx.h"
#include <strsafe.h>
void ShowLastError(LPTSTR lpszFunction) {
// Retrieve the system error message for the last-error code
LPVOID lpMsgBuf;
LPVOID lpDisplayBuf;
DWORD dw = GetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0, NULL );
// Display the error message and exit the process
lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT,
(lstrlen((LPCTSTR)lpMsgBuf) + lstrlen((LPCTSTR)lpszFunction) + 40) * sizeof(TCHAR));
StringCchPrintf((LPTSTR)lpDisplayBuf,
LocalSize(lpDisplayBuf) / sizeof(TCHAR),
TEXT("%s failed with error %d: %s"),
lpszFunction, dw, lpMsgBuf);
printf((LPTSTR)lpDisplayBuf);
LocalFree(lpMsgBuf);
LocalFree(lpDisplayBuf);
}
bool LuidsMatch(LUID l1, LUID l2)
{
return l1.LowPart == l2.LowPart && l1.HighPart == l2.HighPart; }
bool AttemptToAddDebugPrivilegeToProcess(HANDLE hToken) {
//Find the LUID for the debug privilege token
LUID luidDebugPrivilege;
if ( !LookupPrivilegeValue(
NULL, // lookup privilege on local system
"SeDebugPrivilege", // privilege to lookup
&luidDebugPrivilege ) ) // receives LUID of privilege
{
printf("LookupPrivilegeValue error: %u\n", GetLastError() );
return false;
}
TOKEN_PRIVILEGES newState;
newState.PrivilegeCount = 1;
newState.Privileges[0].Luid = luidDebugPrivilege;
newState.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if(AdjustTokenPrivileges(
hToken,
FALSE,
&newState,
sizeof(newState),
NULL, //&previousState,
0))
{
if(GetLastError() == ERROR_NOT_ALL_ASSIGNED)
{
printf("Couldn't set debug!!!");
return false;
}
//*************************************************************
//IF YOU MADE IT HERE, THE USER HAS THE DEBUG PROGRAMS PRIVILEGE
//*************************************************************
printf("DEBUG OK!!!");
return true;
}
printf("AdjustTokenPrivileges returned false!!!");
ShowLastError("AdjustTokenPrivileges");
return false;
}
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE hToken;
// Get the calling thread's access token.
if (!OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, TRUE, &hToken))
{
if (GetLastError() != ERROR_NO_TOKEN)
{
printf("CAN'T GET THREAD TOKEN!!!\n");
return -1;
}
// Retry against process token if no thread token exists.
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, &hToken))
{
printf("CAN'T GET PROCESS TOKEN!!!\n");
return -1;
}
}
//Find the LUID for the debug privilege token
LUID luidDebugPrivilege;
if ( !LookupPrivilegeValue(
NULL, // lookup privilege on local system
"SeDebugPrivilege", // privilege to lookup
&luidDebugPrivilege ) ) // receives LUID of privilege
{
printf("LookupPrivilegeValue error: %u\n", GetLastError() );
return -1;
}
//Find if the "debug programs" privilege is already assigned to this process
DWORD dwReturnedDataSize;
GetTokenInformation(
hToken,
TokenPrivileges,
NULL,
0,
&dwReturnedDataSize);
BYTE* pData = new BYTE[dwReturnedDataSize];
GetTokenInformation(
hToken,
TokenPrivileges,
pData,
dwReturnedDataSize,
&dwReturnedDataSize);
TOKEN_PRIVILEGES* pPrivileges = (TOKEN_PRIVILEGES*)pData;
bool bFound = false;
for(unsigned int count = 0; count PrivilegeCount; count++)
{
LUID_AND_ATTRIBUTES& luidAndAttrs = pPrivileges->Privileges[count];
if(LuidsMatch(luidAndAttrs.Luid, luidDebugPrivilege))
{
bFound = true;
if((luidAndAttrs.Attributes & SE_PRIVILEGE_ENABLED) == SE_PRIVILEGE_ENABLED)
{
//**************************************************************
//IF YOU MADE IT HERE, THE USER HAS THE DEBUG PROGRAMS PRIVILEGE
//**************************************************************
}
else
{
printf("THIS PROCESS DOES NOT HAVE THE DEBUG PROGRAMS PRIVILEGE ENABLED\n"); AttemptToAddDebugPrivilegeToProcess(hToken);
}
}
}
if(!bFound)
{
printf("THIS PROCESS DOES NOT HAVE THE DEBUG PROGRAMS PRIVILEGE ENABLED\n");
AttemptToAddDebugPrivilegeToProcess(hToken);
}
return 0;
}
The function GetTokenInformation can be used to retrieve the list of privileges for the process. PrivilegeCheck checks if a privilege is enabled or disabled, and privileges not held by the user will always be disabled. Privileges held by the user may or may not be disabled (some are disabled by default)
From your question, I think what you really want is to check is if the user is an administrator.
If I understand you correct you can use LsaEnumerateAccountRights to get the list of privileges which the user has.
I have some code, with which I'm trying to get the current session user token:
#include <Wtsapi32.h>
DWORD activeSessionId = WTSGetActiveConsoleSessionId();
HANDLE currentToken;
BOOL queryRet = WTSQueryUserToken(activeSessionId, ¤tToken);
if (!queryRet) {
DWORD err = GetLastError();
return 0;
}
Value of err is 1314.
Update 1
No luck so far, tried to grant the current process SE_TCB_NAME - but still get same error from WTSQueryUserToken (1314).
HANDLE process = GetCurrentProcess();
HANDLE processToken;
BOOL openTokenRet = OpenProcessToken(
process, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &processToken);
if (!openTokenRet)
{
DWORD err = GetLastError();
return 0;
}
TOKEN_PRIVILEGES tokenPrivs;
BOOL lookupRet = LookupPrivilegeValue(
NULL, SE_TCB_NAME, &tokenPrivs.Privileges[0].Luid);
if (!lookupRet)
{
DWORD err = GetLastError();
return 0;
}
tokenPrivs.PrivilegeCount = 1;
tokenPrivs.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
BOOL adjustRet = AdjustTokenPrivileges(
processToken, FALSE, &tokenPrivs, 0, (PTOKEN_PRIVILEGES)NULL, 0);
if (!adjustRet)
{
DWORD err = GetLastError();
return 0;
}
// get the user in the active session
HANDLE currentToken;
BOOL queryRet = WTSQueryUserToken(activeSessionId, ¤tToken);
if (!queryRet) {
DWORD err = GetLastError();
return 0;
}
Update 2:
Added some more debug info, but prevState.PrivilegeCount is 0...
TOKEN_PRIVILEGES prevState;
DWORD prevStateLen = 0;
BOOL adjustRet = AdjustTokenPrivileges(
processToken, FALSE, &tokenPrivs,
sizeof(TOKEN_PRIVILEGES), &prevState, &prevStateLen);
DWORD adjustErr = GetLastError();
if (!adjustRet)
{
return 0;
}
Solution:
Looks like WTSQueryUserToken can only be used when running as LocalSystem, which means I'll have to run as a service and debug from there... Doh!
Error 1314 is ERROR_PRIVILEGE_NOT_HELD. You need SE_TCB_NAME privilege to call WTSQueryUserToken.
This privilege is typically held only by code running as Local System. If this privilege is present in your token, but disabled, you can use AdjustTokenPrivileges to enable it. Since SE_TCB_NAME is a potentially very dangerous privilege to have, you should disable it again immediately after you use it. An easy way to see if you have this privilege is with Process Explorer in the Security table of the process properties window.
Per Update 1 - is AdjustTokenPrivileges return success, but GetLastError() is set to ERROR_NOT_ALL_ASSIGNED? MSDN indicates it could return this if privileges weren't enabled. Can you verify that your process does have SE_TCB_NAME privilege, but it is disabled? What account is your process running as?