Запуск процесса на экране приветствия Windows 7

Итак, вот совок:

Некоторое время назад я написал крошечное приложение на C#, которое отображает имя хоста, IP-адрес, отображаемую дату, состояние оттаивания (мы используем DeepFreeze), текущий домен и текущую дату / время, чтобы отображать их на экране приветствия наших лабораторных компьютеров с Windows 7., Это должно было заменить наш предыдущий информационный блок, который был установлен статически при запуске и фактически вставил текст в фон, чем-то более динамичным и функциональным. Приложение использует таймер для обновления IP-адреса, состояния глубокой заморозки и часов каждую секунду и проверяет, вошел ли пользователь в систему, и убивает себя, когда обнаруживает такое состояние.

Если мы просто запустим его с помощью нашего сценария запуска (установленного с помощью групповой политики), он сохранит сценарий открытым, и машина никогда не перейдет к приглашению входа в систему. Если мы используем что-то вроде команд start или cmd, чтобы запустить его в отдельной оболочке / процессе, он запускается до завершения сценария запуска, и в этот момент Windows, похоже, очищает все дочерние процессы сценария. В настоящее время мы можем обойти это, используя psexec -s -d -i -x запустить его, что позволяет ему сохраняться после завершения сценария запуска, но может быть невероятно медленным, добавляя от 5 секунд до минуты к нашему времени запуска.

Мы экспериментировали с использованием другого приложения на C# для запуска процесса через класс Process, с использованием вызовов WMI (Win32_Process и Win32_ProcessStartup) с различными флагами запуска и т. Д., Но все заканчиваются тем же результатом завершения сценария и получения процесса информационного блока убит. Я возился с переписыванием приложения как сервиса, но сервисы никогда не предназначались для взаимодействия с рабочим столом, не говоря уже об окне входа в систему, и заставить работать вещи в правильном контексте никогда не получалось.

Итак, на вопрос: есть ли у кого-нибудь хороший способ сделать это? Запустить задачу, чтобы она не зависела от сценария запуска, и запустить ее в верхней части экрана приветствия?

5 ответов

Решение

Это можно сделать с помощью множества вызовов Win32 API. Мне удалось получить программу с графическим интерфейсом на рабочем столе Winlogon (прежде чем кто-либо спросит, это не интерактивный графический интерфейс). По сути, вам нужно запустить загрузчик как SYSTEM, который затем вызовет новый процесс. Поскольку вы, скорее всего, хотите, чтобы этот процесс запускался при запуске, вы можете использовать планировщик задач для запуска загрузчика как SYSTEM или использовать службу для того же. В настоящее время я использую сервис, но я попытался использовать планировщик задач, и он работал просто отлично.

Краткое содержание:

  1. Захватить процесс Winlogon.exe (как процесс)
  2. Получите токен winlogon, используя OpenProcessToken, используя.handle процесса
  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");
        }

Это один из тех вопросов, которые вам действительно нужны для этого. Microsoft очень старается блокировать приложения, запущенные на экране запуска - каждый бит кода в Windows, который взаимодействует с экраном входа в систему, очень тщательно анализируется, потому что последствия для безопасности ошибки в коде, выполняемом на экране входа в систему, ужасны - если вы винт даже немного, вы позволите вредоносным программам проникнуть на компьютер.

Почему вы хотите запустить вашу программу на экране входа? Может быть, есть документированный способ сделать это, который не так рискован.

Я перевел приведенный выше код на 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;
}

Я думаю, что вы можете сделать это, но это довольно сложно. Интерактивные приложения обычно не могут запускаться на экране приветствия. На высоком уровне вам необходимо:

  • Создать службу Windows, которая запускается автоматически
  • Используйте службу Windows для создания другого процесса в текущем сеансе и на рабочем столе (используя методы Win32 WTSGetActiveConsoleSessionId а также OpenInputDesktop)

Я написал приложение, которое может несколько взаимодействовать с экраном входа в систему, но оно не отображает никакого пользовательского интерфейса. Это, вероятно, можно сделать, но это может быть еще более сложным.

Примечание: я обнаружил, что не смог получить результаты от OpenInputDesktop из моей службы Windows. Вместо этого мне пришлось сделать вызов в другом процессе и уведомить службу, чтобы перезапустить процесс на правильном рабочем столе.

Я надеюсь, что, по крайней мере, вы можете начать. Удачи!

Игнорируя ОС до Vista, предполагая, что у вас есть привилегии TCB на вашем токене (в основном они работают как система), вы можете использовать сделать это.

Пример запуска от имени системы (например, служба NT или ), который запустит блокнот в сеансе консоли 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");
    }
}

Или, если вы предпочитаете С#:

      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, MakingQuota), включенных в вашем токене. Этот код также допускает утечку дескрипторов, не формулирует полную командную строку, не использует константы имен и т. д. и предназначен только в качестве пояснительной ссылки, а не в качестве готового решения.

Другие вопросы по тегам