How to copy privileges from one primary user token to another? - c++

My goal is to copy privileges from one primary user token to another before I start a user mode process with the destination token. I created a sample pseudo code to illustrate what I need to accomplish.
The following will be run from a local system service:
//dwSessionId = user session ID to run process in
HANDLE hToken1 = NULL; //Source user token
WTSQueryUserToken(dwSessionId, &hToken1);
HANDLE hSelfToken = NULL; //User token for system service
HANDLE hToken2 = NULL; //Adjusted self-token
OpenProcessToken(::GetCurrentProcess(), TOKEN_ALL_ACCESS, &hSelfToken);
DuplicateTokenEx(hSelfToken, TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS,
NULL, SecurityIdentification, TokenPrimary, &hToken2);
//Specify user session to run in
SetTokenInformation(hToken2, TokenSessionId, &dwSessionId, sizeof(dwSessionId));
//Now I need to set privileges in 'hToken2' as they are in 'hToken1'
...
//Then use 'hToken2' in CreateProcessAsUser() to start a process
Any idea how to copy privileges from hToken1 to hToken2?

Use the following code to obtain hToken2 from hToken, and then use it in CreateProcessAsUser. There is no need to use hSelfToken, actually.
HANDLE GetAdjustedToken(HANDLE hSrcToken)
{
TOKEN_LINKED_TOKEN admin = {};
HANDLE hTarToken = 0;
DWORD dw = 0;
if (GetTokenInformation(hSrcToken, (TOKEN_INFORMATION_CLASS)TokenLinkedToken, &admin, sizeof(TOKEN_LINKED_TOKEN), &dw))
{
hTarToken = admin.LinkedToken;
}
else
{
DuplicateTokenEx(hSrcToken, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &hTarToken);
}
return hTarToken;
}
And if you want to create the new process at a low mandatory integrity level, see this MSDN article.

First you have to call GetTokenInformation using TokenPrivileges class for your original token to obtain TOKEN_PRIVILEGES structure. Then call AdjustTokenPrivileges with it to update your cloned token. You will have to call GetTokenInformation twice - first one to obtain buffer length and second - to get actual data.
HANDLE hSelfToken = NULL; //User token for system service
HANDLE hToken2 = NULL; //Adjusted self-token
OpenProcessToken(::GetCurrentProcess(), TOKEN_ALL_ACCESS, &hSelfToken);
DuplicateTokenEx(hSelfToken, TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS,
NULL, SecurityIdentification, TokenPrimary, &hToken2);
PTOKEN_PRIVILEGES pPriv = NULL;
DWORD dwLen = 0;
GetTokenInformation(hSelfToken, TokenPrivileges, (LPVOID)pPriv, 0, &dwLen);
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
return(0);
pPriv = (PTOKEN_PRIVILEGES)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwLen);
if (!GetTokenInformation(hSelfToken, TokenPrivileges, (LPVOID)pPriv, dwLen, &dwLen))
return(0);
AdjustTokenPrivileges(hToken2, FALSE, pPriv, dwLen, NULL, NULL);

OK. I might have gotten something close to what I needed. It's not really copying privileges, but for the purpose of "stripping out" privileges I can create a restricted token that will have almost all privileges removed (except SeChangeNotifyPrivilege and SeSystemtimePrivilege.)
//Call the following instead of DuplicateTokenEx() in my example above
CreateRestrictedToken(hSelfToken, DISABLE_MAX_PRIVILEGE,
0, NULL, 0, NULL, 0, NULL,
&hToken2);
FYI: Just a curious fact. For some reason I was not able to remove the SeSystemtimePrivilege from the token. Not with this method, nor using AdjustTokenPrivileges with its attribute set to SE_PRIVILEGE_REMOVED. All other privileges could be removed just fine, except this one. So if anyone have any idea why, I'd be glad to learn it?

Related

Credential Providers V2 - Add code after submit and check user's password

How do I add my code after click submit button and check user password?
I add my code in GetSerialization function after successfully calling KerbInteractiveUnlockLogonPack and RetrieveNegotiateAuthPackage. But in this state first run my code and later check user password.
I want to first check user password and if that correct then run my code. How do I do?
if (_fIsLocalUser)
{
PWSTR pwzProtectedPassword;
hr = ProtectIfNecessaryAndCopyPassword(_rgFieldStrings[SFI_PASSWORD], _cpus, &pwzProtectedPassword);
if (SUCCEEDED(hr))
{
PWSTR pszDomain;
PWSTR pszUsername;
hr = SplitDomainAndUsername(_pszQualifiedUserName, &pszDomain, &pszUsername);
if (SUCCEEDED(hr))
{
KERB_INTERACTIVE_UNLOCK_LOGON kiul;
hr = KerbInteractiveUnlockLogonInit(pszDomain, pszUsername, pwzProtectedPassword, _cpus, &kiul);
if (SUCCEEDED(hr))
{
// We use KERB_INTERACTIVE_UNLOCK_LOGON in both unlock and logon scenarios. It contains a
// KERB_INTERACTIVE_LOGON to hold the creds plus a LUID that is filled in for us by Winlogon
// as necessary.
hr = KerbInteractiveUnlockLogonPack(kiul, &pcpcs->rgbSerialization, &pcpcs->cbSerialization);
if (SUCCEEDED(hr))
{
ULONG ulAuthPackage;
hr = RetrieveNegotiateAuthPackage(&ulAuthPackage);
if (SUCCEEDED(hr))
{
pcpcs->ulAuthenticationPackage = ulAuthPackage;
pcpcs->clsidCredentialProvider = CLSID_CSample;
// At this point the credential has created the serialized credential used for logon
// By setting this to CPGSR_RETURN_CREDENTIAL_FINISHED we are letting logonUI know
// that we have all the information we need and it should attempt to submit the
// serialized credential.
*pcpgsr = CPGSR_RETURN_CREDENTIAL_FINISHED;
}
}
}
CoTaskMemFree(pszDomain);
CoTaskMemFree(pszUsername);
}
CoTaskMemFree(pwzProtectedPassword);
}
}
I have found the following line in my code to check username/password pair:
bRet = LogonUserExA(lpszUsername, NULL, lpszPassword, LOGON32_LOGON_NETWORK,
LOGON32_PROVIDER_DEFAULT, NULL, NULL, NULL, NULL, NULL);
lpszUsername us the full UPN in one of forms domain\user or user#domain.
The key constant is LOGON32_LOGON_NETWORK:
This logon type is intended for high performance servers to
authenticate plaintext passwords. The LogonUserEx function does not
cache credentials for this logon type.
See MS Docs for details on parameters and constant values and also check the remarks section.
In your case it can looks like this:
if(LogonUserEx(_pszQualifiedUserName, NULL, _rgFieldStrings[SFI_PASSWORD],
LOGON32_LOGON_NETWORK, LOGON32_PROVIDER_DEFAULT, NULL, NULL, NULL, NULL, NULL))
{
...
}

Retrieving currently logged in user SID in a multiple logged in users system

I'm facing an issue where user X, which is a non-admin user, runs an elevated program, retrieves the wrong SID information while fetching and querying the token associated with the current process.
My main limitation here is that I must use winXP compatible code, so WSTx functions are out of the question.
Methods I've tried:
I tried extracting the SID from the interactive desktop / main window station, but these yield odd results.
Expanded env variables: %USERPROFILE%
Used GetUserName()
The last 2 actually retrieved the elevated user.
my code:
HANDLE hTok = NULL;
if (false == OpenProcessToken(/*hProcess*/GetCurrentProcess(), TOKEN_QUERY, &hTok))
{
LOG_ERROR(L"Failed obtaining process' token");
return false;
}
// get user info size
LPBYTE pBuf = nullptr;
DWORD dwSize = 0;
bool bSuccess = false;
if (false == GetTokenInformation(hTok, TokenUser, NULL, 0, &dwSize))
{
if (ERROR_INSUFFICIENT_BUFFER != GetLastError())
{
LOG_ERROR(L"Failed getting token information");
return false;
}
}
do
{
pBuf = (BYTE*)::LocalAlloc(LPTR, dwSize);
if (nullptr == pBuf)
{
LOG_ERROR(L"Failed allocating buffer for token information");
break;
}
WCHAR* pSid = nullptr;
if (GetTokenInformation(
hTok,
TokenUser,
pBuf,
dwSize,
&dwSize))
{
PTOKEN_USER pUserToken = reinterpret_cast<PTOKEN_USER>(pBuf);
if (false == ConvertSidToString(pUserToken->User.Sid, &pSid))
{
LOG_ERROR(L"Failed converting sid to string");
break;
}
bSuccess = true;
::LocalFree(pSid);
}
} while (false);
if (pBuf)
::LocalFree(pBuf);
if (hTok && INVALID_HANDLE_VALUE != hTok)
::CloseHandle(hTok);
return bSuccess;
Another idea I had in mind, is to open explorer.exe's token, but I encountered another issue when 2 users are logged in, how can I differentiate between the running explorer.exes instances now?
Edit: If i retrieve Active desktop's SID using GetUserObjectInformation with UOI_USER_SID, i get the Logon Session which is 20 in length, is it possible to somehow translate this logon session into user session?
The WTS functions have been around since Windows 2000.
I can think of a few approaches to your issue. Your elevated process can either:
use WTSQuerySessionInformation() to query the current session for WTSUserName/WTSDomainName, or WTSSessionInfo (which also provides domain/user), then pass those values to LookupAccountName() to get that user's SID.
use OpenDesktop() to open the current desktop, then use
GetUserObjectInformation() to query it for UOI_USER_SID.
create a separate service running in the LocalSystem account that uses WTSQueryUserToken() to get the current session's user token (your app could pass its current SessionID to the service so it knows which session to query), then use GetTokenInformation() to query for its TokenLogonSid, and pass the SID back to your app.

Starting process as different user

I hope someone can help me with this! I have a similar problem as decribed in:
Impersonate standard user
I want to be able to create a process as standard user from application that runs with elevated admin privileges ( UAC Execution level: requireAdministrator ). The user starts the application by borrowing privileges from one of the administrator accounts.
I have succeeded in acquiring a handle to explorer.exe process of this user and it is stored in variable m_hExplorerProc. After that I proceed as follows:
HANDLE hProcToken = NULL;
BOOL success = OpenProcessToken(m_hExplorerProc, TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_ASSIGN_PRIMARY | TOKEN_ADJUST_PRIVILEGES, &hProcToken);
BOOL lookupRet = LookupPrivilegeValue(NULL, SE_ASSIGNPRIMARYTOKEN_NAME,
&(tokenPrivs.Privileges[0].Luid));
tokenPrivs.PrivilegeCount = 1;
tokenPrivs.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
BOOL adjustRet = AdjustTokenPrivileges(hProcToken, FALSE, &tokenPrivs, 0, NULL, NULL);
lookupRet = LookupPrivilegeValue(NULL, SE_INCREASE_QUOTA_NAME, &(tokenPrivs.Privileges[0].Luid));
tokenPrivs.PrivilegeCount = 1;
tokenPrivs.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
adjustRet = AdjustTokenPrivileges(hProcToken, FALSE, &tokenPrivs, 0, NULL, NULL);
HANDLE hDuplicatedToken = NULL;
success = DuplicateTokenEx(hProcToken,
TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_ASSIGN_PRIMARY | TOKEN_ADJUST_PRIVILEGES,
NULL,
SECURITY_IMPERSONATION_LEVEL::SecurityImpersonation,
TOKEN_TYPE::TokenPrimary,
&hDuplicatedToken);
int err = 0;
if(FALSE == success)
err = GetLastError();
LPCTSTR appName = L"C:\\testapp.exe";
PROCESS_INFORMATION procInfo;
ZeroMemory(&procInfo, sizeof(procInfo));
STARTUPINFO startupInfo;
ZeroMemory(&startupInfo, sizeof(startupInfo));
startupInfo.cb = sizeof(startupInfo);
startupInfo.wShowWindow = SW_NORMAL;
startupInfo.dwFlags = STARTF_USESHOWWINDOW;
success = CreateProcessAsUser(hDuplicatedToken, appName, NULL, NULL, NULL, FALSE,
NORMAL_PRIORITY_CLASS | CREATE_DEFAULT_ERROR_MODE,
NULL, L"C:\\", &startupInfo, &procInfo);
if(FALSE == success)
err = GetLastError();
The process is not created and last error is 1314 which translates to: "A required privilege is not held by the client".
In this code I am just trying to execute a dummy app but I eventually want to run browser which was selected as default by this user. Does someone have an idea what am I doing wrong, or perhaps suggest an alternate solution to my problem?
I know it's not cool to answer your own question but three month have passed and still no one suggested an answer.
I have not been able so solve the problem described above using function CreateProcessAsUser but I have stumbled upon an alternate method using ShellExecute from IShellDispatch2 interface which enables you to start a program as current interactive user. Complete code can be found at:
https://code.google.com/p/mulder/source/browse/trunk/Utils/nsis_stdutils/Contrib/StdUtils/ShellExecAsUser.cpp?r=327
I hope this helps someone with a similar problem!
I know this question is very old, but I would like to add something useful (I think). If you want to use CreateProcessAsUser you need privileges, which can be obtained by impersonating a powerful token. See this answer for detail.

Prevent user process from being killed with "End Process" from Process Explorer

I noticed that GoogleToolbarNotifier.exe cannot be killed from Process Explorer. It returns "Access Denied". It runs as the user, it runs "Normal" priority, and it runs from Program Files.
How did they do it?
I think there might be a way to modify the ACL, or mark the process as 'critical', but I cannot seem to locate anything.
Update:
I found the answer with a good bit of digging. #Alex K. was correct in that PROCESS_TERMINATE permission was removed for the process, but I wanted to supply the answer in code:
static const bool ProtectProcess()
{
HANDLE hProcess = GetCurrentProcess();
EXPLICIT_ACCESS denyAccess = {0};
DWORD dwAccessPermissions = GENERIC_WRITE|PROCESS_ALL_ACCESS|WRITE_DAC|DELETE|WRITE_OWNER|READ_CONTROL;
BuildExplicitAccessWithName( &denyAccess, _T("CURRENT_USER"), dwAccessPermissions, DENY_ACCESS, NO_INHERITANCE );
PACL pTempDacl = NULL;
DWORD dwErr = 0;
dwErr = SetEntriesInAcl( 1, &denyAccess, NULL, &pTempDacl );
// check dwErr...
dwErr = SetSecurityInfo( hProcess, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, pTempDacl, NULL );
// check dwErr...
LocalFree( pTempDacl );
CloseHandle( hProcess );
return dwErr == ERROR_SUCCESS;
}
The code given in the question is misleading. It constructs a DACL with no allow entries and one deny entry; that might make sense if you were applying the DACL to a file with inheritance enabled, but in this case the deny entry is redundant. In the Windows access control model, if a DACL exists but contains no matching ACE, access is implicitly denied.
Here's my version, which applies an empty DACL, denying all access. (Note that it returns an error code rather than a boolean.)
DWORD ProtectProcess(void)
{
HANDLE hProcess = GetCurrentProcess();
PACL pEmptyDacl;
DWORD dwErr;
// using malloc guarantees proper alignment
pEmptyDacl = (PACL)malloc(sizeof(ACL));
if (!InitializeAcl(pEmptyDacl, sizeof(ACL), ACL_REVISION))
{
dwErr = GetLastError();
}
else
{
dwErr = SetSecurityInfo(hProcess, SE_KERNEL_OBJECT,
DACL_SECURITY_INFORMATION, NULL, NULL, pEmptyDacl, NULL);
}
free(pEmptyDacl);
return dwErr;
}
When running my copy of that has Deny set on the Terminate permission (Process Explorer shows this).
Presumably they call SetKernelObjectSecurity to change/remove the ACLs when their process loads.
I have tried to do it with the help of writing windows services ..and then making some changes
here is the link to write a simple windows service
http://code.msdn.microsoft.com/windowsdesktop/CppWindowsService-cacf4948
and we can update Servicabase.cpp file with the following two statements..
fCanStop=FALSE;
fCanShutdown=FALSE;

Does using ReadDirectoryChangesW require administrator rights?

The MSDN says that using ReadDirectoryChangesW implies the calling process having the Backup and Restore privileges.
Does this mean that only process launched under administrator account will work correctly?
I've tried the following code, it fails to enable the required privileges when running as a restricted user.
void enablePrivileges()
{
enablePrivilege(SE_BACKUP_NAME);
enablePrivilege(SE_RESTORE_NAME);
}
void enablePrivilege(LPCTSTR name)
{
HANDLE hToken;
DWORD status;
if (::OpenProcessToken(::GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
{
TOKEN_PRIVILEGES tp = { 1 };
if( ::LookupPrivilegeValue(NULL, name, &tp.Privileges[0].Luid) )
{
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
BOOL result = ::AdjustTokenPrivileges(hToken, FALSE, &tp, 0, NULL, NULL);
verify (result != FALSE);
status = ::GetLastError();
}
::CloseHandle(hToken);
}
}
Am I doing something wrong? Is there any workaround for using ReadDirectoryChangesW from a non-administrator user account? It seems that the .NET's FileSystemWatcher can do this. Thanks!
Update: Here is the full code of the class:
class DirectoryChangesWatcher
{
public:
DirectoryChangesWatcher(wstring directory)
{
enablePrivileges();
hDir = ::CreateFile(directory.c_str(),
FILE_LIST_DIRECTORY | FILE_FLAG_OVERLAPPED,
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS, NULL);
ensure (hDir != INVALID_HANDLE_VALUE, err::SystemException);
::ZeroMemory(&overlapped, sizeof(OVERLAPPED));
overlapped.hEvent = dirChangedEvent.getHandle();
}
~DirectoryChangesWatcher() { ::CloseHandle(hDir); }
public:
Event& getEvent() { return dirChangedEvent; }
FILE_NOTIFY_INFORMATION* getBuffer() { return buffer; }
public:
void startAsyncWatch()
{
DWORD bytesReturned;
const BOOL res = ::ReadDirectoryChangesW(
hDir,
&buffer,
sizeof(buffer),
TRUE,
FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_SIZE,
&bytesReturned,
&overlapped,
NULL);
ensure(res != FALSE, err::SystemException);
}
private:
void enablePrivileges()
{
enablePrivilege(SE_BACKUP_NAME);
enablePrivilege(SE_RESTORE_NAME);
}
void enablePrivilege(LPCTSTR name)
{
HANDLE hToken;
DWORD status;
if (::OpenProcessToken(::GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
{
TOKEN_PRIVILEGES tp = { 1 };
if( ::LookupPrivilegeValue(NULL, name, &tp.Privileges[0].Luid) )
{
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
BOOL result = ::AdjustTokenPrivileges(hToken, FALSE, &tp, 0, NULL, NULL);
verify (result != FALSE);
status = ::GetLastError();
}
::CloseHandle(hToken);
}
}
private:
HANDLE hDir;
OVERLAPPED overlapped;
Event dirChangedEvent;
FILE_NOTIFY_INFORMATION buffer[1024];
};
}
Update: Good news! It turned out the problem really was in the FILE_SHARE_WRITE flag in the call to CreateFile. The notifications did not come unless I was an admin. When I removed this flag, everything is now working ona non-admin account too.
I have used ReadDirectoryChangesW without requiring administrator rights, at least on Vista. I don't think you need to manually elevate the process in order to use it on a folder the user already has permissions to see.
It would be more helpful to see the actual code you are using to call ReadDirectoryChangesW, including how you create the handle you pass in.
I don't see where MSDN says you need either backup or restore privileges. It instructs you to call CreateFile with the File_Flag_Backup_Semantics flag set, and in that flag's description, MSDN says this:
The system ensures that the calling process overrides file security checks when the process has SE_BACKUP_NAME and SE_RESTORE_NAME privileges.
The way I read it, if you have those privileges, then the system will override the file security checks for you. So if you don't have those privileges, then the program will simply continue to be bound by whatever file security checks would ordinarily be in effect.
Alex, in your CreateFile() call you put FILE_FLAG_OVERLAPPED into wrong position. It should be moved from 2nd to 6th parameter.