How to detect if "Debug Programs" Windows privilege is set? - winapi

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.

Related

Get current logon security token for SHGetFolderPath from elevated permissions

I need to get system folders for current user via SHGetFolderPath, but in the installer application the system elevates this to the admin account and it returns admin account folders instead. How to get the security token for the current user? I tried this:
HANDLE token = NULL;
PWTS_SESSION_INFO sessions;
DWORD cnt = 0;
WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, &sessions, &cnt);
for (DWORD i=0; i<cnt; i++)
{
if (WTSActive == sessions->State)
{
WTSQueryUserToken(sessions->SessionId, &token);
break;
};
sessions++;
};
But it doesn't really work. Any ideas?
possible get shell process id (explorer), take token from it and use this token in query.
if (HWND hwnd = GetShellWindow())
{
ULONG dwProcessId;
if (GetWindowThreadProcessId(hwnd, &dwProcessId))
{
HANDLE hToken;
if (HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, dwProcessId))
{
BOOL b = OpenProcessToken(hProcess, TOKEN_QUERY|TOKEN_IMPERSONATE, &hToken);
CloseHandle(hProcess);
if (b)
{
if (0 <= SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, hToken, &pszPath))
{
DbgPrint("<%S>\n", pszPath);
CoTaskMemFree(pszPath);
}
CloseHandle(hToken);
}
}
}
}

SendInput fails on UAC prompt

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

Create a mutex that is only visible within the same session

I have a windows app that (for company policy reasons) I would like to be limited to a single user at a time, but allowing multiple instances to run simultaneously within a single session.
I am using a mutex to do this much like as already asked and answered in
How to Avoid Multiple Instances of Different Users but Allow Multiple Instances on Single User Session
VB6: Single-instance application across all user sessions
Making a singleton application across all users
one instance of app per Computer, how?
The first one on the list is promising, but the proposed solution is not clear if it is just for "the same user with the same session id" as asked, or only for "the same user in any session" as is implied in the second answer.
My question is, will the first approach really work to restrict access rights to the mutex to the same user in the same session, or is it just to the same user?
More precisely, does the default security descriptor contain any restriction information for the session id? I suspect that is doesn't, meaning that the same user in a different session would have the same default security descriptor and would still be able to get access rights the mutex.
Am I correct about this?
If so, how would I clone the default security descriptor and add a restriction on being in the same session as the creator?
if you like to be limited to a single user at a time, most logical solution - create some named object in common global namespace (so visible for all) and allow only concrete user Sid access this object. as result all instance which run by the same user can open this object, when another users, will be have another Sid and can fail open it, if it already exist. we not mandatory need mutex. the best use named event - this is most simply and small object. also need use not CreateEvent api but CreateEventExW because it let specify dwDesiredAccess - this is important in case some code will be run as low/untrusted integrity. he fail open existing object with all access (if we not add untrusted label to object), but can open object with generic read/generic execute access. we can say request only SYNCHRONIZE access for event. the poc code:
ULONG BOOL_TO_ERROR(BOOL f)
{
return f ? NOERROR : GetLastError();
}
volatile UCHAR guz = 0;
ULONG CanRun(BOOL& bCan)
{
bCan = false;
HANDLE hToken;
ULONG dwError = BOOL_TO_ERROR(OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken));
if (dwError == NOERROR)
{
ULONG cb = 0, rcb = sizeof(TOKEN_USER) + GetSidLengthRequired(5);
PVOID stack = alloca(guz);
union {
PVOID buf;
PTOKEN_USER User;
};
do
{
if (cb < rcb)
{
cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
}
dwError = BOOL_TO_ERROR(GetTokenInformation(hToken, TokenUser, buf, cb, &rcb));
} while (dwError == ERROR_INSUFFICIENT_BUFFER);
CloseHandle(hToken);
if (dwError == NOERROR)
{
PSID UserSid = User->User.Sid;
SECURITY_DESCRIPTOR sd;
SECURITY_ATTRIBUTES sa = { sizeof(sa), &sd, FALSE };
PACL Dacl = (PACL)alloca(cb = sizeof(ACL) + FIELD_OFFSET(ACCESS_ALLOWED_ACE, SidStart) + GetLengthSid(UserSid));
if (
InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION) &&
InitializeAcl(Dacl, cb, ACL_REVISION) &&
AddAccessAllowedAce(Dacl, ACL_REVISION, GENERIC_ALL, UserSid) &&
SetSecurityDescriptorDacl(&sd, TRUE, Dacl, FALSE) &&
CreateEventExW(&sa, L"Global\\<Your Unique Name>", CREATE_EVENT_MANUAL_RESET, SYNCHRONIZE)
)
{
bCan = true;
}
else
{
switch (dwError = GetLastError())
{
case ERROR_ACCESS_DENIED:
dwError = NOERROR;
break;
}
}
}
}
return dwError;
}
so we query TOKEN_USER from current process token and create (or open) named event "Global\\<Your Unique Name>". if even yet not exist - we create it and assign SD which allow only to the same user sid open it. any other users( even localsystem) fail open this event (without change it dacl first) with ERROR_ACCESS_DENIED.
demo usage:
BOOL bCan;
ULONG dwError = CanRun(bCan);
if (dwError == NOERROR)
{
MessageBoxW(0, 0, bCan ? L"Yes" : L"No", bCan ? MB_ICONINFORMATION : MB_ICONWARNING);
}
else
{
PWSTR sz;
if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM, 0, dwError, 0, (PWSTR)&sz, 0, 0))
{
MessageBoxW(0, sz, 0, MB_ICONHAND);
LocalFree(sz);
}
}

How to check if a TRUSTEE is me

I'm using the GetSecurityInfo funtion to get my own process's Discretional Access Control List (DACL):
PACL oldAcl;
Pointer se;
GetSecurityInfo(GetCurrentProcess, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION,
null, null, ref oldAcl, nil, ref se);
And then i can use the GetExplicitEntriesFromAcl to crack open the ACL to get at it's list of Access Control Entries (ACE) inside:
UInt32 nCount;
EXPLICIT_ACCESS[] list;
GetExplicitEntriesFromAcl(oldAcl, ref nCount, ref list);
I can go through the list of three entries on my process:
STACKOVERFLOW\ian (S-1-5-21-6198258843-697258998-2146844275-1109) [SidTypeUser]
Grant Access
0x00010000 DELETE
0x00020000 READ_CONTROL
0x00040000 WRITE_DAC
0x00080000 WRITE_OWNER
0x00100000 SYNCHRONIZE
...
0x00000008 PROCESS_VM_OPERATION
0x00000010 PROCESS_VM_READ
0x00000020 PROCESS_VM_WRITE
NT AUTHORITY\SYSTEM (S-1-5-18) [SidTypeWellKnownGroup]
Grant Access
0x00010000 DELETE
0x00020000 READ_CONTROL
0x00040000 WRITE_DAC
0x00080000 WRITE_OWNER
0x00100000 SYNCHRONIZE
...
0x00000008 PROCESS_VM_OPERATION
0x00000010 PROCESS_VM_READ
0x00000020 PROCESS_VM_WRITE
NT AUTHORITY\LogonSessionId_0_20117843 (S-1-5-5-0-20117843) [SidType_11]
Grant Access
0x00020000 READ_CONTROL
0x00100000 SYNCHRONIZE
0x00000010 PROCESS_VM_READ
0x00000400 PROCESS_QUERY_INFORMATION
0x00001000 PROCESS_QUERY_LIMITED_INFORMATION
I now want to go through and update the DACL for the process (which of course i'm allowed to do since i have WRITE_DACL - and because i'm the Owner, which means i implicitly have WRITE_DACL).
But i only want to re-write access control entries that apply to "me".
In this case there happen to be three Trustees:
S-1-5-21-6198258843-697258998-2146844275-1109 (user me)
S-1-5-18 (user LocalSystem - not me)
S-1-5-5-0-20117843 (group LoginSession - me)
Trustees are presented to us as a TRUSTEE object (note, not all of which have an SID). I know from experience that i am two of those trustees; but not the third.
Is there a function that i can use to compare "me" against a TRUSTEE?
Boolean DoIMatchThisTrustee(TRUSTEE trustee)
{
}
Why am i doing this?
No reason. I'm removing PROCESS_VM_READ, PROCESS_VM_WRITE, and PROCESS_VM_OPERATION from myself on my own process.
you actually want check Discretional Access Control List (DACL) for Sids which is "me" - so enabled members of your process token.
To determine whether a SID is enabled in a token we can use CheckTokenMembership function.
use GetSecurityInfo not the best choice i think, much better use GetKernelObjectSecurity here. however you can check and trustees if want ("note, not all of which have an SID" - this is in general case, but in case DACL you will got only TRUSTEE_IS_SID trustees). code can be next:
void Test()
{
HANDLE hToken, hImpToken;
if (OpenProcessToken(NtCurrentProcess(), TOKEN_QUERY|TOKEN_DUPLICATE, &hToken))
{
BOOL fOk = DuplicateToken(hToken, ::SecurityIdentification, &hImpToken);
CloseHandle(hToken);
if (fOk)
{
ULONG cb = 0, rcb = 256;
static volatile UCHAR guz = 0;
PVOID stack = alloca(guz);
union {
PVOID buf;
PSECURITY_DESCRIPTOR pSD;
};
do
{
if (cb < rcb)
{
cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
}
if (GetKernelObjectSecurity(NtCurrentProcess(), DACL_SECURITY_INFORMATION, pSD, cb, &rcb))
{
BOOL bPresent, bDefault;
union {
PACL Acl;
PBYTE pb;
PACE_HEADER pah;
PACCESS_ALLOWED_ACE paaa;
};
if (GetSecurityDescriptorDacl(pSD, &bPresent, &Acl, &bDefault) && bPresent && Acl)
{
CheckSidsInAcl(Acl);
if (USHORT AceCount = Acl->AceCount)
{
Acl++;
do
{
if (pah->AceType == ACCESS_ALLOWED_ACE_TYPE)
{
BOOL IsMember;
if (CheckTokenMembership(hImpToken, &paaa->SidStart, &IsMember))
{
PWSTR sz;
if (ConvertSidToStringSid(&paaa->SidStart, &sz))
{
DbgPrint("%x %S\n", IsMember, sz);
LocalFree(sz);
}
}
else
{
GetLastError();
}
}
} while (pb += pah->AceSize, --AceCount);
}
}
break;
}
} while (GetLastError() == ERROR_INSUFFICIENT_BUFFER);
CloseHandle(hImpToken);
}
}
}
I don't think there are any functions to compare nor normalize a TRUSTEE.
The ACEs in a ACL always store a SID (as far as I know) so it is probably safe to assume that the TRUSTEE will be a TRUSTEE_IS_*SID form when you get it from a ACL.
PSID GetSID(const TRUSTEE&t)
{
if (TRUSTEE_IS_SID == t.TrusteeForm) return (PSID) t.ptstrName;
if (TRUSTEE_IS_OBJECTS_AND_SID == t.TrusteeForm) return ((OBJECTS_AND_SID*)t.ptstrName)->pSid;
return NULL;
}
bool DoIMatchThisTrustee(TRUSTEE&t)
{
PSID tsid = GetSID(t);
PSID mysid = GetMySid(); // From process/thread token or somewhere else
return tsid && EqualSid(tsid, mysid);
}
If you don't want to assume then you can use LookupAccountName on the string forms to get a SID.
If for whatever reason you don't want to lookup any strings you can do things NT4 style and work with the ACL directly. Call GetAce to enumerate the ACL and use something like this:
PSID GetAllowedSID(const ACE_HEADER&ah)
{
switch(ah.AceType)
{
case ACCESS_ALLOWED_ACE_TYPE: return (PSID) &((ACCESS_ALLOWED_ACE*)&ah)->SidStart;
case ACCESS_ALLOWED_CALLBACK_ACE_TYPE: return (PSID) &((ACCESS_ALLOWED_CALLBACK_ACE*)&ah)->SidStart;
case ACCESS_ALLOWED_CALLBACK_OBJECT_ACE_TYPE: return (PSID) &((ACCESS_ALLOWED_CALLBACK_OBJECT_ACE*)&ah)->SidStart;
case ACCESS_ALLOWED_OBJECT_ACE_TYPE: return (PSID) &((ACCESS_ALLOWED_OBJECT_ACE*)&ah)->SidStart;
default: return NULL;
}
}
If you are doing this to increase security you might want to do things the other way around and only allow "NT AUTHORITY\SYSTEM" and "BUILTIN\Administrators" these rights.
Whether or not the logon session is "you" is debatable but you cannot compare the full SID to find out, just the SECURITY_NT_AUTHORITY+SECURITY_LOGON_IDS_RID parts.
RbMm's comment reminded me that i already knew the answer somewhere deep in my brain: CheckTokenMembeship:
//Get the ACL on the process
PACL oldAcl;
Pointer se;
GetSecurityInfo(GetCurrentProcess, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION,
null, null, ref oldAcl, nil, ref se); //allocates memory into se, which we must LocalFree later
//oldAcl is a pointer to inside the se blob
//Crack open the ACL, to feast on the entries inside
UInt32 nCount;
EXPLICIT_ACCESS[] list;
GetExplicitEntriesFromAcl(oldAcl, ref nCount, ref list); //allocates memory into list, which we must localFree later
//the flags i want to remove me from having
DWORD removeFlags = PROCESS_VM_READ || PROCESS_VM_WRITE || PROCESS_VM_OPERATION;
//Go through the list, looking for entries that are "me"
for (EXPLICIT_ACCESS ea in list)
{
if (ea.Trustee.TrusteeForm != TRUSTEE_IS_SID)
continue;
BOOL isMember;
CheckTokenMembership(0, PSID(ea.Trustee.ptstrName), out isMember);
if (!isMember)
continue;
//Remove the permissions
ea.grfAccessPermissions = ea.grfAccessPermissions && (!removeFlags);
aclUpdateNeeded = true;
}
//write the new DACL
SetEntriesInAcl(nCount, list, null, out newAcl); //allocates a new acl
SetSecurityInfo(GetCurrentProcess, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION,
null, null, newAcl, null); //apply the new acl
LocalFree(list); //free the memory allocated by GetExplicitEntriesFromAcl
LocalFree(se); //free the memory allocated by GetSecurityInfo

How can I get the current user token for the physical session?

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, &currentToken);
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, &currentToken);
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?

Resources