你如何在NamePipeServerStream.RunAsClient中LoadUserPofile?

时间:2014-05-23 14:40:11

标签: c# service named-pipes impersonation

我从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 Documentation说..

  

从Windows XP Service Pack 2(SP2)和Windows Server 2003开始,调用者必须是管理员或LocalSystem帐户。调用者仅仅模仿管理员或LocalSystem帐户是不够的。

所以这特别告诉我在开始模仿另一个用户后我无法调用LoadUserProfile。这令人困惑,因为我认为我需要首先冒充用户才能获取模拟用户令牌的副本以传递给LoadUserProfile。我的意思是,我没有使用LogonUser或其他带有用户名和密码的API调用,它在调用impersonate之前给我一个令牌,而我正在尝试使用NamedPipeServerStream的RunAsClient()方法来冒充而不必拥有令牌

鉴于我学到了什么,如果在我调用RunAsClient之后我无法获得令牌,我该如何获得令牌?如果我在冒充时获得了令牌,那么当我返回时,令牌仍然有效(恢复为自己)?如果我希望完全冒充用户,就好像他们已登录(环境,注册表访问等),我必须拨打哪些电话?

仅供参考,在所有情况下,连接用户都将登录控制台或通过远程桌面登录(即远程客户端的管道连接将被拒绝)。

[编辑]

我已尝试使用pinvoke ImpersonateNamedPipeClient来模拟客户端,以便使用OpenThreadToken获取令牌,然后调用RevertToSelf并最终调用LoadUserProfile。我觉得我正在重新发明轮子,有人已经在某个地方发布过最佳实践。

我的要求是从服务中作为请求用户运行某个API。此第三方API显然使用特定于用户的内部信息进行初始化。因此,我不能只是模仿线程,我将不得不启动一个新进程(如果我错了,请纠正我)。我想避免使用类似Unix“fork()”之类的东西来加载另一个EXE,但是我找不到相近的Windows。

1 个答案:

答案 0 :(得分:0)

由于没有答案,我会给出我发现的......

无法在模拟用户的上下文中调用LoadUserProfile(对于普通用户)。它不打算成为。您必须在模拟之前加载配置文件,因此您必须在之前获取用户令牌。因此,在获得用户令牌之后和调用LoadUserProfile之后,必须调用RunAsClient()。

但是,您可以调用RunAsClient()来获取用户令牌并返回。然后,您可以在特权上下文中使用用户令牌来设置用户的环境,然后再次模拟。

但是,由于我使用的第三方API在服务的进程空间中执行用户特定的初始化,我决定无法在Windows服务中完成工作(否则我愿意接受建议)。相反,我为模拟用户启动了一个新流程。

所以,我的方法是......

  1. RunAsClient - 获取模拟用户的令牌
  2. DuplicateToken - (在返回并恢复为LocalSystem之后)
  3. CreateEnvironmentBlock - (第三方API需要这个)
  4. 创建匿名管道以与流程进行通信
  5. CreateProcessAsUser(使用令牌,将句柄传递给管道)
  6. 我必须在整个过程中解决标志和参数问题,并且我计划在有空的时候更新一下。我希望这有助于其他人,因为我有足够的时间将我需要的信息拼凑起来。