Determine User Name of Account from Which Elevated Process Launched [duplicate] - winapi

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.

Related

Get AzureAD email address from username using Windows API?

I am using
WTSEnumerateSessionsEx(WTS_CURRENT_SERVER_HANDLE, &level, 0, &pSessionInfo, &count);
to get session information. It will tell me all logged in users. If I use it on Windows 11 with another user logged in,
WTS User: DESKTOP-VVVVVVV\joew, session id: 1, state: 0, session name: Console
WTS User: AzureAD\JoeWillcoxso_garbage, session id: 17, state: 4
From the state, joew is the active user and JoeWillcoxso_garbage. It's not exactly that, but I'm protecting it...you get the idea.
The session info will give me the domain and username. When I login to Windows for an AzureAD with a non-standard credential provider, I use my AzureAD email address and the CP is smart enough to use AzureAD as the domain name. But, when the machine was joined to AzureAD, it created a local account for the email address as JoeWillcoxso_garbage. ( I write "created a local account", but in PowerShell if I do get-localuser *, that user does not show up in the list.)
I can use LsaEnumerateLogonSessions() and LsaGetLogonSessionData() to get session data from the local security authority. I can match things between the two calls (LsaGetLogonSessionData and WTSEnumerateSessionsEx) using session id and username. However, there is no way to get an email address. I can get a PSID in the session data, not sure if there is a way to turn that into an email address. On my local machine, I'm not attached to an AD. Just running as WORKGROUP.
For an AzureAD user, calling NetUserGetInfo() always fails for the AAD user even when running elevated. I haven't yet tried as NT_AUTHORITY account...
I'm looking for an API or maybe some way to query WMI to turn the AzureAD\JoeWillcoxso_garbage user name into joe#someaad.onmicrosoft.com email address. Or, maybe need a web API to hit login.windows.net. There are places in the registry where I can find the email address, but I don't think they are guaranteed to always be there.
Since a user may be logged in already (and on Windows 10/11 with fast switching), sometimes an unlock is not CPUS_UNLOCK_WORKSTATION but CPUS_LOGON. With fast switching, if I lock, come back 10 minutes later, and do an unlock, it's not an unlock but a logon (CPUS_LOGON) although it acts just like an unlock used to do.
For instance, at HKEY_USERS\THE_SID_OF_THE USER\Software\Microsoft\Office\Teams, HomeUserUpn has the email address. I can get the SID of the logged on users...so I could possible get the email to match up... but, that's assuming that key is there. I want something more bullet proof.
• You can surely get the email address from the UPN for the signed in user through the below powershell commands very easily as you must have remote script execution rights over the systems in your network.
a) Whoami /upn --> Displays the full email address of the logged in user
b) "$env:USERNAME#$env:USERDNSDOMAIN" --> Displays the full email address along with the DNS zone in which its entry is mapped to
c) $msAccountName = ([adsisearcher]"(samaccountname=$env:USERNAME)").FindOne().Properties.mail
$msAccountName --> Displays the email address from the online O365/Azure AD account
Thus, accordingly as per the above powershell commands, you can get the email address of the logged in session user for Azure AD. Also, if you want to use the WTS query function for this purpose, kindly refer to the below link for more details as it aptly describes the usage of WTS query commands for the said purpose: -
https://www.idrix.fr/Root/Samples/upn.cpp
I wanted something to use from Win32, hence the tag. What I did find is that from a process running as NT_System account, I could iterate all the LSA sessions.
I could use LSAEnumerateLogonSessions() to get a list of all the sessions.
Once I had the sessions, for each session I could call LsaGetLogonSessionData() which would return a PSECURITY_LOGON_SESSION_DATA which importantly returns session ID, logon domain, logon name, and SID of the user. Looking at the logon domain, I could know if it was AzureAD because it would literally be AzureAD. Knowing it was AzureAD, I could then use the SID and do a registry lookup at key (not entire function):
CString s;
s.Format(L"SOFTWARE\\Microsoft\\IdentityStore\\Cache\\%s\\IdentityCache\\%s", lpwzSid, lpwzSid);
ATL::CRegKey regKey;
if (ERROR_SUCCESS == regKey.Open(HKEY_LOCAL_MACHINE, s, KEY_READ))
{
WCHAR szUserName[MAX_PATH] = { 0 };
ULONG ulChars = MAX_PATH;
if (ERROR_SUCCESS == regKey.QueryStringValue(L"UserName", szUserName, &ulChars) && ulChars > 0)
{
return CString(szUserName);
}
}
That almost works except for one little nit... sometimes the AzureAD user has logged out but still has an open LSA session. I have no idea how this happens, but I have observed it on Windows 11. IDK if this is for all AzureAD users, but I have noticed it can be the case for the account used to join the AzureAD domain. So, in that case, it is necessary to backcheck and use WTSEnumerateSessionsEx() and verify that there really is an open session.

Last Logon timestamp gets changed for admin users whenever run as administartor action triggered

I logged in to my Windows 7 Enterprise machine by tests user which is standard user and runs command to check last logon info. Pleaser refer below output.
>net user tests | findstr "logon"
Last logon 9/16/2021 12:18:17 PM
Then I run same command for testa user which is administrator user for same machine.
>net user testa | findstr "logon"
Last logon 9/16/2021 11:36:17 PM
Till now net user was showing expected information. Later I started services.msc with run as
administrator by providing testa user's credentials, and again executed same command. But this time last logon timestamp was changed as mentioned below.
>net user testa | findstr "logon"
Last logon 9/16/2021 12:19:17 PM
So, Please anyone can help me with explanation what's happening here because mostly I worked in linux platform quite new to windows.
Does windows starts session for admin users whenever run as administrator action is triggered ?
Does there is any better way to find out last logon time for all users in windows ?
There are in a way two types of logons.
The "full" type of logon where a user clicks their name to log in and a new TS session+window station+desktop is created and their shell (usually Explorer.exe) is started.
The other type of logon where the LogonUser function (or other related functions that require username and password) is called. This function creates a token that represents the user and their groups and privileges. The "full" logon also calls this function.
The Net API (and net.exe) is simply telling you about the last LogonUser authentication event.
I'm sure there are ways to figure out the "other" type of logon event as well but nothing pops into my head right now.
Programatically you could create a NT service and get notified about session changes (look at the WTS* API).

Get restricted user folder when an app requires admin rights and started under admin

There is a Windows app that requires admin rights and this is declared in its manifest. When a restricted user starts it she has to input admin credentials. It's OK and the application works well, but it can't obtain original user folders anymore (ShellAPI returns admin's ones).
Since the application is started under admin initially, there is no point where I can store original user's folder paths to use them later.
Is there a way to get initial restricted user credentials?
Regards,
Because of your manifest, your app is running as an admin user, not the currently logged in restricted user. As David Heffernan mentioned, you should redesign your app to not require the entire app to be run elevated. Delegate your admin tasks to a separate process that runs elevated when needed.
That being said, if you must run your entire app elevated, all is not lost, but you are going to have to do some extra work.
Start by getting the Session ID that your elevated app is running in. You can do that using WTSQuerySessionInformation() with WTS_CURRENT_SESSION, or ProcessIdToSessionId() with GetCurrentProcessId(), or open the current process's token with OpenProcessToken() and then use GetTokenInformation().
Once you have the Session ID, use EnumProcesses(), GetProcessImageFileName() (or equivalent), OpenProcessToken(), and GetTokenInformation() to find the instance of explorer.exe (or whatever the PC's registered shell app is, which you can find in the Registry) that is running in the same Session ID as your app.
When found, you have the user token for that process from OpenProcessToken(). Duplicate it using DuplicateTokenEx() to get its primary token, and then you can use that token with APIs like LoadUserProfile(), SHGetFolderPath() and SHGetKnownFolderPath() as needed.

Get session account user name in Windows command/batch running as administrator [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.

Get UserToken from Logon ID (LUID) (C++)

I'm trying to understand better how Windows sessions work, so if I have some weird mistakes in the question, please, let me know.
I use LsaEnumerateLogonSessions() to get all the logged on sessions in the system.
Now I have LUID that represents a log-on, and if I understand correctly, it represents a user that logged on or a build it user like SYSTEM.
Now, if user X starts a process, Windows gives that process a token that represents X.
Is there a way (in a Windows service) to get the user's token from LUID?
I know I can get it from a process HANDLE, but that is not what I want.
You can use LsaGetLogonSessionData to get the session id and then use WTSQueryUserToken to get the token from that. Note that you may need to run as LocalSystem for WTSQueryUserToken (see "How can I get the current user token for the physical session?" regarding that).

Resources