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

Vista UAC: The Definitive Guide

By , 21 Mar 2008
 

Introduction

When I began this project, I didn't think it would take me two weeks to complete. Well, here I am, finally, writing the article portion of the whole thing. Don't worry, I took really good notes. I am splitting this article into three major segments: The first segment is a broad overview for those who really don't care about the nitty-gritty details of UAC and just want to get to the good stuff. The second segment gets into the really nasty stuff...for those of you who enjoy information overload. The last segment covers UAC Permanent Links - load multiple elevated DLLs, execute multiple elevated functions, and start multiple processes elevated - all of that with just displaying one UAC dialog from a non-elevated process. And now, without further ado, the article.

The Big Picture

Ah. Vista. Many things have been said about it. Some good, some bad, but that doesn't really matter. Vista looks pretty, has new APIs, and has UAC (Microsoft TechNet, Wikipedia).

Ah. UAC. For the acronymically challenged, it stands for User Account Control. The bane of numerous existing software applications, and the whole reason I ended up creating the Elevate package. I initially created it for my own users, but figured the rest of you developers need it as well.

We would all like to think our applications automatically work under Vista. However, the rude awakening that follows simply plastering Vista on the end of Win95/98/Me/NT/2000/XP/2003 causes us to actually do something about it. If you are reading this, I can only assume you have run into one of several barriers to successful application deployment that UAC introduces.

The majority of this article covers how UAC operates and how CreateProcessElevated(), half of the Elevate package, is possible. The other half of the Elevate package is to solve the major annoyance of UAC for non-elevated applications: the constant requirement to go through the UAC dialog to perform all operations that can only be done within elevated processes. If you find yourself in this position but don't want to switch your whole application to only run as an elevated process, this article is for you as well.

A few Google searches on UAC usually turns up someone's blog discussing how to use ShellExecuteEx() with the undocumented "runas" verb to force a process to start elevated. Alternatively, a modified manifest file can result in the same thing when a user runs the executable. There is also an article here on CodeProject on how to bypass ShellExecuteEx() by using an NT Service to do the job, but it has various issues besides the obvious system security-related ones.

But if you are like me, ShellExecuteEx() is not an option, and I didn't want to bypass UAC either - Microsoft made UAC for a reason, and I want to play nice. I have a large library, and depend heavily on CreateProcess(). Unfortunately, Microsoft failed to make a CreateProcessElevated() API for people like me. Fortunately, I found a way to make a series of CreateProcess...Elevated() APIs with nearly complete functionality.

Before I get to CreateProcessElevated(), let's look at some code that uses ShellExecuteEx():

  SHELLEXECUTEINFOA TempInfo = {0};

  TempInfo.cbSize = sizeof(SHELLEXECUTEINFOA);
  TempInfo.fMask = 0;
  TempInfo.hwnd = NULL;
  TempInfo.lpVerb = "runas";
  TempInfo.lpFile = "C:\\TEMP\\UAC\\Test.exe";
  TempInfo.lpParameters = "";
  TempInfo.lpDirectory = "C:\\TEMP\\UAC\\";
  TempInfo.nShow = SW_NORMAL;

  ::ShellExecuteExA(&TempInfo);

That is your basic, run-of-the-mill, let's force Vista to elevate this process, function call. As was stated before, the magic word here is "runas". As far as I can tell, the "runas" verb is completely undocumented in the MSDN Library. At least, I never found any reference to it during my research.

Let me take this time right now and say that, wherever possible, avoid using the "runas" verb and prefer manifest file modifications. Which brings me to, uh, manifest modifications.

If you have never used a manifest, you probably live in the console realm. Or a cave. Or both. I generally only use a manifest when I need to let the Windows loader know to load the latest Common Controls. Manifests are required to load Common Controls 6 and later (theme support) via what are called Side-by-Side Assemblies (SxS, you may have noticed the "assembly" directory in C:\WINDOWS and wondered what that is). This is Microsoft's solution to DLL Hell, and it generally works.

A manifest is a plain-ol' XML file. Let's look at your average manifest file with Common Controls 6 support:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" 
          manifestVersion="1.0"> 
<dependency> 
    <dependentAssembly> 
        <assemblyIdentity 
            type="win32" 
            name="Microsoft.Windows.Common-Controls" 
            version="6.0.0.0" 
            processorArchitecture="X86" 
            publicKeyToken="6595b64144ccf1df" 
            language="*" 
        /> 
    </dependentAssembly> 
</dependency> 
</assembly>

The Internet tells us that, for administrative privileges under Vista, modify the above file to be:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" 
          manifestVersion="1.0"> 
<dependency> 
    <dependentAssembly> 
        <assemblyIdentity 
            type="win32" 
            name="Microsoft.Windows.Common-Controls" 
            version="6.0.0.0" 
            processorArchitecture="X86" 
            publicKeyToken="6595b64144ccf1df" 
            language="*" 
        /> 
    </dependentAssembly> 
</dependency> 
<v3:trustInfo xmlns:v3="urn:schemas-microsoft-com:asm.v3">
  <v3:security>
    <v3:requestedPrivileges>
      <v3:requestedExecutionLevel level="requireAdministrator" />
    </v3:requestedPrivileges>
  </v3:security>
</v3:trustInfo>
</assembly>

<BZZZZZZZT!>

Wrong answer! Apparently, a malformed v3 manifest file will cause Windows XP SP2 to crash. And not any ordinary crash either. A Blue Screen Of Death (BSOD), complete with reboot. Without actually taking CreateProcess() apart, this is probably due to an XML parser crash in kernel mode (only way I know of to BSOD XP is to crash the kernel in ring 0). Also, apparently v2 manifests are significantly more stable and yet still work. So, for maximum stability, the correct format for the manifest file should be:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" 
          manifestVersion="1.0"> 
<dependency> 
    <dependentAssembly> 
        <assemblyIdentity 
            type="win32" 
            name="Microsoft.Windows.Common-Controls" 
            version="6.0.0.0" 
            processorArchitecture="X86" 
            publicKeyToken="6595b64144ccf1df" 
            language="*" 
        /> 
    </dependentAssembly> 
</dependency> 
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
    <security>
        <requestedPrivileges>
            <requestedExecutionLevel 
                level="requireAdministrator" 
                uiAccess="false"/>
        </requestedPrivileges>
    </security>
</trustInfo>
</assembly>

requestedExecutionLevel's "level" option can be one of 'asInvoker', 'requireAdministrator', or 'highestAvailable'. I will cover the "uiAccess" option later on in this article.

For those who are COM/DCOM/whatever addicts and heavily invested in that technology, you're probably already way ahead of me, but I'll post some redundant moniker out-of-process elevation code for you:

HRESULT CreateElevatedComObject(HWND hwnd, 
                                REFCLSID rclsid, 
                                REFIID riid, 
                                __out void ** ppv)
{
    BIND_OPTS3 bo;
    WCHAR  wszCLSID[50];
    WCHAR  wszMonikerName[300];

    StringFromGUID2(rclsid, wszCLSID, cntof(wszCLSID)); 
    HRESULT hr = StringCchPrintf(wszMonikerName, 
                                 cntof(wszMonikerName)), 
                                 L"Elevation:Administrator!new:%s", 
                                 wszCLSID);
    if (FAILED(hr))
        return hr;
    memset(&bo, 0, sizeof(bo));
    bo.cbStruct = sizeof(bo);
    bo.hwnd = hwnd;
    bo.dwClassContext = CLSCTX_LOCAL_SERVER;
    return CoGetObject(wszMonikerName, &bo, riid, ppv);
}

// cntof() is defined as:
#define cntof(a) (sizeof(a)/sizeof(a[0]))

I honestly have no idea what that code does. But apparently, it is magically delicious. I did spot the "Elevation:Administrator!new:" string in ShellExecuteEx(), so I can only assume the code does work. I try to avoid COM wherever and whenever possible. COM is kind of like the plague.

User Interface Integration

If you are going to do process elevation, you have got to do it right and do it stylishly as well. The Vista shield icon is plastered pretty much everywhere where there is an administrative task to be performed. Task Manager, for instance, won't show all running processes from all users until you click the button with the shield icon and go through the UAC elevation process.

Fortunately, Microsoft makes it easy to plant the shield icon onto dialog buttons:

GetDlgItem(IDC_SOMEBUTTONID)->SendMessage(BCM_SETSHIELD, 0, TRUE);
// OR use the new macro:
Button_SetElevationRequiredState(ButtonHWnd, fRequired);

Unfortunately, however, putting the shield icon anywhere else is a pain. You can call the new Vista-only function SHGetStockIconInfo() to extract it as an HICON:

HICON ShieldIcon;
SHSTOCKICONINFO sii = {0};
sii.cbSize = sizeof(sii);
SHGetStockIconInfo(SIID_SHIELD, SHGFI_ICON | SHGFI_SMALLICON, &sii);
ShieldIcon = sii.hIcon;

However, unless you are designing a Vista-only application, you will want to do the whole LoadLibrary()/GetProcAddress() thing for SHGetStockIconInfo().

Alternatively, LoadIcon() with IDI_SHIELD can be used to get an HICON, but that apparently loads a "low-quality" shield icon. The new LoadIconMetric() API for Vista is a better solution because it loads a "high-quality" shield icon, which will apparently be used for high DPI displays.

UAC Weirdness

So far, I've just covered the most prominent portions of UAC, but UAC is way more than just displaying a dialog to the user. It is a way of life. Or something like that. And, as with most things in life, UAC can be downright weird.

Many of us have existing applications. Some of us even have badly written applications that write to the "Program Files" directory. And some applications are really badly behaved, and write both to the Windows directory and the HKLM registry key.

UAC treats misbehaving (non-elevated) applications as illegitimate children. There is something called the "virtual store" for each application that misbehaves. This consists of files and registry keys that are written to in places that are deemed bad to write for non-elevated processes. What happens when a write operation occurs is, the OS copies the original file to the user's virtual store and redirects all requests to that location instead. Thus, the application makes changes to the file/registry key in the virtual store, and not the actual location it was intending to make changes to. But, only for that user. Other users will see the original file or a copy in their virtual store.

But wait! It gets weirder. Let's say the application goes to delete a file it has modified in Program Files. Well, the OS redirects the request to the virtual store instead. Yet, even though the file was deleted, if the application goes back, it can see that the file still exists. Once removed from the virtual store, the OS allows the application to see the original file. However, if the application attempts to delete the file again, it will cause an error to occur. It makes sense, but it is weird.

UAC also causes a number of other minor issues to come up: Interactive Services (of the GUI variety) are completely hosed (i.e., creating windows from NT Services), administrative shares have major issues, various functions that take or create tokens (e.g., HANDLE hToken) are affected by the split token (e.g., LogonUser), and side-by-side isolation issues.

Which, of course, brings me to split tokens. Split tokens are just weird. When you log onto Windows Vista and later (obviously), you get a split token. Basically, the logon architecture (which changed...again...for Vista) takes your initially really powerful user token and creates a second token with all Administrator privileges stripped out. This second token is used to launch all applications essentially as what XP called a Limited User Account (LUA). When UAC prompts for elevation and you accept, the first token is used to create the process instead of the second token. Essentially, what UAC elevation prompts are asking is, "Do you really want me to use your super powerful administrative token to start this application?"

Another weird UAC thing is the elevation dialog. If you let it sit there for an extended period of time, it will automatically cancel itself. Found that out during my research.

The last weird thing about UAC is that once a process has been started as an elevated process, it becomes very difficult to start a process non-elevated from the elevated process. This becomes a major nuisance for authors of software installers. Us, software developer types, like to let users try out the software at the end of an installation so the user has the tendency to actually use the software and have a greater chance of buying it. There is an article that shows how to ride the Vista elevator by using a global hook and hooking Explorer.exe to create a process at a lower Integrity Level.

CreateProcess() Fails With ERROR_ELEVATION_REQUIRED

Those are the basics of UAC, all in one concise location. For many people, ShellExecuteEx(), modified manifests, switching to HKCU, and not writing to naughty places on the hard drive, are "good enough" solutions. However, some of you want to know more. And some of you need CreateProcess() and all the power it contains (and a few people might need ShellExecute()/ShellExecuteEx() with custom verbs).

Now, on to the Elevate package and CreateProcessElevated().

CreateProcess() fails miserably for processes in Vista that require elevation via their manifest file. It should be noted that the manifest file doesn't really say "you have to use the administrative token to start this process". Instead, it says, "you can't use a token with fewer than these rights to start this process". So, the error message returned from CreateProcess(), ERROR_ELEVATION_REQUIRED (740), is somewhat misleading.

Regardless, if you are reading this, I can only assume you are desperate for a CreateProcessElevated() solution. Which is what the Elevate package is for. The Elevate package (Elevate_BinariesAndDocs.zip) consists of two components plus comprehensive documentation: the Elevation API DLL (Elevate.dll) and the Elevation Transaction Coordinator (Elevate.exe). Both must be placed in the same directory for the elevation process to work properly. The Elevation API DLL exports the following functions:

Link_Create()
Link_CreateAsUser()
Link_CreateWithLogon()
Link_CreateWithToken()
Link_Destroy()
Link_CreateProcessA()
Link_CreateProcessW()
Link_ShellExecuteExA()
Link_ShellExecuteExW()
Link_ShellExecuteA()
Link_ShellExecuteW()
Link_LoadLibraryA()
Link_LoadLibraryW()
Link_SendData()
Link_GetData()
Link_SendFinalize()

CreateProcessElevatedA()
CreateProcessElevatedW()
CreateProcessAsUserElevatedA()
CreateProcessAsUserElevatedW()
CreateProcessWithLogonElevatedW()
CreateProcessWithTokenElevatedW()
SH_RegCreateKeyExElevatedA()
SH_RegCreateKeyExElevatedW()
SH_RegOpenKeyExElevatedA()
SH_RegOpenKeyExElevatedW()
SH_RegCloseKeyElevated()
ShellExecuteElevatedA()
ShellExecuteElevatedW()
ShellExecuteExElevatedA()
ShellExecuteExElevatedW()

IsUserAnAdmin()

You will note that the elevated APIs have very similar names to their non-elevated counterparts (e.g., CreateProcess() and CreateProcessElevated()). And this package includes both ANSI and Unicode versions too (A vs. W). You may note that IsUserAnAdmin() is already a function exported from Shell32.dll, but MSDN says it may go away. IsUserAnAdmin() currently defers to the Shell32.dll version, but can fall back to internal code if it does disappear.

Moving along. You probably have existing code that looks something like this:

Result = CreateProcess(...ParameterList...);
if (!Result)  return FALSE;
...Do something with process like WaitForSingleObject()...
::CloseHandle()'s;

which works great until you try to launch a process that requires elevation. To use the Elevate package, just drop it on the system, and LoadLibrary()/GetProcAddress() the CreateProcessElevatedA() function:

Result = CreateProcess(...ParameterList...);  // Existing line of code.
if (!Result && GetLastError() == ERROR_ELEVATION_REQUIRED)
{
  HMODULE LibHandle = LoadLibrary("Elevate.dll");
  if (LibHandle != NULL)
  {
    DLL_CreateProcessElevated =
           (typecast)GetProcAddress("CreateProcessElevatedA");
    if (DLL_CreateProcessElevated)
    {
      ...Custom handle changes here *...
      Result = DLL_CreateProcessElevated(...ParameterList...);
      ...Custom handle connections here *...
    }
    FreeLibrary(LibHandle);
  }
}
if (!Result)  return FALSE;
// Continue as usual with existing code...

The "...ParameterList..." options are _nearly_ identical. The only time you have to change the parameter is when you use the STARTF_USESTDHANDLES flag in the STARTUPINFO structure you pass in. If you do redirection of the standard handles (stdin, stdout, stderr), things can get a bit funky. Read the documentation, but, in short, you will need to familiarize yourself with named pipes and read the rest of this article in its entirety.

Note that you must use LoadLibrary()/GetProcAddress(). The DLL will intentionally fail to load on any Windows OS prior to Vista.

ShellExecute...Elevated()?!

Some of you are probably raising eyebrows as to the need for a ShellExecuteElevated() set of APIs. There are two reasons to do this. The first reason is actually a special case problem that the "runas" verb introduces. The lpVerb/lpOperation parameter only allows one verb to be used at a time. So, if someone needs to force a process to run elevated (that would normally run non-elevated) and, for example, use the "print" verb, the regular ShellExecute()/ShellExecuteEx() won't cut it.

The second reason is because someone may specifically need to run a ShellExecute()/ShellExecuteEx() command from within an elevated environment. There is no way to do this either.

Now, I will admit the need for the ShellExecuteElevated() APIs is going to be rare, but I am including them for completeness.

Demo Application

For the lack of a better spot to put this, the demo application is an example of CreateProcessElevated() in action, in all of its glory. To use the demo, first download it, and extract it to some directory you know how to get to from the Command Prompt. Then, start a non-elevated Command Prompt, and go to the directory you extracted the files to. Type in "TestParent", and press Enter. What follows should look like this:

Screenshot - Elevate01.png

Screenshot - Elevate02.png

Screenshot - Elevate03.png

Ignoring the obvious reference to my knowledge of cool phrases, what happens is really quite impressive. Normally, an elevated console-based program started from a non-elevated console-based program would have a separate console window. In this case, TestChild.exe (elevated) and TestParent.exe (non-elevated) share the same console. Additionally, TestChild.exe's stderr is being routed to TestParent.exe. This is possible by very carefully sifting through tons and tons of documentation and some experimentation.

Also, while the program doesn't show it, TestChild.exe shares the same environment variables as TestParent.exe.

The Nitty Gritty

Before I can discuss how Elevate.dll and Elevate.exe work to make the demo even possible, I have to cover some of the nastier details of how UAC elevation works. Bear with me, it gets pretty in-depth.

When I started this project, I wanted to avoid using ShellExecuteEx(), so to do that, I had to figure out what made the function "tick". My first thought was, "Well, they have to call CreateProcess() and related kin somewhere along the line. So, there's some trick to the call, right?" My first stop was the Detours traceapi.dll file. I hooked it into a test process with the ShellExecuteEx() API, and...nada. Nothing. I thought maybe traceapi.dll was broken, so I wasted a day on figuring out that maybe ShellExecuteEx() wasn't using CreateProcess() at all.

So, the next step was to dig into the actual call with the Visual Studio Disassembler. I quickly realized I needed to run out to the symbol store and get the symbols for the Vista Shell32.dll and other related DLLs. I know Microsoft massages their symbols on the symbol server to trim out stuff they don't want people knowing. I was hoping UAC elevation wouldn't be part of that. Turns out, they didn't remove that information, or just simply forgot to do so. Either way, you can be eternally grateful the information was there.

Stepping through ShellExecuteEx() is quite confusing. In fact, I'm still not sure what some of the stuff does. If you are following along with a debugger, your call stack will eventually look like this:

     shell32.dll!CShellExecute::ExecuteNormal()  + 0x7a
     shell32.dll!ShellExecuteNormal()  + 0x33
     shell32.dll!_ShellExecuteExW@4()  + 0x42
     shell32.dll!_ShellExecuteExA@4()  + 0x4a
     ShellExecuteTest.exe!main()  Line 18 + 0xc
     ShellExecuteTest.exe!mainCRTStartup()  Line 259 + 0x19
     kernel32.dll!@BaseThreadInitThunk@12()  + 0x12
     ntdll.dll!__RtlUserThreadStart@8()  + 0x27

Looks promising, right? Well, under a normal function, yeah, that's probably about where you would execute a command. In this case, however, ShellExecuteEx() is just getting warmed up to...start a new thread. Seriously. To start a new process, a new thread is started. It is sickening. So, we step through with the debugger inside the new thread until the call stack looks like this:

     shell32.dll!CExecuteApplication::Execute()  + 0x22
     shell32.dll!CExecuteAssociation::_DoCommand()  + 0x5b
     shell32.dll!CExecuteAssociation::_TryApplication()  + 0x32
     shell32.dll!CExecuteAssociation::Execute()  + 0x30
     shell32.dll!CShellExecute::_ExecuteAssoc()  + 0x82
     shell32.dll!CShellExecute::_DoExecute()  + 0x4c
     shell32.dll!CShellExecute::s_ExecuteThreadProc()  + 0x25
     shlwapi.dll!WrapperThreadProc()  + 0x98
     kernel32.dll!@BaseThreadInitThunk@12()  + 0x12
     ntdll.dll!__RtlUserThreadStart@8()  + 0x27

Now, that looks promising, right? Well, to get here was an abysmal mess that takes something like five hours of pressing F10 and F11. You go through a huge mess of COM objects. Yup, that's right. COM is now involved in starting a new process. And that means bringing in a mess of DLLs with it. But guess what? We ain't done yet.

Inside the Execute() method, a call to CExecuteApplication::_VerifyExecTrust() is made. This uses COM, and takes a LOT of CPU (along with a bunch of blatant and unnecessary replication, code-wise). I sort of gave up trying to figure out what it did. My general impression was that it most likely has something to do with the pop up dialogs received when launching an EXE downloaded from the Internet (IZoneIdentifier). Probably looks for the existence of a NTFS stream called 'Zone.Identifier' so it can pop up that nifty (and annoying) dialog you see for downloaded files before executing them.

I backed out of _VerifyExecTrust(), and went down the only remaining path. The call stack turned hilarious:

     shell32.dll!AicpMsgWaitForCompletion()  + 0x36
     shell32.dll!AicpAsyncFinishCall()  + 0x2c
     shell32.dll!AicLaunchAdminProcess()  + 0x2ee
     shell32.dll!_SHCreateProcess()  + 0x59d0
     shell32.dll!CExecuteApplication::_CreateProcess()  + 0xac
     shell32.dll!CExecuteApplication::_TryCreateProcess()  + 0x2e
     shell32.dll!CExecuteApplication::_DoApplication()  + 0x3c
     shell32.dll!CExecuteApplication::Execute()  + 0x33
     shell32.dll!CExecuteAssociation::_DoCommand()  + 0x5b
     shell32.dll!CExecuteAssociation::_TryApplication()  + 0x32
     shell32.dll!CExecuteAssociation::Execute()  + 0x30
     shell32.dll!CShellExecute::_ExecuteAssoc()  + 0x82
     shell32.dll!CShellExecute::_DoExecute()  + 0x4c
     shell32.dll!CShellExecute::s_ExecuteThreadProc()  + 0x25
     shlwapi.dll!WrapperThreadProc()  + 0x98
     kernel32.dll!@BaseThreadInitThunk@12()  + 0x12
     ntdll.dll!__RtlUserThreadStart@8()  + 0x27

Gotta love the names given to the functions. It sort of eggs you on to see what's next. _DoExecute(), _Execute(), _TryApplication(), _DoCommand()... Okay, now we're going to create the process. Oh, wait, never mind, now we're going to create the process. Oh, sorry about that, _now_ we're going to create the process... The marketing engine has gotten into the source code. Poor developers. I feel sorry for you guys who have to work with that mess every single day.

At any rate, the function of interest is AicLaunchAdminProcess(). Once we get to AicpMsgWaitForCompletion(), it will have gone too far. It took me almost two days of scratching my head to figure out how entering a MsgWaitForMultipleObjects() could cause UAC to display the elevation dialog unchecked and then continue as if nothing had happened when Accept/Cancel was clicked.

Turns out, not only is a new thread started and a half dozen COM objects instantiated, but RPC is brought into the mix as well. RPC (Remote Procedure Call for the acronymically challenged) is a pretty old technology, but mostly just used by Microsoft for NT Services. It combines with MIDL and allows you to, uh, call remote procedures. The target function could be on another computer or the same computer. Sort of one of those unexploited security holes because it is also an obscure technology that few know how to use.

And, this is where things get interesting. The RPC call is to a brand new NT Service in Vista called AppInfo (UUID "{201ef99a-7fa0-444c-9399-19ba84f12a1a}"). AppInfo is where the magic happens. But before I get to the magic, a short discussion on Sessions and Integrity Levels is in order.

Vista Sessions and Integrity Levels

Part of the changes in Vista and how UAC works is the introduction of what are known as Sessions. They are also occasionally referred to as "Secure Desktops". Vista has two Sessions, but could technically have many more than that. Session 0 and Session 1 are the official names. Session 0 is where all NT Services reside. Session 1 is where the "WinSta0\Default" desktop resides along with Explorer and user applications. Just to clarify, Window Stations are not Sessions.

It is important to note that some documentation on Vista UAC implicitly claims each Session is supposedly completely segmented from communication with other Sessions. This is not true. Most likely, the authors are referring to window messages or an early beta. Official Microsoft documentation, Impact of Session 0 Isolation on Services and Drivers in Windows Vista, says to use RPC and Named Pipes to communicate between processes running on different Sessions. RPC, Named Pipes, and sockets are all positively confirmed Interprocess Communication (IPC) mechanisms under Vista.

The other major change in Vista with UAC is what are known as Integrity Levels (ILs). There are four Integrity Levels: System (NT Services), High (Elevated processes), Medium (most user processes), Low (processes like Protected-mode IE). Every object created has an IL associated with it, and ILs are checked before DACLs. The process/thread IL is checked against the object IL before granting access to it. It is important to note that objects created by elevated and system processes, by default, have a Medium Integrity Level. (For the observant person, that last sentence is the key to why the Elevate package works.)

AppInfo and consent.exe

AppInfo is, obviously, the key to UAC elevation. ShellExecuteEx() forwards all elevation requests to the AppInfo NT Service via an RPC call. AppInfo then turns around and calls an executable in the SYSTEM context, called "consent.exe". As the name implies, this is the executable that brings up the dialog that the user consents to.

However, consent.exe is not your average, run-of-the-mill application. What you see when the dialog is active is not Session 1's WinSta0\Default. Nope. What you see is a desktop on Session 0. Hence the reason it is called a "secure desktop". consent.exe takes a snapshot of the screen, switches to the Session 0 desktop, plops the screenshot on the desktop, and displays the dialog. It only looks like Session 1's desktop. You can't click on anything but the dialog, because there literally isn't anything to click on. Once you accept/cancel, the desktop is switched again back to Session 1 and consent.exe exits.

AppInfo then takes the results from consent.exe and determines if it needs to start a new process (i.e., you accepted the elevation request). AppInfo then creates a process using the full administrative token (remember that split token thing?) of the logged in user on the Session 1 desktop with a High Integrity Level. If you fire up Task Manager, you can see that elevated processes indeed run as the current user. We know it also runs on the Session 1 desktop because GUI windows can be created, seen, and interacted with.

To create a process as the current user on a different desktop in a different Session is a seven stage process:

  1. AppInfo goes and talks to the Local Security Authority to get the elevated token of the logged in user of Session 1.
  2. AppInfo loads up a STARTUPINFOEX structure (new to Vista), and calls the brand new Vista API InitializeProcThreadAttributeList() with room for one attribute.
  3. OpenProcess() is called to get a handle to the process that initiated the RPC call.
  4. UpdateProcThreadAttribute() is called with PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, and uses the handle retrieved in step 3.
  5. CreateProcessAsUser() is called with EXTENDED_STARTUPINFO_PRESENT and the results of steps 1 and 4.
  6. DeleteProcThreadAttributeList() is called.
  7. Results are gathered, and handles are cleaned up.

Once AppInfo succeeds in launching the process, it transfers some information back over the RPC interface to the application that called ShellExecuteEx(). ShellExecuteEx() meanders around a bit, and cleans up after itself, and eventually returns through the whole mess of function calls, closes the thread, and returns to the caller.

CreateProcessElevated() Without ShellExecuteEx()?

Once I had learned about roughly where in ShellExecuteEx() makes its RPC call to AppInfo and how AppInfo worked, I wanted to know if it was possible to create a fully-functional CreateProcessElevated() without making that insanely expensive call to ShellExecuteEx(). By fully functional, I meant I wanted complete control over the STARTUPINFO structure, with support for the STARTF_USESTDHANDLES flag.

This process took me about three days to complete, and was quite exhausting. I eventually narrowed everything down to a few lines of assembler inside AicLaunchAdminProcess():

mov     edi, [ebp+VarStartupInfo]
mov     eax, [edi+_STARTUPINFOW.lpTitle]
mov     [ebp+VarStartupInfo_Title], eax
mov     eax, [edi+_STARTUPINFOW.dwX]
mov     [ebp+VarStartupInfo_X], eax
mov     eax, [edi+_STARTUPINFOW.dwY]
mov     [ebp+VarStartupInfo_Y], eax
mov     eax, [edi+_STARTUPINFOW.dwXSize]
mov     [ebp+VarStartupInfo_XSize], eax
mov     eax, [edi+_STARTUPINFOW.dwYSize]
mov     [ebp+VarStartupInfo_YSize], eax
mov     eax, [edi+_STARTUPINFOW.dwXCountChars]
mov     [ebp+VarStartupInfo_XCountChars], eax
mov     eax, [edi+_STARTUPINFOW.dwYCountChars]
mov     [ebp+VarStartupInfo_YCountChars], eax
mov     eax, [edi+_STARTUPINFOW.dwFillAttribute]
mov     [ebp+VarStartupInfo_FillAttr], eax
mov     eax, [edi+_STARTUPINFOW.dwFlags]
mov     [ebp+VarStartupInfo_Flags], eax
mov     cx, [edi+_STARTUPINFOW.wShowWindow]
mov     [ebp+VarStartupInfo_ShowWindow], cx
...
push    eax
push    ebx
push    0FFFFFFFFh
push    [ebp+hwnd]
lea     eax, [ebp+VarStartupInfo_Title]
push    eax
push    [ebp+hMemToWinSta0_Desktop]
push    [ebp+VarExpandedCurrDir]
push    [ebp+ArgCreationFlags]
push    [ebp+arg_8]  ; Probably bInheritHandles
push    [ebp+VarExpandedCommandLine]
push    [ebp+VarExpandedApplicationName]
push    StaticBindingHandle
lea     eax, [ebp+pAsync]
push    eax
call    _RAiLaunchAdminProcess@52

RAiLaunchAdminProcess() takes a whole bunch of parameters, and then routes the whole thing behind the scenes through a very nasty MIDL call. You will note the lea (Load Effective Address) on [ebp+VarStartupInfo_Title]. For you non-assembler gurus, this is essentially passing the address of the address of a data block from Title to ShowWindow. So, this means only a limited amount of information from STARTUPINFO is actually passed onto the target process. This tells me that AppInfo doesn't handle very much information.

At this point, I chucked the idea of doing my own RPC thing with AppInfo out the window, cried for two seconds for wasting a couple days, and then went to figure out how to use ShellExecuteEx() to do what I wanted.

Routing via ShellExecuteEx()

I quickly decided to create a split DLL/EXE approach. My target audience was initially my own customer base, so it needed to be clean and easy to understand. The DLL would export functions that looked and felt a lot like Win32 APIs. The DLL would then package up each function's data and shuttle it across to the EXE, which would take that data, unpack it, and execute the correct function as an elevated process.

My initial approach was to shuttle data across using the lpParameters member of SHELLEXECUTEINFO. To my surprise, doing that caused an extremely meaningless error message to pop up: "The data area passed to a system call is too small." (Error code 122). It took a lot of experimentation, but apparently sending more than 2048 bytes (2K) of data in the lpParameters member causes that error message to occur.

My solution was to pass the process ID and thread ID to Elevate.exe from Elevate.dll, and use those bits of information to open a named pipe, and connect to a named pipe server running in Elevate.dll to send the information.

About Named Pipes

But I'm getting way ahead of myself there. I got the idea for using named pipes once I learned about Integrity Levels and the fact that objects created at higher ILs only have a Medium Integrity Level...the same level as non-elevated processes.

But pipes are HANDLE based. And, there's the "gotcha". Remember that whole roundabout mess to get a process elevated? Well, while HANDLEs are inheritable, the process that actually starts the elevated process is AppInfo, and not the process making the ShellExecuteEx() call. And, even if HANDLEs were passable, there is the issue of the Session 0 to Session 1 barrier. So, passing raw HANDLE values won't work.

Someone may point out that AppInfo's method of starting a process does actually inherit the handles of the original process and would include the Standard HANDLEs (stdin, stdout, stderr). However, while this is true and the HANDLEs are inheritable, AppInfo would have to populate the STARTUPINFO structure with HANDLEs that can't cross the Session 0 to Session 1 barrier.

The solution lies in named pipes. Named pipes have, um, names. Sometimes ANSI, sometimes Unicode, but those names are strings. And, unlike HANDLEs, strings are passable via IPC. CreateNamedPipe() and CreateFile() with the same string equates to a HANDLE that points at the same pipe. And, since both objects have a Medium Integrity Level, it works too. For security reasons and for DACL reasons, the DLL calls CreateNamedPipe() and the EXE calls CreateFile() to connect to the named pipe.

The MSDN Library confirms that starting a process using named pipes for redirection is possible.

How Elevate Works

Let's assume CreateProcessElevated() is being called. Elevate.dll is loaded up, and the function is called. Just before ShellExecuteEx() is called, one named pipe and three event objects are created. Elevate.dll starts Elevate.exe as an elevated process. Upon successful return (process started), Elevate.dll waits for Elevate.exe to finish initializing. Note that the event objects time out after 10 seconds and bail. This is so that the application doesn't hang.

Once the two processes are in sync with each other, Elevate.dll packs up the data sent to the function, and sends it as a packet of data to Elevate.exe. Elevate.exe receives the packet of data and unpacks it.

From here, Elevate.exe prepares to run the process. It attaches to the console of the process that started Elevate.exe. Also, I use the STARTF_USESTDHANDLES flag every so often, and Elevate.exe takes named pipe names and sets up the appropriate HANDLEs for stdin, stdout, and stderr along with building the rest of the STARTUPINFO structure. Attaching to a console gives access to that console's stdin, stdout, and stderr HANDLEs.

After that, the correct function is executed with correct parameters for everything. The only thing interesting to note is the use of the CREATE_SUSPENDED flag. This flag starts the process suspended. The reason for this is so that information about the process and main thread are sent back to Elevate.dll. Remember, raw HANDLEs can't be passed, but process and thread IDs can be. Also, keep in mind that it is possible for the started process to have such a short lifetime that it would exit before Elevate.dll could open appropriate HANDLEs for synchronization purposes.

Elevate.dll then receives the information about the process, processes it, and then fires the event to let Elevate.exe know that it has received and processed the information.

Elevate.exe resumes the process (if applicable), and exits. Elevate.dll returns, and the caller resumes normal operations.

UAC Permanent Links

The Elevation API DLL (Elevate.dll) exports the following functions:

Link_Create()
Link_CreateAsUser()
Link_CreateWithLogon()
Link_CreateWithToken()
Link_Destroy()
Link_CreateProcessA()
Link_CreateProcessW()
Link_ShellExecuteExA()
Link_ShellExecuteExW()
Link_ShellExecuteA()
Link_ShellExecuteW()
Link_LoadLibraryA()
Link_LoadLibraryW()
Link_SendData()
Link_GetData()
Link_SendFinalize()

Now, you may be wondering, or have already wondered, what good those functions are. The term UAC Permanent Link is something I made up. Hey, there's nothing wrong with a little artistic license, right?

What exactly is a UAC Permanent Link? Suppose you have an application that you wrote and worked great on Windows XP, but required you to create a separate executable for Vista due to elevation issues. You plastered the shield icon wherever elevation would take place, and released the updated software.

Bam. Some users complained about the excessive UAC dialog use. However, you don't want to convert the whole application to require elevation to operate. This is where UAC Permanent Links come in handy.

Link_Create() instantiates a UAC Permanent Link. It launches Elevate.exe, which shows the UAC dialog. The user clicks "Allow", and Elevate.exe starts. The HANDLE that is returned from Link_Create() is then able to be passed to the other Link_...() functions. When the application is done with the UAC Permanent Link (e.g., at the end of the program), Link_Destroy() is called. Elevate.exe exits, and all resources are cleaned up.

The possibilities for this are endless. Once the UAC dialog has been displayed and allowed, your non-elevated program has free reign in the elevated process space.

CreateProcessElevated() and other exported functions actually instantiate a UAC Permanent Link, call Link_CreateProcess() or whatever is appropriate, and then call Link_Destroy().

Instead of starting processes, consider making a DLL. You can even display dialogs that return data to the non-elevated process. It does get somewhat complicated as all data sent/received has to be serialized and unserialized, but it may be worth saving the overhead of starting multiple processes, and two-way communication with the non-elevated application could be important as well. If you take this route, I recommend looking at the source code of Elevate to get a feel for how it works.

The DLL approach also works great if you need to load one or more API hook DLLs into the elevated process space.

The nice part about UAC Permanent Links is that this approach falls completely within Microsoft's defined parameters for how UAC is to be used.

Source Code

For those of you who download the source code, please note that the source code is just the Application Layer (Safe C++ terminology). The Base Library is not included. The source code is being made available in case someone finds a bug, they can point it out and maybe even fix it. I also included it so you could more or less follow along with this article.

More .manifest Issues

As stated earlier, an associated manifest file with an executable can have a requestedExecutionLevel's "level" of 'asInvoker', 'requireAdministrator', or 'highestAvailable'. 'asInvoker' means a non-elevated token or higher can start the process. 'requireAdministrator' means elevated or higher can start the process. 'highestAvailable' means the process will start as the highest level permitted by the user's split token.

I said earlier that I would discuss the 'uiAccess' option. 'uiAccess' is designed to grant a select set of non-elevated processes access to elevated process user interfaces. This is specifically designed for UI automation tasks (e.g., macro recorder and playback tools). Most of the time, you will want to specify the 'uiAccess' option as 'false'.

But what if you want to specify it as 'true'? Well, get ready to spend some money. You'll see recommendations for Verisign Code Signing Certificates, but they run ridiculous sums of green ($400/year). Alternatively, you can try one of these companies...just be sure they support Code Signing (and possibly time stamping - the more 'yes's, the better).

According to the comments, Tucows.com offers Comodo Code Signing certificates for $75/year. Very nice.

If you just need to try something out for personal use, you won't need to shell out any dough, but you will need the latest Authenticode tools and an EXE to sign that contains a manifest with uiAccess set to 'true'. If you have that set up, run the following commands from an elevated command prompt:

1) Create a trusted root certificate:

   Browse to the folder that you wish to contain a copy of the certificate.
   In the command shell, execute the following commands:

   makecert -r -pe -n "CN=Test Certificate" -ss PrivateCertStore testcert.cer

   certmgr.exe -add testcert.cer -s -r localMachine root

2) Sign your file:

   SignTool sign /v /s PrivateCertStore /n "Test Certificate"
            /t http://timestamp.verisign.com/scripts/timestamp.dll
            YourApp.exe

   Where YourApp.exe is your application.

Note: If you sign your executable with a certificate from a trusted code signing certificate authority (CA), the orange UAC elevation dialog becomes a dull gray.

IShellExecuteHook vs. ShellExecuteWithHookElevated()

For those who use IShellExecuteHook DLLs, you are probably well aware, Microsoft has "deprecated" this COM interface for Vista, and is not currently offering an alternative. It is turned off, by default, because of problems with older hooks crashing the Vista shell. The interface can be turned on by setting:

[HKLM or HKCU]\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer

EnableShellExecuteHooks=1 (DWORD)

An alternative to writing a IShellExecuteHook DLL is to hook ShellExecute(), ShellExecuteEx(), and IsUserAnAdmin(). When IsUserAnAdmin() is called by those functions, rewrite the ShellExecute() call to use the ShellExecute...Elevated() calls. This method is not perfect because various COM interfaces bypass the APIs to get at the elevation mechanism. You also have to be really careful to not get caught in an infinite loop (the Elevate package calls ShellExecuteEx()).

Another approach is to do a combination of the two. Write an IShellExecuteHook DLL and then also hook IsUserAnAdmin(). This has a better chance of catching everything that is about to do the whole UAC thing, but then you have to worry about if the COM interface will even work.

Additional References

History

  • June 2007: Initial release.
  • July 2007: Added ShellExecute...Elevated() and discussion on Hook DLLs.
  • August 2007: Corrected discussion on Sessions. Minor article cleanup.
  • March 2008: Major rewrite of Elevate package. UAC Permanent Links. Article modifications. Fixed bugs with starting processes as another user (medium IL vs. high IL issue).

License

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

About the Author

Thomas Hruska
Web Developer
United States United States
Member
Been writing software for a really long time - something like 18 years. Started on the TI/99-4A, moved to the Tandy 1000, and somehow managed to skip all the lousy hardware/software jumps (286, 386, first Pentiums, Win95, etc.)
 
I now run a small software business called CubicleSoft with a few products you might be interested in. VerifyMyPC and MyUpdate Toolkit are the most popular. I'm also the author of a book called "Safe C++ Design Principles".

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   
GeneralMy vote of 5membergndnet19 Jul '12 - 3:33 
very good article
GeneralMy vote of 1memberMember 82318782 Nov '11 - 5:52 
project files are not included, unhelpful comments about manifest users
GeneralNot the best way...memberCodyBatt2 Oct '11 - 15:24 
I wasted half a day adapting my code to work with this approach. Moved things into a separate process, set up pipes for comminication over stdin, stdout etc. Finally, I was able to load the dll but when I called CreateProcessElevated, I got the elevation prompt and after clicking OK I got an Access Violation in elevate.dll! Doh. It didn't look like all the code was available in order to rebuild with symbols and debug the crash.
 
So, I decided to try the COM Elevation Moniker. Way easier! No extra binaries required. No need to marshal data over stdin/stdout. The author complains about COM as "the plague" but I don't know why. The COM approach is much simpler. The technique used here is a clever hack, but it is not the way Microsoft intended you to do this. For example: I noticed when debugging with elevate.exe that when it puts up the elevation dialog I could continue messing around in my application UI while the elevation dialog was up. Not what I would expect. This doesn't happen with the COM technique which is clearly the way Microsoft intended for you to elevate. After implementing this I found that someone else has already written up a step-by-step walk through just in case the letters C O M are scary to you:
 
A step-by-step guide to elevate and write in the Registry[^]
GeneralGreat article! Am I missing something, sample does not work as advertised...membermiesch116 Jun '11 - 11:48 
Really well-written and informative article. Much appreciated.
 
When I try to run the TestParent.exe sample from a non-elevated command prompt, I get a message "Error running process." This behavior has been observed on both Vista and Windows 7. Am I doing something wrong?
 
Thanks,
Mike
GeneralMy vote of 5memberlxl1233326 Oct '10 - 17:23 
5
GeneralMY vote of 5memberspinoza18 Sep '10 - 0:44 
Excellent, I love your article, fun to read and I could really sympathize and follow your experiences thru this adventure.
//Spinoza

GeneralMy vote of 5memberspinoza18 Sep '10 - 0:43 
Excellent, I love your article, fun to read and I could really sympathize and follow your experiences thru this adventure.
GeneralMy vote of 5memberfloste21 Aug '10 - 7:29 
In depth and clear at the same time
GeneralA Quick-Start Guide of Process Mandatory Level Checking and Self-elevation under UACmemberjialge17 Mar '10 - 19:03 
A Quick-Start Guide of Process Mandatory Level Checking and Self-elevation under UAC[^]
Newsrunas as ordinary usermvpElmue24 Feb '10 - 13:35 
Hello
 
Microsoft implemented the "runas" to run a process as administrator.
But the stupid guys forget to imeplement a command to run a process as normal user.
This problem occurres when an installer has finished his work "as admin" and then wants to start the freshly installed application under the account of the currently logged in user.
It is incredible, but true: This is not provided by Microsoft!
 
If you are looking for a solution of this common problem, you find a working code to do this here:
http://www.codeproject.com/Messages/3264682/Here-the-cleaned-and-bugfixed-code.aspx
 
Elmü
GeneralCopy a file into your installed product directorymemberpvandijk2817 Feb '10 - 9:06 
FYI:
 
To copy a file with elevation, without using p/invokes i use:
 
System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo();
startInfo.UseShellExecute = true; // make sure we UseShellExecute as it is capable of doing elevation
startInfo.WorkingDirectory = System.Environment.CurrentDirectory; //or other if you prefer
startInfo.FileName = "cmd.exe";
startInfo.Arguments = String.Format("/c copy /y \"{0}\" \"{1}\" /b", fromFilename, toFilename);
startInfo.Verb = "runas"; // use elevation
 
// Two lines below make the UAC dialog modal to this app
startInfo.ErrorDialog = true;
startInfo.ErrorDialogParentHandle = this.Handle; // this.Handle...e.g. this is a Form.
 
System.Diagnostics.Process p = System.Diagnostics.Process.Start(startInfo);
 
// block this UI until the launched process exits
// I.e. make it modal
p.WaitForExit();
 
return p.ExitCode == 0;
GeneralAbsolutely great got-my-5-out-of-5-articlememberJP_S15 Dec '09 - 21:34 
Thank you for writing this.
It is a very informative yet not dull but nice to read piece of work.
The highest of my personal appreciation.
 
One question might be allowed:
Do you have any idea how to use this by C#.
 
Greetings
JP_S
 
It is better to keep one's tongue and be thought of as dumb than to open the mouth and remove all doubts.
GeneralMy Vote of 5memberHasbi Allah31 Oct '09 - 14:07 
great work actually Excellent.
 
i was wondering how to consume your great dll with c# i tried [DllImport] but with no success.
 
i hope that you guide with method's names and how to use them in a c# example .
 
thanks again keep up the good work Thumbs Up | :thumbsup:
GeneralLink_Create & Link_LoadLibraryWmemberLau26 Oct '09 - 22:45 
Hello,
First of all, thanks for this great set of codes. It solves a lot of problem I had. Right now, I am stuck in the following code:
 
	
HMODULE LibHandle = LoadLibrary(_T("elevate.dll"));
if (LibHandle != NULL){
   DLL_Link_CreateType LC = (DLL_Link_CreateType)GetProcAddress(LibHandle,"Link_Create");
   if (LC){
      hLink = LC();    
      if (hLink!=NULL){
	  DLL_Link_LoadLibraryWType LL = (DLL_Link_LoadLibraryWType)GetProcAddress(LibHandle,"Link_LoadLibraryW");
	  if (LL) {
	     DWORD dwLib = (DWORD)LL(hLink,_T("Mydll.dll"));
 
          // dwLib is always 0
          // all EXEs & DLLs are in the same folder.				

The Link_LoadLibraryW() call would always fail with error code 183 - Cannot create a file when that file already exists
Does anyone know the reason why? Help appreciated.Thanks
 
-Lau
Question64 bit versions of elevate.dll and elevate.exe ?memberMarc Meertens30 Sep '09 - 0:57 
Hi Thomas,
 
Excellent article ! Software works as expected.
Now I have a problem that I need this functionality under Windows 7, 64 bit.
Any chance of posting 64 bit versions of the mentioned programs ?
 
Thanks.
Marc.
Generaleasier with Vtubemembercengizsf22 Sep '09 - 16:01 
I think easier to use Vtube activex D'Oh! | :doh:
at:
http://www.pda-tec.com/Vtube.shtml
GeneralRe: easier with Vtubemembercertit22 Feb '10 - 17:40 
Vtube does not start a process with elevate credentials like the "run as administrator" prompt. Vtube runs the process as the system account which is not the same as a real account. Infact the same can be done with running "psexec -i -s executable.exe".
GeneralWaowh !memberElmue27 Jun '09 - 9:31 
Hey,
this a realllllllllllllllllly great article!!
 
I love the real crackers which make an experienced programmer look like a pale beginner!
 
I'm sorry that I could rate your article only with a five. (A ten is not possible)
 
Elmü
GeneralWOW, Excellent - You Got My 5memberThe Code Guru27 Apr '09 - 23:37 
You really nailed the subject and answered all questions in a very clear style and I thank you for your efforts put in this article. Thumbs Up | :thumbsup:
 
"Imagination is more important than knowledge.."
{Albert Einstein}

QuestionShellExecuteAsUser?memberJoeF227 Feb '09 - 8:33 
We are using the elevate dll in a service on Vista in session 0, and it works really well creating elevated processes in the user session.
The only drawback is that ShellExecuteEx doesn't allow to create a process as user in the user session.
I know that this is really a Windows API issue, and there are workarounds, e.g., by searching the file extension in the registry and executing the associated program. But it would be nice to have a better way.
I was wondering if there is a way to do this within this library. Kind of creating the elevate exe process in the user session, with the named pipe across sessions.
Thanks.
-Joe
QuestionCreateProcessElevated and process environment blockmemberGkarRacer21 Nov '08 - 9:23 
I think there is a potential issue with the default environment block usage of CreateProcessElevated. Passing NULL for lpEnvironment, the child process normally inherits the parent's environment block. But, under the elevated regime, it will inherit the environment of the elevated parent (elevate.exe in this case). In the ElevateDLL.cpp file ProcessEnvironment does nothing if you have NULL for lpEnvironment. But, I think this is incorrect. Shouldn't it call GetEnvironmentStrings and pass that instead? Otherwise the child process will have the wrong environment set.
 
This is how ShellExecuteEx works, i.e. using the wrong environment block. But, I see no reason for CreateProcessElevated to be limited this way.
 
I haven't tested fully yet to be certain that this is the case. But, if I understand correctly how elevation works, then this is what will happen.
GeneralStandard user with UAC problemmemberMember 149478222 Sep '08 - 6:42 
I try to install a program on VISTA with a Standard user ID. The UAC asks me for administrator password to continute the installation because the installer requires administrator privilege. Once the installer stared with administrator privilege, is there a way to launch a program with Standard user privilege?
GeneralElevate the Main Process LatermemberRonakkumar Patel25 Jul '08 - 9:01 
We have a Main Process Called SMC.exe which is running as normal user.
 
In that GUI there is a Module that accesses WMI Performance Data. Is there a way When some one clicks on that Module Button it asks to Elevate Main Process.
 
(Same as what Task Manager does when you click on Show Running Process by all User)
 
Right now we have to start the Main Process elevated if we have to access that particular part of the produce.
GeneralCreateProcessWithLogonElevatedmemberpibo063 Jul '08 - 0:12 
Hi,
I'm rewriting a part of a VB6 application to fix it with Vista UAC.
At this time I use then CreateProcessElevatedA function an it works fine.
I've a domain user account defined for the application with administration level.
I would like to use this credential with the function CreateProcessWithLogonElevated so users don't have to know this user and password.
I've a problem with that function, it takes few second to respond and return me 258 in err.lastdllerror
 
Here is my code :
 
If wUser = "" Then
'' if no user define, use CreateProcessElevated
Result = GetProcAddress(LibHandle, "CreateProcessElevatedA")
If Result Then
Result = DLL_CreateProcessElevated(vbNullString, App, sec1, sec2, False, pclass, 0&, WorkDir, sinfo, pinfo)
End If
Else
'' if user
Result = GetProcAddress(LibHandle, "CreateProcessWithLogonElevatedW")
If Result Then
Result = DLL_CreateProcessWithLogonElevated(wUser, "", wPassword, LOGON_NETCREDENTIALS_ONLY,
vbNullString ,App ,pclass, 0&, WorkDir, sinfo, pinfo)
End If
If Result = 0 Then
MsgBox "erreur " ; Str(Err.LastDllError)
End If
End If
GeneralRe: CreateProcessWithLogonElevatedmemberBillJam1123 Nov '10 - 12:46 
I'm getting error 258 also with CreateProcessWithLogonElevated(W). It works fine with the regular call to CreateProcessWithLogonW. I wonder what we are doing wrong???
 
Doesn't appear that the author monitors this topic much so I doubt I will ever know.
GeneralElevate program itselfmemberSega-Zero26 Jun '08 - 3:26 
Hello. Really inspired of your article. thx a lot, you are maybe the only one who do not suggest to add a manifest Dead | X|
I'd like to ask you. As I noticed after the elevation is done, the unstripped token is applied and any "unprotected" functions are available. So, I have a question - I need to elevate the right not only to another executable, but to the program itself to do some portions of actions. I want to call th elevation dialog, ask user to change the rights and then make a code with full administrative rights and all this within single exe. Is it possible?
 
Thx for any answer and would be glad for any info on how to do it.
 
P.S. I do not want to make the program elevated forever. I wanna do it in only one place I need, and then re-launch itself non-elevated. Thank you.
Questionissue related to security and Visual themes? [modified]memberJeanLuc_26 May '08 - 3:18 
Hi, first congrats on one of the most interesting article I've read on the subject. This certainly has enlighted me a lot.
 
Which leads me to this situation for which I wonder if this is related or not.
 
The problem context is this: we have an MFC42 application, no manifest in it, that runs fine in Vista. Call this TOOL.exe. We have built a DLL that provides an API to the TOOL.exe. Call this INTERFACE.dll.
 
INTERFACE.dll simply CreateProcess() to start the TOOL.exe.
 
Now, when building an MFC with VS2005 application, TEST.exe, that loads INTERFACE.DLL, all runs fine. This TEST.exe does not take advantage nor uses Visual Themes.
 
We then have another application, APPCLIENT.exe, which does take advantage of Visual Themes. This application loads a plugin DLL, which loads or INTERFACE.dll which CreateProcess() the TOOL.exe.
 
In this case, it fails loading the TOOL.exe (seems TOOL.exe receives an ExitProcess() of some sort).
 
However, when we change properties of the APPCLIENT.exe to disable Visual Themes, all runs fine.
 
I was wondering if this might be related to elevated process or not, or, if this is a known issue? I've been google search for weeks without any luck.
 
Thanks in advance!
 
Here is the typical CreateProcess call we do:
 
m_Started = CreateProcess(
		0,
		(LPSTR) strProcessNameInQuotes.c_str(),
		&SecurityAttributes, &SecurityAttributes,
		FALSE,
		CREATE_SUSPENDED|ABOVE_NORMAL_PRIORITY_CLASS|CREATE_DEFAULT_ERROR_MODE|CREATE_PRESERVE_CODE_AUTHZ_LEVEL|CREATE_NEW_PROCESS_GROUP,
		0 /*szEnvironment*/,
		m_ToolExePath.c_str(),
		&si,
		&m_pi
		);
 
It does not matter if we use si.lpDesktop = "winsta0\\default"; or not either. The CREATE_SUSPENDED is normal: we do some Write/Read process memory (hence we use CreateProcess to get a process handle instead of using ShellExecute) and then a Resume thread on the process thread. It does not changes either with or without security descriptors than we init this way:
 
	// Create the security secriptor
	SECURITY_DESCRIPTOR SecurityDescriptor;
	InitializeSecurityDescriptor(&SecurityDescriptor, SECURITY_DESCRIPTOR_REVISION);
	SecurityDescriptor.Dacl = NULL;
 
	// Create the security attributes
	SECURITY_ATTRIBUTES SecurityAttributes;
	SecurityAttributes.nLength = sizeof(SecurityAttributes);
	SecurityAttributes.lpSecurityDescriptor = &SecurityDescriptor;
	SecurityAttributes.bInheritHandle = true;
 
and there is no difference either if we use the full set of process creation flags like above, or just the CREATE_SUSPENDED.
 
modified on Monday, May 26, 2008 10:42 AM

GeneralAnd the RegSetValueEx function? [modified]memberCaetano.n.a15 May '08 - 2:46 
1 - I need this function to set values in registry. Do you have this function? If you have registry operations examples i really stay interested.
2 - I use c++builder 2007, so i don't know to make manifest files. Do you know?
Thanks!
 
Regards
 
modified on Thursday, May 15, 2008 8:58 AM

AnswerClarification on the difference between Sessions and WinStations (long)memberJakob Bohm7 Apr '08 - 2:16 
The article as it currently stands completely confuses the levels of divisions used by Windows Vista. The UAC dialog is not displayed in Session 0 (almost nothing is), it is displayed on the "Secure Desktop" named WinSta0\WinLogon in the same session (session 1 or higher) as the WinSta0\Default containing the program that needs the elevation.
 
Windows Vista divides onscreen threads at 4 levels within each other, from outermost to innermost:
  1. Sessions are the outermost layer and have numbers such as 0, 1, 2, ... Each session has its own copy of the parts of Windows that deals with the user interface, even the display driver! Sessions were introduced in "NT 4.0 Terminal Server Edition" to represent the "virtual computer" for each of the active remote terminal users. Services always run in Session 0. In NT 4.0 and Windows 2000, the display and keyboard physically connected to the computer was always connected to Session 0 too, and all other session numbers were thus remote terminals. In Windows XP and Windows 2003 this was still the default behaviour, but two features ("Fast User Switching" and "Remote Desktop Console Access") allowed this to be switched around, so the local display got connected to a different session number, while session 0 got connected to a remote terminal or to nothing at all. In Windows Vista, the local display is almost never connected to session 0, unless an old incompatible service tries to display a Window on WinSta0\Default in Session 0, in which case a special popup dialog allows the user to temporarily connect the display and keyboard to the almost empty desktop in session 0 to deal with it. The names of Events, Mutexes, Shared Memory, Named Pipes etc. have their own namespace for each session, plus one namespace for objects to be shared across sessions. If you pass a name such as "Local\\YourCompanyFoo" to the Win32 APIs, it means "\\DirectoryForYourSession\\YourCompanyFoo". If you pass "Global\\YourCompanyFoo", it means "\\DirectoryForSharedObjects\\YourCompanyFoo" "DirectoryForSharedObjects" may or may not be the same as "DirectoryForSession0", depending on the Windows version. If you pass simply "YourCompanyFoo", Windows starts guessing about your intentions, so only do that on NT 4.0-not-terminal-server or older. Security-wise, each token on Windows 2000 or later includes the applicable session number as a separate property. The easiest way to determine the current session id of your own process is to call GetProcessSessionId(). The session id of all processes can be seen in the process tab in Task Manager.
  2. WinStations ("Window Stations") is a much less memory hungry feature originally intended to do what ended up being done with Sessions. WinStations have names such as WinSta0, WinSta-0x1-0x2 etc. Introduced in NT 3.51 (or maybe even NT 3.10), each Window Station logically represents a Display/Keyboard combination connected to the computer. In the real world, each Session contains only the following WinStations: WinSta0 is connected to the display and keyboard of the Session, almost all programs run there. Winsta-HexNumbers is connected to nothing at all and is used to run any non-interactive services, one WinSta-Xxxx for each user account. Each Window station has its own clipboard, global atoms table and global display graphics state. Security-wise, each Window Station has its own security descriptor, just like events, mutexes etc. do. You can get a handle to the WinStation of your own process by calling GetProcessWindowStation().
  3. Desktops represent a logical screen layout with zero or more Windows on it. Desktops have names such as "Default" "WinLogon" "MyOwnName" (there is a documented API to create your own) and exists as items within a Window Station, resulting in full names such as "WinSta0\\Default". "Desktops" were introduced in NT 3.10. Window handles, window messages, "WindowHooks" etc. all live within a single desktop and cannot cross that boundary. Unlike Sessions and WinStations, each thread in a process may be connected to a different Desktop within the WinStation of its process, and this association can be changed on the fly in any thread which does not have any windows (not even hidden ones). Security-wise, each Desktop has its own security descriptor. You can get a handle to the Desktop of your own process by calling GetThreadDesktop(), for other tasks, OpenDesktop() and OpenInputDesktop() and CreateDesktop() are interesting APIs.
  4. Integrity levels are new in Windows Vista. They take a small number of enumerated values known as "Low", "Medium", "High" and "System" and are stored in the token as an extra Sid. Any object with an ACL also has an associated integrity level and cross-integrity-level-ACL stored in a magic entry in the SACL (The one that normally specifies auditing logging rules). Various UI objects such as windows and the per-thread message queues are also associated with the integrity level of the process that created them. Integrity levels compensate for the (wrong) assumption made by all versions of Windows and Unix, that processes run under the same user logon need never be protected from one another ("there are no evil programs, only deliberately evil users") and also compensate for the fact that the user interface part of NT-based Windows had no security protection amongst programs on the same Desktop.
Thus what the UAC prompt does is:
  1. Copy a screenshot of WinSta0\Default to WinSta0\WinLogon, but dim its colors.
  2. Tell WinSta0 to switch the output to WinSta0\WinLogon.
  3. Display the dialog and wait for an answer or timeout
  4. Tell WinSta0 to switch back to WinSta0\Default
  5. Tell WinLogon to launch the chosen program under the "High"-level token associated with the "Medium"-level token that requested the prompt.
All in the same session as the one the request came from.
To go the other way, processes running with enough privileges can get the other of a linked pair of tokens using GetTokenInformation() with the TokenLinkedToken argument.
 
This message is hasty and is not to be taken as serious, professional or legally binding.
I work with low level C/C++ in user and kernel mode, but also dabble in other areas.

GeneralRe: Clarification on the difference between Sessions and WinStations (long)memberThomas Hruska7 Apr '08 - 4:17 
That is an extensive explanation. When I wrote the article, I was still trying to make heads and tails of how all four layers worked. Clearly I need to go back and re-write that section of the article. Somehow Elevate worked. Wink | ;)
 
I do have a couple questions: According to you and everything I've read, NT Services run in Session 0. AppInfo is an NT Service. So it runs in Session 0. Everything I've read indicates that Consent.exe just displays the dialog (and faded background) and AppInfo manages everything else.
 
1) I made the assumption that AppInfo would start Consent.exe within the same Session. Getting a screenshot of something in Session 1 baffled me. So, just to confirm, does AppInfo, which resides in Session 0, start Consent.exe in Session 1? If so, that definitely explains a number of things.
 
2) Referring to the order of operations of "what the UAC prompt does": How does one copy a screenshot between desktops _before_ switching? Shouldn't it be: BitBlt() on "WinSta0\Default", fade content, switch output, display full screen window containing faded content, display dialog, get user response, switch output, launch program?
 
Thanks for the GetTokenInformation() tip. That will help to clarify a few bits of the 7-step process - probably will make it 8 or 9 steps. Smile | :)
 
--
Thomas Hruska
CubicleSoft President

GeneralRe: Clarification on the difference between Sessions and WinStations (long) [modified]memberDecipator11 May '08 - 6:39 
Consent.exe is started in the session of the user (beginning with 1). It is a SYSTEM process.
1. start consent.exe in Session 1
consent.exe does
2.get a screenshot
3.call SwitchDesktop to winsta0\WinLogon on Session 1 and not Session 0 as you stated in your article. Desktops are secured by default (if you set the DACL correct and disallow hooks).
3.call SetThreadDesktop
4.Show Prompt
5.call SwitchDesktop and SetThreadDesktop to get back to default desktop
And now there are at least 2 possible solutions.
I. Let the AppInfo service handle the request. It can simply call CreateProcessAsUser and set the Token SessionID.
II. Consent.exe can also create the process since it is SYSTEM user and has TCB privilege.
It calls
1. WTSQueryUserToken to get the users token (using session ID).
2. GetTokenInformation for the twin token
3. CreateProcessAsUser for the rest
Of course there must be a way to return the result of the operation.
 
I don't know what way is used.
 
modified on Wednesday, May 14, 2008 8:17 AM

GeneralFor the standard (limited) users who do not know admin passwordmemberk77724 Mar '08 - 10:25 
I never work on Vista and so my question will sound stupid.
When you say "UAC elevated", it seems the admin user's token privileges are elevated enough for the installation when he/she agrees on a consent dialog.
 
Is it possible to write a program so that the standard (or limited) users can run the install packages without a credential dialog?
GeneralRe: For the standard (limited) users who do not know admin passwordmemberThomas Hruska24 Mar '08 - 15:06 
When an Administrator is logged in they, by default, have many privileges stripped out of their security token. The full administrator token is only used when the elevation dialog is consented to. Limited users can install packages by entering the Administrator login credentials when prompted by the UAC dialog.
 
There is no way around this on a default setup.
 
--
Thomas Hruska
CubicleSoft President

GeneralRe: For the standard (limited) users who do not know admin passwordmemberk77725 Mar '08 - 3:41 
I know. I should have refined my question. You said "no way around this on a default setup". Does that mean there is a way while UAC is enabled? Will your library work for that case?
If not, is your library mainly to suppress the subsequent UAC consent dialogs for the users who are members of administrators?
Sorry; I do not intend to be rude. I just want to understand UAC and your library. TIA.
GeneralRe: For the standard (limited) users who do not know admin passwordmemberThomas Hruska25 Mar '08 - 15:29 
It seems to me that there is some sort of lack of communication here - probably on my behalf (I have that tendency). The Elevate package is designed to be integrated into existing software. The background for this package is that I use the CreateProcess() API call extensively within my own software. Microsoft, however, failed to include a CreateProcessElevated() API (or equivalent) into Windows and their ShellExecuteEx() solution would just not cut it. So I made the Elevate package that exports the APIs I need to use. Behind the scenes, the Elevate package uses the Microsoft-approved method of elevation to execute a near-feature-complete CreateProcess() call from an elevated process (Elevate.exe).
 
In other words, this is not a magical library that you just install somewhere and then it works to bypass UAC. It requires specific code within the application to get it to work. And it still uses the Microsoft-recommended solution to invoke an elevated application (i.e. it invokes the UAC dialog through approved means).
 
Now what I meant by "a default setup" is that UAC is turned on by default but can be turned off by the Administrator of the system. However, those settings only affect the Administrator account - Limited Users would still have to use Administrator credentials to elevate a process. (Actually, the medium IL Administrator token, by default, basically has the equivalent privileges of a Limited User).
 
With the latest version of the Elevate package, I introduce UAC Permanent Links. These are a fairly complex beast to describe and are probably what confused you. They can be used to only show the UAC dialog one time per application run. They still follow the Microsoft-approved approach to using UAC. Again, this depends on the application utilizing either the Elevate package or a similar approach within their code.
 
The best way to see how UAC works in conjunction with the Elevate package is to use Vista and try out the demo application at the top of the article.
 
If you are wanting to bypass UAC altogether, you probably need to look at NT Services - those processes run at the highest IL the OS offers. Installing a NT Service requires Administrator privileges. However, having user code bypass UAC (e.g. via a NT Service) to start processes at ILs above medium is not the Microsoft-approved approach to proper Windows application design.
 
--
Thomas Hruska
CubicleSoft President

GeneralRe: For the standard (limited) users who do not know admin passwordmemberk77726 Mar '08 - 2:01 
I am completely happy with your sincere explanation. Thank you very very much!!!
GeneralBlogs...mvpJeffrey Walton22 Mar '08 - 13:56 
> A few Google searches on UAC usually turns
> up someone's blog discussing...
I'm at the point I wish I could exclude all blogs except those on MSDN...
 
Jeff
GeneralArticle UpdatedmemberThomas Hruska12 Mar '08 - 18:41 
The Elevate package has gone through a drastic re-write. I've created what I like to call "UAC Permanent Links". One of the major gripes about UAC is the fact that every elevated operation has to go through the elevation dialog. UAC Permanent Links are the answer to that - the non-elevated application only needs to display the dialog one time and then a Permanent Link is established. A side-effect of this approach is a much simpler DLL.
 
--
Thomas Hruska
CubicleSoft President

GeneralI need helpmemberdjsoul12 Mar '08 - 13:16 
im building a windows application, i have a frame inside my window i want 2run another application inside this frame (several old program i have done previously)my application is like a toolkit.So i want to run those applications(*.exe) inside this frame. The problem is When the application is on the computer (dont need 2specify the path) it work, but when i specify the path it run separately:
 
//this following code launch myprogram that exist on the machine(c:\)
private void button1_Click(object sender, EventArgs e)
{
ProcessStartInfo pinfo = new ProcessStartInfo ("myprogram");
Process p = System.Diagnostics.Process.Start(pinfo);
p.WaitForInputIdle();
SetParent(p.MainWindowHandle, this.Lable1.Handle);
 

}
//So what if i want to specify the path of for example (e:\myprogram) as my main application(parent) guna launch from a cd and all exuctable application guna be on a cd.
 
private void button2_Click(object sender, EventArgs e)
{
Process proc = new Process();
proc.StartInfo.FileName = @"e:\myprogram.exe";
proc.Start();
proc.WaitForInputIdle();
SetParent(proc.MainWindowHandle, this.label1.Handle);
}
 
what happened in the second chunck of code is that myprogram run in a new window not inside the label1.
 
i want it to run inside the label.
 
THZXsavcsv swv ad a as

GeneralRe: I need helpmemberThomas Hruska12 Mar '08 - 18:32 
Sorry - I don't know C# (or any other .NET language) and this isn't a general help forum. If it were UAC related, I might have some suggestions but I don't know what is going on and have never seen anyone attempt to run external executables inside frame windows - sounds pretty hacky to me. Try asking your question in the C# forum here on CodeProject.
 
--
Thomas Hruska
CubicleSoft President

GeneralIn-Process ElevationmemberRichard Gran4 Mar '08 - 15:06 
First of all, what an awesome in-depth explanation of elevation! Great work Thomas!
 
Going back to the topic, is this possible? I have this Outlook COM Add-in client that can un-install itself within Outlook and needs to remove HKLM hive entries. Since Outlook is running in a non-elevated mode which renders the COM client to run in that mode as well, these hive entries doesn't get removed.
 
At the moment, the only way I can think of removing these hive entries is to create a separate un-install program. Are there any workaround that you can suggest to do the un-install by the Add-in itself?
 
Thanks!
GeneralRe: In-Process ElevationmemberThomas Hruska4 Mar '08 - 18:14 
Doesn't regsvr32 require elevation to properly register/unregister COM objects from the CLSID section of the HKCR\classes hive? The UnregisterServer function seems like it would have to run in an elevated environment. Even if regsvr32 isn't doing elevation, you should be able to force it to do so with the "runas" verb. Seems like an ideal spot to do uninstallation sorts of things. I haven't touched COM stuff in a while. I tend to avoid it - procrastination definitely plays a part in that.
 
--
Thomas Hruska
CubicleSoft President

GeneralI don't know what happened.memberThomas Hruska29 Feb '08 - 15:26 
For some reason I haven't been receiving messages from CodeProject until today. Apologies.
 
--
Thomas Hruska
CubicleSoft President

QuestionCan't get 'CreateProcessWithLogonElevatedW' to work on Windows 2008memberMichael B. Hansen28 Feb '08 - 23:58 
Hi Thomas,
 
First let me say - impressive work and article - you got my 5 Smile | :)
 
I recently ran into the UAC problem myself - when I prepared my code to run on Windows 2008. In this I have a special process, which needs to execute another process under another dedicated system account. Up until now I have used CreateProcessWithLogon to execute this process - but on Win2k8 I keep getting "The requested operation requires elevation".
 
Ok - I therefore added a manifest file to the EXE - with "requireAdministrator" applied - however this didn't solve the problem.
 

I therefore turned to your Elevate DLL - however it also result in "The requested operation requires elevation" - even though the credentials are correct. The UAC prompts correctly regarding the "Elevate.exe" process - but 'CreateProcessWithLogonElevatedW' then returns "The requested operation requires elevation".
 
My small sample is just a win32 console program - which tries to execute my dedicated EXE under another user account. Running the console program with "Run as Administrator" also result in this error.
 
I can add, that in addition to being member of the "Administrators" group in the domain - the dedicated user account is member of some other groups which a 'typical' administrator account isn't member of - why running the process as an administrator isn't sufficient in my case (and no, making the administrator account member of these groups also isn't an option).
 

I don't know if this is a general problem - or it has something to do with the updates made in Win2k8 / Vista SP1 (the core of Vista SP1 is apparently identical with the Win2k8 core).
 
Has anybody tried using the Execute DLL on Win2k8 / Vista SP1 ?
 

Best regards,
 
Michael
AnswerRe: Can't get 'CreateProcessWithLogonElevatedW' to work on Windows 2008memberMichael B. Hansen29 Feb '08 - 4:50 
Hi again,
 
Shortly after posting the above issue - which is valid enough - I thought of a quick 'hack' to launch a process with evelated rights without any known issues.
 
Now, I do my development primarely in C#, but this trick should work in C++ also.
 
The hack was to launch a small process under the desired user account. This small process should not require any evelated rights - but should only be used to launch the actual desired process.
 
If anybody is interested in this quick 'hack', then my script is as the following:
 
Dim oShell, sFile, sArg, i
 
sArg = ""
sFile = WScript.Arguments(0)
 
If WScript.Arguments.Count<>1 Then
For i=1 To WScript.Arguments.Count-1
sArg = sArg & " " & WScript.Arguments(i)
Next
End If
 
Set oShell = CreateObject("WScript.Shell")
call oShell.Run (Chr(34) & sFile & Chr(34) & sArg, 1, false)
Set oShell = Nothing

 

and is launch as the following:
 
wscript [EXE file] [arguments if any]
 

 
One should be able to use the normal CreateProcessWithLogin to launch wscript with the above script as argument - and voila, the script is run under the desired user account - which in turn starts the desired EXE file.
 
The user is of course prompted by UAC - but so did the Evelate.exe.
 

Best regards,
 
Michael
GeneralRe: Can't get 'CreateProcessWithLogonElevatedW' to work on Windows 2008memberThomas Hruska29 Feb '08 - 19:14 
I am now not sure about what I said before about your method working. Are you _SURE_ that the process is elevated under the new user and not the user with the Administrator token? That is, does Task Manager definitively show that the elevated process is running under the user you want it to run under? The script might run under the desired user but that doesn't necessarily guarantee the elevated EXE is running under the same user. Step 1 of the 7 stage elevation process doesn't indicate which logged in user's token it will ask LSA for.
 
--
Thomas Hruska
CubicleSoft President

GeneralRe: Can't get 'CreateProcessWithLogonElevatedW' to work on Windows 2008memberThomas Hruska1 Mar '08 - 10:34 
And now that I'm awake and can think straight, I'm thinking your way of starting a process as another user may actually be correct. It could be that the security token for the user is being created at the Medium Integrity Level. You'll recall from the article that all objects have a default Integrity Level of Medium, which is why Elevate works. So the user token is created at the Medium IL and then the process attempts to create another process with that token at the High IL and can't because the user token is a Medium IL token, so it issues an "elevation" error because the token is a Medium IL. Why CreateProcess() works is because it just reuses the existing elevated token. So the workaround is to create another process as the user at the Medium IL, then do the elevation thing, which gets a High IL user token from LSA, and then starts the process using that token. So, that would result in a process under the target user.
 
That makes more sense than what I was blabbing about late last night.
 
--
Thomas Hruska
CubicleSoft President

GeneralRe: Can't get 'CreateProcessWithLogonElevatedW' to work on Windows 2008memberMichael B. Hansen2 Mar '08 - 20:55 
Hi Thomas,
 
Sorry for the late reply - but I have my Win2k8 machines running on VMWare installations, and needed to be at work to do a second check.
 
Yes, the process is running under the desired user account.
 

Also, your ideas about launching the process with Medium IL - and then elevating it - is what I think makes my solution work (actually, this was what led me to my solution).
 

But, disregarding my workaround, keep up the excellent work. It would have been nice if you had provided the full source code - but I fully understand your considerations about this.
 

Best regards,
 
Michael
GeneralRe: Can't get 'CreateProcessWithLogonElevatedW' to work on Windows 2008memberThomas Hruska3 Mar '08 - 3:01 
Thank you. That confirmation helps considerably.
 
--
Thomas Hruska
CubicleSoft President

GeneralRe: Can't get 'CreateProcessWithLogonElevatedW' to work on Windows 2008memberThomas Hruska21 Mar '08 - 20:19 
Michael,
 
Apologies for the delay.
 
I just updated the article. I'm pretty sure I've fixed the bug. The solution is very similar to the one you came up with. Elevate.exe is launched with an extra parameter ("link") as the target user. Elevate.exe then turns around and ShellExecuteEx()'s itself to elevate itself (minus the extra parameter). This deals with the medium IL issue and ends up with a correctly elevated process as the target user - a UAC Permanent Link is returned to the caller. It gets tricky to explain, so if you want to see the details, look at the available source code.
 
If you could confirm that the updated Elevate package works for your situation, that would be excellent.
 
--
Thomas Hruska
CubicleSoft President

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

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130516.1 | Last Updated 22 Mar 2008
Article Copyright 2007 by Thomas Hruska
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid