Launching process in non-elevated mode from an admin account using CreateProcessWithTokenW() - c

I followed Frank K.'s proposed solution for launching a normal user process from an elevated user process.
I have however some difficulties on getting the proposed solution working (Win 7 x64 Professional; the "normal user" process is launched from a domain account having administrative rights). The process creation code looks like this:
HANDLE processHandle = getProcessHandle("explorer.exe");
if (OpenProcessToken(processHandle, MAXIMUM_ALLOWED, &hToken))
{
if (DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL,
SecurityImpersonation, TokenPrimary, &hNewToken))
{
LPWSTR pointer = const_cast<LPWSTR>(commandLine.c_str());
bRet = CreateProcessWithTokenW(hNewToken,
0, // logon flags
0, // application name
pointer, // command-line
0, // creation flags
NULL, // environment - inherit from parent
NULL, // current directory
&StartupInfo,
&ProcInfo);
...
}
}
Now the process gets created after the CreateProcessWithTokenW, but my method for checking if the process has administrative rights (see below) says the process has admin rights (as well as ProcessExplorer, which lists in the process properties Security tab: Group: BUILTIN\Administrators --> Flags: Owner).
BOOL hasAdministratorRights()
{
SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
PSID AdministratorsGroup;
BOOL b = AllocateAndInitializeSid(
&NtAuthority,
2,
SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_ADMINS,
0, 0, 0, 0, 0, 0,
&AdministratorsGroup);
if (b)
{
if (!CheckTokenMembership(NULL, AdministratorsGroup, &b))
{
b = FALSE;
}
FreeSid(AdministratorsGroup);
}
return b;
}
Note: if I am calling hasAdministratorRights() above in a process/app started through runAs Windows command (and a given existing local "user" account), it will return false (so it confirms that the process has user rights only, which is what I was expecting). But it is returning true when called in the process created with CreateProcessWithTokenW() above.
Any ideas what I might be doing wrong and why my user process will not get created correctly using CreateProcessWithTokenW?
In Frank K.'s proposed solution, are there differences in behavior of CreateProcessWithTokenW() (and the other APIs) when calling them from a local admin account or from a domain account with admin privileges?
Best regards,
Marius

The problem was that UAC was disabled on the machine in question, so no split token was created and the Explorer process had full administrator privilege.
In principle, you could work around this using CreateRestrictedToken(), but if UAC is disabled you should probably assume that this was deliberate, which would usually make the default behaviour, i.e., giving the new process admin privilege, the most sensible choice.
If you need to confirm that the reason a particular token has administrative privilege is because UAC is disabled (including the case where the user is the local Administrator account) you can use GetTokenInformation() with the TokenLinkedToken option.

Related

Revert to normal user in Windows program with requireAdministrator in manifest?

I'm trying to encapsulate a number of repetitive installation tasks into a private setup program. The program is for in-house use setting up custom, single purpose systems for industrial users. I need Administrator privileges to tweak a number of Windows settings for our environment and then I need to set some current user settings for the application software packages to use.
Is it possible for a Windows program (in plain C, created with Visual Studio 2017) that uses requireAdministrator in its manifest to revert to the user that started the program when the admin privilege is no longer needed? I've seen this done in linux, but have been unable to find any examples (or even mentions) of this being done in Windows. Help? Please?
You don't.
A way given awhile back is to track down the running instance of explorer, and spawn a remote thread in it (with CreateRemoteThread) that does what you want. However CreateRemoteTherad is touchy and what if explorer isn't running?
If you know the username you can use CreateService() to create a service that runs as that user and ServiceStart() to start it but that's its own pain and now the code has to deal with no access to the desktop. Getting code running as the user on the desktop involves digging into the undocumented.
The correct design is to use two executables, the first with asInvoker that starts the second with requireAdministrator using the API call ShellExecuteEx, waits for it to finish, checks the exit code, and on success does the individual user steps.
There is no way to de-elevate a running process. You might be able to lower your rights a little bit but not all the way. Even if it was possible, you would have to hardcode the list of groups and privileges to remove/disable in your token because I don't believe there is a API to restrict a token just like UAC does.
There are many half-assed solutions out there to start a un-elevated child process:
Using the Task Scheduler
IShellDispatch2::ShellExecute in the "main" Explorer.exe instance
CreateProcessAsUser with the token from the "main" Explorer.exe instance
Bootstrapper instance elevates another instance and communicates back to parent when it needs to perform un-elevated actions
Take advantage of Explorer bug and simply execute "%windir\Explorer.exe" "c:\path\to\myapp.exe"
All of those solutions have issues related to:
Explorer might not be running (custom shell or Explorer crash)
Explorer is running elevated
Non-admin users elevate with a different administrator account with a different SID
RunAs.exe has been used and your parent process is not the same as the "main" logon session nor Explorer.exe
Final Answer
When I was finally able to install a clean Windows 10 on a spare system for testing and create a pure non-admin account to test with, the new, improved answer did not work. I added a GetUserName() call after the impersonate and wrote it to the debug log to learn that the user name was the same before and after the impersonate, even though all of the functions returned success. I can only assume that the GetShellWindow() [I also tried GetDesktopWindow(), just in case] returned a handle to an explorer/shell under the Admin context. So, now I'm using a cleaned up (to make it plain C) version of the GetConsoleUserToken() function I found here: https://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/17db92be-0ebd-4b54-9e88-a122c7bc351d/strange-problem-with-wtsqueryusertoken-and-impersonateloggedonuser?forum=windowsgeneraldevelopmentissues This is working on both my development system AND on the clean Windows 10 non-admin user. The function actually searches all running processes for the explorer.exe that belongs (is attached?) to the console session and while debugging it, I did see that it find more than one explorer.exe process, but only ONE is the right one.
Thanks for all of the suggestions and comments! They gave me good ideas and put me on a path that allowed me to use better search terms to find what I needed.
To Microsoft: This seems quite unnecessarily complex to do something that should not be difficult for an administrator-level process to do.
New, Improved Answer:
Following the suggestion made by eryksun, I created the following example function that shows all of the steps my program needed to get the current user key opened. My program has requireAdministrator in the manifest, if yours does not, you might need to make changes or additions to my sample. Here is what is working perfectly for me (but apparently not on a clean machine under a non-admin account):
BOOL ChangeHkcuSettings( void )
{
BOOL bResult = FALSE; // HKCU was not accessed
HWND hwndShell;
DWORD dwThreadId;
DWORD dwProcessId;
HKEY hKeyUserHive;
HANDLE hToken;
HANDLE hProcess;
hwndShell = GetShellWindow();
dwThreadId = GetWindowThreadProcessId( hwndShell, &dwProcessId );
hProcess = OpenProcess( PROCESS_QUERY_INFORMATION, FALSE, dwProcessId );
if( NULL != hProcess )
{
if( OpenProcessToken( hProcess,
TOKEN_QUERY
| TOKEN_DUPLICATE
| TOKEN_IMPERSONATE,
&hToken ) )
{
if( ImpersonateLoggedOnUser( hToken ) )
{
if( ERROR_SUCCESS == RegOpenCurrentUser( KEY_ALL_ACCESS, &hKeyUserHive ) )
{
// ... use the user hive key to access necessary HKCU items ...
RegCloseKey( hKeyUserHive );
bResult = TRUE; // HKCU was accessed
}
RevertToSelf();
}
CloseHandle( hToken );
}
CloseHandle( hProcess );
}
return bResult;
}
Thanks again to eryksun for the suggestion!
ORIGINAL ANSWER: I think I found another way that will work for me... Instead of using HKEY_CURRENT_USER (which will be the Administrator) the admin account can open the specific user's registry key(s) under HKEY_USERS instead. I will need to find the appropriate user's SID, but my setup knows the user's name (and password, for setting auto logon), so I think this is do-able. For me, this is much easier, since all of the code already exists in a single program that formerly write EVERYTHING to HKEY_LOCAL_MACHINE, which was easy and worked great. Trying to be "correct" is much more work, perhaps more than it should be! :-(

How to get the user name of the current windows desktop? [duplicate]

I have a simple C++ program that prompt the user name
#include <windows.h>
#include <Lmcons.h>
#include <winbase.h>
int _tmain(int argc, _TCHAR* argv[])
{
wchar_t username[UNLEN + 1];
DWORD username_len = UNLEN + 1;
::GetUserName(username, &username_len);
MessageBox(NULL, username, NULL, 1);
return 1;
}
GetUserName() performs as expected in administrator accounts, meaning print the real user name.
However, when run as administrator in a non-administrator account, I get the administrator name, and not the the real logged user.
I believe this behavior is expected since it is documented in GetUserName():
If the current thread is impersonating another client, the GetUserName function returns the user name of the client that the thread is impersonating.
Question
Is there a way to get the real logged in user (the non-admin one), even if the process run as administrator?
I believe the question you want to ask Windows is "which user is logged into the current session".
To do this, call ProcessIdToSessionId() with your own process's ID to determine the current session ID.
Then call WTSQuerySessionInformation() with the WTSUserName option to fetch the user name.
The problem is not a thread which is impersonating. You're running the entire application under the administrator login. That's why Windows asked you to login with an administrator account, when you started it from a non-admin account.
Thus, the result you get from GetUserName() is correct. That name is the real logged-in user of your app.
If you wanted the other name, the standard solution is to start as a normal user, and have an "elevate" button which restarts your application with elevated privileges. Look as Task Manager, it does this if you want to see all running processes. At this point you can of course pass anything you want to the new process, including that user name.

MS CryptoAPI - Machine Keystore with Error 0x80090016 (NTE_BAD_KEYSET) with certreq created keys

Summary
I create a PKCS#10 CSR with certreq and have set the option Exportable=TRUE. This successfully creates a key under the location REQUEST. I also have a valid certificate with key in MY. If I try to access any one of them the CryptoAPI reports error code 0x80090016.
Running under different access rights could not solve this problem so far.
Goal
My goal is to get both the keys in MY and REQUEST. If I call CryptAcquireContextA() on any of those, it fails.
System
Windows 7 x64
Sample Source Code
My complete code looks like this:
hStore = CertOpenStore(CERT_STORE_PROV_SYSTEM_A, 0, 0, CERT_SYSTEM_STORE_LOCAL_MACHINE, "REQUEST");
pCert = CertFindCertificateInStore(hStore, X509_ASN_ENCODING, 0, CERT_FIND_SUBJECT_STR_A, "CERTIFICATE_SUBJECT", NULL);
CertGetCertificateContextProperty(pCert, CERT_KEY_PROV_INFO_PROP_ID, NULL, &len);
pinfo = (CRYPT_KEY_PROV_INFO *) malloc(len);
CertGetCertificateContextProperty(pCert, CERT_KEY_PROV_INFO_PROP_ID, pinfo, &len);
provname = wide_to_asc(pinfo->pwszProvName);
contname = wide_to_asc(pinfo->pwszContainerName);
if(!CryptAcquireContextA(&hCryptProv, contname, provname, pinfo->dwProvType, 0)) {
err = GetLastError();
fprintf(stderr, "Error: 0x%x\n", err);
}
CryptGetUserKey(hCryptProv, pinfo->dwKeySpec, &hUserkey);
This code is mostly copied from the OpenSSL capi engine. Since the engine failed, I created the smallest possible code to search the error.
The error
If I run this, it fails with the output Error: 0x80090016. This means one of three things according to Microsoft:
Key container does not exist.
You do not have access to the key container.
The Protected Storage Service is not running.
What have I done so far?
Started service "Protected Storage"
Verified container exists with MMC & Certificate Snap-In for Local Computer
Ran the same code on the User store in user context - it worked
File System Permissions
After some googling, I tried to change permissions on the file system. I found the files by looking at the contname variable of my code and searching for the file. I changed permissions on them (more accurate, I changed permissions on the parent folder). While this fixed the issue for MY, it seems I cannot change it for REQUEST.
One note here is that my container for MY seems to be here:
%APPDATA%\Microsoft\Crypto\RSA\S-1-5-21-1650336054-1974872081-316617838-545102
For REQUEST I found it under a different address:
%ALLUSERSPROFILE%\Microsoft\Crypto\RSA\MachineKeys
I am not sure on the workings here so I cannot explain why it would put them in different locations (one being user centric, the other one a system folder). The MY store was created with a regular administrator prompt and the command certreq -new inf_file.inf cert-csr.csr and after I received my certificate, I issued certreq -accept cert.pem. Then I created a new csr with the same command.
Different privilege levels
I tried to execute my program with the following privileges:
my local user account
admin prompt (cmd->start as administrator)
nt authority\system (whoami output)
To recieve a service prompt, I executed psexec.exe –ids cmd.exe according to a tip from MaaSters Center
Final words
Any help or guidance on how to further narrow this problem down will be greatly appreciated.
I was finally able to solve this problem and it is a lot simpler than I thought. I was sure that I would receive an unambiguous container name and don't need to be more specific but CryptAcquireContext actually requires me to pass a flag CRYPT_MACHINE_KEYSET.
So my function call has to look like this:
CryptAcquireContextA(&hCryptProv, contname, provname, pinfo->dwProvType, CRYPT_MACHINE_KEYSET)
Unfortunately this is not supported by the OpenSSL engine, so you would have to alter it yourself in the engine.
See MSDN: "A key container created without this flag by a user that is not an administrator can be accessed only by the user creating the key container and the local system account."
Complete details here: https://msdn.microsoft.com/en-us/library/windows/desktop/aa379886(v=vs.85).aspx

Can't open semaphore from another process

I'm creating a global semaphore object in a process like this:
CreateSemaphore(NULL, 1, 1, "Global\\bitmap");
now, when I'm trying to open it in a child process (it's a special case of "another process", it's not going to be a child that opens the semaphore created) like this:
bitmapSem = OpenSemaphore(NULL, TRUE, "Global\\bitmap");
the bitmapSem variable equals NULL and I'm getting error 5 (ERROR_ACCESS_DENIED) from GetLastError().
Any ideas?
I must add a clarification to other answers, and a security warning.
First, passing NULL as the lpSemaphoreAttributes argument to ::CreateSemaphore() does not mean no access to anybody; rather, it means that default access control will be assigned. MSDN is crystal clear on that: If this parameter is NULL, the semaphore gets a default security descriptor. The ACLs in the default security descriptor for a semaphore come from the primary or impersonation token of the creator.
Normally, the semaphore can be opened and used by the same user identity. So, if the semaphore is shared by processes running in the same interactive session, or under the same service identity, it may be opened by another process even if created with the default security descriptor. As #hmjd already noted, you must always explicitly call out the right that you want to assert on the semaphore: SYNCHRONIZE|SEMAPHORE_MODIFY_STATE allows both waiting on and releasing it.
Second of all, a word of caution. By granting Everyone full access to the semaphore, as it was suggested above, a security hole for a DoS attack is potentially created. You should consider whether you want arbitrary processes to be able to grab and release the semaphore. Is it intended for unrestricted public use? It is always a good practice to assign minimal, narrowly permitting ACLs to objects. Using SDDL is probably the easiest way to encode a security descriptor, albeit the script itself is not very readable.
The first argument to OpenSemaphore() is documented as:
dwDesiredAccess [in]
The access to the semaphore object. The function fails if the security descriptor of the specified object does not permit the requested access for the calling process. For a list of access rights, see Synchronization Object Security and Access Rights.
In the posted code NULL is specified: which is not documented as having a special meaning. Change to one of the access rights documented at Synchronization Object Security and Access Rights:
bitmapSem = OpenSemaphore(SYNCHRONIZE, TRUE, "Global\\bitmap");
EDIT:
To create a security descriptor that would grant access to Everyone try the following (untested) code:
/* Create a security descriptor that has an an empty DACL, to
grant access to 'Everyone'. */
SECURITY_DESCRIPTOR sd;
if (0 == InitializeSecurityDescriptor(&sd,
SECURITY_DESCRIPTOR_REVISION) ||
0 == SetSecurityDescriptorDacl(&sd,
TRUE,
(PACL)0,
FALSE))
{
/* Failed to create security descriptor. */
}
else
{
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = &sd;
sa.bInheritHandle = FALSE;
HANDLE sh = CreateSemaphore(&sa, 1, 1, "Global\\bitmap");
}
lpSemaphoreAttributes [in, optional]
A pointer to a
SECURITY_ATTRIBUTES structure. If this parameter is NULL, the handle
cannot be inherited by child processes.
Pass an LPSECURITY_ATTRIBUTES with an empty DACL and the bInheritHandle member set appropriately as the 1st argument.
An example in VB would be:
'Setup the security descriptor
InitializeSecurityDescriptor SecurityDescriptor, SECURITY_DESCRIPTOR_REVISION
SetSecurityDescriptorDacl SecurityDescriptor, 1, ByVal 0, 0 'Dacl is present and empty
'Setup the security attributes
SecurityAttributes.nLength = Len(SecurityAttributes)
SecurityAttributes.lpSecurityDescriptor = VarPtr(SecurityDescriptor)
SecurityAttributes.bInheritHandle = False

Get the logged in Windows user name associated with a desktop

I wish to enumerate all desktops in a system and get the logged in user name for that desktop. So far I have the following code snippit as an example of obtaining a HDESK handle and trying to determine the user name associated with it (if any), but the call to LookupAccountSid fails with ERROR_NONE_MAPPED ("No mapping between account names and security IDs was done").
HDESK desk = OpenDesktop( "Default", 0, FALSE, READ_CONTROL | DESKTOP_READOBJECTS );
DWORD size = 4096;
SID * sid = (SID *)malloc( size );
GetUserObjectInformation( desk , UOI_USER_SID, sid, size, &size );
char name[512], domain[512];
int namesz = 512, domainsz = 512;
LookupAccountSid( NULL, sid, &name, &namesz, &domain, &domainsz, &s);
It might be because I am pulling out a logon SID via GetUserObjectInformation rather then a user SID. If so can I convert that to the logged in users SID?
Can anybody point me in the right direction for getting the logged in user name for an arbitrary desktop (via either it's respective HDESK or HNWD handle or even the desktop's stations HWINSTA handle)? thanks in advance.
If what you want is the user information then this will work.
call WTSEnumerateSessions to obtain an array of WTS_SESSION_INFO structures. for each structure, pass the SessionId member to WTSQuerySessionInformation with the WTSInfoClass member set to WTSUserName. This will give you the name of the user (if there is one) associated with the session.
Alternatively you can set the WTSInfoClass to WTSSessionInfo and get a WTSINFO structure back. This contains a lot of information including the user name and domain. Look at the header file definition of WTSINFO though as the MSDN page is wrong.
You have to call WTSEnumerateSessions twice, once to get the required buffer size and then once to get your information.
Relationships: One or more Desktop objects are in a Windows Station. A Windows Station is associated with a Session.
The problem is that desktops aren't associated with users at all. Try using psexec to run Notepad under the SYSTEM account. It's running on your window station, on your desktop. Otherwise, you wouldn't be able to see it.
But if you want to get the session associated with the window station, then yes it's possible. You need to call NtQueryObject with ObjectNameInformation to get the name of the object. For example, here's what I get: \Sessions\1\Windows\WindowStations\WinSta0. There's your session ID.
This is not a solution but is a good description of station/desktop. From http://www.microsoft.com/technet/security/bulletin/fq00-020.mspx
What's a windows station?
A windows station is a secure container that contains a clipboard, some global information, and a set of one or more desktops. A Windows 2000 session will have several windows stations, one assigned to the logon session of the interactive user, and others assigned to the Winlogon process, the secure screen saver process, and any service that runs in a security context other than that of the interactive user.
The interactive window station assigned to the logon session of the interactive user also contains the keyboard, mouse, and display device. The interactive window station is visible to the user and can receive input from the user. All other window stations are noninteractive, which means that they cannot be made visible to the user, and cannot receive user input.
What's a desktop?
A desktop is a secure container object that is contained within a window station. There may be many desktops contained within a windows station.
A desktop has a logical display surface and contains windows, menus, and hooks. Only the desktops of the interactive window station can be visible and receive user input. On the interactive window station, only one desktop at a time is active. This active desktop, also known as the input desktop, is the one that is currently visible to the user and that receives user input.
You could extract it from the end of the %USERPROFILE% environment variable
nbtstat used to be able to do this from the command line, with either a machine name or IP address. It's been a long time since I looked at it, though.
Correct code that worked for me:
TCHAR username[UNLEN + 1];
DWORD size = UNLEN + 1;
GetUserName((TCHAR*)username, &size);
I'm using Visual Studio Express 2012 on Windows 7 x86

Resources