April 25th, 2019
likeheart2 reactions

How can I launch an unelevated process from my elevated process, redux

Some time ago, I showed how you can launch an unelevated process from an elevated process by asking Explorer to launch the program on your behalf.

There’s another way which is a bit more direct, but it assumes that the thing you want to do can be done with a direct Create­Process call. In other words, if you need the system to look up the user’s file associations or default browser, then this technique is not for you.

The idea is to take advantage of PROCESS_CREATE_PROCESS access and the accompanying PROC_THREAD_ATTRIBUTE_PARENT_PROCESS process thread attribute:

PROC_THREAD_ATTRIBUTE_PARENT_PROCESS

The lpValue parameter is a pointer to a handle to a process to use instead of the calling process as the parent for the process being created. The process to use must have the PROCESS_CREATE_PROCESS access right.

Attributes inherited from the specified process include handles, the device map, processor affinity, priority, quotas, the process token, and job object. (Note that some attributes such as the debug port will come from the creating process, not the process specified by this handle.)

Basically, this lets you tell the Create­Process function, “Hey, like, um, pretend that other guy over there is creating the process.”

Here’s a Little Program to demonstrate. Remember that Little Programs do little to no error checking so that they can demonstrate the underlying technique without distractions.

int main(int, char**)
{
  HWND hwnd = GetShellWindow();

  DWORD pid;
  GetWindowThreadProcessId(hwnd, &pid);

  HANDLE process =
    OpenProcess(PROCESS_CREATE_PROCESS, FALSE, pid);

  SIZE_T size;
  InitializeProcThreadAttributeList(nullptr, 1, 0, &size);
  auto p = (PPROC_THREAD_ATTRIBUTE_LIST)new char[size];

  InitializeProcThreadAttributeList(p, 1, 0, &size);
  UpdateProcThreadAttribute(p, 0,
    PROC_THREAD_ATTRIBUTE_PARENT_PROCESS,
    &process, sizeof(process),
    nullptr, nullptr);

  wchar_t cmd[] = L"C:\\Windows\\System32\\cmd.exe";
  STARTUPINFOEX siex = {};
  siex.lpAttributeList = p;
  siex.StartupInfo.cb = sizeof(siex);
  PROCESS_INFORMATION pi;

  CreateProcessW(cmd, cmd, nullptr, nullptr, FALSE,
    CREATE_NEW_CONSOLE | EXTENDED_STARTUPINFO_PRESENT,
    nullptr, nullptr, &siex.StartupInfo, &pi);

  CloseHandle(pi.hProcess);
  CloseHandle(pi.hThread);
  delete[] (char*)p;
  CloseHandle(process);
  return 0;
}

We start by getting the shell window and using that to identify the process that is responsible for the shell.

We then use that process ID to open the process with the magic PROCESS_CREATE_PROCESS access.

We then ask the system how much memory is required to create a PROC_THREAD_ATTRIBUTE_LIST that holds one attribute, and allocate it.

We initialize the newly-allocated attribute list and update it with our process handle, saying that we want this to be the parent for the process we’re about to create.

We set up a STARTUPINFOEX structure like usual, but we take the extra step of setting the lpAttributeList to point to the attribute list.

Finally, we call Create­Process, but also set the EXTENDED_STARTUPINFO_PRESENT flag to tell it, “Hey, I gave you a STARTUPINFOEX, and if you look inside, you might find a surprise!”

A little bit of cleaning up, and we’re done.

This program runs a copy of cmd.exe using the shell process (usually explorer.exe) as its parent, which means that if the shell process is unelevated, then so too will the cmd.exe process. Of course, if the user is an administrator and has disabled UAC, then Explorer will still be elevated, and so too will be the cmd.exe. But in that case, the user wants everything to run elevated, so you’re just following the user’s preferences.

Topics

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

15 comments

Discussion is closed. Login to edit/delete existing comments.

Sort by :
  • dmex

    There are issues using the PROC_THREAD_ATTRIBUTE_PARENT_PROCESS flag... Environment variables for the new process are copied from the High-IL process not the Medium-IL process which cause issues with shell functions and some directory paths (%temp%, %userprofile% etc...) The token security descriptor for the process is also created without an ACE for the current user blocking access to most resources and the token security descriptor will also have a High-IL even though the process itself has a Medium-IL... This blocks the new process from accessing any system objects (events/pipes/sections/IPC etc...) while also blocking the process from opening its own process token with...

    Read more
  • Gavin Lambert

    There's a big caveat to this technique.  If the user has used "runas" or CreateProcessAsUser or similar to launch your process (or some parent of your process) under a different user account from the shell, then this would probably either fail with a permissions error or work but launch the child process under the original user account (which might annoy the actual user, since they presumably had a reason to switch user accounts in the first place).  There also might be security problems if you then try to communicate between the two processes.

    Like Joshua Hudson stated, I've found that the...

    Read more
    • Gavin Lambert

      Also, curse this new blog system and its lack of proper paragraph formatting!  (And its erasure of all the comments on previous blog posts, which were often very useful.)

  • Heiko Wetzel

    As I saw your article I was very excited, because I'm looking for a working way to launch an unelevated process from an elevated process already for a long time. I used your code (enhanced with error checking) and was able to spawn an unelevated process. That's great. But I made the observation, that the unelevated process runs in a compatibility mode, the environment variable __COMPAT_LAYER in the unelevated process has the value "Installer". Is there a way to prevent enabling the compatibility mode (for the new process) or can you explain, why the compatibility mode ist applied in this...

    Read more
  • Joe Beans

    Going back to the spirit of the blog entry that led to this, is there a way we can just use the session token to create the new process exactly like an unelevated user? The only deterministic way of using the idea on this page is to always start a process chain with an unelevated stub zombie process so that you always have it available. Then inherit the full-access process handle to the “real” process that actually does the work so that you’re guaranteed to have a secure working handle without a risky OpenProcess call.