Create a mutex that is only visible within the same session - windows

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);
}
}

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);
}
}
}
}

Restarting explorer.exe process for all logged users on Windows to enforce SRP rules

I need to intercept certain signal in a windows service, and then find all explorer.exe processes and restart them.
So far it works fine if only one user is logged into the system and the service is in "foreground" mode, e.g - started as process for the current user. Explorer is killed and restarted just fine. However if there are multiple users - the explorer is killed but started only for the current user.
Moreover, as a service - there is a different user that owns the service itself, and while killing explorer works fine - it can't be started most of the time, and if starts - it is started for the service user only.
With some digging in Win32 API it seems possible to use something like CreateProcessWithLogonW() - but that requires knowing user's credentials.
Is there any way to restart / send some signal to explorer so it will know that it needs to restart / re-read SRP rules?
UPD: just for the reference if someone is looking for the hints:
https://gist.github.com/LiamHaworth/1ac37f7fb6018293fc43f86993db24fc
and copy if the original vanishes: https://gist.github.com/jdevelop/22c59371a0747918d9b43d65add1fb75
if you run from service you in most case (if service not special restricted) will have SE_TCB_NAME privilege. as result you can call WTSQueryUserToken for obtain the primary access token of the logged-on user specified by the session ID. the explorer run exactly with this token. and you can use CreateProcessAsUserW for start explorer again with this token (again i assume your service not restricted and have SE_ASSIGNPRIMARYTOKEN_NAME privilege.). so you can enumerate all user terminal sessions with WTSEnumerateSessionsW function, got token for every session, found explorer in session and restart it.
but here question how found explorer process(es). of course we can do this by short file name. enumerate all processes. but i think more reliable way will be call GetShellWindow and then GetWindowThreadProcessId. but we can not do this direct from service. this need do exactly from user session(s). so possible solution - first launch self exe in every terminal session, with special command line as marker, and then already from session call GetShellWindow and restart explorer with simply CreateProcessW call
WCHAR ExeName[MAX_PATH];
PROCESS_INFORMATION pi;
STARTUPINFO si = { sizeof(si) };
static const WCHAR tagCmdLine[] = L"|";
if (wcscmp(GetCommandLineW(), tagCmdLine))
{
GetModuleFileNameW(0, ExeName, _countof(ExeName));
if (GetLastError() == NOERROR)
{
ULONG Count;
PWTS_SESSION_INFO pSessionInfo;
if (WTSEnumerateSessionsW(WTS_CURRENT_SERVER_HANDLE, 0, 1, &pSessionInfo, &Count))
{
if (Count)
{
pSessionInfo += Count;
do
{
HANDLE hToken;
if (WTSQueryUserToken((--pSessionInfo)->SessionId, &hToken))
{
PVOID lpEnvironment;
if (CreateEnvironmentBlock(&lpEnvironment, hToken, FALSE))
{
if (CreateProcessAsUserW(hToken, ExeName, const_cast<PWSTR>(tagCmdLine),
0, 0, 0, CREATE_UNICODE_ENVIRONMENT, lpEnvironment, 0, &si, &pi))
{
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
DestroyEnvironmentBlock(lpEnvironment);
}
CloseHandle(hToken);
}
} while (--Count);
}
WTSFreeMemory(pSessionInfo);
}
}
}
else
{
if (HWND hwnd = GetShellWindow())
{
ULONG dwProcessId, dwThreadId;
if (dwThreadId = GetWindowThreadProcessId(hwnd, &dwProcessId))
{
if (HANDLE hProcess = OpenProcess(PROCESS_TERMINATE|SYNCHRONIZE|
PROCESS_QUERY_LIMITED_INFORMATION, FALSE, dwProcessId))
{
ULONG cch = _countof(ExeName);
if (QueryFullProcessImageNameW(hProcess, 0, ExeName, &cch))
{
PostThreadMessageW(dwThreadId, WM_QUIT, 0, 0);
if (WaitForSingleObject(hProcess, 1000) == WAIT_OBJECT_0 ||
TerminateProcess(hProcess, 0) ||
RtlGetLastNtStatus() == STATUS_PROCESS_IS_TERMINATING)
{
if (CreateProcessW(ExeName, 0, 0, 0, 0, 0, 0, 0, &si, &pi))
{
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
}
}
CloseHandle(hProcess);
}
}
}
ExitProcess(0);

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

Killing "System Error" dialog box under csrss.exe

I'm running an automated test of an application that has insufficient dependencies (missing dll), and got this error message:
When the test runner kills the application, the above message box isn't disappearing. Apparently it's owned by csrss.exe, which can't be killed.
My problem is that the only way I can get this message box to close is by manually logging in to to the machine and clicking the X on that message window. Is there another way?
Note that I can easily fix the dependency error, but I would like a robust way of terminating the application under test with all its messages when such errors are encountered.
Try avoiding the dialog in the first place: Call SetErrorMode(SEM_FAILCRITICALERRORS) in your test runner, this will cause calls to CreateProcess to fail silently if the loader cannot resolve every import.
If you want to create a query function so you can tell if the process can load, add the CREATE_SUSPENDED flag when calling CreateProcess and call TerminateProcess if it succeeded.
you can do next:
1) get csrss.exe process id in your session
2) enumerate windows via EnumWindows, for every window query it process id (GetWindowThreadProcessId), compare it with csrss.exe process id. if equal - get window class name (GetClassName ) and if it is L"#32770" - post WM_CLOSE message to this window
3) if you want be more precise - after you found L"#32770" window class, query the window caption text via GetWindowText and ensure that it begin with "module.exe - " or how your exe is named ?
struct FIND_WND_CONTEXT
{
PCWSTR szCaptionBegin;
ULONG lenCaptionBegin;
ULONG dwProcessId;
};
BOOL CALLBACK EnumWndProc(HWND hwnd, FIND_WND_CONTEXT& fwc)
{
ULONG dwProcessId;
if (GetWindowThreadProcessId(hwnd, &dwProcessId) && dwProcessId == fwc.dwProcessId)
{
PWSTR Name = (PWSTR)alloca( max(fwc.lenCaptionBegin * sizeof(WCHAR), sizeof(L"#32770") + sizeof(WCHAR)) );
if (GetClassNameW(hwnd, Name, sizeof(L"#32770") + sizeof(WCHAR)) && !wcscmp(Name, L"#32770"))
{
if (GetWindowText(hwnd, Name, fwc.lenCaptionBegin))
{
_wcslwr(Name);
if (!wcscmp(Name, fwc.szCaptionBegin))
{
PostMessage(hwnd, WM_CLOSE, 0, 0);
}
}
}
}
return TRUE;
}
void CloseTest()
{
const WCHAR module_exe[] = L"module.exe - ";
FIND_WND_CONTEXT fwc = { module_exe, RTL_NUMBER_OF(module_exe) };
if (fwc.dwProcessId = GetMySessionCsrssId())
{
EnumWindows((WNDENUMPROC)EnumWndProc, (LPARAM)&fwc);
}
}
ULONG GetMySessionCsrssId()
{
ULONG cb = 0, rcb = 0x10000;
static volatile UCHAR guz;
PVOID stack = alloca(guz);
union {
PVOID buf;
PBYTE pb;
PSYSTEM_PROCESS_INFORMATION pspi;
};
ULONG SessionId;
ProcessIdToSessionId(GetCurrentProcessId(), &SessionId);
NTSTATUS status;
do
{
if (cb < rcb)
{
cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
}
if (0 <= (status = ZwQuerySystemInformation(SystemProcessInformation, buf, cb, &rcb)))
{
ULONG NextEntryOffset = 0;
do
{
pb += NextEntryOffset;
STATIC_UNICODE_STRING(csrss, "csrss.exe");
if (pspi->SessionId == SessionId && RtlEqualUnicodeString(&pspi->ImageName, &csrss, TRUE))
{
return PtrToUlong(pspi->UniqueProcessId);
}
} while (NextEntryOffset = pspi->NextEntryOffset);
return 0;
}
} while (status == STATUS_INFO_LENGTH_MISMATCH);
return 0;
}
somebody of course ask - why use "undocumented", ("no longer available" (this is direct lie in msdn - available from win200 to latest win10 1703), "unsupported", etc) ZwQuerySystemInformation with SystemProcessInformation instead CreateToolhelp32Snapshot + Process32First + Process32Next ? because we need got SessionId information for process. the SYSTEM_PROCESS_INFORMATION containing ULONG SessionId member, while psapi shell over this function by unknown reason drop this member - PROCESSENTRY32 - no here SessionId - it dropped. of course we can retrieve SessionId by addition call to ProcessIdToSessionId, but this function internally open process by Id, for query it SessionId. but for open csrss.exe you need SeDebugPriviledge enabled in your toke (+ 3 extra call to kernel - open process, query information, close handle)

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

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.

Resources