Click here to Skip to main content
Click here to Skip to main content

Creating a process with Medium Integration Level from the process with High Integration Level in Vista

By , 26 Jan 2008
 

Introduction

Since Windows Vista introduced process integration level (IL), it sometimes is highly desired to be specified when creating a new process. This article suggests the way of launching a process with Medium IL from the process with High IL.

Background

There are the several reasons to specify IL explicitly when launching a new process.

  • The most common task is to launch the application in the end of installation. It’s usual for an application to be executed without elevation, but installer always runs with High IL, therefore the child process will start with the same (High) IL, unless additional enforcement is specified by a developer.
  • If a part if the application is loaded as DLL into explorer.exe, it’s required for the application to execute with the same IL as explorer.exe
  • If an application consists of a number of processes which sends messages to each others, all the processes must have the same IL.
  • If an application connects to the object in ROT, it must have the same IL as the server which registered the object.

There are some solutions for this problem. Kenny Kerr in his article Windows Vista for Developers – A New Series suggests to create new token and correct the elevation level for it, however this is not a complete solution because the set of privileges will still correspond to the set of the parent process. Also, the created process doesn't refer to ROT correctly.

The most correct way is explained in the following articles:

Riding the Vista UAC elevator, up and down

In-depth analysis of Vista UAC and the creation of CreateProcess...Elevated() APIs

However, this way requires to utilize an external DLL (one DLL for 32-bits caller and another one DLL for 64-bits caller).

Solution

This article suggest direct way of launching a process with Medium IL from the process with High IL. In Vista the function looks up for explorer and get a token to its process. Then, the token is duplicated, and its privileges are adjusted. Finally it's passed to CreateProcessWithTokenW function. In the other OS it simply starts new process using CreateProcess().

This is explained step-by-step below:

Prologue

HRESULT CreateProcessWithExplorerIL(LPWSTR szProcessName, LPWSTR szCmdLine)
{
HRESULT hr=S_OK;

BOOL bRet;
HANDLE hToken;
HANDLE hNewToken;

bool bVista=false;
{ // When the function is called from IS12, GetVersionEx returns dwMajorVersion=5 on Vista!
    HMODULE hmodKernel32=LoadLibrary(L"Kernel32");
    if(hmodKernel32 && GetProcAddress(hmodKernel32, "GetProductInfo"))
        bVista=true;
    if(hmodKernel32) FreeLibrary(hmodKernel32);
}

PROCESS_INFORMATION ProcInfo = {0};
STARTUPINFO StartupInfo = {0};

if(bVista)
{

1. Obtain a handle Shell Window and obtain the ID of its process (explorer.exe)

DWORD dwCurIL=SECURITY_MANDATORY_HIGH_RID; 
DWORD dwExplorerID=0, dwExplorerIL=SECURITY_MANDATORY_HIGH_RID;

HWND hwndShell=::FindWindow( _T("Progman"), NULL);
if(hwndShell)
    GetWindowThreadProcessId(hwndShell, &dwExplorerID);

hr=GetProcessIL(dwExplorerID, &dwExplorerIL);
if(SUCCEEDED(hr))
    hr=GetProcessIL(GetCurrentProcessId(), &dwCurIL);

2. Get the token of the process

if(SUCCEEDED(hr))
{
    if(dwCurIL==SECURITY_MANDATORY_HIGH_RID && dwExplorerIL==SECURITY_MANDATORY_MEDIUM_RID)
    {
        HANDLE hProcess=OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwExplorerID);
        if(hProcess)
        {
            if(OpenProcessToken(hProcess, TOKEN_ALL_ACCESS, &hToken))
            {

3. Duplicate the token

                if(!DuplicateTokenEx(hToken, TOKEN_ALL_ACCESS, NULL,
                    SecurityImpersonation, TokenPrimary, &hNewToken))
                    hr=HRESULT_FROM_WIN32(GetLastError()); 

4. Modify the set of privileges by removing/disabling the privileges which doesn’t relay to a process with Medium IL

                if(SUCCEEDED(hr))
                {
                    if(dwCurIL==SECURITY_MANDATORY_MEDIUM_RID && dwExplorerIL==SECURITY_MANDATORY_MEDIUM_RID)
                    {
                        hr=ReducePrivilegesForMediumIL(hNewToken);
                    }//if(dwCurIL==...)

5. Creating new process using CreateProcessWithTokenW

                    if(SUCCEEDED(hr))
                    {
                        typedef BOOL (WINAPI *LPFN_CreateProcessWithTokenW)(
                            HANDLE hToken,
                            DWORD dwLogonFlags,
                            LPCWSTR lpApplicationName,
                            LPWSTR lpCommandLine,
                            DWORD dwCreationFlags,
                            LPVOID lpEnvironment,
                            LPCWSTR lpCurrentDirectory,
                            LPSTARTUPINFOW lpStartupInfo,
                            LPPROCESS_INFORMATION lpProcessInfo
                            );
                        LPFN_CreateProcessWithTokenW fnCreateProcessWithTokenW=NULL;
                        HINSTANCE hmodAdvApi32=LoadLibraryA("AdvApi32");
                        if(hmodAdvApi32)
                            fnCreateProcessWithTokenW=(LPFN_CreateProcessWithTokenW)GetProcAddress(hmodAdvApi32, "CreateProcessWithTokenW");
                        if(fnCreateProcessWithTokenW)
                        {
                            bRet=fnCreateProcessWithTokenW(hNewToken, 0, 
                                szProcessName, szCmdLine, 
                                0, NULL, NULL, &StartupInfo, &ProcInfo);
                            if(!bRet)
                                hr=HRESULT_FROM_WIN32(GetLastError());
                        }
                        else
                            hr=E_UNEXPECTED;
                        if(hmodAdvApi32)
                            FreeLibrary(hmodAdvApi32);
                    }//if(SUCCEEDED(hr))

Epilogue

                    CloseHandle(hNewToken);
                }//if (DuplicateTokenEx(...)
                else
                    hr=HRESULT_FROM_WIN32(GetLastError());
                CloseHandle(hToken);
            }//if(OpenProcessToken(...))
            else
                hr=HRESULT_FROM_WIN32(GetLastError());
            CloseHandle(hProcess);
        }//if(hProcess)
    }//if(dwCurIL==SECURITY_MANDATORY_HIGH_RID && dwExplorerIL==SECURITY_MANDATORY_MEDIUM_RID)
    else if(dwCurIL==SECURITY_MANDATORY_MEDIUM_RID && dwExplorerIL==SECURITY_MANDATORY_HIGH_RID)
        hr=E_ACCESSDENIED;
}//if(SUCCEEDED(hr))
}//if(bVista)

if(SUCCEEDED(hr) && !ProcInfo.dwProcessId)
{// 2K | XP | Vista & !UAC
    bRet = CreateProcess(szProcessName, szCmdLine, 
        NULL, NULL, FALSE, 0, NULL, NULL, &StartupInfo, &ProcInfo);
    if(!bRet)
        hr=HRESULT_FROM_WIN32(GetLastError());
}// 2K | XP | Vista & !UAC

return hr;
}

Using the code

Using the code is very simple. Just copy a code into your project and call CreateProcessWithExplorerIL().

CreateProcessWithExplorerIL(L"C:\\Program Files\\Microsoft Visual Studio\\Common\\Tools\\irotview.exe", NULL);

Comments

This function cannot be used in service processes due to finding shell window will fail. In this case the ID of shell process should be obtained using another way. Moreover, there may be several explorer.exe processes.
The parent of the created process will be taskmgr.exe, but not the caller process.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Volirvag Yexela
Software Developer
Reunion Reunion
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionHow to runmemberisaprgmr16 Oct '12 - 19:30 
I am not too familiar with C++. How do I run this example? I think I need this but in C#.
Do I start the Service manually first? If so, how?
 
any help appreciated.
QuestionThis works well. Thank you.memberflobadob197521 Sep '12 - 1:44 
I used this code to obtain the mapped drives that a user would normally be able to see. Since my application must run elevated, it normally can't see the same mapped drives that the user can. Thanks!
GeneralMethod that doesn't require injection or process token changesmemberLeo Davidson24 Feb '10 - 1:02 
This seems like a nice way to do it, provided you don't care about situations where the Shell is not running (e.g. possibly some Terminal Services application-only setups, perhaps, though I'm not sure):
 
http://brandonlive.com/2008/04/27/getting-the-shell-to-run-an-application-for-you-part-2-how/
 
It gets an interface to Explorer.exe, which should be running in the user's normal context, and asks Explorer to execute a command in its behalf. This is done just using simple, documented COM interfaces and without having to mess around with process tokens or code/DLL injection.
GeneralRe: Method that doesn't require injection or process token changesmemberGif13 Jul '11 - 19:55 
Thanks it works!
I successfully launched IE in protected mode from High Integrity level process using this method.
GeneralLaunching this from a dllmemberYusuf Cinar5 Feb '10 - 8:29 
I added this into a standard dll and tried to call it from InstallShield using DoAction upon user clicking OK-Finish at the end of install. However, it prompts an error "Unknown source 'c$'" and the log shows the following. When I launch the dll custom action in the Executionj Sequence it works, but I dont really understand why dll doesnt work when launched as a DoAction at Finish. I will try creating an exe with the same code and launch an exe custom action instead of dll one.
 
I would appreciate any input. Thanks.
 
Action ended 19:14:01: DLLWrapCleanup. Return value 1.
MSI (c) (BC:68) [19:14:01:576]: Doing action: SetupCompleteSuccess
Action start 19:14:01: SetupCompleteSuccess.
MSI (c) (BC:10) [19:14:03:451]: PROPERTY CHANGE: Adding LAUNCHPROGRAM property. Its value is '1'.
MSI (c) (BC:10) [19:14:04:357]: Doing action: LaunchwithToken2
Action start 19:14:04: LaunchwithToken2.
MSI (c) (BC:10) [19:14:04:373]: Invoking remote custom action. DLL: C:\DOCUME~1\fbaggins\LOCALS~1\Temp\MSI4FD.tmp, Entrypoint: DLL2
Action ended 19:14:13: LaunchwithToken2. Return value 3.
Info 2896.Executing action LaunchwithToken2 failed.
Action ended 19:14:13: SetupCompleteSuccess. Return value 3.
GeneralRe: Launching this from a dllmemberYusuf Cinar5 Feb '10 - 14:09 
I actually figured out the problem. A std dll custom action cant be launched at that stage, it has to be an MSI DLL.
NewsSIX bugs in your code !!memberElmue7 Nov '09 - 15:37 
Hello
 
Your article is interesting.
I want to use it in an installer which runs under admin privileges and at the end of the installation it should run the installed application under the account of the currently logged in user.
______________
 
In your code is missing a: #include "Windows.h"
______________
 
The worst is: Your code does not work.
It has multiple problems.
 
I tried it on Vista under a User account.
I compile the EXE and run it "As Administrator".
The Vista Elevation Windows pops up and asks to enter the admin password.
 
What happens is VERY strange.
 
I narrowed down your code to the follwing test code which demonstrates what is happening:
 
int main(int argc, char* argv[])
{
   DWORD dwExplorerIL=0;
   HRESULT hr=GetProcessIL(2896, &dwExplorerIL);
   printf("ExplorerIL=0x%X", dwExplorerIL);
   return 0;
}
The explorer process has ID 2896 during my tests.
 
The STRANGE thing is that
the output shows ExplorerIL=0x2000 (MEDIUM_RID) when running the EXE as the currently logged in user, but
the output shows ExplorerIL=0x3000 (HIGH_RID) when running the EXE "As Administrator"!!!
 
This is complete nonsense!
The explorer does not change it's IL!!
But exactly this happens!
 
The reason is your crappy error handling:
Instead of returning IL=0 when an error occurres your function GetProcessIL() returns HIGH_RID.
_________________
 
The cause is that
HANDLE hProcess=OpenProcess(PROCESS_ALL_ACCESS, ..., ...);
returns ERROR_ACCESS_DENIED although the program runs "As Administrator".
 
I thought that ALL_ACCESS is quite exaggerated and changed it into PROCESS_QUERY_INFORMATION.
This must be replaced once in
(BUG 1) GetProcessIL() and another time in
(BUG 2) CreateProcessWithExplorerIL()
After that there is no more ERROR_ACCESS_DENIED.
________________
 
And the code has another error:
OpenProcessToken(hProcess, TOKEN_ALL_ACCESS, &hToken)
also returns ERROR_ACCESS_DENIED
 
Again I thought that TOKEN_ALL_ACCESS is quite exaggerated and changed it into
(BUG 3) TOKEN_QUERY in the function GetProcessIL() and into
(BUG 4) TOKEN_DUPLICATE in the function CreateProcessWithExplorerIL().
________________
 
There is another BUG 5:
Your function ReducePrivilegesForMediumIL() will NEVER be called!
First you check
if (dwCurIL==HIGH_RID && dwExplorerIL==MEDIUM_RID)
if this is true you check some lines later
if (dwCurIL==MEDIUM_RID && dwExplorerIL==MEDIUM_RID)
which will NEVER happen!
I don't understand why you wrote this function at all?
If Explorer already runs with MEDIUM_RID why do you want to reduce the privileges to MEDIUM_RID then?
 
Why do you check at all if Explorer runs with MEDIUM IL ?
Did you ever see that it didn't ?
 
So you can completely remove the unused functions SetPrivilege() and ReducePrivilegesForMediumIL()
_______________
 
The BUG 6 is another time crappy error handling:
If GetSidSubAuthority() returns NULL your application will crash.
_______________
 
LoadLibrary("Kernel32") and then FreeLibrary() can be replaced by
GetModuleHandle("Kernel32")
_______________
 
Your comments dont always make sense:
// If Explorer has Medium IL, and the current process has High IL, new token is created, its rights are adjusted and CreateProcessWithTokenW is called. 
// If Explorer has Medium IL, and the current process has High IL, error is returned.
_______________
 
Your code is NOT a cleanly written code and it does NOT have clean error handling.
Nevertheless: thanks for your code!
 
The interesting thing is that although the admin runs your code the new process which is created will run under the user name of the currenlty logged-in user as I can see in Taskmanager.
This is exactly what I need for my installer because the new process must access the correct "Documents and Settings" folder.
 
Elmü
NewsHere the cleaned and bugfixed codememberElmue8 Nov '09 - 5:52 
I cleaned your code and fixed the bugs.
I tested it on Windows 2000,XP,Vista and Windows 7.
Now it works seamlessly.
 
#include "stdafx.h"
#include "windows.h"

#ifndef SECURITY_MANDATORY_HIGH_RID
	#define SECURITY_MANDATORY_UNTRUSTED_RID            (0x00000000L)
	#define SECURITY_MANDATORY_LOW_RID                  (0x00001000L)
	#define SECURITY_MANDATORY_MEDIUM_RID               (0x00002000L)
	#define SECURITY_MANDATORY_HIGH_RID                 (0x00003000L)
	#define SECURITY_MANDATORY_SYSTEM_RID               (0x00004000L)
	#define SECURITY_MANDATORY_PROTECTED_PROCESS_RID    (0x00005000L)
#endif
 
#ifndef TokenIntegrityLevel
	#define TokenIntegrityLevel ((TOKEN_INFORMATION_CLASS)25)
#endif
 
#ifndef TOKEN_MANDATORY_LABEL
	typedef struct  
	{
		SID_AND_ATTRIBUTES Label;
	} TOKEN_MANDATORY_LABEL;
#endif
 
typedef BOOL (WINAPI *defCreateProcessWithTokenW)
		(HANDLE,DWORD,LPCWSTR,LPWSTR,DWORD,LPVOID,LPCWSTR,LPSTARTUPINFOW,LPPROCESS_INFORMATION);
 

// Writes Integration Level of the process with the given ID into pu32_ProcessIL
// returns Win32 API error or 0 if succeeded
DWORD GetProcessIL(DWORD u32_PID, DWORD* pu32_ProcessIL)
{
	*pu32_ProcessIL = 0;
	
	HANDLE h_Process   = 0;
	HANDLE h_Token     = 0;
	DWORD  u32_Size    = 0;
	BYTE*  pu8_Count   = 0;
	DWORD* pu32_ProcIL = 0;
	TOKEN_MANDATORY_LABEL* pk_Label = 0;
 
	h_Process = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, u32_PID);
	if (!h_Process)
		goto _CleanUp;
 
	if (!OpenProcessToken(h_Process, TOKEN_QUERY, &h_Token))
		goto _CleanUp;
				
	if (!GetTokenInformation(h_Token, TokenIntegrityLevel, NULL, 0, &u32_Size) &&
		 GetLastError() != ERROR_INSUFFICIENT_BUFFER)
		goto _CleanUp;
						
	pk_Label = (TOKEN_MANDATORY_LABEL*) HeapAlloc(GetProcessHeap(), 0, u32_Size);
	if (!pk_Label)
		goto _CleanUp;
 
	if (!GetTokenInformation(h_Token, TokenIntegrityLevel, pk_Label, u32_Size, &u32_Size))
		goto _CleanUp;
 
	pu8_Count = GetSidSubAuthorityCount(pk_Label->Label.Sid);
	if (!pu8_Count)
		goto _CleanUp;
					
	pu32_ProcIL = GetSidSubAuthority(pk_Label->Label.Sid, *pu8_Count-1);
	if (!pu32_ProcIL)
		goto _CleanUp;
 
	*pu32_ProcessIL = *pu32_ProcIL;
	SetLastError(ERROR_SUCCESS);
 
	_CleanUp:
	DWORD u32_Error = GetLastError();
	if (pk_Label)  HeapFree(GetProcessHeap(), 0, pk_Label);
	if (h_Token)   CloseHandle(h_Token);
	if (h_Process) CloseHandle(h_Process);
	return u32_Error;
}
 
// Creates a new process u16_Path with the integration level of the Explorer process (MEDIUM IL)
// If you need this function in a service you must replace FindWindow() with another API to find Explorer process
// The parent process of the new process will be svchost.exe if this EXE was run "As Administrator"
// returns Win32 API error or 0 if succeeded
DWORD CreateProcessMediumIL(WCHAR* u16_Path, WCHAR* u16_CmdLine)
{
	HANDLE h_Process = 0;
	HANDLE h_Token   = 0;
	HANDLE h_Token2  = 0;
	PROCESS_INFORMATION k_ProcInfo    = {0};
	STARTUPINFOW        k_StartupInfo = {0};
 
	BOOL b_UseToken = FALSE;
 
	// Detect Windows Vista, 2008, Windows 7 and higher
	if (GetProcAddress(GetModuleHandleA("Kernel32"), "GetProductInfo"))
	{
		DWORD u32_CurIL;
		DWORD u32_Err = GetProcessIL(GetCurrentProcessId(), &u32_CurIL);
		if (u32_Err)
			return u32_Err;
 
		if (u32_CurIL > SECURITY_MANDATORY_MEDIUM_RID)
			b_UseToken = TRUE;
	}
 
	// Create the process normally (before Windows Vista or if current process runs with a medium IL)
	if (!b_UseToken)
	{
		if (!CreateProcessW(u16_Path, u16_CmdLine, 0, 0, FALSE, 0, 0, 0, &k_StartupInfo, &k_ProcInfo))
			return GetLastError();
 
		CloseHandle(k_ProcInfo.hThread);
		CloseHandle(k_ProcInfo.hProcess); 
		return ERROR_SUCCESS;
	}
 
	defCreateProcessWithTokenW f_CreateProcessWithTokenW = 
		(defCreateProcessWithTokenW) GetProcAddress(GetModuleHandleA("Advapi32"), "CreateProcessWithTokenW");
 
	if (!f_CreateProcessWithTokenW) // This will never happen on Vista!
		return ERROR_INVALID_FUNCTION; 
	
	HWND h_Progman = ::GetShellWindow();
 
	DWORD u32_ExplorerPID = 0;		
	GetWindowThreadProcessId(h_Progman, &u32_ExplorerPID);
 
	// ATTENTION:
	// If UAC is turned OFF all processes run with SECURITY_MANDATORY_HIGH_RID, also Explorer!
	// But this does not matter because to start the new process without UAC no elevation is required.
	h_Process = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, u32_ExplorerPID);
	if (!h_Process)
		goto _CleanUp;
 
	if (!OpenProcessToken(h_Process, TOKEN_DUPLICATE, &h_Token))
		goto _CleanUp;
 
	if (!DuplicateTokenEx(h_Token, TOKEN_ALL_ACCESS, 0, SecurityImpersonation, TokenPrimary, &h_Token2))
		goto _CleanUp;
 
	if (!f_CreateProcessWithTokenW(h_Token2, 0, u16_Path, u16_CmdLine, 0, 0, 0, &k_StartupInfo, &k_ProcInfo))
		goto _CleanUp;
 
	SetLastError(ERROR_SUCCESS);
 
	_CleanUp:
	DWORD u32_Error = GetLastError();
	if (h_Token)   CloseHandle(h_Token);
	if (h_Token2)  CloseHandle(h_Token2);
	if (h_Process) CloseHandle(h_Process);
	CloseHandle(k_ProcInfo.hThread);
	CloseHandle(k_ProcInfo.hProcess); 
	return u32_Error;
}
 
int main(int argc, char* argv[])
{
	DWORD u32_Err = CreateProcessMediumIL(L"C:\\Windows\\System32\\Calc.exe", NULL);
 
	printf("CreateProcessMediumIL() exited with error %d\r\n", u32_Err);
	Sleep(2000);
	return 0;
}

GeneralRe: Here the cleaned and bugfixed codememberBrunhilde8 Nov '09 - 7:07 
Thanks for the code!
It is incredible that we have to write so much code because the <*!stupid!*> guys at Microsoft did not imeplement a "runas CurrentUser" !!!
GeneralRe: Here the cleaned and bugfixed codememberflamierd15 Feb '11 - 5:40 
Hey, super coder genius...
 
_T("Progman").
 
If you are going to correct code at least make sure it is compatible with the current set of compilers/OS, such as 2008 and XP.
 
Why you have to be the ultimate jerk is unknown, but good luck with that.
What you say?

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130523.1 | Last Updated 26 Jan 2008
Article Copyright 2008 by Volirvag Yexela
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid