zoukankan      html  css  js  c++  java
  • 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         #endregion
    134 
    135         /// <summary>
    136         /// Launches the given application with full admin rights, and in addition bypasses the Vista UAC prompt
    137         /// </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 id
    147             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 token
    158             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, meaning
    174             // the window station has a desktop that is invisible and the process is incapable of receiving
    175             // 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 = @"winsta0default";
    180  
    181             // flags that specify the priority and creation method of the process
    182             int dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT;
    183 
    184             // create a new process in the current user's logon session
    185             bool result = CreateProcessAsUser(hUserTokenDup,        // client's access token
    186                                             null,                   // file to execute
    187                                             commandLine,            // command line
    188                                             ref sa,                 // pointer to process SECURITY_ATTRIBUTES
    189                                             ref sa,                 // pointer to thread SECURITY_ATTRIBUTES
    190                                             false,                  // handles are not inheritable
    191                                             dwCreationFlags,        // creation flags
    192                                             EnvironmentFromUser,    // pointer to new environment block 
    193                                             null,                   // name of current directory 
    194                                             ref si,                 // pointer to STARTUPINFO structure
    195                                             out procInfo            // receives information about new process
    196                                             );
    197            
    198             // invalidate the handles
    199             CloseHandle(hPToken);
    200             CloseHandle(hUserTokenDup);
    201             DestroyEnvironmentBlock(EnvironmentFromUser);
    202 
    203             return result; // return the result
    204         }
    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=usersAlvin

      的确是要访问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 filessub dirprogram 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 filessub dirprogram name
    c:program filessub.exe dirprogram name
    c:program filessub dirprogram.exe name
    c:program filessub dirprogram name.exe

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

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

  • 相关阅读:
    开始核心攻坚
    Features postponed for ASP.NET 2.0 Beta 2
    设计模式的认识
    如果您想要提高开发效率,那么给大家推荐一本书,比较实用
    asp.net 2.0 个性化服务探讨
    对于数据缓存依赖的认识
    ASP.NET 2.0学习(1)——XmlDataSource控件中XPath属性之疑惑
    写作的四个境界
    ASP.NET 2.0 product design changes between Beta 1 and Beta 2(a new message from asp.net forum)
    验证控件的问题
  • 原文地址:https://www.cnblogs.com/darling131499/p/4082557.html
Copyright © 2011-2022 走看看