Как запустить процесс из службы Windows в текущий пользовательский сеанс - PullRequest
44 голосов
/ 25 ноября 2010

Мне нужно запустить программу из Windows Service. Эта программа является приложением пользовательского интерфейса. Более того, это приложение должно быть запущено под определенной учетной записью пользователя.

Проблема в том, что Window Services запускаются в сеансе № 0, но вошедшие в систему пользовательские сеансы имеют 1,2 и т.д.

Таким образом, вопрос заключается в следующем: как запустить процесс из службы окна таким образом, чтобы он выполнялся в текущем сеансе пользователя, вошедшего в систему?

Я бы подчеркнул, что вопрос не в том, как запустить процесс под определенной учетной записью (это очевидно - Process.Start (new ProcessStartInfo ("..") {UserName = .., Password = ..}) ). Даже если я установлю свои окна для запуска под текущей учетной записью пользователя, служба все равно будет работать в сеансе # 0. Настройка «Разрешить службе взаимодействовать с рабочим столом» не помогает.

Моя служба Windows основана на .net.

UPDATE : Во-первых, .NET здесь не имеет ничего общего, на самом деле это чисто Win32. Вот что я делаю. Следующий код находится в моей службе Windows (C # с использованием функции win32 через P / Inkove, я пропустил импорт подписей, они все здесь - http://www.pinvoke.net/default.aspx/advapi32/CreateProcessWithLogonW.html):

    var startupInfo = new StartupInfo()
        {
            lpDesktop = "WinSta0\\Default",
            cb = Marshal.SizeOf(typeof(StartupInfo)),
        };
    var processInfo = new ProcessInformation();
    string command = @"c:\windows\Notepad.exe";
    string user = "Administrator";
    string password = "password";
    string currentDirectory = System.IO.Directory.GetCurrentDirectory();
    try
    {
        bool bRes = CreateProcessWithLogonW(user, null, password, 0,
            command, command, 0,
            Convert.ToUInt32(0),
            currentDirectory, ref startupInfo, out processInfo);
        if (!bRes)
        {
            throw new Win32Exception(Marshal.GetLastWin32Error());
        }
    }
    catch (Exception ex)
    {
        writeToEventLog(ex);
        return;
    }
    WaitForSingleObject(processInfo.hProcess, Convert.ToUInt32(0xFFFFFFF));
    UInt32 exitCode = Convert.ToUInt32(123456);
    GetExitCodeProcess(processInfo.hProcess, ref exitCode);
    writeToEventLog("Notepad has been started by WatchdogService. Exitcode: " + exitCode);

    CloseHandle(processInfo.hProcess);
    CloseHandle(processInfo.hThread);

Код идет в строку «Блокнот был запущен WatchdogService. Код выхода:» + exitCode. Код выхода 3221225794. И нет никакого нового начатого блокнота. Где я не прав?

Ответы [ 8 ]

37 голосов
/ 09 июня 2014

Проблема с ответом Шрайка заключается в том, что он не работает с пользователем, подключенным через RDP.Вот мое решение, которое правильно определяет текущий сеанс пользователя перед созданием процесса.Он был протестирован для работы на XP и 7.

https://github.com/murrayju/CreateProcessAsUser

Все, что вам нужно, упаковано в один класс .NET статическим методом:

public static bool StartProcessAsCurrentUser(string appPath, string cmdLine, string workDir, bool visible)
27 голосов
/ 26 ноября 2010

Блог MSDN описывает решение

Это потрясающий полезный пост о запуске нового процесса в интерактивном сеансе из службы Windows в Vista / 7.

Для не-LocalSystem сервисов основная идея:

  • Перечислите процесс, чтобы получить дескриптор Проводника.

  • OpenProcessToken должен предоставить вам токен доступа. Примечание. Учетная запись, под которой работает ваша служба, должна иметь соответствующие права для вызова этого API и получения токена процесса.

  • Как только у вас есть токен, вызовите CreateProcessAsUser с этим токеном. Этот токен уже имеет правильный идентификатор сессии.

8 голосов
/ 25 ноября 2010

Это плохая идея.Хотя, вероятно, это не является абсолютно невозможным, Microsoft сделала все возможное, чтобы сделать это как можно сложнее, поскольку она позволяет так называемые Shatter Attacks .Посмотрите, что Ларри Остерман написал об этом еще в 2005 году :

Основная причина этой плохой идеи состоит в том, что интерактивные сервисы включают класс угроз, известных как атаки "Shatter".(потому что они "разбивают окна", я полагаю).

Если вы выполните поиск по запросу «разбить атаку», вы сможете увидеть некоторые детали того, как работают эти угрозы безопасности.Microsoft также опубликовала статью KB 327618 , которая расширяет документацию об интерактивных сервисах, а Майкл Ховард написал статью об интерактивных сервисах для библиотеки MSDN.Первоначально атаки shatter были направлены на компоненты Windows, которые имели фоновые насосы сообщений окна (которые уже давно исправлены), но они также использовались для атаки на сторонние сервисы, которые открывают пользовательский интерфейс.

Вторая причина - этоплохая идея в том, что флаг SERVICE_INTERACTIVE_PROCESS просто не работает правильно.Пользовательский интерфейс службы выскакивает в системном сеансе (обычно сеанс 0).Если, с другой стороны, пользователь работает в другом сеансе, пользователь никогда не увидит пользовательский интерфейс.Существует два основных сценария, в которых пользователь подключается к другому сеансу - службы терминалов и быстрое переключение пользователей.TS не так уж часто встречается, но в домашних сценариях, когда несколько человек используют один компьютер, часто включается FUS (например, у нас на компьютере, на нашей кухне, почти все время заходит 4 человека).

Третья причина, по которой интерактивные сервисы - это плохая идея, заключается в том, что интерактивные сервисы не гарантированно работают с Windows Vista :). В рамках процесса повышения безопасности, который прошел в Windows Vista, интерактивные пользователи входят в сеансы, отличные отсистемный сеанс - первый интерактивный пользователь запускается в сеансе 1, а не в сеансе 0. Это приводит к полному прекращению атак с разбивкой на коленях - пользовательские приложения не могут взаимодействовать с окнами с высокими привилегиями, работающими в службах.

Предложенный обходной путь будет заключаться в использовании приложения в системном трее пользователя.

Если вы можете безопасно игнорировать вышеизложенные проблемы и предупреждения, вы можете следовать приведенным здесь инструкциям.:

Разработка для Windows: изоляция сеанса 0

6 голосов
/ 14 октября 2014

Вот как я это реализовал.Он попытается запустить процесс в качестве текущего пользователя (из службы).Это основано на нескольких источниках, собранных вместе для чего-то, что работает.

Это действительно ДЕЙСТВИТЕЛЬНО ЧИСТЫЙ WIN32 / C ++, поэтому может не быть на 100% полезным для исходных вопросов.Но я надеюсь, что это может спасти других людей в поисках чего-то подобного.

Требуется Windows XP / 2003 (не работает с Windows 2000).Вы должны сослаться на Wtsapi32.lib

#define WINVER 0x0501
#define _WIN32_WINNT 0x0501
#include <Windows.h>
#include <WtsApi32.h>

bool StartInteractiveProcess(LPTSTR cmd, LPCTSTR cmdDir) {
    STARTUPINFO si;
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    si.lpDesktop = TEXT("winsta0\\default");  // Use the default desktop for GUIs
    PROCESS_INFORMATION pi;
    ZeroMemory(&pi, sizeof(pi));
    HANDLE token;
    DWORD sessionId = ::WTSGetActiveConsoleSessionId();
    if (sessionId==0xffffffff)  // Noone is logged-in
        return false;
    // This only works if the current user is the system-account (we are probably a Windows-Service)
    HANDLE dummy;
    if (::WTSQueryUserToken(sessionId, &dummy)) {
        if (!::DuplicateTokenEx(dummy, TOKEN_ALL_ACCESS, NULL, SecurityDelegation, TokenPrimary, &token)) {
            ::CloseHandle(dummy);
            return false;
        }
        ::CloseHandle(dummy);
        // Create process for user with desktop
        if (!::CreateProcessAsUser(token, NULL, cmd, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, cmdDir, &si, &pi)) {  // The "new console" is necessary. Otherwise the process can hang our main process
            ::CloseHandle(token);
            return false;
        }
        ::CloseHandle(token);
    }
    // Create process for current user
    else if (!::CreateProcess(NULL, cmd, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, cmdDir, &si, &pi))  // The "new console" is necessary. Otherwise the process can hang our main process
        return false;
    // The following commented lines can be used to wait for the process to exit and terminate it
    //::WaitForSingleObject(pi.hProcess, INFINITE);
    //::TerminateProcess(pi.hProcess, 0);
    ::CloseHandle(pi.hProcess);
    ::CloseHandle(pi.hThread);
    return true;
}
5 голосов
/ 26 июня 2013

Я нашел решение здесь:

http://www.codeproject.com/Articles/35773/Subverting-Vista-UAC-in-Both-32-and-64-bit-Archite

Я думаю, что это отличная ссылка.

2 голосов
/ 25 ноября 2010

Я не знаю, как это сделать в .NET, но в целом вам нужно будет использовать функцию Win32 API CreateProcessAsUser() (или ее эквивалент .NET), указав маркер доступа нужного пользователя и имя рабочего стола,Это то, что я использую в своих C ++ сервисах, и оно отлично работает.

1 голос
/ 06 марта 2018

Принятый ответ не работал в моем случае, так как приложение, которое я запускал, требовало прав администратора. Я создал пакетный файл для запуска приложения. Он содержал следующее:

C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe  "%~dp0MySoft.exe"

Затем я передал расположение этого файла методу StartProcessAsCurrentUser (). Сделал трюк.

1 голос
/ 05 ноября 2017

Внедрен код @ murrayju в службу Windows на W10. Запуск исполняемого файла из Program Files всегда вызывал ошибку -2 в VS. Я полагаю, что это произошло из-за начального пути Service к System32. Указание workDir не устраняло проблему, пока я не добавил следующую строку перед CreateProcessAsUser в StartProcessAsCurrentUser :

if (workDir != null)
   Directory.SetCurrentDirectory(workDir);

PS: Это должен был быть комментарий, а не ответ, но у меня пока нет необходимой репутации. Отладка заняла у меня некоторое время, надеюсь, это сэкономит кому-то время.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...