Ignoring pre-Vista OS's, assuming you have TCB privs on your token (are running as System, basically), you can use CreateProcessAsUser
to do this.
Example to be run as System (e.g.: an NT Service or with psexec -s
) which will start notepad in the console session winlogon desktop:
#define WIN32_LEAN_AND_MEAN
#pragma comment(lib, "Userenv.lib")
#include <Windows.h>
#include <UserEnv.h>
#include <iostream>
#include <string>
HANDLE GetTokenForStart();
LPVOID GetEnvBlockForUser(HANDLE hToken);
void StartTheProcess(HANDLE hToken, LPVOID pEnvironment);
int main(int argc, wchar_t* argv[])
{
//while (!IsDebuggerPresent()) Sleep(500);
try
{
HANDLE hUserToken = GetTokenForStart();
LPVOID env = GetEnvBlockForUser(hUserToken);
StartTheProcess(hUserToken, env);
}
catch (std::wstring err)
{
auto gle = GetLastError();
std::wcerr << L"Error: " << err << L" GLE: " << gle << L"\r\n";
return -1;
}
}
HANDLE GetTokenForStart()
{
HANDLE hToken = 0;
{
HANDLE processToken = 0;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_QUERY_SOURCE | TOKEN_EXECUTE, &processToken))
{
throw std::wstring(L"Could not open current process token");
}
if (!DuplicateTokenEx(processToken, MAXIMUM_ALLOWED, NULL, SecurityImpersonation, TokenPrimary, &hToken))
{
throw std::wstring(L"Could not duplicate process token");
}
}
DWORD consoleSessionId = WTSGetActiveConsoleSessionId();
if (!SetTokenInformation(hToken, TokenSessionId, &consoleSessionId, sizeof(consoleSessionId)))
{
throw std::wstring(L"Could not set session ID");
}
return hToken;
}
LPVOID GetEnvBlockForUser(HANDLE hToken)
{
LPVOID pEnvironment = NULL;
if (!CreateEnvironmentBlock(&pEnvironment, hToken, FALSE))
{
throw std::wstring(L"Could not create env block");
}
return pEnvironment;
}
void StartTheProcess(HANDLE hToken, LPVOID pEnvironment)
{
STARTUPINFO si = { 0 };
si.cb = sizeof(si);
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_SHOW;
si.lpDesktop = (LPWSTR)L"winsta0\\winlogon";
wchar_t path[MAX_PATH] = L"notepad.exe";
PROCESS_INFORMATION pi = { 0 };
if (!CreateProcessAsUser(hToken, NULL, path, NULL, NULL, FALSE,
CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT, pEnvironment, NULL, &si, &pi))
{
throw std::wstring(L"Could not start process");
}
if (!CloseHandle(pi.hThread))
{
throw std::wstring(L"Could not close thread handle");
}
}
Or, if you prefer C#:
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Text;
namespace StartWinlogonManaged
{
class Program
{
static void Main(string[] args)
{
var hUserToken = GetTokenForStart();
var env = GetEnvBlockForUser(hUserToken);
StartTheProcess(hUserToken, env);
}
const string
Advapi32 = "advapi32.dll",
Userenv = "userenv.dll",
Kernel32 = "kernel32.dll";
[DllImport(Kernel32, ExactSpelling = true, SetLastError = true)]
public static extern IntPtr GetCurrentProcess();
[DllImport(Advapi32, ExactSpelling = true, SetLastError = true)]
public static extern bool OpenProcessToken(IntPtr ProcessToken, int DesiredAccess, out IntPtr TokenHandle);
[DllImport(Advapi32, ExactSpelling = true, SetLastError = true)]
public static extern bool DuplicateTokenEx(IntPtr ExistingToken, int DesiredAccess,
IntPtr TokenAttributes, int ImpersonationLevel, int TokenType, out IntPtr NewToken);
[DllImport("kernel32.dll", ExactSpelling = true)]
static extern int WTSGetActiveConsoleSessionId();
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetTokenInformation(IntPtr hToken,
int tokenInfoClass, ref int pTokenInfo, int tokenInfoLength);
static IntPtr GetTokenForStart()
{
IntPtr hToken = IntPtr.Zero;
{
IntPtr processToken = IntPtr.Zero;
if (!OpenProcessToken(GetCurrentProcess(), 0x2001f /* TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_QUERY_SOURCE | TOKEN_EXECUTE */, out processToken))
{
throw new Win32Exception("Could not open current process token");
}
if (!DuplicateTokenEx(processToken, 0x02000000 /* MAXIMUM_ALLOWED */, IntPtr.Zero, 2 /* SecurityImpersonation */, 1 /* TokenPrimary */, out hToken))
{
throw new Win32Exception("Could not duplicate process token");
}
}
int consoleSessionId = WTSGetActiveConsoleSessionId();
if (!SetTokenInformation(hToken, 12 /* TokenSessionId */, ref consoleSessionId, 4 /* sizeof(int) */))
{
throw new Win32Exception("Could not set session ID");
}
return hToken;
}
[DllImport(Userenv, CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool CreateEnvironmentBlock(out IntPtr lpEnvironment, IntPtr hToken, bool bInherit);
static IntPtr GetEnvBlockForUser(IntPtr hToken)
{
IntPtr pEnvironment = IntPtr.Zero;
if (!CreateEnvironmentBlock(out pEnvironment, hToken, true))
{
throw new Win32Exception("Could not create env block");
}
return pEnvironment;
}
[DllImport(Advapi32, CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool CreateProcessAsUser(IntPtr hToken,
StringBuilder appExeName, StringBuilder commandLine, IntPtr processAttributes,
IntPtr threadAttributes, bool inheritHandles, uint dwCreationFlags,
IntPtr environment, string currentDirectory, ref STARTUPINFO startupInfo,
out PROCESS_INFORMATION startupInformation);
[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public uint dwProcessId;
public uint dwThreadId;
}
[StructLayout(LayoutKind.Sequential)]
internal struct STARTUPINFO
{
public int cb;
public IntPtr lpReserved;
[MarshalAs(UnmanagedType.LPWStr)]
public string lpDesktop;
[MarshalAs(UnmanagedType.LPWStr)]
public string lpTitle;
public int dwX;
public int dwY;
public int dwXSize;
public int dwYSize;
public int dwXCountChars;
public int dwYCountChars;
public int dwFillAttribute;
public int dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[DllImport(Kernel32, ExactSpelling = true, SetLastError = true)]
public static extern bool CloseHandle(IntPtr handle);
static void StartTheProcess(IntPtr hToken, IntPtr pEnvironment)
{
var si = new STARTUPINFO();
si.cb = Marshal.SizeOf<STARTUPINFO>();
si.dwFlags = 1 /* STARTF_USESHOWWINDOW */;
si.wShowWindow = 5 /* SW_SHOW */;
si.lpDesktop = "winsta0\\winlogon";
var path = new StringBuilder("notepad.exe", 260);
PROCESS_INFORMATION pi;
if (!CreateProcessAsUser(hToken, null, path, IntPtr.Zero, IntPtr.Zero, false,
0x410 /* CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT */, pEnvironment, null, ref si, out pi))
{
throw new Win32Exception("Could not start process");
}
if (!CloseHandle(pi.hThread))
{
throw new Win32Exception("Could not close thread handle");
}
}
}
}
Note that this does require several privileges (TCB, AssignPrimaryToken, IncreaseQuota) enabled in your token. This code also leaks handles, does not formulate a full command line, use name constants, etc..., and is only intended as an expository reference - not as a ready solution.