我试图调用CreateProcessAsUser为用户从服务中运行桌面应用程序。(见从服务启动用户会话中的进程和来自服务和用户安全问题的CreateProcessAsUser)
它基本上可以工作,但是有些应用程序不能正常运行。最后,经过一些调试,我发现一个C#应用程序失败了,因为Environment.GetFolderPath
返回了一个特殊文件夹的null
。我认为这可能是因为被调用的用户注册中心或其中的某些路径没有正确地加载到用户配置文件中。
在CreateProcessAsUserW备注部分中,有一些提示在为用户创建进程时要考虑什么。我已经在使用CreateEnvironmentBlock创建一个环境块(环境变量似乎可以工作),但它还说:
CreateProcessAsUser不会将指定用户的配置文件加载到HKEY_USERS注册表项中。因此,要访问HKEY_CURRENT_USER注册表项中的信息,必须在调用CreateProcessAsUser之前使用LoadUserProfile函数将用户的配置文件信息加载到HKEY_USERS中。..。
我查看了LoadUserProfile的文档,其中显示了类似的内容:
请注意,在调用HKEY_USERS之前,您有责任将用户的注册表单元加载到带有LoadUserProfile函数的CreateProcessAsUser注册表项中。这是因为CreateProcessAsUser没有将指定的用户配置文件加载到HKEY_USERS中。这意味着访问HKEY_CURRENT_USER注册表项中的信息可能不会产生与正常交互登录一致的结果。
但是,我没有找到任何示例或进一步的细节,没有找到如何将用户的注册表单元加载到HKEY_USERS注册表项和注册表API (RegLoadKey看起来非常有希望,但我认为这不是正确的方法,或者在CreateProcessAsUser之前如何将它与目标进程一起使用)不做LoadUserProfile上描述的事情。此外,我看过的所有StackOverflow问题都没有提到这一点,或者只是在他们使用这个函数的一方提到了这个问题。
我认为传递概要文件可能是STARTUPINFOEX的一部分,但我也没有在文献资料中找到任何关于“配置文件”或“注册表”的内容。
我真的很难找到如何处理LoadUserProfile结果( struggling ),以及如何将它与CreateProcessAsUserW函数一起使用。如果您能给我一些提示,说明我到底想用这个函数做什么,以及如何在CreateProcessAsUserW中使用它,我会很高兴,因为我已经用WTSQueryUserToken获得了一个用户令牌。
这是我当前的C#代码,我是如何生成这个过程的(它还包含一些管道代码,为了便于阅读,我忽略了这些代码):
public class Win32ConPTYTerminal
{
public bool HasExited { get; private set; }
private IntPtr hPC;
private IntPtr hPipeIn = MyWin32.INVALID_HANDLE_VALUE;
private IntPtr hPipeOut = MyWin32.INVALID_HANDLE_VALUE;
private readonly TaskCompletionSource<int> tcs;
private readonly SemaphoreSlim exitSemaphore = new SemaphoreSlim(1);
private IntPtr hProcess;
private int dwPid;
public Win32ConPTYTerminal()
{
CreatePseudoConsoleAndPipes(out hPC, out hPipeIn, out hPipeOut);
tcs = new TaskCompletionSource<int>();
}
private static unsafe int InitializeStatupInfo(
ref MyWin32.STARTUPINFOEXW pInfo, IntPtr hPC)
{
pInfo.StartupInfo.cb = (uint)sizeof(MyWin32.STARTUPINFOEXW);
pInfo.StartupInfo.dwFlags = MyWin32.StartFlags.STARTF_USESTDHANDLES;
pInfo.StartupInfo.hStdInput = IntPtr.Zero;
pInfo.StartupInfo.hStdOutput = IntPtr.Zero;
pInfo.StartupInfo.hStdError = IntPtr.Zero;
fixed (char* title = "Console Title")
pInfo.StartupInfo.lpTitle = title;
var attrListSize = IntPtr.Zero;
MyWin32.InitializeProcThreadAttributeList(IntPtr.Zero, 1, 0,
ref attrListSize);
pInfo.lpAttributeList = Marshal.AllocHGlobal(attrListSize.ToInt32());
if (MyWin32.InitializeProcThreadAttributeList(
pInfo.lpAttributeList, 1, 0, ref attrListSize))
{
if (!MyWin32.UpdateProcThreadAttribute(
pInfo.lpAttributeList,
0,
MyWin32.PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
hPC,
new IntPtr(sizeof(IntPtr)),
IntPtr.Zero,
IntPtr.Zero
))
{
Marshal.FreeHGlobal(pInfo.lpAttributeList);
return Marshal.GetLastWin32Error();
}
else
{
return 0;
}
}
else
{
Marshal.FreeHGlobal(pInfo.lpAttributeList);
return Marshal.GetLastWin32Error();
}
}
private unsafe IntPtr ObtainUserToken(out string activeWinsta)
{
activeWinsta = @"Winsta0\default";
var process = MyWin32.GetCurrentProcess();
if (!MyWin32.OpenProcessToken(process,
MyWin32.AccessMask.TOKEN_ADJUST_PRIVILEGES
| MyWin32.AccessMask.TOKEN_QUERY
| MyWin32.AccessMask.TOKEN_DUPLICATE
| MyWin32.AccessMask.TOKEN_ASSIGN_PRIMARY,
out IntPtr processToken))
{
if (MyWin32.OpenProcessToken(process,
MyWin32.AccessMask.TOKEN_QUERY
| MyWin32.AccessMask.TOKEN_DUPLICATE
| MyWin32.AccessMask.TOKEN_ASSIGN_PRIMARY,
out var fallbackToken))
{
PrintWarning("could not obtain high privilege token for UI",
win32: true);
return fallbackToken;
}
throw new Win32Exception(
Marshal.GetLastWin32Error(),
"Unexpected win32 error code opening process token");
}
int sessionId = -1;
if (MyWin32.WTSEnumerateSessionsW(MyWin32.WTS_CURRENT_SERVER_HANDLE, 0, 1, out IntPtr pSession, out int sessionCount))
{
var sessions = (MyWin32.WTS_SESSION_INFOW*)pSession;
for (int i = 0; i < sessionCount; i++)
{
var session = sessions[i];
if (session.State != MyWin32.WTS_CONNECTSTATE_CLASS.WTSActive)
continue;
var winsta = Marshal.PtrToStringUni((IntPtr)session.pWinStationName);
PrintTrace("Detected active session " + winsta);
// activeWinsta = winsta;
sessionId = session.SessionId;
}
}
else
{
PrintWarning("WTSEnumerateSessionsW failed", win32: true);
}
if (sessionId == -1)
sessionId = MyWin32.WTSGetActiveConsoleSessionId();
if (sessionId == -1)
{
PrintWarning("no desktop user logged in to use",
win32: true);
return processToken;
}
var tokenPrivs = new MyWin32.TOKEN_PRIVILEGES();
if (!MyWin32.LookupPrivilegeValueW(null, "SeTcbPrivilege",
out var luid))
{
PrintWarning(
"could not change to desktop user (LookupPrivilegeValue)",
win32: true);
return processToken;
}
tokenPrivs.PrivilegeCount = 1;
tokenPrivs.Privileges.Luid = luid;
tokenPrivs.Privileges.Attributes = MyWin32.SE_PRIVILEGE_ENABLED;
if (!MyWin32.AdjustTokenPrivileges(processToken, false,
ref tokenPrivs, 0, IntPtr.Zero, IntPtr.Zero))
{
PrintWarning(
"could not change to desktop user (AdjustTokenPrivileges)",
win32: true);
return processToken;
}
try
{
if (!MyWin32.WTSQueryUserToken(sessionId, out var currentToken))
{
PrintWarning(
"could not change to desktop user on session " + sessionId + " (WTSQueryUserToken)",
win32: true);
return processToken;
}
return currentToken;
}
finally
{
tokenPrivs.Privileges.Attributes =
MyWin32.SE_PRIVILEGE_DISABLED;
MyWin32.AdjustTokenPrivileges(processToken, false,
ref tokenPrivs, 0, IntPtr.Zero, IntPtr.Zero);
}
}
private unsafe string? TraceTokenInfo(IntPtr token)
{
if (!MyWin32.GetTokenInformation(token,
MyWin32.TOKEN_INFORMATION_CLASS.TokenUser,
IntPtr.Zero,
0,
out int returnLength))
{
var error = Marshal.GetLastWin32Error();
if (error != MyWin32.ERROR_INSUFFICIENT_BUFFER)
{
PrintWarning(
"could not determine token user (GetTokenInformation)",
win32: true);
return null;
}
}
string username;
var userInfoPtr = Marshal.AllocHGlobal(returnLength);
try
{
if (!MyWin32.GetTokenInformation(token,
MyWin32.TOKEN_INFORMATION_CLASS.TokenUser,
userInfoPtr,
returnLength,
out int returnLength2))
{
PrintWarning(
"could not determine token user (GetTokenInformation)",
win32: true);
return null;
}
var user = (MyWin32.TOKEN_USER*)userInfoPtr;
var userSid = (*user).User.Sid;
StringBuilder name = new StringBuilder(255);
int nameLength = name.Capacity;
StringBuilder domainName = new StringBuilder(255);
int domainNameLength = domainName.Capacity;
if (!MyWin32.LookupAccountSidW(
null,
userSid,
name, ref nameLength,
domainName, ref domainNameLength,
out var peUse))
{
PrintWarning(
"could not determine token user (LookupAccountSidW)",
win32: true);
return null;
}
username = name.ToString();
PrintTrace("Running process with user " + username);
}
finally
{
Marshal.FreeHGlobal(userInfoPtr);
}
return username;
}
public void Start(ProcessStartInfo startInfo, CancellationToken cancel)
{
HasExited = false;
var startupinfo = new MyWin32.STARTUPINFOEXW();
var result = InitializeStatupInfo(ref startupinfo, hPC);
if (result != 0)
throw new Exception("Unexpected win32 error code " + result);
var token = ObtainUserToken(out var winsta);
var username = TraceTokenInfo(token);
string environment = MakeEnvironment(token, startInfo.Environment);
var cmdLine = new StringBuilder();
cmdLine
.Append(LocalProcessExecutor.EscapeShellParam(startInfo.FileName))
.Append(' ')
.Append(startInfo.Arguments);
cancel.ThrowIfCancellationRequested();
MyWin32.PROCESS_INFORMATION piClient;
var withProfileInfo = new WithProfileInfo(token, username);
if (!withProfileInfo.LoadedProfileInfo)
PrintWarning("Failed to LoadUserProfile, registry will not work as expected", true);
unsafe
{
fixed (char* winstaPtr = winsta)
{
startupinfo.StartupInfo.lpDesktop = winstaPtr;
result = MyWin32.CreateProcessAsUserW(
token,
startInfo.FileName,
cmdLine,
IntPtr.Zero, // Process handle not inheritable
IntPtr.Zero, // Thread handle not inheritable
false, // Inherit handles
MyWin32.ProcessCreationFlags.EXTENDED_STARTUPINFO_PRESENT // use startupinfoex
| MyWin32.ProcessCreationFlags.CREATE_UNICODE_ENVIRONMENT // environment is wstring
// | MyWin32.ProcessCreationFlags.CREATE_NEW_PROCESS_GROUP // make this not a child (for maximum compatibility)
,
environment,
startInfo.WorkingDirectory,
ref startupinfo.StartupInfo,
out piClient
) ? 0 : Marshal.GetLastWin32Error();
}
}
if (result != 0)
{
Marshal.FreeHGlobal(startupinfo.lpAttributeList);
withProfileInfo.Dispose();
throw result switch
{
2 => new FileNotFoundException(
"Could not find file to execute",
startInfo.FileName
),
3 => new FileNotFoundException(
"Could not find path to execute",
startInfo.FileName
),
_ => new Win32Exception(result),
};
}
dwPid = piClient.dwProcessId;
hProcess = piClient.hProcess;
cancel.Register(() =>
{
if (HasExited || hProcess == IntPtr.Zero)
return;
Kill();
});
Task.Run(async () =>
{
MyWin32.WaitForSingleObject(hProcess, -1);
MyWin32.GetExitCodeProcess(hProcess, out var exitCode);
HasExited = true;
// wait a little bit for final log lines
var exited = await exitSemaphore.WaitAsync(50)
.ConfigureAwait(false);
if (exited)
exitSemaphore.Release();
tcs.TrySetResult(exitCode);
withProfileInfo.Dispose();
MyWin32.CloseHandle(piClient.hThread);
MyWin32.CloseHandle(piClient.hProcess);
hProcess = IntPtr.Zero;
MyWin32.DeleteProcThreadAttributeList(
startupinfo.lpAttributeList);
Marshal.FreeHGlobal(startupinfo.lpAttributeList);
Dispose();
});
}
private unsafe string MakeEnvironment(IntPtr token, IDictionary<string, string> envOverride)
{
StringBuilder environment = new StringBuilder();
foreach (var kv in envOverride)
{
environment.Append(kv.Key)
.Append('=')
.Append(kv.Value)
.Append('\0');
}
if (!MyWin32.CreateEnvironmentBlock(out var lpEnvironment, token, bInherit: false))
{
PrintWarning(
"could not generate environment variables (CreateEnvironmentBlock)",
win32: true);
return environment.ToString();
}
var envPtr = (char*)lpEnvironment;
int i = 0;
while (i == 0 || !(envPtr[i] == '\0' && envPtr[i - 1] == '\0'))
{
int start = i;
while (envPtr[i] != '\0' && envPtr[i] != '=')
{
i++;
}
// malformed check, needs =
if (envPtr[i] == '\0')
continue;
var name = Marshal.PtrToStringUni((IntPtr)(envPtr + start), i - start);
i++;
start = i;
while (envPtr[i] != '\0')
{
i++;
}
var value = Marshal.PtrToStringUni((IntPtr)(envPtr + start), i - start);
i++;
if (!envOverride.Keys.Contains(name))
{
environment.Append(name)
.Append('=')
.Append(value)
.Append('\0');
}
}
if (!MyWin32.DestroyEnvironmentBlock(lpEnvironment))
{
PrintWarning(
"failed to free environment block, leaking memory (DestroyEnvironmentBlock)",
win32: true);
}
environment.Append('\0');
return environment.ToString();
}
public Task<int> WaitForExit()
{
return tcs.Task;
}
public void Kill(int statusCode = -1)
{
if (HasExited || hProcess == IntPtr.Zero)
return;
MyWin32.EnumWindows((hwnd, _) => {
MyWin32.GetWindowThreadProcessId(hwnd, out var pid);
if (pid == dwPid)
{
MyWin32.PostMessageW(hwnd, MyWin32.Message.WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
}
return true;
}, IntPtr.Zero);
if (MyWin32.WaitForSingleObject(hProcess, 1000) != 0)
{
MyWin32.TerminateProcess(hProcess, statusCode);
MyWin32.WaitForSingleObject(hProcess, 500);
}
else
{
statusCode = 0;
}
// wait a little bit for final log lines
if (exitSemaphore.Wait(50))
exitSemaphore.Release();
// actual exit code is more accurate, give watching thread time to set it first
Thread.Sleep(50);
// if thread hung up, set manually
tcs.TrySetResult(statusCode);
}
private bool disposed;
private readonly object _lockObject = new object();
public void Dispose()
{
lock (_lockObject)
{
if (disposed)
return;
disposed = true;
}
if (hPC != IntPtr.Zero)
MyWin32.ClosePseudoConsole(hPC);
hPC = IntPtr.Zero;
DisposePipes();
exitSemaphore.Dispose();
}
private void DisposePipes()
{
if (hPipeOut != MyWin32.INVALID_HANDLE_VALUE)
MyWin32.CloseHandle(hPipeOut);
hPipeOut = MyWin32.INVALID_HANDLE_VALUE;
if (hPipeIn != MyWin32.INVALID_HANDLE_VALUE)
MyWin32.CloseHandle(hPipeIn);
hPipeIn = MyWin32.INVALID_HANDLE_VALUE;
}
}
public unsafe class WithProfileInfo : IDisposable
{
private MyWin32.PROFILEINFOW profileInfo;
private IntPtr token;
private char* username;
public bool LoadedProfileInfo { get; }
public WithProfileInfo(IntPtr token, string username)
{
this.token = token;
this.username = (char*)Marshal.StringToHGlobalUni(username);
profileInfo.dwSize = sizeof(MyWin32.PROFILEINFOW);
profileInfo.lpUserName = this.username;
if (!MyWin32.LoadUserProfileW(token, ref profileInfo))
{
LoadedProfileInfo = false;
}
else
{
LoadedProfileInfo = true;
}
}
public void Dispose()
{
if (LoadedProfileInfo)
MyWin32.UnloadUserProfile(token, profileInfo.hProfile);
Marshal.FreeHGlobal((IntPtr)username);
}
}
然后,当我试图在应用程序中打印出一堆已知的文件夹(使用SHGetSpecialFolderPathW)时,我在服务中得到以下内容:
FOLDERID_LocalAppData:
(Win32 Error for above) SHGetKnownFolderPath: 5 - Access is denied.
FOLDERID_RoamingAppData:
(Win32 Error for above) SHGetKnownFolderPath: 5 - Access is denied.
FOLDERID_Desktop:
(Win32 Error for above) SHGetKnownFolderPath: 2 - The system cannot find the file specified.
FOLDERID_Documents:
(Win32 Error for above) SHGetKnownFolderPath: 2 - The system cannot find the file specified.
FOLDERID_Downloads:
(Win32 Error for above) SHGetKnownFolderPath: 2 - The system cannot find the file specified.
FOLDERID_Favorites:
(Win32 Error for above) SHGetKnownFolderPath: 2 - The system cannot find the file specified.
FOLDERID_Links: C:\Users\testuser\Links
FOLDERID_Music:
(Win32 Error for above) SHGetKnownFolderPath: 2 - The system cannot find the file specified.
FOLDERID_Pictures:
(Win32 Error for above) SHGetKnownFolderPath: 2 - The system cannot find the file specified.
FOLDERID_Programs:
(Win32 Error for above) SHGetKnownFolderPath: 5 - Access is denied.
FOLDERID_SavedGames: C:\Users\testuser\Saved Games
FOLDERID_Startup:
(Win32 Error for above) SHGetKnownFolderPath: 2 - The system cannot find the file specified.
FOLDERID_Templates:
(Win32 Error for above) SHGetKnownFolderPath: 2 - The system cannot find the file specified.
FOLDERID_Videos:
(Win32 Error for above) SHGetKnownFolderPath: 2 - The system cannot find the file specified.
FOLDERID_Fonts: C:\WINDOWS\Fonts
FOLDERID_ProgramData: C:\ProgramData
FOLDERID_CommonPrograms: C:\ProgramData\Microsoft\Windows\Start Menu\Programs
FOLDERID_CommonStartup: C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup
FOLDERID_CommonTemplates: C:\ProgramData\Microsoft\Windows\Templates
FOLDERID_PublicDesktop: C:\Users\Public\Desktop
FOLDERID_PublicDocuments: C:\Users\Public\Documents
FOLDERID_PublicDownloads: C:\Users\Public\Downloads
FOLDERID_PublicMusic: C:\Users\Public\Music
FOLDERID_PublicPictures: C:\Users\Public\Pictures
FOLDERID_PublicVideos: C:\Users\Public\Videos
在服务之外,我得到:
FOLDERID_LocalAppData: C:\Users\testuser\AppData\Local
FOLDERID_RoamingAppData: C:\Users\testuser\AppData\Roaming
FOLDERID_Desktop: C:\Users\testuser\Desktop
FOLDERID_Documents: C:\Users\testuser\Documents
FOLDERID_Downloads: C:\Users\testuser\Downloads
FOLDERID_Favorites: C:\Users\testuser\Favorites
FOLDERID_Links: C:\Users\testuser\Links
FOLDERID_Music: C:\Users\testuser\Music
FOLDERID_Pictures: C:\Users\testuser\Pictures
FOLDERID_Programs: C:\Users\testuser\AppData\Roaming\Microsoft\Windows\Start Menu\Programs
FOLDERID_SavedGames: C:\Users\testuser\Saved Games
FOLDERID_Startup: C:\Users\testuser\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup
FOLDERID_Templates: C:\Users\testuser\AppData\Roaming\Microsoft\Windows\Templates
FOLDERID_Videos: C:\Users\testuser\Videos
FOLDERID_Fonts: C:\WINDOWS\Fonts
FOLDERID_ProgramData: C:\ProgramData
FOLDERID_CommonPrograms: C:\ProgramData\Microsoft\Windows\Start Menu\Programs
FOLDERID_CommonStartup: C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup
FOLDERID_CommonTemplates: C:\ProgramData\Microsoft\Windows\Templates
FOLDERID_PublicDesktop: C:\Users\Public\Desktop
FOLDERID_PublicDocuments: C:\Users\Public\Documents
FOLDERID_PublicDownloads: C:\Users\Public\Downloads
FOLDERID_PublicMusic: C:\Users\Public\Music
FOLDERID_PublicPictures: C:\Users\Public\Pictures
FOLDERID_PublicVideos: C:\Users\Public\Videos
我不知道为什么SHGetKnownFolderPath会这样失败。(在C#中,它只返回null)
我发现了另一个问题,似乎也有同样的问题,但是我已经打电话给LoadUserProfile了,所以这个解决方案对我不起作用:塞塞森德
发布于 2022-01-17 09:41:22
问题是,我向进程传递了错误的环境变量。我正确地使用了CreateEnvironmentBlock
,但提供了一种通过API覆盖环境变量的方法。
我的API就是使用ProcessStartInfo,它将其环境字典默认为正在运行的流程环境(系统服务环境),后者覆盖了为用户生成的环境变量。
这导致已知文件夹API SHGetKnownFolderPath
与错误2(系统找不到指定的文件)或错误5(访问被拒绝)失败。
因此,没有用服务用户环境覆盖环境变量,解决了问题,现在程序正在正确运行。
https://stackoverflow.com/questions/70393105
复制相似问题