11

所以这是独家新闻:

不久前我编写了一个小型 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)等,但都以脚本完成和信息块进程获取的相同结果结束被杀。我尝试将应用程序重写为服务,但服务从未被设计为与桌面交互,更不用说登录窗口了,而且让事情在正确的上下文中运行似乎从来没有真正成功过。

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

4

5 回答 5

12

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

简短的摘要:

  1. 抓取 Winlogon.exe 进程(作为进程)
  2. 使用进程的 .handle 使用 OpenProcessToken 获取 winlogon 的令牌
  3. 创建一个新令牌并将 winlogon 令牌复制到它
  4. 提升token的权限
  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");
        }
于 2010-06-29T17:11:31.040 回答
6

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

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

于 2010-06-19T02:03:27.680 回答
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;
}
于 2014-07-23T20:11:46.300 回答
1

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

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

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

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

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

于 2010-06-18T16:40:22.710 回答
1

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

作为系统运行的示例(例如:NT 服务或 with 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)。此代码还泄漏句柄,没有制定完整的命令行,使用名称常量等......,并且仅用作说明性参考 - 而不是作为现成的解决方案。

于 2022-01-10T02:44:12.897 回答