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 = @"winsta0\default";
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=\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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM