Как вы LoadUserPofile из внутри NamePipeServerStream.RunAsClient?

1

Я заимствовал код CodeProject (Wayne Ye), чтобы вывести LoadUserProfile(). Моя цель - предоставить услугу (в настоящее время учетную запись LOCAL_SYSTEM) олицетворять другого пользователя, а именно пользователя, подключающегося к именованному каналу.

Без использования LoadUserProfile моя программа отлично работает для одного пользователя, но не для другого, и я отслеживал, что профиль пользователя не загружается. Вот почему я пытаюсь вызвать LoadUserProfile из нелицензионного контекста.

Когда я запускаю следующий код, LoadUserProfile() возвращает false, с ошибкой 5, доступ запрещен.

Что я делаю не так?

Вот пример сервиса (вам нужно добавить собственный установщик), демонстрирующий проблему.

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.ServiceProcess;
using System.Text;
using System.Threading;

namespace TestService
{
    public class TestService : ServiceBase
    {
        [DllImport("userenv.dll", SetLastError = true, CharSet = CharSet.Auto, EntryPoint = "LoadUserProfileW")]
        public static extern bool LoadUserProfile(IntPtr hToken, ref ProfileInfo lpProfileInfo);
        [DllImport("Userenv.dll", CallingConvention = CallingConvention.Winapi
            , SetLastError      = true
            , CharSet           = CharSet.Auto
            , EntryPoint        = "UnloadUserProfileW")]
        public static extern bool UnloadUserProfile(IntPtr hToken, IntPtr lpProfileInfo);
        private System.ComponentModel.IContainer components = null;
        private System.Diagnostics.EventLog eventLog1;

        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
                components.Dispose();
            base.Dispose(disposing);
        }
        private void InitializeComponent()
        {
            this.eventLog1 = new System.Diagnostics.EventLog();
            ((System.ComponentModel.ISupportInitialize)(this.eventLog1)).BeginInit();
            this.ServiceName = "TestService";
            ((System.ComponentModel.ISupportInitialize)(this.eventLog1)).EndInit();
        }

        public TestService()
        {
            InitializeComponent();
            if (!System.Diagnostics.EventLog.SourceExists("TestServiceSource"))
            {
                System.Diagnostics.EventLog.CreateEventSource(
                    "TestServiceSource", "TestServiceLog");
            }
            eventLog1.Source = "TestServiceSource";
            eventLog1.Log = "TestServiceLog";
        }

        protected override void OnStart(string[] args)
        {
            ThreadPool.QueueUserWorkItem(
                new WaitCallback(o =>
                {
                    try
                    {
                        //open a named pipe
                        PipeSecurity secu = new PipeSecurity();
                        secu.AddAccessRule(
                            new PipeAccessRule(
                                "Everyone",
                                PipeAccessRights.ReadWrite,
                                System.Security.AccessControl.AccessControlType.Allow)
                            );

                        NamedPipeServerStream pipe = new
                           NamedPipeServerStream(
                              "MyNamedPipe"
                              , PipeDirection.In
                              , 1
                              , PipeTransmissionMode.Byte
                              , PipeOptions.WriteThrough
                                | PipeOptions.Asynchronous
                              , 2048
                              , 2048
                              , secu
                              );

                        eventLog1.WriteEntry("Server awaiting connection");
                        pipe.WaitForConnection();
                        eventLog1.WriteEntry("Server Connection Request complete");
                        var worker = new ClientWorker(pipe, eventLog1);
                        pipe.RunAsClient(worker.Work);
                        eventLog1.WriteEntry("Server disconnect");
                        pipe.Disconnect();
                    }
                    catch (Exception ex)
                    {
                        eventLog1.WriteEntry("Server Exception: " + ex.Message);
                        eventLog1.WriteEntry("Server StackTrace:\n" + ex.StackTrace);
                    }
                }));
        }

        public class ClientWorker
        {
            Stream _in;
            EventLog _eventlog;

            public ClientWorker(Stream inStream, EventLog eventLog)
            {
                _in = inStream;
                _eventlog = eventLog;
            }
            public void Work()
            {
                try
                {
                    int len = 0;
                    len += _in.ReadByte() * 256;
                    len += _in.ReadByte();
                    var buffer = new byte[len];

                    _eventlog.WriteEntry("ClientWork Reading");
                    var count = _in.Read(buffer, 0, buffer.Length);
                    _eventlog.WriteEntry("ClientWork read complete");
                    _eventlog.WriteEntry("ClientWork received: " + 
                        UnicodeEncoding.Unicode.GetString(buffer));

                    var current = WindowsIdentity.GetCurrent(TokenAccessLevels.Read);
                    var name = current.Name;
                    var name2 = name.Substring(name.LastIndexOf('\\') + 1);
                    var tokenDuplicate = current.Token;

                    _eventlog.WriteEntry("ClientWork user name: " + name2);

                    // Load user profile
                    ProfileInfo profileInfo = new ProfileInfo();
                    profileInfo.dwSize = Marshal.SizeOf(profileInfo);
                    profileInfo.lpUserName = name2;
                    profileInfo.dwFlags = 1;

                    Boolean loadSuccess = LoadUserProfile(tokenDuplicate, 
                         ref profileInfo);
                    if (!loadSuccess)
                    {
                        var err = Marshal.GetLastWin32Error();
                        throw new Win32Exception(err, "LoadUserProfile() failed with error code: " + err);
                    }
                    if (profileInfo.hProfile == IntPtr.Zero)
                    {
                        var err = Marshal.GetLastWin32Error();
                        throw new Win32Exception(err, "LoadUserProfile() failed - HKCU handle was not loaded. Error code: " + err);
                    }
                }
                catch (Exception ex)
                {
                    _eventlog.WriteEntry("Impersonated Client Exception: " + ex.Message);
                    _eventlog.WriteEntry("Impersonated Client StackTrace:\n" + ex.StackTrace);
                }
            }
        }
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct ProfileInfo
    {
        public int dwSize;
        public int dwFlags;
        public string lpUserName;
        public string lpProfilePath;
        public string lpDefaultPath;
        public string lpServerName;
        public string lpPolicyPath;
        public IntPtr hProfile;
    }    
}

И вот клиентский код для вызова службы.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO.Pipes;
using System.Linq;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace TestClient
{
    public class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private System.ComponentModel.IContainer components = null;
        private System.Windows.Forms.Button button1;

        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
                components.Dispose();
            base.Dispose(disposing);
        } 
        private void InitializeComponent()
        {
            this.button1 = new System.Windows.Forms.Button();
            this.SuspendLayout();
            // 
            // button1
            // 
            this.button1.Location = new System.Drawing.Point(19, 16);
            this.button1.Name = "button1";
            this.button1.Size = new System.Drawing.Size(75, 23);
            this.button1.TabIndex = 0;
            this.button1.Text = "button1";
            this.button1.UseVisualStyleBackColor = true;
            this.button1.Click += new System.EventHandler(this.button1_Click);
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(284, 262);
            this.Controls.Add(this.button1);
            this.Name = "Form1";
            this.Text = "Form1";
            this.ResumeLayout(false);
        }

        private void button1_Click(object sender, EventArgs e)
        {
            NamedPipeClientStream _strm = new 
                NamedPipeClientStream(
                    "."
                    , "MyNamedPipe"
                    , PipeDirection.Out
                    , PipeOptions.Asynchronous | PipeOptions.WriteThrough
                    , TokenImpersonationLevel.Impersonation
                    );

            _strm.Connect();
            string data = "Hello, world!";
            int len = UnicodeEncoding.Unicode.GetByteCount(data);
            byte[] buffer = new byte[2];
            buffer[0] = (byte)((len >> 8) & 0xff);
            buffer[1] = (byte)(len & 0xff);
            _strm.Write(buffer, 0, buffer.Length);
            _strm.Write(UnicodeEncoding.Unicode.GetBytes(data), 0, len);
        }
    }
}

[РЕДАКТИРОВАТЬ]

Я работаю над этим и продолжаю больше недели. Я надеялся, что кто-то может дать что-то более окончательное, но вместо этого я расскажу, что я нашел.

У нескольких людей на SO была аналогичная проблема, когда LoadUserProfile возвращает Access Denied (5) при вызове после вызова какой-либо формы олицетворения. Документация MSDN говорит..

Начиная с Windows XP с пакетом обновления 2 (SP2) и Windows Server 2003, вызывающим абонентом должен быть администратор или учетная запись LocalSystem. Недостаточно, чтобы вызывающий пользователь просто выдавал себя за администратор или учетную запись LocalSystem.

Так что это конкретно говорит мне, что я не могу вызвать LoadUserProfile после того, как я начинаю выдавать себя за другого пользователя. Это сбивало с толку, потому что я думал, что мне нужно сначала выдать себя за пользователя, чтобы получить копию олицетворенного токена пользователя, чтобы перейти к LoadUserProfile. Я имею в виду, что я не использую LogonUser или другой вызов API, который принимает имя пользователя и пароль, который дает мне токен перед вызовом выдачи себя, вместо этого я пытаюсь использовать метод RunAsClient() для NamedPipeServerStream, который олицетворяет собой без необходимости иметь токен,

Учитывая то, что я узнал, как мне получить токен, если я не могу получить его до тех пор, пока не позвоню RunAsClient? Если я получу токен при выдаче себя за себя, будет ли токен действительным, когда я вернусь (вернитесь к себе)? И если я хочу полностью олицетворять пользователя так, как если бы они вошли в систему (среда, доступ к реестру и т.д.), Какие вызовы я должен сделать?

FYI, во всех ситуациях подключающиеся пользователи будут либо войти в систему на консоли, либо войти в систему через Remote Desktop (т.е. Будут удалены соединения с удаленными клиентами).

[РЕДАКТИРОВАТЬ]

Я попробовал pinvoke ImpersonateNamedPipeClient, чтобы олицетворять клиента, чтобы получить токен с помощью OpenThreadToken, затем вызвал RevertToSelf и, наконец, вызвал загруженный LoadUserProfile. Мне кажется, что я изобретаю колесо и что кто-то уже где-то опубликовал лучшую практику.

Мои требования состоят в том, чтобы запустить определенный API в качестве запрашивающего пользователя из службы. Этот сторонний API, по-видимому, инициализируется внутренней информацией, специфичной для пользователя. Поэтому я не могу просто олицетворять в потоке, мне придется запустить новый процесс (исправьте меня, если я ошибаюсь). Я хотел бы избежать загрузки другого EXE, используя что-то вроде Unix "fork()", но я не могу найти Windows почти эквивалентной.

Теги:
service
named-pipes
impersonation

1 ответ

0
Лучший ответ

Поскольку ответов нет, я дам то, что нашел...

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

Однако вы можете вызвать RunAsClient(), чтобы получить токен пользователя и вернуть его. Затем вы можете использовать токен пользователя в привилегированном контексте для настройки пользовательской среды, а затем повторить выдачу себя.

Но поскольку сторонний API, который я использую, выполняет инициализацию, специфичную для пользователя, в пространстве процессов службы, я решил, что не могу выполнять работу в службе Windows (в других случаях я открыт для предложений). Вместо этого я запустил новый процесс для олицетворенного пользователя.

Итак, мой подход был...

  1. RunAsClient - для получения маркера олицетворенного пользователя
  2. DuplicateToken - (после возврата и возврата в LocalSystem)
  3. CreateEnvironmentBlock - (для этого необходим сторонний API)
  4. Создайте анонимный канал для связи с процессом
  5. CreateProcessAsUser (используя токен, передавая дескриптор в канал)

Мне пришлось разрабатывать проблемы с флагами и параметрами на этом пути, и я планирую обновить это немного позже, когда у меня будет время. Надеюсь, это поможет кому-то другому, потому что у меня было достаточно времени, чтобы собрать информацию, необходимую мне для этого.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню