所以这是独家新闻:
我在前一段时间写了一个小小的C#应用程序,显示主机名,IP地址,图像日期,解冻状态(我们使用DeepFreeze),当前域和当前日期/时间,以显示在我们Windows的欢迎屏幕上7台实验室机器。这是为了取代我们以前的信息块,它是在启动时静态设置的,实际上是嵌入到背景中的文本,带有更多动态和功能。该应用程序使用Timer来每秒更新IP地址,deepfreeze状态和时钟,并检查用户是否已登录并在检测到此类情况时自行终止。
如果我们只是通过我们的启动脚本(通过组策略设置)运行它,它会保持脚本打开,并且机器永远不会进入登录提示。如果我们使用类似start或cmd命令的东西在单独的shell /进程下启动它,它将运行直到启动脚本完成,此时Windows似乎清理脚本的所有子进程。我们现在能够绕过它使用psexec -s -d -i -x
来启动它,这使它在启动脚本完成后仍然存在,但速度可能非常慢,在启动时间的5秒到一分钟之间增加。
我们已经尝试使用另一个C#应用程序来启动进程,通过Process类,使用WMI调用(Win32_Process和Win32_ProcessStartup)以及各种启动标志等,但所有结束都与脚本完成和信息的结果相同阻止进程被杀死。我修改了将应用程序重写为服务,但服务从未设计为与桌面交互,更不用说登录窗口,并且在正确的上下文中运行的东西似乎从未真正解决过。
所以对于这个问题:有没有人有一个很好的方法来实现这个目标?启动一个任务,使其独立于启动脚本并在欢迎屏幕上运行?
答案 0 :(得分:11)
这可以通过许多Win32 API调用来完成。我已经设法将一个带有GUI的程序放到Winlogon桌面上(在任何人要求之前,它不是一个交互式GUI)。基本上,您需要以SYSTEM身份运行加载程序进程,然后将生成新进程。由于您很可能希望此过程在启动时运行,因此您可以使用任务计划程序以SYSTEM身份运行加载程序,也可以使用服务执行相同的操作。我目前正在使用服务,但我尝试使用任务调度程序,它确实工作得很好。
简短摘要:
代码示例:
// grab the winlogon process
Process winLogon = null;
foreach (Process p in Process.GetProcesses()) {
if (p.ProcessName.Contains("winlogon")) {
winLogon = p;
break;
}
}
// grab the winlogon's token
IntPtr userToken = IntPtr.Zero;
if (!OpenProcessToken(winLogon.Handle, TOKEN_QUERY | TOKEN_IMPERSONATE | TOKEN_DUPLICATE, out userToken)) {
log("ERROR: OpenProcessToken returned false - " + Marshal.GetLastWin32Error());
}
// create a new token
IntPtr newToken = IntPtr.Zero;
SECURITY_ATTRIBUTES tokenAttributes = new SECURITY_ATTRIBUTES();
tokenAttributes.nLength = Marshal.SizeOf(tokenAttributes);
SECURITY_ATTRIBUTES threadAttributes = new SECURITY_ATTRIBUTES();
threadAttributes.nLength = Marshal.SizeOf(threadAttributes);
// duplicate the winlogon token to the new token
if (!DuplicateTokenEx(userToken, 0x10000000, ref tokenAttributes, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation,
TOKEN_TYPE.TokenImpersonation, out newToken)) {
log("ERROR: DuplicateTokenEx returned false - " + Marshal.GetLastWin32Error());
}
TOKEN_PRIVILEGES tokPrivs = new TOKEN_PRIVILEGES();
tokPrivs.PrivilegeCount = 1;
LUID seDebugNameValue = new LUID();
if (!LookupPrivilegeValue(null, SE_DEBUG_NAME, out seDebugNameValue)) {
log("ERROR: LookupPrivilegeValue returned false - " + Marshal.GetLastWin32Error());
}
tokPrivs.Privileges = new LUID_AND_ATTRIBUTES[1];
tokPrivs.Privileges[0].Luid = seDebugNameValue;
tokPrivs.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
// escalate the new token's privileges
if (!AdjustTokenPrivileges(newToken, false, ref tokPrivs, 0, IntPtr.Zero, IntPtr.Zero)) {
log("ERROR: AdjustTokenPrivileges returned false - " + Marshal.GetLastWin32Error());
}
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
STARTUPINFO si = new STARTUPINFO();
si.cb = Marshal.SizeOf(si);
si.lpDesktop = "Winsta0\\Winlogon";
// start the process using the new token
if (!CreateProcessAsUser(newToken, process, process, ref tokenAttributes, ref threadAttributes,
true, (uint)CreateProcessFlags.CREATE_NEW_CONSOLE | (uint)CreateProcessFlags.INHERIT_CALLER_PRIORITY, IntPtr.Zero,
logInfoDir, ref si, out pi)) {
log("ERROR: CreateProcessAsUser returned false - " + Marshal.GetLastWin32Error());
}
Process _p = Process.GetProcessById(pi.dwProcessId);
if (_p != null) {
log("Process " + _p.Id + " Name " + _p.ProcessName);
} else {
log("Process not found");
}
答案 1 :(得分:6)
这是其中一个“你真的需要一个很好的理由去做这个”的问题。 Microsoft非常努力地阻止在启动屏幕上运行的应用程序 - Windows中与登录屏幕交互的每一段代码都经过仔细的代码审查,因为在登录屏幕上运行的代码中的错误的安全后果是可怕的 - 如果你搞砸了甚至稍微提高,您将允许恶意软件进入计算机。
为什么要在登录屏幕上运行程序?也许有一种记录在案的方式并没有风险。
答案 2 :(得分:6)
我在C ++中翻译了上面的代码,如果其他人需要它...注意我的代码部分有引用,但无论如何它可能会有所帮助:
static bool StartProcess(LPCTSTR lpApplicationPath)
{
CAutoGeneralHandle hWinlogonProcess = FindWinlogonProcess();
if (hWinlogonProcess == INVALID_HANDLE_VALUE)
{
DU_OutputDebugStringff(L"ERROR: Can't find the 'winlogon' process");
return false;
}
CAutoGeneralHandle hUserToken;
if (!OpenProcessToken(hWinlogonProcess, TOKEN_QUERY|TOKEN_IMPERSONATE|TOKEN_DUPLICATE, &hUserToken))
{
DU_OutputDebugStringff(L"ERROR: OpenProcessToken returned false (error %u)", GetLastError());
return false;
}
// Create a new token
SECURITY_ATTRIBUTES tokenAttributes = {0};
tokenAttributes.nLength = sizeof tokenAttributes;
SECURITY_ATTRIBUTES threadAttributes = {0};
threadAttributes.nLength = sizeof threadAttributes;
// Duplicate the winlogon token to the new token
CAutoGeneralHandle hNewToken;
if (!DuplicateTokenEx(hUserToken, 0x10000000, &tokenAttributes,
SECURITY_IMPERSONATION_LEVEL::SecurityImpersonation,
TOKEN_TYPE::TokenImpersonation, &hNewToken))
{
DU_OutputDebugStringff(L"ERROR: DuplicateTokenEx returned false (error %u)", GetLastError());
return false;
}
TOKEN_PRIVILEGES tokPrivs = {0};
tokPrivs.PrivilegeCount = 1;
LUID seDebugNameValue = {0};
if (!LookupPrivilegeValue(nullptr, SE_DEBUG_NAME, &seDebugNameValue))
{
DU_OutputDebugStringff(L"ERROR: LookupPrivilegeValue returned false (error %u)", GetLastError());
return false;
}
tokPrivs.Privileges[0].Luid = seDebugNameValue;
tokPrivs.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
// Escalate the new token's privileges
if (!AdjustTokenPrivileges(hNewToken, false, &tokPrivs, 0, nullptr, nullptr))
{
DU_OutputDebugStringff(L"ERROR: AdjustTokenPrivileges returned false (error %u)", GetLastError());
return false;
}
PROCESS_INFORMATION pi = {0};
STARTUPINFO si = {0};
si.cb = sizeof si;
si.lpDesktop = L"Winsta0\\Winlogon";
// Start the process using the new token
if (!CreateProcessAsUser(hNewToken, lpApplicationPath, nullptr, &tokenAttributes, &threadAttributes,
true, CREATE_NEW_CONSOLE|INHERIT_CALLER_PRIORITY, nullptr, nullptr, &si, &pi))
{
DU_OutputDebugStringff(L"ERROR: CreateProcessAsUser returned false (error %u)", GetLastError());
return false;
}
return true;
}
答案 3 :(得分:1)
我认为你可以做到,但它非常复杂。通常不允许在欢迎屏幕上运行交互式应用程序。在较高的层次上,您需要:
WTSGetActiveConsoleSessionId
和OpenInputDesktop
)我编写了一个可以与登录屏幕进行某种程度交互的应用程序,但它没有显示任何UI。它可能已经完成,但可能更为复杂。
注意:我发现我无法从Windows服务中获取OpenInputDesktop
的结果。我不得不在另一个进程中进行调用,并通知服务在正确的桌面上重新启动进程。
我希望至少能让你开始。祝你好运!