locked
While trying to launch a process with credentials of the interactive user, GetTokenInformation returns error code 1312 on windows 10 even if UAC is turned on to the maximum RRS feed

  • Question

  • I am trying to launch a process from a windows service [running under local system account] with credentials of the interactive user but administrator privileges. This works if I start the PC and log in to an adminstrator user. However, it fails on normal users, as described below.

    On some previous installations of Windows 10, I had faced the problem While trying to launch a process with credentials of the interactive user, GetTokenInformation returns error code 1312 on Windows 7 and some installations of windows 10 if UAC is turned off. I got the solution to that in https://social.msdn.microsoft.com/Forums/en-US/116f7616-b89f-4e6d-b41f-b6e5f79f2e16/while-trying-to-launch-a-process-with-credentials-of-the-interactive-user-gettokeninformation?forum=vcgeneral

    I dont remember whether I tried the above solution with a non-administrator user. But at present,

    I am facing a similar problem with a non-adminstrator user, in which GetTokenInformation fails with error 1312.

    The code and log output are below.

    The method GetTokenForProcessId fails on GetTokenInformation with error 1312, and SetTokenInformation fails with error 1375

    GetProcessIdByNameAndSessionId

    Method: LaunchProcessElevated

    Process Name: ProcessMonitor-X64.exe
    Explorer ProcessId: 10724
    Winlogon ProcessId: 1820

    Method:  GetTokenForProcessId

    GetTokenForProcessId::10724
    OpenProcess reurned Handle:464
    OpenProcessToken reurned Handle:468
    Token Inf Length:44
    Failed to Get Primary Token:1312 
    Explorer hTokenDst: 468

    Method:  GetTokenForProcessId

    GetTokenForProcessId::1820
    OpenProcess reurned Handle:472
    OpenProcessToken reurned Handle:476
    Token Inf Length:28
    Failed to Get Primary Token:1312
    Winlogn hTokenSrc: 476

    Method:  CopyPrivileges

    CopyPrivileges: Original Tokens:476:468
    GetImpersonationToken reurned Handle:476
    DuplicateToken reurned Handle:480
    GetImpersonationToken reurned Handle:468
    DuplicateToken reurned Handle:484


    CopyPrivileges: Impersonated Tokens:480:484
    GetTokenPrivileges Buffer size:256
    Privilege Count:5
    Adjust Privileges Succeeded
    SetTokenInformation succeded
    Failed to SetTokenInformation:Error: 1375

    Method:  CreateProcessWithToken

    Failed to start Process Monitor Process

    STDMETHODIMP CProcessManager::LaunchProcessElevated(BSTR commandLine, DWORD* dwProcessId)
    {
    	logger->ClearLog();
    	ZeroMemory(buffer, sizeof(buffer));
    
    
    	CComBSTR temp(commandLine);
    	LPWSTR lpszProcessName = temp.operator LPWSTR();
    	Report(L"Process Name: %s", (LPCWSTR)lpszProcessName);
    	
    	DWORD dwSessionId = WTSGetActiveConsoleSessionId();
    	DWORD dwProcessIdExplorer = GetProcessIdByNameAndSessionId(L"explorer.exe", dwSessionId);
    	Report(L"Explorer ProcessId: %d", dwProcessIdExplorer);
    	DWORD dwProcessIdWinLogon= GetProcessIdByNameAndSessionId(L"winlogon.exe", dwSessionId);
    	Report(L"Winlogon ProcessId: %d", dwProcessIdWinLogon);
    
    	HANDLE hTokenDst = GetTokenForProcessId(dwProcessIdExplorer);
    	Report(L"Explorer hTokenDst: %d", hTokenDst);
    	HANDLE hTokenSrc = GetTokenForProcessId(dwProcessIdWinLogon);
    	Report(L"Winlogn hTokenSrc: %d", hTokenSrc);
    	if (CopyPrivileges(hTokenSrc, hTokenDst)) {
    
    
    
    		if (!SetTokenInformation(hTokenDst, TokenSessionId, &dwSessionId, sizeof(dwSessionId)))
    		{
    			Report(L"Failed to SetTokenInformation:Error: %d", GetLastError());
    			return S_FALSE;
    
    		}
    
    		//Report(lpszProcessName);
    		DWORD dwProcessIdNew = 0;
    		if (CreateProcessWithToken(hTokenDst, lpszProcessName, (DWORD*)& dwProcessIdNew))
    		{
    			*dwProcessId = dwProcessIdNew;
    			return S_OK;
    		}
    		else 
    		{
    			Report(L"Failed to CreateProcessWithToken: Error: %d", GetLastError());
    			return S_FALSE;
    		}
    
    		
    	}
    	else 
    	{
    		Report(L"Failed to Copy Privileges:Error: %d", GetLastError());
    		return S_FALSE;
    	}
    }
    
    HANDLE CProcessManager::GetTokenForProcessId(UINT dwProcessId)
    {
    	ZeroMemory(buffer, sizeof(buffer));
    	_ltow_s(dwProcessId, buffer, len, 10);
    	Report(L"GetTokenForProcessId::%d", dwProcessId);
    	HANDLE hProcess = 0;
    	HANDLE hToken = 0;
    	hProcess = OpenProcess(MAXIMUM_ALLOWED, 0, dwProcessId);
    	if (hProcess == NULL) {
    		Report(L"OpenProcess Returned Null: %d", GetLastError());
    	}
    	ZeroMemory(buffer, sizeof(buffer));
    	_ltow_s((long)hProcess, buffer, len, 10);
    	Report(L"OpenProcess reurned Handle:%s", buffer);
    	// obtain a handle to the access token of the winlogon process
    	if (!OpenProcessToken(hProcess, TOKEN_ALL_ACCESS | TOKEN_READ | TOKEN_WRITE | TOKEN_EXECUTE, &hToken))
    	{
    		ZeroMemory(buffer, sizeof(buffer));
    		Report(L"OpenProcesToken Returned Null: %d", GetLastError());
    		CloseHandle(hProcess);
    
    		return 0;
    	}
    	ZeroMemory(buffer, sizeof(buffer));
    	Report(L"OpenProcessToken reurned Handle:%d", hToken);
    	DWORD TokenInfLength = 0;
    
    	//-http://www.microsoft-questions.com/microsoft/Platform-SDK-Security/35984508/how-to-run-a-process-with-elevated-privileges-run-as-administrat.aspx
    	// first call gets lenght of TokenInformation
    	GetTokenInformation(hToken, TokenUser, 0, TokenInfLength, &TokenInfLength);
    	Report(L"Token Inf Length:%d", TokenInfLength);
    
    	HANDLE* TokenInformation = (HANDLE*)LocalAlloc(LMEM_ZEROINIT, TokenInfLength);
    	TokenInfLength = sizeof(HANDLE);
    
    
    	if (!GetTokenInformation(hToken, TokenLinkedToken, TokenInformation, TokenInfLength, &TokenInfLength))
    	{
    		DWORD err = GetLastError();
    		Report(L"Failed to Get Primary Token:%d", err);
    		return hToken;
    	}
    	else {
    		HANDLE hPrimaryToken = *TokenInformation;
    		Report(L"Primary Token:%d", hPrimaryToken);
    		return hPrimaryToken;
    	}
    	return NULL;
    }
    DWORD GetProcessIdByNameAndSessionId(LPCWSTR lpszProcessName, DWORD sessionId)
    {
    	//LaunchDebugger();
    	wchar_t* p = 0;
    	wchar_t* q = 0;
    	wchar_t* ProcessNameW = (wchar_t*)lpszProcessName;
    	DWORD processId = 0;
    	wchar_t lowerCaseInputProcess[512];
    	ZeroMemory(lowerCaseInputProcess, sizeof(lowerCaseInputProcess));
    	//Convert InputProcess to lower case
    	p = ProcessNameW;
    	q = lowerCaseInputProcess;
    
    	for (; *p; ++p, ++q) * q = tolower(*p);
    
    	//wprintf(L"Target:%s\n", lowerCaseInputProcess);
    
    
    	NTSTATUS status;
    	PVOID buffer;
    	PSYSTEM_PROCESS_INFO spi;
    
    	buffer = VirtualAlloc(NULL, 1024 * 1024, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); // We need to allocate a large buffer because the process list can be large.
    
    	if (!buffer)
    	{
    	//	Report(L"\nError: Unable to allocate memory for process list: %d\n", GetLastError());
    		return -1;
    	}
    
    	//Report(L"\nProcess list allocated at address %#x\n", buffer);
    	spi = (PSYSTEM_PROCESS_INFO)buffer;
    
    	if (!NT_SUCCESS(status = NtQuerySystemInformation(SystemProcessInformation, spi, 1024 * 1024, NULL)))
    	{
    		//Report(L"\nError: Unable to query process list (%#x)\n", status);
    
    		VirtualFree(buffer, 0, MEM_RELEASE);
    		return -1;
    	}
    
    
    
    
    	wchar_t imageName[512];
    	wchar_t lowerCaseImageName[512];
    
    	cout << "ok";
    
    	while (spi->NextEntryOffset)
    	{
    		// Loop over the list until we reach the last entry.
    		//The following wsprintf is necessary because any access , include wcslen , other than wprintf causes an exception
    		//So copied buffer to another buffer for further processing.
    		//Report(lowerCaseImageName, L"%s", spi->ImageName.Buffer);
    		cout << spi->ImageName.Buffer << endl;
    		p = spi->ImageName.Buffer;
    		q = lowerCaseImageName;
    		ZeroMemory(lowerCaseImageName, sizeof(lowerCaseImageName));
    		if (p) {
    			for (; *p; ++p, ++q) * q = tolower(*p);
    		}
    		//Compare Names
    		if ((StrCmpCW(lowerCaseInputProcess, lowerCaseImageName)) == 0) {
    			processId = (DWORD)spi->ProcessId;
    			DWORD dwSessionId = 0;
    			if (ProcessIdToSessionId(processId, &dwSessionId))
    			{
    
    				if (sessionId == dwSessionId) {
    
    					//Report(L"Found a Match:%s\d", processId);
    					VirtualFree(buffer, 0, MEM_RELEASE); // Free the allocated buff
    					return processId;
    				}
    			}
    
    
    		}
    		spi = (PSYSTEM_PROCESS_INFO)((LPBYTE)spi + spi->NextEntryOffset); // Calculate the address of the next entry.
    	}
    	processId = 0;
    	VirtualFree(buffer, 0, MEM_RELEASE); // Free the allocated buffer.
    
    	return processId;
    }
    BOOL CProcessManager::CreateProcessWithToken(HANDLE hToken, LPCWSTR lpszCommandLine, DWORD* dwProcessId)
    {
    
    
    	_ltow_s((long)hToken, buffer, len, 10);
    	wstring entry = wstring(L"CreateProcessWithToken:");
    	entry += lpszCommandLine;
    	Report(entry.c_str(), buffer);
    
    	// By default CreateProcessAsUser creates a process on a non-interactive window station, meaning
    	// the window station has a desktop that is invisible and the process is incapable of receiving
    	// user input. To remedy this we set the lpDesktop parameter to indicate we want to enable user 
    	// interaction with the new process.
    	STARTUPINFO si;
    	ZeroMemory(&si, sizeof(si));
    	si.cb = sizeof(si);
    	si.lpDesktop = L"winsta0\\default"; // interactive window station parameter; basically this indicates that the process created can display a GUI on the desktop
    
    										// flags that specify the priority and creation method of the process
    	UINT dwCreationFlags = (int)(NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE);
    
    
    
    	SECURITY_ATTRIBUTES sa;
    	ZeroMemory(&sa, sizeof(sa));
    	sa.nLength = sizeof(sa);
    	PROCESS_INFORMATION pi;
    
    	ZeroMemory(&pi, sizeof(pi));
    
    
    
    
    
    	LPWSTR szCmdLine = _tcsdup(lpszCommandLine);
    	Report(szCmdLine);
    	//LPCWSTR commandLine = CharToWideChar(lpszCommandLine);
    
    	//	MessageBox(0, lp, L"Path", MB_OK);
    	BOOL result = CreateProcessAsUser(hToken,        // client's access token
    		0,        // command line
    		szCmdLine,                   // file to execute
    
    		&sa,                 // pointer to process SECURITY_ATTRIBUTES
    		&sa,                 // pointer to thread SECURITY_ATTRIBUTES
    		0,                  // handles are not inheritable
    		dwCreationFlags,        // creation flags
    		0,            // pointer to new environment block 
    		0,                   // name of current directory 
    		&si,                 // pointer to STARTUPINFO structure
    		&pi            // receives information about new process
    	);
    	if (!result) {
    		//_ltow_s((DWORD)GetLastError(), buffer, len, 10);
    		//MessageBoxA(0, "OpenProces Returned Null",buffer, MB_OK);
    		Report(L"CreateProcessAsUser Returned Null. Error:%d", GetLastError());
    	}
    	*dwProcessId = pi.dwProcessId;
    	CloseHandle(pi.hProcess);
    	CloseHandle(pi.hThread);
    	return result;
    }
    
    BOOL CProcessManager::CopyPrivileges(HANDLE hTokenSrc, HANDLE hTokenDst)
    {
    
    	_ltow_s((long)hTokenSrc, buffer, len, 10);
    	wstring entry = wstring(buffer);
    	_ltow_s((long)hTokenDst, buffer, len, 10);
    	entry += wstring(L":") + wstring(buffer);
    
    	Report(L"CopyPrivileges: Original Tokens:%s", entry.c_str());
    
    	hTokenSrc = GetImpersonationToken(hTokenSrc);
    	hTokenDst = GetImpersonationToken(hTokenDst);
    
    
    	_ltow_s((long)hTokenSrc, buffer, len, 10);
    	entry = wstring(buffer);
    	_ltow_s((long)hTokenDst, buffer, len, 10);
    	entry += wstring(L":") + wstring(buffer);
    
    	Report(L"CopyPrivileges: Impersonated Tokens:%s", entry.c_str());
    
    
    	TOKEN_PRIVILEGES Priv;
    	ZeroMemory(&Priv, sizeof(Priv));
    	DWORD dwLen = 0;
    	GetTokenInformation(hTokenSrc, TokenPrivileges, &Priv, sizeof(TOKEN_PRIVILEGES), &dwLen);
    	if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
    		/**///)
    		//_ltow_s(GetLastError(), buffer, len, 10);
    		Report(L"GetTokenPrivileges First Call Failed with Error:%d", GetLastError());
    
    		return(0);
    	}
    
    	//_ltow_s(dwLen, buffer, len, 10);
    	Report(L"GetTokenPrivileges Buffer size:%d", dwLen);
    
    	PTOKEN_PRIVILEGES pPriv = (PTOKEN_PRIVILEGES)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwLen * 10);
    	if (!GetTokenInformation(hTokenDst, TokenPrivileges, (LPVOID)pPriv, dwLen * 10, &dwLen)) {
    		//_ltow_s(GetLastError(), buffer, len, 10);
    		Report(L"GetTokenPrivileges Failed with Error:%d", GetLastError());
    		return(0);
    	}
    	//_ltow_s(pPriv->PrivilegeCount, buffer, len, 10);
    	Report(L"Privilege Count:%d", pPriv->PrivilegeCount);
    
    	if (!AdjustTokenPrivileges(hTokenDst, FALSE, pPriv, dwLen, NULL, NULL)) {
    		//_ltow_s(GetLastError(), buffer, len, 10);
    		Report(L"AdjustTokenPrivileges Failed with Error:%d", GetLastError());
    		return false;
    	}
    
    	LUID luid;
    	ZeroMemory(&luid, sizeof(luid));
    	if (!LookupPrivilegeValue(0, SE_TCB_NAME, &luid))
    	{
    		//_ltow_s(GetLastError(), buffer, len, 10);
    		Report(L"LookupPrivilegeValue Failed with Error:%d", GetLastError());
    		return false;
    	}
    	TOKEN_PRIVILEGES tp;
    	ZeroMemory(&tp, sizeof(tp));
    	tp.PrivilegeCount = 1;
    	tp.Privileges->Luid.HighPart = luid.HighPart;
    	tp.Privileges->Luid.LowPart = luid.LowPart;
    
    	DWORD dwReturn = 0;
    	if (!AdjustTokenPrivileges(hTokenDst, SE_PRIVILEGE_ENABLED, &tp, sizeof(tp), 0, &dwReturn))
    	{
    		//_ltow_s(GetLastError(), buffer, len, 10);
    		Report(L"AdjustTokenPrivileges for TCB_NAME Failed with Error:%d", GetLastError());
    		return false;
    
    	}
    
    	Report(L"Adjust Privileges Succeeded");
    	DWORD dwSessionId = WTSGetActiveConsoleSessionId();
    	if (!SetTokenInformation(hTokenDst, TokenSessionId, &dwSessionId, sizeof(dwSessionId)))
    	{
    		//_ltow_s(GetLastError(), buffer, len, 10);
    		Report(L"SetTokenInformation Failed with Error:%d", GetLastError());
    		return false;;
    	}
    	Report(L"SetTokenInformation succeded");
    	return true;
    }





    • Edited by Sagar R. Kapadia Wednesday, July 31, 2019 2:23 PM Additional Information
    Wednesday, July 31, 2019 2:22 PM

All replies

  • I am trying to launch a process from a windows service [running under local system account] with credentials of the interactive user but administrator privileges. This works if I start the PC and log in to an adminstrator user. However, it fails on normal users, as described below.

    And why does this surprise you? A standard user, otherwise known as a limited user account is an account that does not have administrative privileges.

    The reason why this works with administrative users is UAC and there has to be a way for Windows to get the full user token for the administrator user account. In other words, these are rights that an administrator already has.

    Limited user accounts never have these rights to start off with, so it would be a major elevation of privileges security flaw if there is a way for a non administrative user to get administrative rights. So there is no linked token for a limited account. The only token for a limited user account is the primary token.

    If you require a process running in a limited user's logon session to have administrative rights then you will require a separate administrator account to log in. You can pass the user's token as an impersonation token to do file system access, but this token can never be used to access anything that requires administrative rights.

    Otherwise you must detect if the process has a linked token and if it doesn't then you should just use the primary token to start the new process. But in this case the token will not have administrative rights which is expected of a token from a user account that doesn't have administrative rights.

    GetTokenInformation can be used to check if the token has been filtered using the TokenHasRestrictions member of the TOKEN_INFORMATION_CLASS, so you shouldn't be blindly calling GetTokenInformation using TokenLinkedToken. The linked token will only exist if TokenHasRestrictions is not zero.


    This is a signature. Any samples given are not meant to have error checking or show best practices. They are meant to just illustrate a point. I may also give inefficient code or introduce some problems to discourage copy/paste coding. This is because the major point of my posts is to aid in the learning process.

    • Edited by Darran Rowe Wednesday, July 31, 2019 3:17 PM
    • Proposed as answer by Guido Franzke Thursday, August 1, 2019 9:02 AM
    Wednesday, July 31, 2019 3:10 PM
  • Thanks for the reply Darran.

    I understand what you mean. But  I remember perhaps having done this previously , that is, giving a standard user administrator rights in the above manner. I am probably wrong, but still, there is the doubt. Will try to confirm or dispel the doubt once more, before stopping

    Just to be sure, what I remember was using a windows service running under local system account to copy privileges from a winlogon.exe process token to an explorer.exe process token, and then setting the sessionId on the explorer.exe token to the session of explorer.exe [The copy privileges caused the session to change to "Local System" account.] As far as I know, this worked. I remember because I tried this with a non administrator user and it worked.

    Friday, August 2, 2019 11:38 PM
  • Also, windows error 1312 , is "A specified logon session does not exist. It may already have been terminated". This does not look like a security exception, though I have very limited knowledge.

    Friday, August 2, 2019 11:43 PM
  • You cannot copy the privileges from an existing token to another existing token.

    Perhaps what you are thinking of is duplicating a token and then using the duplicate in some fashion.

    Saturday, August 3, 2019 12:34 AM
  • I understand what you mean. But I remember perhaps having done this previously , that is, giving a standard user administrator rights in the above manner. I am probably wrong, but still, there is the doubt. Will try to confirm or dispel the doubt once more, before stopping

    No, I can safely say that you never done this previously.

    You must remember one very important thing, the user token is the user's identity. When a user logs on, Windows assigns the groups to the user token that the user is part of and then assigns the user rights that the user is allowed. The list of rights is defined by the system policy and is easily viewable.

    The important thing to note is that if you compare this against the list of privileges, very few of them are assigned to users.

    What's more, Windows does not provide a way for any process beyond the logon service to add privileges or accounts to a user token. The only way to add is through the logon related stuff, and these only work during the logon process.

    Just to be sure, what I remember was using a windows service running under local system account to copy privileges from a winlogon.exe process token to an explorer.exe process token, and then setting the sessionId on the explorer.exe token to the session of explorer.exe [The copy privileges caused the session to change to "Local System" account.] As far as I know, this worked. I remember because I tried this with a non administrator user and it worked.

    The fact that the session "changed to 'Local System' account" means that what you did was run the application in the user's session using the Local System account. As I said, the user token is the user's identity, so if it was using the user token of that user account, it would identify itself as the user account not Local System.

    What's more, this is a major security vulnerability because you have placed a process with a very high level of privileges into the same logon session as untrusted users.

    Also think about it, if there was a way to manufacture an administrative token out of a standard user's token, this would be a major security vulnerability.

    Also, windows error 1312 , is "A specified logon session does not exist. It may already have been terminated". This does not look like a security exception, though I have very limited knowledge.

    Without knowing the implementation of GetTokenInformation and the layout of the kernel objects it is hard to say if this error is related or not. For example, if the implementation linked a dummy token to a token that has not been restricted and the very first thing that it does is check the session ID to see if it is valid as a way of determining whether there is a linked token then it is relevant. However, I agree that something like ERROR_NO_LINKED_TOKEN or the like would be better.

    But this doesn't change the fact that your code is bad in the first place. You know that there is a possibility that the linked token doesn't exist due to Windows 7 with UAC disabled. But instead of doing the proper thing of calling GetTokenInformation with TokenElevationType to test if the token has a linked token, you just blindly called GetTokenInformation with TokenLinkedToken. In the post that you linked to in your original post, I told you that you should do this but you just totally ignored it.


    This is a signature. Any samples given are not meant to have error checking or show best practices. They are meant to just illustrate a point. I may also give inefficient code or introduce some problems to discourage copy/paste coding. This is because the major point of my posts is to aid in the learning process.

    Saturday, August 3, 2019 1:09 AM
  • You cannot copy the privileges from an existing token to another existing token.

    Perhaps what you are thinking of is duplicating a token and then using the duplicate in some fashion.

    By copying privileges, I meant the following, and not DuplicateToken

    BOOL CProcessManager::CopyPrivileges(HANDLE hTokenSrc, HANDLE hTokenDst)
    {
    
    	_ltow_s((long)hTokenSrc, buffer, len, 10);
    	wstring entry = wstring(buffer);
    	_ltow_s((long)hTokenDst, buffer, len, 10);
    	entry += wstring(L":") + wstring(buffer);
    
    	Report(L"CopyPrivileges: Original Tokens:%s", entry.c_str());
    
    	hTokenSrc = GetImpersonationToken(hTokenSrc);
    	hTokenDst = GetImpersonationToken(hTokenDst);
    
    
    	_ltow_s((long)hTokenSrc, buffer, len, 10);
    	entry = wstring(buffer);
    	_ltow_s((long)hTokenDst, buffer, len, 10);
    	entry += wstring(L":") + wstring(buffer);
    
    	Report(L"CopyPrivileges: Impersonated Tokens:%s", entry.c_str());
    
    
    	TOKEN_PRIVILEGES Priv;
    	ZeroMemory(&Priv, sizeof(Priv));
    	DWORD dwLen = 0;
    	GetTokenInformation(hTokenSrc, TokenPrivileges, &Priv, sizeof(TOKEN_PRIVILEGES), &dwLen);
    	if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
    		/**///)
    		//_ltow_s(GetLastError(), buffer, len, 10);
    		Report(L"GetTokenPrivileges First Call Failed with Error:%d", GetLastError());
    
    		return(0);
    	}
    
    	//_ltow_s(dwLen, buffer, len, 10);
    	Report(L"GetTokenPrivileges Buffer size:%d", dwLen);
    
    	PTOKEN_PRIVILEGES pPriv = (PTOKEN_PRIVILEGES)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwLen * 10);
    	if (!GetTokenInformation(hTokenDst, TokenPrivileges, (LPVOID)pPriv, dwLen * 10, &dwLen)) {
    		//_ltow_s(GetLastError(), buffer, len, 10);
    		Report(L"GetTokenPrivileges Failed with Error:%d", GetLastError());
    		return(0);
    	}
    	//_ltow_s(pPriv->PrivilegeCount, buffer, len, 10);
    	Report(L"Privilege Count:%d", pPriv->PrivilegeCount);
    
    	if (!AdjustTokenPrivileges(hTokenDst, FALSE, pPriv, dwLen, NULL, NULL)) {
    		//_ltow_s(GetLastError(), buffer, len, 10);
    		Report(L"AdjustTokenPrivileges Failed with Error:%d", GetLastError());
    		return false;
    	}
    
    	LUID luid;
    	ZeroMemory(&luid, sizeof(luid));
    	if (!LookupPrivilegeValue(0, SE_TCB_NAME, &luid))
    	{
    		//_ltow_s(GetLastError(), buffer, len, 10);
    		Report(L"LookupPrivilegeValue Failed with Error:%d", GetLastError());
    		return false;
    	}
    	TOKEN_PRIVILEGES tp;
    	ZeroMemory(&tp, sizeof(tp));
    	tp.PrivilegeCount = 1;
    	tp.Privileges->Luid.HighPart = luid.HighPart;
    	tp.Privileges->Luid.LowPart = luid.LowPart;
    
    	DWORD dwReturn = 0;
    	if (!AdjustTokenPrivileges(hTokenDst, SE_PRIVILEGE_ENABLED, &tp, sizeof(tp), 0, &dwReturn))
    	{
    		//_ltow_s(GetLastError(), buffer, len, 10);
    		Report(L"AdjustTokenPrivileges for TCB_NAME Failed with Error:%d", GetLastError());
    		return false;
    
    	}
    
    	Report(L"Adjust Privileges Succeeded");
    	DWORD dwSessionId = WTSGetActiveConsoleSessionId();
    	if (!SetTokenInformation(hTokenDst, TokenSessionId, &dwSessionId, sizeof(dwSessionId)))
    	{
    		//_ltow_s(GetLastError(), buffer, len, 10);
    		Report(L"SetTokenInformation Failed with Error:%d", GetLastError());
    		return false;;
    	}
    	Report(L"SetTokenInformation succeded");
    	return true;
    }

    Saturday, August 3, 2019 2:31 AM
  • I understand what you mean. But I remember perhaps having done this previously , that is, giving a standard user administrator rights in the above manner. I am probably wrong, but still, there is the doubt. Will try to confirm or dispel the doubt once more, before stopping

    No, I can safely say that you never done this previously.

    You must remember one very important thing, the user token is the user's identity. When a user logs on, Windows assigns the groups to the user token that the user is part of and then assigns the user rights that the user is allowed. The list of rights is defined by the system policy and is easily viewable.

    The important thing to note is that if you compare this against the list of privileges, very few of them are assigned to users.

    What's more, Windows does not provide a way for any process beyond the logon service to add privileges or accounts to a user token. The only way to add is through the logon related stuff, and these only work during the logon process.

    Just to be sure, what I remember was using a windows service running under local system account to copy privileges from a winlogon.exe process token to an explorer.exe process token, and then setting the sessionId on the explorer.exe token to the session of explorer.exe [The copy privileges caused the session to change to "Local System" account.] As far as I know, this worked. I remember because I tried this with a non administrator user and it worked.

    The fact that the session "changed to 'Local System' account" means that what you did was run the application in the user's session using the Local System account. As I said, the user token is the user's identity, so if it was using the user token of that user account, it would identify itself as the user account not Local System.

    What's more, this is a major security vulnerability because you have placed a process with a very high level of privileges into the same logon session as untrusted users.

    Also think about it, if there was a way to manufacture an administrative token out of a standard user's token, this would be a major security vulnerability.

    Also, windows error 1312 , is "A specified logon session does not exist. It may already have been terminated". This does not look like a security exception, though I have very limited knowledge.

    Without knowing the implementation of GetTokenInformation and the layout of the kernel objects it is hard to say if this error is related or not. For example, if the implementation linked a dummy token to a token that has not been restricted and the very first thing that it does is check the session ID to see if it is valid as a way of determining whether there is a linked token then it is relevant. However, I agree that something like ERROR_NO_LINKED_TOKEN or the like would be better.

    But this doesn't change the fact that your code is bad in the first place. You know that there is a possibility that the linked token doesn't exist due to Windows 7 with UAC disabled. But instead of doing the proper thing of calling GetTokenInformation with TokenElevationType to test if the token has a linked token, you just blindly called GetTokenInformation with TokenLinkedToken. In the post that you linked to in your original post, I told you that you should do this but you just totally ignored it.


    This is a signature. Any samples given are not meant to have error checking or show best practices. They are meant to just illustrate a point. I may also give inefficient code or introduce some problems to discourage copy/paste coding. This is because the major point of my posts is to aid in the learning process.

    Actually, I tried that out just now, from some code I had previously.  I tried the following

    HANDLE CProcessManager::GetTokenForProcessId(UINT dwProcessId)
    {
    	ZeroMemory(buffer, sizeof(buffer));
    	_ltow_s(dwProcessId, buffer, len, 10);
    	Report(L"GetTokenForProcessId::%d", dwProcessId);
    	HANDLE hProcess = 0;
    	HANDLE hToken = 0;
    	hProcess = OpenProcess(MAXIMUM_ALLOWED, 0, dwProcessId);
    	if (hProcess == NULL) {
    		Report(L"OpenProcess Returned Null: %d", GetLastError());
    	}
    	ZeroMemory(buffer, sizeof(buffer));
    	_ltow_s((long)hProcess, buffer, len, 10);
    	Report(L"OpenProcess reurned Handle:%s", buffer);
    	// obtain a handle to the access token of the winlogon process
    	if (!OpenProcessToken(hProcess, TOKEN_DUPLICATE | TOKEN_ALL_ACCESS | TOKEN_READ | TOKEN_WRITE | TOKEN_EXECUTE, &hToken))
    	{
    		ZeroMemory(buffer, sizeof(buffer));
    		Report(L"OpenProcesToken Returned Null: %d", GetLastError());
    		CloseHandle(hProcess);
    
    		return 0;
    	}
    	ZeroMemory(buffer, sizeof(buffer));
    	Report(L"OpenProcessToken reurned Handle:%d", hToken);
    
    	//Get Primary token from impersonation token. This step is optional. If UAC is turned off, this step will fail. It will succeed only if UAC is turned on.if UAC is turned off, we DONT need the real token to launch a process as user
    
    	DWORD neededSize1 = 0;
    	HANDLE* realToken = new HANDLE;
    	if (GetTokenInformation(hToken, (::TOKEN_INFORMATION_CLASS) TokenLinkedToken, realToken, sizeof(HANDLE), &neededSize1))
    	{
    		CloseHandle(hToken);
    		hToken = *realToken;
    	}
    	else
    	{
    		//log error
    		Report(L"Exception in GetTokenInformation while trying to get Primary Token! UAC is turned off:%d", GetLastError());
    		Report(L"Since UAC is turned off, we DONT need the real token to launch a process as user, otherwise we would");
    		//continue;
    	}
    
    
    	// cannot be assigned the null value.
    	SECURITY_ATTRIBUTES sa;
    	ZeroMemory(&sa, sizeof(sa));
    	HANDLE hUserTokenDup = NULL;
    	// copy the access token of the winlogon process; 
    	// the newly created token will be a primary token
    	if (!DuplicateTokenEx(hToken, TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS | MAXIMUM_ALLOWED, &sa,
    		SecurityIdentification,
    		TokenPrimary, &hUserTokenDup))
    	{
    		Report(L"DuplicateTokenEx Returned Null: %d", GetLastError());
    		CloseHandle(hProcess);
    		//CloseHandle(hToken);
    		return 0;
    	}
    	Report(L"DuplicateTokenEx resulted in %d", hUserTokenDup);
    	return hUserTokenDup;
    }

    But this doesn't change the fact that your code is bad in the first place. You know that there is a possibility that the linked token doesn't exist due to Windows 7 with UAC disabled. But instead of doing the proper thing of calling GetTokenInformation with TokenElevationType to test if the token has a linked token, you just blindly called GetTokenInformation with TokenLinkedToken. In the post that you linked to in your original post, I told you that you should do this but you just totally ignored it.


    I did not do exactly as you said (GetTokenInformation with TokenElevationType), and I ignored GetTokenInformation if if failed. But, it works with UAC turned on or off for an administrator. 

    For a standard user, GetTokenInformation fails  with 1312, irrespective of whether UAC is turned on or off in windows 10.

    Should this always fail for a standard user? [I am not exactly sure where it fails, I will check and update. I need to sign out for this and try again with a standard user]

    However, the process does get launched  in the interactive users session and with his credentials, but no admin rights. I guess this is because DuplicateToken succeds.

    Saturday, August 3, 2019 2:46 AM
  • Darran,

    I really appreciate your taking the time and effort to help me out. Not many people would go to this extent. Thanks a lot. I will update what fails in a reply.

    Sagar

    Saturday, August 3, 2019 2:47 AM
  • Is it possible to use SetTokenInformation to set session Id? The following does not return an error,  but does not change session Id either. The created process is in Local System account. if I can change the session Id to interactive user, this would work perhaps.

    DWORD dwSessionId = WTSGetActiveConsoleSessionId();
    DWORD dwProcessIdWinLogon = GetProcessIdByNameAndSessionId(L"winlogon.exe", dwSessionId);
    Report(L"Winlogon ProcessId: %d", dwProcessIdWinLogon);

    HANDLE hTokenSrc = GetTokenForProcessId(dwProcessIdWinLogon);
    Report(L"Winlogn hTokenSrc: %d", hTokenSrc);

    if (!SetTokenInformation(hTokenSrc, TokenSessionId, &dwSessionId, sizeof(dwSessionId)))
    {
    Report(L"Failed to SetTokenInformation:Error: %d", GetLastError());
    return S_FALSE;

    }
    DWORD dwProcessIdNew = 0;
    if (CreateProcessWithToken(hTokenSrc, lpszProcessName, (DWORD*)& dwProcessIdNew))
    {
    *dwProcessId = dwProcessIdNew;
    return S_OK;
    }
    else
    {
    Report(L"Failed to CreateProcessWithToken: Error: %d", GetLastError());
    return S_FALSE;
    }

    Saturday, August 3, 2019 3:25 AM
  • Another attempt , based on https://social.msdn.microsoft.com/Forums/ie/en-US/2f6944ad-578d-4644-b8db-a475e6e8ff4f/problem-with-settokeninformation-cannot-set-session-id-information?forum=windowssecurity

    STDMETHODIMP CProcessManager::LaunchProcessElevated(BSTR commandLine, DWORD* dwProcessId)
    {
    	logger->ClearLog();
    	ZeroMemory(buffer, sizeof(buffer));
    
    
    	CComBSTR temp(commandLine);
    	LPWSTR lpszProcessName = temp.operator LPWSTR();
    	Report(L"Process Name: %s", (LPCWSTR)lpszProcessName);
    
    	DWORD dwSessionId = WTSGetActiveConsoleSessionId();
    	DWORD dwProcessIdExplorer = GetProcessIdByNameAndSessionId(L"explorer.exe", dwSessionId);
    	Report(L"Explorer ProcessId: %d", dwProcessIdExplorer);
    	DWORD dwProcessIdWinLogon = GetProcessIdByNameAndSessionId(L"winlogon.exe", dwSessionId);
    	Report(L"Winlogon ProcessId: %d", dwProcessIdWinLogon);
    
    	HANDLE hTokenDst = GetTokenForProcessId(dwProcessIdExplorer);
    	Report(L"Explorer hTokenDst: %d", hTokenDst);
    	HANDLE hTokenSrc = GetTokenForProcessId(dwProcessIdWinLogon);
    	Report(L"Winlogn hTokenSrc: %d", hTokenSrc);
    
    	if (!SetTokenInformation(hTokenSrc, TokenSessionId, &dwSessionId, sizeof(dwSessionId)))
    	{
    		Report(L"Failed to SetTokenInformation:Error: %d", GetLastError());
    		return S_FALSE;
    
    	}
    
    	if (!WTSQueryUserToken(dwSessionId, &hTokenDst)) {
    		Report(L"Error in WTSQueryUserToken:%d", GetLastError());
    	}
    	else {
    		Report(L"WTSQueryUserToken succeeded");
    	}
    
    	if (!EnableWindowsPrivilege(SE_IMPERSONATE_NAME) ){
    		Report(L"Failed to enale Windows Privilege");
    	}
    	else {
    		Report(L"Enabled Windows Privilege");
    	}
    	if (ImpersonateLoggedOnUser(hTokenDst)) {
    		Report(L"Error in ImpersonateLoggedOnUser:%d",GetLastError());
    	}
    	else 
    	{
    		Report(L"ImpersonateLoggedOnUser succeeded");
    	}
    	DWORD dwProcessIdNew = 0;
    	if (CreateProcessWithToken(hTokenSrc, lpszProcessName, (DWORD*)& dwProcessIdNew))
    	{
    		RevertToSelf();
    		*dwProcessId = dwProcessIdNew;
    		return S_OK;
    	}
    	else
    	{
    		RevertToSelf();
    		Report(L"Failed to CreateProcessWithToken: Error: %d", GetLastError());
    		return S_FALSE;
    	}
    
    }
    
    ImpersonateLoggedOnUser fails with error 3.

    Saturday, August 3, 2019 4:12 AM
  • Still, why do you need admin rights for the process that the service starts?

    The process could use the service to do anything that requires admin rights. The service has the token for the user so it can impersonate. The service also has LocalSystem rights, so it can do the things that requires admin rights.


    This is a signature. Any samples given are not meant to have error checking or show best practices. They are meant to just illustrate a point. I may also give inefficient code or introduce some problems to discourage copy/paste coding. This is because the major point of my posts is to aid in the learning process.

    Saturday, August 3, 2019 1:55 PM
  • I am trying to register an object with the RunningObjectTable, so I need the credentials of the interactive user, and because I am trying to monitor start and stop of processes, I need administrator rights

    Monday, August 5, 2019 7:58 AM
  • I am trying to register an object with the RunningObjectTable, so I need the credentials of the interactive user, and because I am trying to monitor start and stop of processes, I need administrator rights

    One possible solution would be to instantiate an out of process COM server in an elevated process but register in the ROT so that un-elevated processes will be able to retrieve an interface pointer from the ROT.

    This can be accomplished through the guidance at Elevated Servers and ROT Registrations.  You should also consider the security implications of this method.

    Monday, August 5, 2019 2:54 PM
  • RLWA32,

    Thanks for the suggestion. That is what I have been trying to do. Launch an elevated process from a windows service with the interactive users credentials. I will check out the link.

    Sagar


    Monday, August 5, 2019 5:14 PM
  • And this is where you keep running into problems because you can't launch an elevated process with the interactive user's credentials when the interactive user doesn't have administrative rights.

    But is there also any reason why you can't make the service the COM server? Or keep the process unelevated and have a private IPC connection (COM, RPC, named pipe) between the process and the service and get the service to do all of the administrative rights.

    By default monitoring the state of processes doesn't require admin rights, you can even connect to the SCM and get the current state of a service with limited user rights. So the big question is are you using this process to monitor the state of the service and restart it if you detect the service has stopped? If so, there are easier and better ways of doing this without forcing the admin rights requirements.

    Starting a process with higher rights than the interactive user has is a major security vulnerability. The shatter attack is still possible in Windows 10 after all.


    This is a signature. Any samples given are not meant to have error checking or show best practices. They are meant to just illustrate a point. I may also give inefficient code or introduce some problems to discourage copy/paste coding. This is because the major point of my posts is to aid in the learning process.

    Monday, August 5, 2019 6:02 PM
  • RLWA32,

    Thanks for the suggestion. That is what I have been trying to do. Launch an elevated process from a windows service with the interactive users credentials. I will check out the link.

    Sagar


    You misunderstand.  The link that I posted does not allow a limited user to instantiate an elevated COM server.  What it does is allow a limited user to obtain an interface pointer from the ROT that has been registered by an elevated server.

    It deals with the problem that ROT registrations by an elevated COM server would not ordinarily be visible to an unelevated client.

    • Edited by RLWA32 Monday, August 5, 2019 6:49 PM
    Monday, August 5, 2019 6:47 PM
  • RLWA32,

    So I figure what I am trying to do is not possible. But I could have sworn that I had this working. My mistake. I have thought of using mailslots for interprocess communication instead of the ROT and COM

    Thanks,

    Sagar

    Tuesday, August 6, 2019 7:47 AM