@@ -52,7 +52,31 @@ namespace Atlas {
5252 enum TOKEN_TYPE { TokenPrimary = 1, TokenImpersonation }
5353 enum TOKEN_INFORMATION_CLASS { TokenLinkedToken = 19 }
5454
55+ [StructLayout(LayoutKind.Sequential)]
56+ struct LUID {
57+ public uint LowPart;
58+ public int HighPart;
59+ }
60+
61+ [StructLayout(LayoutKind.Sequential)]
62+ struct TOKEN_PRIVILEGES {
63+ public int PrivilegeCount;
64+ public LUID Luid;
65+ public int Attributes;
66+ }
67+
68+ [StructLayout(LayoutKind.Sequential)]
69+ struct WTS_SESSION_INFO {
70+ public uint SessionId;
71+ public IntPtr pWinStationName;
72+ public int State; // WTS_CONNECTSTATE_CLASS; 0 = WTSActive
73+ }
74+
5575 const int TOKEN_DUPLICATE = 0x0002;
76+ const int TOKEN_ADJUST_PRIVILEGES = 0x0020;
77+ const int TOKEN_QUERY = 0x0008;
78+ const int SE_PRIVILEGE_ENABLED = 0x0002;
79+ const int ERROR_NOT_ALL_ASSIGNED = 1300;
5680 const uint MAXIMUM_ALLOWED = 0x02000000;
5781 const int CREATE_UNICODE_ENVIRONMENT = 0x00000400;
5882 const int CREATE_NO_WINDOW = 0x08000000;
@@ -61,9 +85,74 @@ namespace Atlas {
6185 [DllImport("kernel32.dll", SetLastError = true)]
6286 static extern uint WTSGetActiveConsoleSessionId();
6387
88+ [DllImport("kernel32.dll")]
89+ static extern IntPtr GetCurrentProcess();
90+
91+ [DllImport("advapi32.dll", SetLastError = true)]
92+ static extern bool OpenProcessToken(IntPtr ProcessHandle, int DesiredAccess, out IntPtr TokenHandle);
93+
94+ [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
95+ static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, out LUID lpLuid);
96+
97+ [DllImport("advapi32.dll", SetLastError = true)]
98+ static extern bool AdjustTokenPrivileges(IntPtr TokenHandle, bool DisableAllPrivileges, ref TOKEN_PRIVILEGES NewState, int BufferLength, IntPtr PreviousState, IntPtr ReturnLength);
99+
100+ [DllImport("wtsapi32.dll", SetLastError = true)]
101+ static extern bool WTSEnumerateSessions(IntPtr hServer, int Reserved, int Version, out IntPtr ppSessionInfo, out int pCount);
102+
103+ [DllImport("wtsapi32.dll")]
104+ static extern void WTSFreeMemory(IntPtr pMemory);
105+
64106 [DllImport("wtsapi32.dll", SetLastError = true)]
65107 static extern bool WTSQueryUserToken(uint sessionId, out IntPtr phToken);
66108
109+ // SYSTEM holds these privileges but they can arrive disabled; WTSQueryUserToken
110+ // needs SeTcb and CreateProcessAsUser needs SeAssignPrimaryToken + SeIncreaseQuota.
111+ static void EnablePrivilege(string name) {
112+ IntPtr token;
113+ if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out token)) {
114+ throw new Win32Exception(Marshal.GetLastWin32Error(), "OpenProcessToken failed.");
115+ }
116+ try {
117+ LUID luid;
118+ if (!LookupPrivilegeValue(null, name, out luid)) {
119+ throw new Win32Exception(Marshal.GetLastWin32Error(), "LookupPrivilegeValue(" + name + ") failed.");
120+ }
121+ TOKEN_PRIVILEGES tp = new TOKEN_PRIVILEGES();
122+ tp.PrivilegeCount = 1;
123+ tp.Luid = luid;
124+ tp.Attributes = SE_PRIVILEGE_ENABLED;
125+ if (!AdjustTokenPrivileges(token, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero) || Marshal.GetLastWin32Error() == ERROR_NOT_ALL_ASSIGNED) {
126+ throw new Win32Exception(Marshal.GetLastWin32Error(), "Enabling privilege " + name + " failed (is the caller SYSTEM?).");
127+ }
128+ }
129+ finally {
130+ CloseHandle(token);
131+ }
132+ }
133+
134+ // The interactive user can be in a non-console session (Hyper-V enhanced session,
135+ // RDP), so pick the first active user session and only fall back to the console.
136+ static uint GetInteractiveSessionId() {
137+ IntPtr pSessions;
138+ int count;
139+ if (WTSEnumerateSessions(IntPtr.Zero, 0, 1, out pSessions, out count)) {
140+ try {
141+ int size = Marshal.SizeOf(typeof(WTS_SESSION_INFO));
142+ for (int i = 0; i < count; i++) {
143+ WTS_SESSION_INFO info = (WTS_SESSION_INFO)Marshal.PtrToStructure(new IntPtr(pSessions.ToInt64() + (long)i * size), typeof(WTS_SESSION_INFO));
144+ if (info.State == 0 && info.SessionId != 0) {
145+ return info.SessionId;
146+ }
147+ }
148+ }
149+ finally {
150+ WTSFreeMemory(pSessions);
151+ }
152+ }
153+ return WTSGetActiveConsoleSessionId();
154+ }
155+
67156 [DllImport("advapi32.dll", SetLastError = true)]
68157 static extern bool DuplicateTokenEx(IntPtr hExistingToken, uint dwDesiredAccess, IntPtr lpTokenAttributes,
69158 SECURITY_IMPERSONATION_LEVEL impersonationLevel, TOKEN_TYPE tokenType, out IntPtr phNewToken);
@@ -95,14 +184,19 @@ namespace Atlas {
95184 // Runs the command line as the active console user; returns the child exit code
96185 // (or 0 when not waiting). Throws Win32Exception on any failure.
97186 public static int Launch(string applicationName, string commandLine, string workingDirectory, bool wait, bool elevated) {
98- uint sessionId = WTSGetActiveConsoleSessionId();
187+ EnablePrivilege("SeTcbPrivilege");
188+ EnablePrivilege("SeAssignPrimaryTokenPrivilege");
189+ EnablePrivilege("SeIncreaseQuotaPrivilege");
190+
191+ uint sessionId = GetInteractiveSessionId();
99192 if (sessionId == 0xFFFFFFFF) {
100- throw new InvalidOperationException("No active console session; there is no interactive user to run as.");
193+ throw new InvalidOperationException("No active user session; there is no interactive user to run as.");
101194 }
102195
103196 IntPtr userToken = IntPtr.Zero;
104197 if (!WTSQueryUserToken(sessionId, out userToken)) {
105- throw new Win32Exception(Marshal.GetLastWin32Error(), "WTSQueryUserToken failed (is the caller SYSTEM with a user logged on?).");
198+ int error = Marshal.GetLastWin32Error();
199+ throw new Win32Exception(error, string.Format("WTSQueryUserToken failed for session {0} (Win32 error {1}).", sessionId, error));
106200 }
107201
108202 IntPtr primaryToken = IntPtr.Zero;
0 commit comments