1

Is it possible to launch process in system context from a parent process thats running under administrator account with elevation(say a command prompt). The problem is similar to what psexec does but more of how it actually implements this.

I was thinking opening the crss.exe/winlogon.exe process duplicating the token and launching a new process using that process token. But I fail to even open the process handle (Getlasterror return 5). Can someone let me know if this is the right approach or the process should be launched differently ?

HANDLE hWinLogonProcess;
for(const auto& ps : running_processes)
{
    if(ps.id == GetCurrentProcessId() ||
        0 != ps.short_name.CompareNoCase(L"winlogon.exe"))
    {
        continue;
    }

    DWORD dwWinLogonSessionId(0);
    if(FALSE == ProcessIdToSessionId(GetCurrentProcessId(), &dwWinLogonSessionId))
    {
        std::wcerr<<"Could not get Winlogon process session id"<<std::endl;
        continue;
    }

    if(dwWinLogonSessionId != dwCurSessionId)
    {
        continue; 
    }

    hWinLogonProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, ps.id);
    if(FALSE == hWinLogonProcess)
    {
        std::wcerr<<"Failed to get winlogon process handle"<<std::endl;
        return;
    }
    else
    {
        std::wcout<<"Able to open process "<<ps.short_name.GetString()<<" handle"<<std::endl;
        break;
    }
}

I am sure its possible as there is a working tool (psexec) but I couldnt find any reference online to do this.

Also this is similar to question, but posting separately as there was details on how it had to be achieved.

Community
  • 1
  • 1
user3279954
  • 556
  • 2
  • 7
  • 22
  • 4
    See [how psexec works](http://forum.sysinternals.com/topic9674_post41829.html#41829). In a nutshell, psexec logs in to an admin user and installs and runs a temporary service to gain system access, then that service runs the command, and then psexec stops and uninstalls the service. psexec uses pipes to exchange input/output between itself and the service, and the service uses pipes to exchange input/output with the running command. – Remy Lebeau Aug 30 '16 at 22:33
  • Thanks @RemyLebeau, I see the psexec creates a service, to achieve this. But I got a dll and dont want to introduce a new exe to this. Is it possible to achieve this programatically. Since my current process is already running at admin privilege. – user3279954 Aug 30 '16 at 22:43
  • 2
    Only a service can gain System privilege, and only an Admin can install a service. You can't run a DLL directly as a service (unless it is a driver) but you don't need to write a separate EXE, either. You can either 1) design the DLL to be callable from [`Rundll32`](https://support.microsoft.com/en-us/kb/164787) and then use that EXE as the service, or 2) have your main EXE install itself as a service with a command-line parameter, then add the relevant service API calls to your `WinMain()` function when the EXE is run with that parameter, and have your "service" use the DLL as needed. – Remy Lebeau Aug 30 '16 at 22:50
  • 1
    @RemyLebeau - "Only a service can gain System privilege" - this is not true - really any who have SE_DEBUG_PRIVILEGE. if have this we can open any process(even protected) with PROCESS_QUERY_LIMITED_INFORMATION, after this open it token, duplicate it and assign to our thread. not need any service – RbMm Aug 30 '16 at 23:49
  • @user3279954 - yes this is possible, i even have special tool for this – RbMm Aug 30 '16 at 23:50
  • Using a service is usually preferable because it is fully supported. In particular, you don't have to guess at what processes are running as local system. But if you're just putting a hack together, using an existing token is fine. – Harry Johnston Aug 31 '16 at 01:17
  • @HarryJohnston "guess what processes are running as local system" - for 100% exact solution can query process token (for TokenSource `*System*`, or for needed privileges set (not all system processes have equal set here)), or as more simply - by processes order and name. and of course something can changed here, like for example "protected processes" added. but until possible implement this solution without create service – RbMm Aug 31 '16 at 07:01
  • @RbMm: sure, but that's far more complicated than launching a service. – Harry Johnston Aug 31 '16 at 07:52
  • @HarryJohnston - i be not say this. service need install first (create files, add to scm db), start, communicate with it. if use SE_DEBUG_PRIVILEGE and known what to do - we can do this more simply and much more effective. without any changes in system (like service install) – RbMm Aug 31 '16 at 08:08
  • @RbMm: you don't need to create any files - your own executable can double as the service executable easily enough - and installing and launching a service is pretty darned straightforward. It seems a lot easier and more reliable than listing all the running processes and iterate through them to find one with a SYSTEM token. YMMV. – Harry Johnston Aug 31 '16 at 09:07

2 Answers2

2

Yes, this is possible (without any service help).

But I fail to even open the process handle

Does your process have the SE_DEBUG_PRIVILEGE privilege enabled?

With this privilege, you can open a system process with all access if it is not protected (smss.exe, csrss.exe, services.exe), and use that handle in CreateProcessAsUser(), or with UpdateProcThreadAttribute(PROC_THREAD_ATTRIBUTE_PARENT_PROCESS) if you also have SE_ASSIGNPRIMARYTOKEN_PRIVILEGE and SE_TCB_PRIVILEGE privileges enabled (for setting the token's SessionId to 0), which you can get in 2 ways:

  • open a thread from an unprotected system process and impersonate it, then open your own thread token and adjust privileges on it.

  • open a token from any system process (this works even for protected processes), duplicate the token, adjust privileges on it, and then impersonate with this token.

To "launch a process in the system context", if you want to run the process:

  • with the LocalSystem token.

  • in the System terminal session (0)

Both, as I say, are possible. And all you need is SE_DEBUG_PRIVILEGE.

  1. more simply - open some system process with PROCESS_CREATE_PROCESS access right. Use this handle with UpdateProcThreadAttribute(PROC_THREAD_ATTRIBUTE_PARENT_PROCESS). As a result, your started process inherits a token from the system process. This will be not work on XP, but there it is possible to hook NtCreateProcess/Ex() to replace HANDLE ParentProcess with your opened handle.

  2. Another way is to use CreateProcessAsUser(). Before creating the process, you will be need SE_ASSIGNPRIMARYTOKEN_PRIVILEGE and SE_TCB_PRIVILEGE privileges to set the token's TokenSessionId (if you want to run in session 0).

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
RbMm
  • 31,280
  • 3
  • 35
  • 56
  • Thanks a lot @RbMm, The solution works! I am able to create a child process with system account. I duplicated the process token, enabled SE_ASSIGNPRIMARYTOKEN_PRIVILEGE , SE_TCB_PRIVILEGE, And after impersonation, launching the process creates the child process as system process – user3279954 Sep 02 '16 at 00:58
0

Thanks to RbMm answer I figured a way to accomplish this task.

For any of you who did not succeed, I leave below something that might help:

//First we need to add debug privilege to this process
HANDLE hToken;
if(!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
    &hToken))
{
    std::cout << "OpenProcessToken failed: " << GetLastError();
    return 0;
}

TOKEN_PRIVILEGES tk;
tk.PrivilegeCount = 1;
tk.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if(!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tk.Privileges[0].Luid))
{
    std::cout << "LookupPrivilegeValue failed: " << GetLastError();
    return 0;
}

AdjustTokenPrivileges(hToken, FALSE, &tk, 0, NULL, 0);
if((DWORD res = GetLastError()) != ERROR_SUCCESS)
{
    std::cout << "AdjustTokenPrivileges failed: " << res;
}

CloseHandle(hToken);

//Now we need a handle to a process that already runs as SYSTEM.
//You can choose any process that is not protected (if OpenProcess fails try with other process)

//pid of chosen process (you can get this by opening task manager and go to
//Details tab or by enumerating all processes and extract that one you need)
DWORD pid;
HANDLE hProcess = OpenProcess(PROCESS_CREATE_PROCESS, FALSE, pid);
if (!hProcess)
{
    std::cout << "OpenProcess with pid " << pid << "failed: " << GetLastError();
    return 0
}

//We need to initialize a list that contains PROC_THREAD_ATTRIBUTE_PARENT_PROCESS 
//to specify that parent process of the process we are going to start is the 
//process we opened earlier (this will make the child process inherit the system context).
//This list will be specified in a STARTUPINFOEX object that CreateProcess will get
STARTUPINFOEX siex = { sizeof(STARTUPINFOEX) };
siex.StartupInfo.cb = sizeof(STARTUPINFOEXW);

//We need to initialize our list. To do this we call InitializeProcThreadAttributeList 
//with a NULL list to get how big our list needs to be to store all attributes 
//we want to specify, then we allocate our list with the size we got from first call 
//and we call again the function to initialize the list.
SIZE_T cbAttributeListSize = 0;
if(!InitializeProcThreadAttributeList(NULL, 1, 0, &cbAttributeListSize))
{
    std::cout << "InitializeProcThreadAttributeList failed: " << GetLastError();
    return 0
}

siex.lpAttributeList = reinterpret_cast<PPROC_THREAD_ATTRIBUTE_LIST>(HeapAlloc(GetProcessHeap(), 0, cbAttributeListSize));
if(!InitializeProcThreadAttributeList(siex.lpAttributeList, 1, 0, &cbAttributeListSize))
{
    std::cout << "InitializeProcThreadAttributeList failed: " << GetLastError();
    return 0
}

if(!UpdateProcThreadAttribute(siex.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &hProcess, sizeof(hProcess), NULL, NULL))
{
    std::cout << "UpdateProcThreadAttribute failed: " << GetLastError();
    return 0
}

//path to program we want to run in system context
LPWSTR szCmdline = _wcsdup(TEXT("C:\\Windows\\System32\\notepad.exe"));
PROCESS_INFORMATION pi = { 0 };

if(!CreateProcess(NULL, szCmdline, nullptr, nullptr, FALSE, EXTENDED_STARTUPINFO_PRESENT, NULL, NULL, reinterpret_cast<LPSTARTUPINFOW>(&siex), &pi))
{
    std::cout << "CreateProcess failed: " << GetLastError();
    return 0
}
INeed ADollar
  • 46
  • 2
  • 9