首页 > 代码库 > Win7中如何在服务中启动一个当前用户的进程——函数CreateProcessAsUser()的一次使用记录

Win7中如何在服务中启动一个当前用户的进程——函数CreateProcessAsUser()的一次使用记录

  这次工作中遇到要从服务中启动一个具有桌面UI交互的应用,这在winXP/2003中只是一个简单创建进程的问题。但在Vista 和 win7中增加了session隔离,这一操作系统的安全举措使得该任务变得复杂了一些。

一、Vista和win7的session隔离

  一个用户会有一个独立的session。在Vista 和 win7中session 0被单独出来专门给服务程序用,用户则使用session 1、session 2...

  这样在服务中通过CreateProcess()创建的进程启动UI应用用户是无法看到的。它的用户是SYSTEM。所以用户无法与之交互,达不到需要的目的。

  关于更多session 0的信息请点击这里查看微软介绍。

 

二、实现代码

  首先贴出自己的实现代码,使用的是C#:

  1 using System;  2 using System.Security;  3 using System.Diagnostics;  4 using System.Runtime.InteropServices;  5   6 namespace Handler  7 {  8     /// <summary>  9     /// Class that allows running applications with full admin rights. In 10     /// addition the application launched will bypass the Vista UAC prompt. 11     /// </summary> 12     public class ApplicationLoader 13     { 14         #region Structures 15  16         [StructLayout(LayoutKind.Sequential)] 17         public struct SECURITY_ATTRIBUTES 18         { 19             public int Length; 20             public IntPtr lpSecurityDescriptor; 21             public bool bInheritHandle; 22         } 23  24         [StructLayout(LayoutKind.Sequential)] 25         public struct STARTUPINFO 26         { 27             public int cb; 28             public String lpReserved; 29             public String lpDesktop; 30             public String lpTitle; 31             public uint dwX; 32             public uint dwY; 33             public uint dwXSize; 34             public uint dwYSize; 35             public uint dwXCountChars; 36             public uint dwYCountChars; 37             public uint dwFillAttribute; 38             public uint dwFlags; 39             public short wShowWindow; 40             public short cbReserved2; 41             public IntPtr lpReserved2; 42             public IntPtr hStdInput; 43             public IntPtr hStdOutput; 44             public IntPtr hStdError; 45         } 46  47         [StructLayout(LayoutKind.Sequential)] 48         public struct PROCESS_INFORMATION 49         { 50             public IntPtr hProcess; 51             public IntPtr hThread; 52             public uint dwProcessId; 53             public uint dwThreadId; 54         } 55  56         #endregion 57  58         #region Enumerations 59  60         enum TOKEN_TYPE : int 61         { 62             TokenPrimary = 1, 63             TokenImpersonation = 2 64         } 65  66         enum SECURITY_IMPERSONATION_LEVEL : int 67         { 68             SecurityAnonymous = 0, 69             SecurityIdentification = 1, 70             SecurityImpersonation = 2, 71             SecurityDelegation = 3, 72         } 73  74         #endregion 75  76         #region Constants 77  78         //public const int TOKEN_DUPLICATE = 0x0002; 79         public const uint MAXIMUM_ALLOWED = 0x2000000; 80         public const int CREATE_NEW_CONSOLE = 0x00000010; 81         public const int CREATE_UNICODE_ENVIRONMENT = 0x00000400; 82  83         public const int NORMAL_PRIORITY_CLASS = 0x20; 84         //public const int IDLE_PRIORITY_CLASS = 0x40;         85         //public const int HIGH_PRIORITY_CLASS = 0x80; 86         //public const int REALTIME_PRIORITY_CLASS = 0x100; 87  88         #endregion 89  90         #region Win32 API Imports   91  92         [DllImport("Userenv.dll", EntryPoint = "DestroyEnvironmentBlock",  93                                     SetLastError = true)] 94         private static extern bool DestroyEnvironmentBlock(IntPtr lpEnvironment); 95  96         [DllImport("Userenv.dll", EntryPoint = "CreateEnvironmentBlock",  97                                     SetLastError = true)] 98         private static extern bool CreateEnvironmentBlock(ref IntPtr lpEnvironment,  99                                                     IntPtr hToken, bool bInherit);100 101         [DllImport("kernel32.dll", EntryPoint = "CloseHandle", SetLastError = true)]102         private static extern bool CloseHandle(IntPtr hSnapshot);103 104         [DllImport("kernel32.dll", EntryPoint = "WTSGetActiveConsoleSessionId")]105         static extern uint WTSGetActiveConsoleSessionId();106 107         [DllImport("Kernel32.dll", EntryPoint = "GetLastError")]108         private static extern uint GetLastError();109 110         [DllImport("Wtsapi32.dll", EntryPoint = "WTSQueryUserToken", SetLastError = true)]111         private static extern bool WTSQueryUserToken(uint SessionId, ref IntPtr hToken);112 113         [DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true,114                                     CharSet = CharSet.Unicode, 115                                     CallingConvention = CallingConvention.StdCall)]116         public extern static bool CreateProcessAsUser(IntPtr hToken, 117                                                       String lpApplicationName,118                                                       String lpCommandLine, 119                                                       ref SECURITY_ATTRIBUTES lpProcessAttributes,120                                                       ref SECURITY_ATTRIBUTES lpThreadAttributes, 121                                                       bool bInheritHandle, 122                                                       int dwCreationFlags, 123                                                       IntPtr lpEnvironment, 124                                                       String lpCurrentDirectory,125                                                       ref STARTUPINFO lpStartupInfo,126                                                       out PROCESS_INFORMATION lpProcessInformation);127 128         [DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")]129         public extern static bool DuplicateTokenEx(IntPtr ExistingTokenHandle, uint dwDesiredAccess,130             ref SECURITY_ATTRIBUTES lpThreadAttributes, int TokenType,131             int ImpersonationLevel, ref IntPtr DuplicateTokenHandle);132         133         #endregion134 135         /// <summary>136         /// Launches the given application with full admin rights, and in addition bypasses the Vista UAC prompt137         /// </summary>138         /// <param name="commandLine">A command Line to launch the application</param>139         /// <param name="procInfo">Process information regarding the launched application that gets returned to the caller</param>140         /// <returns></returns>141         public static bool StartProcessAndBypassUAC(String commandLine, out PROCESS_INFORMATION procInfo)142         {143             IntPtr hUserTokenDup = IntPtr.Zero, hPToken = IntPtr.Zero, hProcess = IntPtr.Zero;            144             procInfo = new PROCESS_INFORMATION();145 146             // obtain the currently active session id; every logged on user in the system has a unique session id147             uint dwSessionId = WTSGetActiveConsoleSessionId();148 149             if (!WTSQueryUserToken(dwSessionId, ref hPToken))150             {151                 return false;152             }153 154             SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();155             sa.Length = Marshal.SizeOf(sa);156 157             // copy the access token of the dwSessionId‘s User; the newly created token will be a primary token158             if (!DuplicateTokenEx(hPToken, MAXIMUM_ALLOWED, ref sa, (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,159                 (int)TOKEN_TYPE.TokenPrimary, ref hUserTokenDup))160             {161                 CloseHandle(hPToken);162                 return false;163             }164 165             IntPtr EnvironmentFromUser = IntPtr.Zero;166             if (!CreateEnvironmentBlock(ref EnvironmentFromUser, hUserTokenDup, false))167             {168                 CloseHandle(hPToken);169                 CloseHandle(hUserTokenDup);170                 return false;171             }172            173             // By default CreateProcessAsUser creates a process on a non-interactive window station, meaning174             // the window station has a desktop that is invisible and the process is incapable of receiving175             // user input. To remedy this we set the lpDesktop parameter to indicate we want to enable user 176             // interaction with the new process.177             STARTUPINFO si = new STARTUPINFO();178             si.cb = (int)Marshal.SizeOf(si);179             si.lpDesktop = @"winsta0\default";180  181             // flags that specify the priority and creation method of the process182             int dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT;183 184             // create a new process in the current user‘s logon session185             bool result = CreateProcessAsUser(hUserTokenDup,        // client‘s access token186                                             null,                   // file to execute187                                             commandLine,            // command line188                                             ref sa,                 // pointer to process SECURITY_ATTRIBUTES189                                             ref sa,                 // pointer to thread SECURITY_ATTRIBUTES190                                             false,                  // handles are not inheritable191                                             dwCreationFlags,        // creation flags192                                             EnvironmentFromUser,    // pointer to new environment block 193                                             null,                   // name of current directory 194                                             ref si,                 // pointer to STARTUPINFO structure195                                             out procInfo            // receives information about new process196                                             );197            198             // invalidate the handles199             CloseHandle(hPToken);200             CloseHandle(hUserTokenDup);201             DestroyEnvironmentBlock(EnvironmentFromUser);202 203             return result; // return the result204         }205     }206 }
View Code

 

三、几个遇到的问题

  1.环境变量

  起初用CreateProcessAsUser()时并没有考虑环境变量,虽然要的引用在桌面起来了,任务管理器中也看到它是以当前用户的身份运行的。进行一些简单的操作也没有什么问题。但其中有一项操作发生了问题,打开一个该程序要的特定文件,弹出如下一些错误:

  Failed to write: %HOMEDRIVE%%HOMEPATH%\...

  Location is not avaliable: ... 

  通过Browser打开文件夹命名看看到文件去打不开!由于该应用是第三方的所以不知道它要做些什么。但是通过Failed to write: %HOMEDRIVE%%HOMEPATH%\...这个错误信息显示它要访问一个user目录下的文件。在桌面用cmd查看该环境变量的值为:

  HOMEDRIVE=C:

  HOMEPATH=\users\Alvin

  的确是要访问user目录下的文件。然后我编写了一个小程序让CreateProcessAsUser()来以当前用户启动打印环境变量,结果其中没有这两个环境变量,及值为空。那么必然访问不到了,出这些错误也是能理解的了。其实CreateProcessAsUser()的环境变量参数为null的时候,会继承父进程的环境变量,即为SYSTEM的环境变量。在MSDN中有说:

  

使用CreateEnvironmentBlock()函数可以得到指定用户的环境变量,不过还是略有差别——没有一下两项:

  PROMPT=$P$G

  SESSIONNAME=Console

这个原因我就不清楚了,求告知。

  值得注意的是,产生的环境变量是Unicode的字符时dwCreationFlags 要有CREATE_UNICODE_ENVIRONMENT标识才行,在MSDN中有解释到:

  An environment block can contain either Unicode or ANSI characters. If the environment block pointed to by lpEnvironment contains Unicode characters, be sure thatdwCreationFlags includes CREATE_UNICODE_ENVIRONMENT. If this parameter is NULL and the environment block of the parent process contains Unicode characters, you must also ensure that dwCreationFlags includes CREATE_UNICODE_ENVIRONMENT.

  C#中字符char、string都是Unicode字符。而且这里的CreateEnvironmentBlock()函数在MSDN中有说到,是Unicode的:

  lpEnvironment [in, optional]

  A pointer to an environment block for the new process. If this parameter is NULL, the new process uses the environment of the calling process.

 

  2.一个比较奇怪的问题

  按以上分析后我加入了环境变量,并添加了CREATE_UNICODE_ENVIRONMENT标识。但是我觉得这是不是也应该把CreateProcessAsUser()的DllImport中的CharSet = CharSet.Ansi改为CharSet = CharSet.Unicode。这似乎合情合理,但是改完之后进程就起不来,且没有错误。一旦改回去就完美运行,并且没有环境变量的问题。想了半天也没有搞明白是为什么,最后问了一个前辈,他要我注意下CreateProcessAsUser()的第三个参数的声明,然后我一琢磨才知道问题的真正原因,大家先看CreateProcessAsUser()的函数声明:

  

  注意第二、三个参数的区别,并查看我写的代码。我用的是第三个参数,第二个我赋null。LPCTSTR是一个支持自动选择字符编码[Ansi或Unicode]  的常量字符串指针;LPTSTR与之的差别是不是常量。它为什么有这样的差别呢,看MSDN的解释:

  The system adds a null character to the command line string to separate the file name from the arguments. This divides the original string into two strings for internal processing.

  在第二个参数为空时,可以用第三个参数完成AppName和CmdLineArg的功能,方法是添加一个null进行分割。那么这为什么能导致函数不起作用呢?原因是C#中string类型是只读的,在我这里给它的赋值是string类型。它不能完成分割的动作,所以会造成访问违例。这其实在MSDN中都有相关的描述:

  The Unicode version of this function, CreateProcessAsUserW, can modify the contents of this string. Therefore, this parameter cannot be a pointer to read-only memory (such as a const variable or a literal string). If this parameter is a constant string, the function may cause an access violation.

  那么为什么用CharSet = CharSet.Ansi可以呢?LPTSTR支持两种字符格式,它会自动将Unicode的字符串转变为Ansi的字符串,即产生另外一个Ansi的字符串,该字符串是复制来的当然可以修改了!!哈哈!

  这里可以看出认认真真看好MSDN的解释是很有帮助的。顺便说下这第三个参数分割办法,以及我们要注意自己的路径。来看MSDN的说明:  

  The lpApplicationName parameter can be NULL. In that case, the module name must be the first white space–delimited token in the lpCommandLine string. If you are using a long file name that contains a space, use quoted strings to indicate where the file name ends and the arguments begin; otherwise, the file name is ambiguous. For example, consider the string "c:\program files\sub dir\program name". This string can be interpreted in a number of ways. The system tries to interpret the possibilities in the following order: 

c:\program.exe files\sub dir\program name
c:\program files\sub.exe dir\program name
c:\program files\sub dir\program.exe name
c:\program files\sub dir\program name.exe

 

  关于CreateProcessAsUser()详细信息请查看http://msdn.microsoft.com/en-us/library/ms682429.aspx

  关于CreateEnvironmentBlock()请查看http://msdn.microsoft.com/en-us/library/bb762270(VS.85).aspx

Win7中如何在服务中启动一个当前用户的进程——函数CreateProcessAsUser()的一次使用记录