在Windows 7欢迎界面运行进程

11

这里是情况说明:

我写了一个小的C#应用程序,可以显示欢迎屏幕上的主机名、IP地址、映像日期、解冻状态(我们使用DeepFreeze)、当前域和当前日期/时间。我们用它替换了以前的信息块,以前的信息块在启动时被静态设置,并将文本嵌入背景中,这个新应用程序更加动态和功能强大。该应用程序使用一个计时器来更新每秒的ip地址、deepfreeze状态和时钟,并检查用户是否已登录,当检测到这种情况时,就会关闭自身。

如果我们通过启动脚本(通过组策略设置)运行它,它会保持脚本开启,而机器永远无法到达登录提示符。如果我们使用像start或cmd命令之类的东西,在单独的shell/进程下启动它,它会一直运行,直到启动脚本完成为止,此时Windows似乎会清理脚本的所有子进程。我们目前能够通过使用来启动,并让它在启动脚本完成后保持运行,但可能会非常慢,从而使启动时间增加5秒甚至超过1分钟。

我们尝试使用另一个C#应用程序启动进程,使用WMI调用(Win32_Process和Win32_ProcessStartup)以及各种启动标志等,但最终都以脚本完成和信息块进程被杀死的同样结果结束。我试着将应用程序重写为服务,但服务从来没有设计过与桌面互动,更不用说登录窗口了,而且在正确的上下文中运行事情似乎并不能真正奏效。

所以问题是:是否有人有好的方法来完成这个任务?启动一个任务,使它独立于启动脚本并在欢迎屏幕顶部运行?

5个回答

13

通过大量的Win32 API调用可以完成这个任务。我成功地把一个带有 GUI 程序放到了 Winlogon 桌面上(在任何人询问之前,它不是交互式 GUI)。你需要以 SYSTEM 身份运行一个加载器进程,然后再生成新进程。由于你最可能希望该进程在启动时运行,所以你可以使用任务计划程序将加载器作为 SYSTEM 运行,或者你可以使用服务来实现相同的功能。我目前正在使用一个服务,但我尝试过使用任务计划程序,它也可以正常工作。

简要总结:

  1. 获取 Winlogon.exe 进程(作为 Process)
  2. 使用 Process 的 .handle 通过 OpenProcessToken 获取 winlogon 的令牌
  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开始,微软重写了登录部分以使用CredentialProviders。希望这可以帮到你。 - Brett Rigby
当尝试运行CreateProcessAsUser时,我收到错误1314。有什么建议吗? string lpAppName = @"D:\SMU\Ubiquitous Computing 7390\Final Project\Locker\Locker\bin\Debug\Locker.exe"; 如果(!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)) - Jake Drew

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

6
这是一个“你确实需要一个好理由来做这件事”的问题。微软非常努力地阻止应用程序在启动屏幕运行 - 与登录屏幕交互的Windows中的每一位代码都经过仔细的代码审查,因为在登录屏幕上运行的代码出现错误的安全后果是可怕的 - 如果稍有差错,就会允许恶意软件进入计算机。
你为什么想要在登录屏幕上运行你的程序?也许有一种文档化的方法可以做到这一点,而不那么冒险。

2
他们需要哪些无法通过WMI远程检索的信息?Windows拥有大量的管理信息,可以使用WMI API读取,这是为支持和运营人员设计的。他们需要的信息是否只能在物理控制台上获得? - Larry Osterman
2
从我的角度来看,我正在努力帮助您解决问题。没有可靠的方法可以在不引入潜在安全威胁的情况下实现您想要的操作,因此我正在尝试提出一种替代机制,让您能够检索所需信息而不需要绕过系统安全功能。大约每三个月,微软内部的某个人都会在我们的内部“Windows技巧”别名中问类似的问题,而我给出的答案总是相同的 - 找到另一种实现您想要的操作的方式,因为这对于我们所有的客户来说更安全。 - Larry Osterman
1
在你回答“如果你不断收到这种请求,难道这不表明你需要让它更加安全吗?”之前,请注意:没有办法解决这个问题——在登录桌面运行的代码容易受到破坏攻击(http://en.wikipedia.org/wiki/Shatter_attack),除非你仔细确保你的UI逻辑不会受到攻击(这意味着对每一位UI代码进行长时间、详尽的代码审查,包括你正在使用的WPF代码库),否则无法确保你的应用程序是完全安全的。找到另一种方法总是更好的选择。 - Larry Osterman
3
你面临的风险是:你的用户界面在桌面上运行,某个聪明的学生发现如何利用你的用户界面,他们现在掌控了你的桌面。然后他们重复这个过程,攻击实验室中的每台机器,现在他们掌控了你的整个实验室。现在,一些域管理员登录实验室中的机器。现在你聪明的学生掌控了你的整个域。一旦聪明的学生掌控了你的域,那么游戏就结束了。有解决方案:例如,我知道一些大学(和公司)每晚都会重新安装实验室电脑的镜像。但这些解决方案往往相当严厉。 - Larry Osterman
2
当用户可以物理访问计算机时,您的安全性已经受到威胁。这个应用程序对我们不构成一个显著的网络威胁,这就是我们真正关心的。如果你所说的事实属实,那么没有办法以安全的方式运行这样的进程,这确实反映了Windows安全性的“改进”。与其使其变得更好,不如限制人们的操作。这里有一个想法,使计算机变得更加安全:我们将它们从网络中断开,并取走键盘和鼠标。 - peelman
显示剩余8条评论

1

忽略早期的操作系统,假设您在令牌上拥有TCB特权(基本上是以System身份运行),您可以使用CreateProcessAsUser来实现此目的。

以下示例将作为System用户运行(例如: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)。此代码还会泄漏句柄,没有构建完整的命令行,使用了名称常量等。本代码仅作为解释性参考,而不是立即可用的解决方案。

1

我认为你可以做到,但这需要相当大的投入。交互式应用程序通常不允许在欢迎屏幕上运行。在高层次上,你需要:

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

我写了一个可以与登录屏幕有所交互的应用程序,但它不显示任何UI界面。这可能是可行的,但可能需要更多的投入。

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

希望这能让你有所启发。祝你好运!


PSexec能够做到这一点,这告诉我这是可能的,只是需要模仿它的启动方式。我会研究你建议的内容,看看能得到什么结果。 - peelman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接