【问题标题】:Running a process at the Windows 7 Welcome Screen在 Windows 7 欢迎屏幕上运行进程
【发布时间】:2010-06-18 13:50:26
【问题描述】:

所以这是独家新闻:

不久前我编写了一个小型 C# 应用程序,它显示主机名、IP 地址、映像日期、解冻状态(我们使用 DeepFreeze)、当前域和当前日期/时间,以显示在我们 Windows 的欢迎屏幕上7台实验室机器。这是为了替换我们之前的信息块,它在启动时静态设置,实际上将文本嵌入到背景中,变得更加动态和实用。该应用程序使用 Timer 每秒更新 ip 地址、deepfreeze 状态和时钟,并检查用户是否已登录并在检测到此类情况时杀死自己。

如果我们只是通过我们的启动脚本(通过组策略设置)运行它,它会使脚本保持打开状态,并且机器永远不会进入登录提示。如果我们使用 start 或 cmd 命令之类的命令在单独的 shell/进程下启动它,它会一直运行到启动脚本完成,此时 Windows 似乎会清理脚本的所有子进程。我们目前能够绕过这一点,使用psexec -s -d -i -x 将其关闭,这可以让它在启动脚本完成后持续存在,但速度可能非常慢,在我们的启动时间中增加了 5 秒到 1 多分钟之间的任何时间。

我们已经尝试使用另一个 C# 应用程序来启动进程,通过 Process 类,使用带有各种启动标志等的 WMI 调用(Win32_Process 和 Win32_ProcessStartup),但都以脚本完成和信息的相同结果结束阻止进程被杀死。我尝试将应用程序重写为服务,但服务从未被设计为与桌面交互,更不用说登录窗口了,而且让事情在正确的上下文中运行似乎从来没有真正成功过。

所以对于这个问题:有没有人有一个很好的方法来做到这一点?启动一个任务,使其独立于启动脚本并在欢迎屏幕上运行?

【问题讨论】:

    标签: windows-7 login-script


    【解决方案1】:

    这可以通过大量的 Win32 API 调用来完成。我已经设法在 Winlogon 桌面上安装了一个带有 GUI 的程序(在有人问之前,它不是交互式 GUI)。基本上,您需要以 SYSTEM 身份运行加载程序进程,然后它将生成新进程。由于您很可能希望此进程在启动时运行,因此您可以使用任务调度程序将加载程序作为 SYSTEM 运行,也可以使用服务来执行相同的操作。我目前正在使用一项服务,但我尝试使用任务调度程序,它确实工作得很好。

    简短的总结:

    1. 获取 Winlogon.exe 进程(作为进程)
    2. 使用进程的.handle使用OpenProcessToken获取winlogon的token
    3. 创建一个新令牌并将 winlogon 令牌复制到它
    4. 提升令牌的权限
    5. 使用 CreateProcessAsUser 创建进程,确保将 lpDesktop 设置为“Winsta0\Winlogon”并使用您创建的令牌。

    代码示例:

            // 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");
            }
    

    【讨论】:

    • @Fluxer Windows XP 的登录过程与 Vista 及以上版本的登录过程有很大不同。 XP 使用 GINA,但从 Vista 开始,Microsoft 重新编写了登录部分以使用 CredentialProviders。希望这会有所帮助。
    • 尝试运行 CreateProcessAsUser 时出现错误 1314。有什么建议么? string lpAppName = @"D:\SMU\Ubiquitous Computing 7390\Final Project\Locker\Locker\bin\Debug\Locker.exe"; if (!CreateProcessAsUser(newToken, lpAppName, null, ref tokenAttributes, ref threadAttributes, true, (uint)CreateProcessFlags.CREATE_NEW_CONSOLE | (uint)CreateProcessFlags.INHERIT_CALLER_PRIORITY, IntPtr.Zero, null, ref si, out pi))
    【解决方案2】:

    这是“你真的需要一个很好的理由这样做”的问题之一。 Microsoft 非常努力地阻止在启动屏幕上运行的应用程序 - Windows 中与登录屏幕交互的每一段代码都经过非常仔细的代码审查,因为在登录屏幕上运行的代码中的错误的安全后果是可怕的 - 如果你搞砸了即使稍微增加,您也会允许恶意软件进入计算机。

    为什么要在登录屏幕上运行程序?也许有一种记录在案的方法,风险不大。

    【讨论】:

    • 您想显示的信息可以放在消息框中吗? Windows 有一种机制,可以在登录时在消息框中显示文本字符串,这可能会执行您想要的操作。
    • 他们需要哪些无法通过 WMI 远程检索的信息? Windows 拥有数量惊人的管理信息,可以使用专供支持和操作人员使用的 WMI API 读取这些信息。是否有他们需要的信息只能在物理控制台上获得?
    • 从我的位置,我正在试图帮助您解决问题。没有可靠的方法可以在不给系统带来潜在安全威胁的情况下做你想做的事,所以我试图想出一种替代机制,让你在不破坏系统安全功能的情况下检索你需要的信息。大约每 3 个月一次,MSFT 内部的某个人就我们内部的“windows tricks”别名提出了一个类似的问题,答案总是与我给出的答案相同 - 找到另一种做你想做的事情的方法,因为它对 所有我们的客户。
    • 这是您面临的风险:您的 UI 在桌面上运行,而一些聪明的学生想出了如何利用您的 UI。他们现在 0wn 您的桌面。他们对实验室中的每台机器重复,他们现在 0wn 你的实验室。现在一些域管理员登录到实验室的机器上。现在你聪明的学生 0 拥有你的域名。一旦聪明的学生 0wns 你的域名,游戏就结束了。对此有解决方案:例如,我知道大学(和公司)每晚都会重新映像每台实验室计算机。但这些补救措施往往相当严厉。
    • 当用户可以物理访问机器时,您的安全性已经受到威胁。这个应用程序不会对我们造成重大网络威胁,而这正是我们真正关心的。如果你说的是真的,那么没有办法以安全的方式运行这样的进程这一事实确实说明了 Windows 安全性的“改进”。与其让它变得更好,不如限制人们可以做的事情。这里有一个让机器更加安全的想法:我们只需将它们从网络上拔下并拿走键盘和鼠标。
    【解决方案3】:

    我用 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;
    }
    

    【讨论】:

      【解决方案4】:

      我认为你可以做到,但它非常复杂。交互式应用程序通常不允许在欢迎屏幕上运行。概括地说,您需要:

      • 创建一个自动启动的 Windows 服务
      • 使用 windows 服务在当前会话和桌面上创建另一个进程(使用 Win32 方法 WTSGetActiveConsoleSessionIdOpenInputDesktop

      我编写了一个可以与登录屏幕交互的应用程序,但它不显示任何 UI。可能可以做到,但可能涉及更多。

      注意:我发现我无法从我的 Windows 服务中获取来自 OpenInputDesktop 的结果。我不得不改为在另一个进程中进行调用并通知服务在正确的桌面上重新启动进程。

      我希望这至少能让你开始。祝你好运!

      【讨论】:

      • PSexec 可以做到这告诉我它是可能的,我猜它只是模仿它是如何启动它的问题。我会研究你的建议,看看它对我有什么帮助。
      【解决方案5】:

      忽略Vista 之前的操作系统,假设您的令牌上有TCB 权限(基本上是作为系统运行),您可以使用CreateProcessAsUser 来执行此操作。

      作为系统运行的示例(例如:NT 服务或psexec -s)将在控制台会话 winlogon 桌面中启动记事本:

      #define WIN32_LEAN_AND_MEAN
      
      #pragma comment(lib, "Userenv.lib")
      
      #include <Windows.h>
      #include <UserEnv.h>
      #include <iostream>
      #include <string>
      
      HANDLE GetTokenForStart();
      LPVOID GetEnvBlockForUser(HANDLE hToken);
      void StartTheProcess(HANDLE hToken, LPVOID pEnvironment);
      
      int main(int argc, wchar_t* argv[])
      {
          //while (!IsDebuggerPresent()) Sleep(500);
      
          try 
          {
              HANDLE hUserToken = GetTokenForStart();
              LPVOID env = GetEnvBlockForUser(hUserToken);
              StartTheProcess(hUserToken, env);
          }
          catch (std::wstring err)
          {
              auto gle = GetLastError();
              std::wcerr << L"Error: " << err << L" GLE: " << gle << L"\r\n";
              return -1;
          }
      }
      
      HANDLE GetTokenForStart()
      {
          HANDLE hToken = 0;
          {
              HANDLE processToken = 0;
              if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_QUERY_SOURCE | TOKEN_EXECUTE, &processToken))
              {
                  throw std::wstring(L"Could not open current process token");
              }
              if (!DuplicateTokenEx(processToken, MAXIMUM_ALLOWED, NULL, SecurityImpersonation, TokenPrimary, &hToken))
              {
                  throw std::wstring(L"Could not duplicate process token");
              }
          }
      
          DWORD consoleSessionId = WTSGetActiveConsoleSessionId();
          if (!SetTokenInformation(hToken, TokenSessionId, &consoleSessionId, sizeof(consoleSessionId)))
          {
              throw std::wstring(L"Could not set session ID");
          }
      
          return hToken;
      }
      
      LPVOID GetEnvBlockForUser(HANDLE hToken)
      {
          LPVOID pEnvironment = NULL;
          if (!CreateEnvironmentBlock(&pEnvironment, hToken, FALSE))
          {
              throw std::wstring(L"Could not create env block");
          }
          return pEnvironment;
      }
      
      void StartTheProcess(HANDLE hToken, LPVOID pEnvironment)
      {
          STARTUPINFO si = { 0 };
          si.cb = sizeof(si);
          si.dwFlags = STARTF_USESHOWWINDOW;
          si.wShowWindow = SW_SHOW;
          si.lpDesktop = (LPWSTR)L"winsta0\\winlogon";
      
          wchar_t path[MAX_PATH] = L"notepad.exe";
      
          PROCESS_INFORMATION pi = { 0 };
          if (!CreateProcessAsUser(hToken, NULL, path, NULL, NULL, FALSE,
              CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT, pEnvironment, NULL, &si, &pi))
          {
              throw std::wstring(L"Could not start process");
          }
      
          if (!CloseHandle(pi.hThread))
          {
              throw std::wstring(L"Could not close thread handle");
          }
      }
      

      或者,如果您更喜欢 C#:

      using System;
      using System.ComponentModel;
      using System.Runtime.InteropServices;
      using System.Text;
      
      namespace StartWinlogonManaged
      {
          class Program
          {
              static void Main(string[] args)
              {
                  var hUserToken = GetTokenForStart();
                  var env = GetEnvBlockForUser(hUserToken);
                  StartTheProcess(hUserToken, env);
              }
      
              const string 
                  Advapi32 = "advapi32.dll",
                  Userenv = "userenv.dll",
                  Kernel32 = "kernel32.dll";
      
              [DllImport(Kernel32, ExactSpelling = true, SetLastError = true)]
              public static extern IntPtr GetCurrentProcess();
      
              [DllImport(Advapi32, ExactSpelling = true, SetLastError = true)]
              public static extern bool OpenProcessToken(IntPtr ProcessToken, int DesiredAccess, out IntPtr TokenHandle);
      
              [DllImport(Advapi32, ExactSpelling = true, SetLastError = true)]
              public static extern bool DuplicateTokenEx(IntPtr ExistingToken, int DesiredAccess,
                  IntPtr TokenAttributes, int ImpersonationLevel, int TokenType, out IntPtr NewToken);
      
              [DllImport("kernel32.dll", ExactSpelling = true)]
              static extern int WTSGetActiveConsoleSessionId();
      
              [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
              [return: MarshalAs(UnmanagedType.Bool)]
              public static extern bool SetTokenInformation(IntPtr hToken,
                  int tokenInfoClass, ref int pTokenInfo, int tokenInfoLength);
      
              static IntPtr GetTokenForStart()
              {
                  IntPtr hToken = IntPtr.Zero;
                  {
                      IntPtr processToken = IntPtr.Zero;
                      if (!OpenProcessToken(GetCurrentProcess(), 0x2001f /* TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_QUERY_SOURCE | TOKEN_EXECUTE */, out processToken))
                      {
                          throw new Win32Exception("Could not open current process token");
                      }
                      if (!DuplicateTokenEx(processToken, 0x02000000 /* MAXIMUM_ALLOWED */, IntPtr.Zero, 2 /* SecurityImpersonation */, 1 /* TokenPrimary */, out hToken))
                      {
                          throw new Win32Exception("Could not duplicate process token");
                      }
                  }
      
                  int consoleSessionId = WTSGetActiveConsoleSessionId();
                  if (!SetTokenInformation(hToken, 12 /* TokenSessionId */, ref consoleSessionId, 4 /* sizeof(int) */))
                  {
                      throw new Win32Exception("Could not set session ID");
                  }
      
                  return hToken;
              }
      
              [DllImport(Userenv, CharSet = CharSet.Unicode, SetLastError = true)]
              public static extern bool CreateEnvironmentBlock(out IntPtr lpEnvironment, IntPtr hToken, bool bInherit);
      
              static IntPtr GetEnvBlockForUser(IntPtr hToken)
              {
                  IntPtr pEnvironment = IntPtr.Zero;
                  if (!CreateEnvironmentBlock(out pEnvironment, hToken, true))
                  {
                      throw new Win32Exception("Could not create env block");
                  }
                  return pEnvironment;
              }
      
              [DllImport(Advapi32, CharSet = CharSet.Unicode, SetLastError = true)]
              public static extern bool CreateProcessAsUser(IntPtr hToken,
                  StringBuilder appExeName, StringBuilder commandLine, IntPtr processAttributes,
                  IntPtr threadAttributes, bool inheritHandles, uint dwCreationFlags,
                  IntPtr environment, string currentDirectory, ref STARTUPINFO startupInfo,
                  out PROCESS_INFORMATION startupInformation);
      
              [StructLayout(LayoutKind.Sequential)]
              internal struct PROCESS_INFORMATION
              {
                  public IntPtr hProcess;
                  public IntPtr hThread;
                  public uint dwProcessId;
                  public uint dwThreadId;
              }
      
              [StructLayout(LayoutKind.Sequential)]
              internal struct STARTUPINFO
              {
                  public int cb;
                  public IntPtr lpReserved;
                  [MarshalAs(UnmanagedType.LPWStr)]
                  public string lpDesktop;
                  [MarshalAs(UnmanagedType.LPWStr)]
                  public string lpTitle;
                  public int dwX;
                  public int dwY;
                  public int dwXSize;
                  public int dwYSize;
                  public int dwXCountChars;
                  public int dwYCountChars;
                  public int dwFillAttribute;
                  public int dwFlags;
                  public short wShowWindow;
                  public short cbReserved2;
                  public IntPtr lpReserved2;
                  public IntPtr hStdInput;
                  public IntPtr hStdOutput;
                  public IntPtr hStdError;
              }
      
              [DllImport(Kernel32, ExactSpelling = true, SetLastError = true)]
              public static extern bool CloseHandle(IntPtr handle);
      
              static void StartTheProcess(IntPtr hToken, IntPtr pEnvironment)
              {
                  var si = new STARTUPINFO();
                  si.cb = Marshal.SizeOf<STARTUPINFO>();
                  si.dwFlags = 1 /* STARTF_USESHOWWINDOW */;
                  si.wShowWindow = 5 /* SW_SHOW */;
                  si.lpDesktop = "winsta0\\winlogon";
      
                  var path = new StringBuilder("notepad.exe", 260);
      
                  PROCESS_INFORMATION pi;
                  if (!CreateProcessAsUser(hToken, null, path, IntPtr.Zero, IntPtr.Zero, false,
                      0x410 /* CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT */, pEnvironment, null, ref si, out pi))
                  {
                      throw new Win32Exception("Could not start process");
                  }
      
                  if (!CloseHandle(pi.hThread))
                  {
                      throw new Win32Exception("Could not close thread handle");
                  }
              }
          }
      }
      

      请注意,这确实需要在您的令牌中启用多个权限(TCB、AssignPrimaryToken、IncreaseQuota)。此代码还泄漏句柄,没有制定完整的命令行,使用名称常量等...,并且仅用作说明性参考 - 不是现成的解决方案。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-07-15
        • 2020-05-19
        • 2015-08-18
        • 1970-01-01
        • 1970-01-01
        • 2020-06-16
        • 1970-01-01
        相关资源
        最近更新 更多