using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Security.Principal;
using System.Text;
/// summary
/// UserSpecificProcess extends the standard Process object to
/// create new processes under a different user than the calling parent process.
/// Also, the standard output of the child process redirected back to the parent process.
/// This is class is designed to operate inside an ASP.NET web application.
/// The assumption is that the calling thread is operating with an impersonated security token.
/// The this class will change the imperonated security token to a primary token,
/// and call CreateProcessAsUser.
/// A System.Diagnostics.Process object will be returned with appropriate properties set.
/// To use this function, the following security priviliges need to be set for the ASPNET account
/// using the local security policy MMC snap-in. CreateProcessAsUser requirement.
/// “Replace a process level token”/SE_ASSIGNPRIMARYTOKEN_NAME/SeAssignPrimaryTokenPrivilege
/// “Adjust memory quotas for a process”/SE_INCREASE_QUOTA_NAME/SeIncreaseQuotaPrivilege
///
/// This class was designed for .NET 1.1 for operating systems W2k an higher.
/// Any other features or platform support can be implemented by using the .NET reflector and
/// investigating the Process class.
/// /summary
public class UserSpecificProcess : Process
{
[StructLayout(LayoutKind.Sequential)]
public class CreateProcessStartupInfo
{
public int cb;
public string lpReserved;
public string lpDesktop;
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;
public CreateProcessStartupInfo()
{
this.cb = Marshal.SizeOf(typeof(CreateProcessStartupInfo));
this.lpReserved = null;
this.lpDesktop = null;
this.lpTitle = null;
this.dwX = 0;
this.dwY = 0;
this.dwXSize = 0;
this.dwYSize = 0;
this.dwXCountChars = 0;
this.dwYCountChars = 0;
this.dwFillAttribute = 0;
this.dwFlags = 0;
this.wShowWindow = 0;
this.cbReserved2 = 0;
this.lpReserved2 = IntPtr.Zero;
this.hStdInput = IntPtr.Zero;
this.hStdOutput = IntPtr.Zero;
this.hStdError = IntPtr.Zero;
}
}
[StructLayout(LayoutKind.Sequential)]
public class CreateProcessProcessInformation
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
public CreateProcessProcessInformation()
{
this.hProcess = IntPtr.Zero;
this.hThread = IntPtr.Zero;
this.dwProcessId = 0;
this.dwThreadId = 0;
}
}
[StructLayout(LayoutKind.Sequential)]
public class SecurityAttributes
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public bool bInheritHandle;
public SecurityAttributes()
{
this.nLength = Marshal.SizeOf(typeof(SecurityAttributes));
}
}
[DllImport("kernel32.dll", CharSet=CharSet.Auto, SetLastError=true, ExactSpelling=true)]
public static extern bool CloseHandle(HandleRef handle);
[DllImport("kernel32.dll", CharSet=CharSet.Auto, SetLastError=true)]
public static extern bool CreateProcess([MarshalAs(UnmanagedType.LPTStr)] string lpApplicationName, StringBuilder lpCommandLine, SecurityAttributes lpProcessAttributes, SecurityAttributes lpThreadAttributes, bool bInheritHandles, int dwCreationFlags, IntPtr lpEnvironment, [MarshalAs(UnmanagedType.LPTStr)] string lpCurrentDirectory, CreateProcessStartupInfo lpStartupInfo, CreateProcessProcessInformation lpProcessInformation);
[DllImport("advapi32.dll", CharSet=CharSet.Unicode, SetLastError=true)]
public static extern bool CreateProcessAsUserW(IntPtr token, [MarshalAs(UnmanagedType.LPTStr)] string lpApplicationName, [MarshalAs(UnmanagedType.LPTStr)] string lpCommandLine, SecurityAttributes lpProcessAttributes, SecurityAttributes lpThreadAttributes, bool bInheritHandles, int dwCreationFlags, IntPtr lpEnvironment, [MarshalAs(UnmanagedType.LPTStr)] string lpCurrentDirectory, CreateProcessStartupInfo lpStartupInfo, CreateProcessProcessInformation lpProcessInformation);
[DllImport("kernel32.dll", CharSet=CharSet.Ansi, SetLastError=true)]
public static extern IntPtr GetStdHandle(int whichHandle);
[DllImport("kernel32.dll", CharSet=CharSet.Auto)]
public static extern IntPtr CreateFile(string lpFileName, int dwDesiredAccess, int dwShareMode, SecurityAttributes lpSecurityAttributes, int dwCreationDisposition, int dwFlagsAndAttributes, HandleRef hTemplateFile);
[DllImport("kernel32.dll", CharSet=CharSet.Auto, SetLastError=true)]
public static extern IntPtr CreateNamedPipe(string name, int openMode, int pipeMode, int maxInstances, int outBufSize, int inBufSize, int timeout, SecurityAttributes lpPipeAttributes);
[DllImport("kernel32.dll", CharSet=CharSet.Auto, SetLastError=true)]
public static extern int GetConsoleOutputCP();
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
public static extern bool DuplicateTokenEx(HandleRef hToken, int access, SecurityAttributes tokenAttributes, int impersonationLevel, int tokenType, ref IntPtr hNewToken);
// WinNT.h ACCESS TYPES
const int GENERIC_ALL = 0x10000000;
// WinNT.h enum SECURITY_IMPERSONATION_LEVEL
const int SECURITY_IMPERSONATION = 2;
// WinNT.h TOKEN TYPE
const int TOKEN_PRIMARY = 1;
// WinBase.h
const int STD_INPUT_HANDLE = -10;
const int STD_ERROR_HANDLE = -12;
// WinBase.h STARTUPINFO
const int STARTF_USESTDHANDLES = 0x100;
// Microsoft.Win23.NativeMethods
static IntPtr INVALID_HANDLE_VALUE = (IntPtr) (-1);
public static HandleRef NullHandleRef = new HandleRef(null, IntPtr.Zero);
/// summary
/// Starts the process with the security token of the calling thread.
/// If the security token has a token type of TokenImpersonation,
/// the token will be duplicated to a primary token before calling
/// CreateProcessAsUser.
/// /summary
/// param name="process"The process to start./param
public void StartAsUser()
{
StartAsUser(WindowsIdentity.GetCurrent().Token);
}
/// summary
/// Starts the process with the given security token.
/// If the security token has a token type of TokenImpersonation,
/// the token will be duplicated to a primary token before calling
/// CreateProcessAsUser.
/// /summary
/// param name="process"/param
public void StartAsUser(IntPtr userToken)
{
if (StartInfo.UseShellExecute)
{
throw new InvalidOperationException("can't call this with shell execute");
}
// just assume that the securityToken is of TokenImpersonation and create a primary.
IntPtr primayUserToken = CreatePrimaryToken(userToken);
CreateProcessStartupInfo startupInfo = new CreateProcessStartupInfo();
CreateProcessProcessInformation processInformation = new CreateProcessProcessInformation();
IntPtr stdinHandle;
IntPtr stdoutReadHandle;
IntPtr stdoutWriteHandle = IntPtr.Zero;
IntPtr stderrHandle;
try
{
stdinHandle = GetStdHandle(STD_INPUT_HANDLE);
MyCreatePipe(out stdoutReadHandle, out stdoutWriteHandle, false);
stderrHandle = GetStdHandle(STD_ERROR_HANDLE);
//assign handles to startup info
startupInfo.dwFlags = STARTF_USESTDHANDLES;
startupInfo.hStdInput = stdinHandle;
startupInfo.hStdOutput = stdoutWriteHandle;
startupInfo.hStdError = stderrHandle;
string commandLine = GetCommandLine();
int creationFlags = 0;
IntPtr environment = IntPtr.Zero;
string workingDirectory = GetWorkingDirectory();
// create the process or fail trying.
if (!CreateProcessAsUserW(
primayUserToken,
null,
commandLine,
null,
null,
true,
creationFlags,
environment,
workingDirectory,
startupInfo,
processInformation))
{
throw new Win32Exception();
}
}
finally
{
// close thread handle
if (processInformation.hThread != INVALID_HANDLE_VALUE)
{
CloseHandle(new HandleRef(this, processInformation.hThread));
}
// close client stdout handle
CloseHandle(new HandleRef(this, stdoutWriteHandle));
}
// get reader for standard output from the child
Encoding encoding = Encoding.GetEncoding(GetConsoleOutputCP());
StreamReader standardOutput = new StreamReader(new FileStream(stdoutReadHandle, FileAccess.Read, true, 0x1000, true), encoding);
// set this on the object accordingly.
typeof(Process).InvokeMember("standardOutput",
BindingFlags.SetField | BindingFlags.NonPublic | BindingFlags.Instance,
null, this, new object[]{standardOutput});
// scream if a process wasn't started instead of returning false.
if (processInformation.hProcess == IntPtr.Zero)
{
throw new Exception("failed to create process");
}
// configure the properties of the Process object correctly
typeof(Process).InvokeMember("SetProcessHandle",
BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance,
null, this, new object[]{processInformation.hProcess});
typeof(Process).InvokeMember("SetProcessId",
BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance,
null, this, new object[]{processInformation.dwProcessId});
}
/// summary
/// Creates a primayToken out of an existing token.
/// /summary
/// param name="userToken"/param
private IntPtr CreatePrimaryToken(IntPtr userToken)
{
SecurityAttributes securityAttributes = new SecurityAttributes();
IntPtr primaryUserToken = IntPtr.Zero;
if (!DuplicateTokenEx(new HandleRef(this, userToken), GENERIC_ALL, securityAttributes, SECURITY_IMPERSONATION, TOKEN_PRIMARY, ref primaryUserToken))
{
throw new Win32Exception();
}
return primaryUserToken;
}
/// summary
/// Gets the appropriate commandLine from the process.
/// /summary
/// param name="process"/param
/// returns/returns
private string GetCommandLine()
{
StringBuilder builder1 = new StringBuilder();
string text1 = StartInfo.FileName.Trim();
string text2 = StartInfo.Arguments;
bool flag1 = text1.StartsWith("\"") text1.EndsWith("\"");
if (!flag1)
{
builder1.Append("\"");
}
builder1.Append(text1);
if (!flag1)
{
builder1.Append("\"");
}
if ((text2 != null) (text2.Length 0))
{
builder1.Append(" ");
builder1.Append(text2);
}
return builder1.ToString();
}
/// summary
/// Gets the working directory or returns null if an empty string was found.
/// /summary
/// returns/returns
private string GetWorkingDirectory()
{
return (StartInfo.WorkingDirectory != string.Empty) ? StartInfo.WorkingDirectory : null;
}
/// summary
/// A clone of Process.CreatePipe. This is only implemented because reflection with
/// out parameters are a pain.
/// Note: This is only finished for w2k and higher machines.
/// /summary
/// param name="parentHandle"/param
/// param name="childHandle"/param
/// param name="parentInputs"Specifies whether the parent will be performing the writes./param
public static void MyCreatePipe(out IntPtr parentHandle, out IntPtr childHandle, bool parentInputs)
{
string pipename = @"\\.\pipe\Global\" + Guid.NewGuid().ToString();
SecurityAttributes attributes2 = new SecurityAttributes();
attributes2.bInheritHandle = false;
parentHandle = CreateNamedPipe(pipename, 0x40000003, 0, 0xff, 0x1000, 0x1000, 0, attributes2);
if (parentHandle == INVALID_HANDLE_VALUE)
{
throw new Win32Exception();
}
SecurityAttributes attributes3 = new SecurityAttributes();
attributes3.bInheritHandle = true;
int num1 = 0x40000000;
if (parentInputs)
{
num1 = -2147483648;
}
childHandle = CreateFile(pipename, num1, 3, attributes3, 3, 0x40000080, NullHandleRef);
if (childHandle == INVALID_HANDLE_VALUE)
{
throw new Win32Exception();
}
}
}