C# 为C中的模拟用户访问HKCU注册表配置单元#
我需要实现模拟域用户的功能。模拟线程需要能够读取/写入模拟用户的HKCU注册表配置单元。我可以模拟用户,但当我尝试加载任何注册表项时,我收到Win32“访问被拒绝”异常 注意:这里的目的是提供一个伪模拟命令行,以作为服务帐户执行一组特定的操作。服务帐户可能没有交互式登录权限,因此我需要使用批登录类型。作为测试,我也尝试了交互式登录类型,但结果是一样的 我作为一个总的向导跟随。以下是我所拥有的:C# 为C中的模拟用户访问HKCU注册表配置单元#,c#,.net,impersonation,C#,.net,Impersonation,我需要实现模拟域用户的功能。模拟线程需要能够读取/写入模拟用户的HKCU注册表配置单元。我可以模拟用户,但当我尝试加载任何注册表项时,我收到Win32“访问被拒绝”异常 注意:这里的目的是提供一个伪模拟命令行,以作为服务帐户执行一组特定的操作。服务帐户可能没有交互式登录权限,因此我需要使用批登录类型。作为测试,我也尝试了交互式登录类型,但结果是一样的 我作为一个总的向导跟随。以下是我所拥有的: partial class Program { [DllImport("advapi32.dl
partial class Program
{
[DllImport("advapi32.dll")]
public static extern int LogonUser(String lpszUserName, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int DuplicateToken(IntPtr hToken, int impersonationLevel, ref IntPtr hNewToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool RevertToSelf();
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool CloseHandle(IntPtr handle);
[DllImport("advapi32.dll", CharSet = CharSet.Auto)]
public static extern int RegOpenCurrentUser(int samDesired, out IntPtr phkResult);
[DllImport("userenv.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool LoadUserProfile(IntPtr hToken, ref ProfileInfo lpProfileInfo);
[DllImport("Userenv.dll", CallingConvention = CallingConvention.Winapi, SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool UnloadUserProfile(IntPtr hToken, IntPtr lpProfileInfo);
[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;
}
private static string ImpUser = string.Empty;
private static string ImpDomain = string.Empty;
private static string FullyQualifiedImpUser
{
get
{
return $"{ImpDomain}\\{ImpUser}";
}
}
private static SecureString ImpSecret = new SecureString();
private static bool CurrentlyImpersonating = false;
private static WindowsIdentity ImpersonatedIdentity = null;
private static IntPtr Token = IntPtr.Zero;
private static IntPtr TokenDuplicate = IntPtr.Zero;
//*** THIS IS THE CORE METHOD ***
private static void EnterModeImpersonated()
{
bool loadSuccess;
int errCode;
try
{
if (RevertToSelf())
{
if (LogonUser(ImpUser, ImpDomain,
ImpSecret.Plaintext(), Constants.LOGON32_LOGON_TYPE_BATCH,
Constants.LOGON32_PROVIDER_DEFAULT, ref Token) != 0)
{
if (DuplicateToken(Token, Constants.SecurityImpersonation, ref TokenDuplicate) != 0)
{
ImpersonatedIdentity = new WindowsIdentity(TokenDuplicate);
using (WindowsImpersonationContext m_ImpersonationContext = ImpersonatedIdentity.Impersonate())
{
if (m_ImpersonationContext != null)
{
#region LoadUserProfile
// Load user profile
ProfileInfo profileInfo = new ProfileInfo();
profileInfo.dwSize = Marshal.SizeOf(profileInfo);
profileInfo.lpUserName = ImpUser;
profileInfo.dwFlags = 1;
//Here is where I die:
loadSuccess = LoadUserProfile(TokenDuplicate, ref profileInfo);
if (!loadSuccess)
{
errCode = Marshal.GetLastWin32Error();
Win32Exception ex = new Win32Exception(errCode);
throw new Exception($"Failed to load profile for {FullyQualifiedImpUser}. Error code: {errCode}", ex);
}
if (profileInfo.hProfile == IntPtr.Zero)
{
errCode = Marshal.GetLastWin32Error();
Win32Exception ex = new Win32Exception(errCode);
throw new Exception($"Failed accessing HKCU registry for {FullyQualifiedImpUser}. Error code: {errCode}", ex);
}
#endregion
CloseHandle(Token);
CloseHandle(TokenDuplicate);
RegistryAgent.GetRootKeys(profileInfo.hProfile);
EnterMode();
UnloadUserProfile(TokenDuplicate, profileInfo.hProfile);
m_ImpersonationContext.Undo();
RegistryAgent.GetRootKeys(Constants.RevertToInvoker);
}
}
}
else
{
Console.WriteLine("DuplicateToken() failed with error code: " +
Marshal.GetLastWin32Error());
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
}
}
catch (Win32Exception we)
{
throw we;
}
catch
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
finally
{
if (Token != IntPtr.Zero) CloseHandle(Token);
if (TokenDuplicate != IntPtr.Zero) CloseHandle(TokenDuplicate);
Console.WriteLine("After finished impersonation: " +
WindowsIdentity.GetCurrent().Name);
}
}
//Toggles on impersonation mode
//Here, we grab the username, domain and password.
private static bool EnableImpersonation(string userInfo)
{
if (userInfo.Contains('\\'))
{
string[] parts = Parameter.ImpUser.TextValue.Split('\\');
ImpUser = parts[1];
ImpDomain = parts[0];
}
else
{
ImpUser = userInfo;
ImpDomain = Environment.UserDomainName;
}
//Prompt for the invoker to enter the impersonated account password
GetSecret();
if (TryImpersonate())
{
CurrentlyImpersonating = true;
}
else
{
DisableImpersonation();
}
return CurrentlyImpersonating;
}
//Toggles off impersontation & cleans up
private static void DisableImpersonation()
{
ImpSecret = null;
ImpersonatedIdentity = null;
Token = IntPtr.Zero;
TokenDuplicate = IntPtr.Zero;
ImpUser = string.Empty;
ImpDomain = string.Empty;
CurrentlyImpersonating = false;
}
//Implements a console prompt to grab the impersonated account password
//as a SecureString object
private static void GetSecret()
{
ImpSecret = new SecureString();
ConsoleKeyInfo key;
Console.Write($"\r\nEnter the password for {FullyQualifiedImpUser}: ");
do
{
key = Console.ReadKey(true);
if (key.Key != ConsoleKey.Backspace && key.Key != ConsoleKey.Enter)
{
ImpSecret.AppendChar(key.KeyChar);
Console.Write("*");
}
else
{
if (key.Key == ConsoleKey.Backspace && ImpSecret.Length != 0)
{
ImpSecret.RemoveAt(ImpSecret.Length - 1);
Console.Write("\b \b");
}
}
}
while (key.Key != ConsoleKey.Enter);
Console.WriteLine();
}
//This method is intended to ensure that the credentials entered
//for the impersonated user are correct.
private static bool TryImpersonate()
{
IntPtr testToken = IntPtr.Zero;
int result;
try
{
result = LogonUser(ImpUser, ImpDomain, ImpSecret.Plaintext(), Constants.LOGON32_LOGON_TYPE_BATCH, Constants.LOGON32_PROVIDER_DEFAULT, ref testToken);
if (result == 0)
{
int errCode = Marshal.GetLastWin32Error();
Win32Exception ex = new Win32Exception(errCode);
throw new Exception($"Failed to impersonate {FullyQualifiedImpUser}. Error code: {errCode}", ex);
}
return true;
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
return false;
}
}
}
我也读过(我没有找到LoadUserProfile()的文章,所以我不得不假设这是被调用的最终COM函数)。它表明:
令牌必须具有令牌\u查询、令牌\u模拟和令牌\u重复访问。。我想知道是否需要以不同的方式创建登录令牌或复制令牌以包含这些权限?我找不到任何关于如何操作令牌权限的文档,尽管…我能够解决这个问题。以下是我所做的: 首先,需要公开几种Win32方法:
[DllImport("advapi32.dll")]
public static extern int LogonUser(String lpszUserName, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
[DllImport("userenv.dll")]
public static extern bool LoadUserProfile(IntPtr hToken, ref ProfileInfo lpProfileInfo);
[DllImport("advapi32.dll", CharSet = CharSet.Auto)]
public static extern int RegDisablePredefinedCache();
您还需要定义一个结构来支持调用LoadUserProfile()
我们将在SecureString对象中存储模拟帐户密码,但我们也希望能够以明文形式轻松访问它
我使用以下方法在控制台提示下填充SecureString密码(已屏蔽):
public static SecureString GetPasswordAsSecureString(string prompt)
{
SecureString pwd = new SecureString();
ConsoleKeyInfo key;
Console.Write(prompt + @": ");
do
{
key = Console.ReadKey(true);
if (key.Key != ConsoleKey.Backspace && key.Key != ConsoleKey.Enter)
{
pwd.AppendChar(key.KeyChar);
Console.Write("*");
}
else
{
if (key.Key == ConsoleKey.Backspace && pwd.Length != 0)
{
pwd.RemoveAt(pwd.Length - 1);
Console.Write("\b \b");
}
}
}
while (key.Key != ConsoleKey.Enter);
Console.WriteLine();
return pwd;
}
var impPassword = GetPasswordAsSecureString($"Enter the password for {impUser}");
我还建议定义以下扩展方法,以便方便地将SecureString转换为普通字符串,因为我们需要使用的Win32方法之一只接受普通字符串:
public static string ToUnSecureString(this SecureString securePassword)
{
if (securePassword == null)
{
return string.Empty;
}
IntPtr unmanagedString = IntPtr.Zero;
try
{
unmanagedString = Marshal.SecureStringToGlobalAllocUnicode(securePassword);
return Marshal.PtrToStringUni(unmanagedString);
}
finally
{
Marshal.ZeroFreeGlobalAllocUnicode(unmanagedString);
}
}
在执行与模拟相关的任何其他操作之前,我们需要调用Win32方法RegDisablePredefinedCache()。就我们的目的而言,此方法告诉Windows动态确定在何处查找HKEY_CURRENT_用户注册表配置单元,而不是使用最初调用进程时的缓存位置(未能调用此方法说明“访问被拒绝”)我以前收到的异常。模拟用户试图加载调用方帐户的HKCU配置单元,这显然是不允许的)
接下来,我们需要在进入模拟线程之前加载该帐户的配置文件。这确保模拟帐户的注册表配置单元在内存中可用。我们调用LogonUser()和LoadUserProfile()COM方法来实现这一点:
// Get a token for the user
const int LOGON32_LOGON_BATCH = 4;
const int LOGON32_PROVIDER_DEFAULT = 0;
//We'll use our extension method to pass the password as a normal string
LogonUser(ImpUser, ImpDomain, ImpPassword.ToUnSecureString(), LOGON32_LOGON_BATCH, LOGON32_PROVIDER_DEFAULT, ref Token);
// Load user profile
ProfileInfo profileInfo = new ProfileInfo();
profileInfo.dwSize = Marshal.SizeOf(profileInfo);
profileInfo.lpUserName = ImpUser;
profileInfo.dwFlags = 1;
bool loadSuccess = LoadUserProfile(Token, ref profileInfo);
//Detect and handle failure gracefully
if (!loadSuccess)
{
errCode = Marshal.GetLastWin32Error();
Win32Exception ex = new Win32Exception(errCode);
throw new Exception($"Failed to load profile for {ImpUser}. Error code: {errCode}", ex);
}
if (profileInfo.hProfile == IntPtr.Zero)
{
errCode = Marshal.GetLastWin32Error();
Win32Exception ex = new Win32Exception(errCode);
throw new Exception($"Failed accessing HKCU registry for {ImpUser}. Error code: {errCode}", ex);
}
最后,多亏了关于这个问题的一条评论,我发现了一个漂亮的nuget包,名为。这混淆了帐户模拟所涉及的大部分复杂性:
//Note that UserCredentials() constructor I chose requires the
//password to be passed as a SecureString object.
var Credentials = new UserCredentials(impDomain, impUser, impPassword);
Impersonation.RunAsUser(Credentials, LogonType.Batch, () =>
{
//Within this bock, you can call methods such as
//Registry.CurrentUser.OpenSubKey()
//and they use the impersonated account's registry hive
}
用户声称已经编写了一个库,可以轻松地完成此操作,也许您可以尝试一下。@CodingYoshi谢谢!这是一个很棒的图书馆。不幸的是,如果登录类型为批处理,我可能无法访问模拟帐户的注册表,因此这一切可能都没有意义:(很酷。请注意,这些都是Win32 API。不是COM。SecureString是一个选项,当您已经有了它的时候。还有一个构造函数接受一个常规字符串。另外,仅供参考-您的代码中不需要
ToUnSecureString
。LogonUser
将接受一个指向非托管内存中字符串的指针,该指针来自Marshal.Sec)ureStringToGlobalAllocUnicode
。有关如何在SimpleImpersonation库中执行此操作的信息,请参阅和。一般来说,将SecureString
转换为常规字符串的方法无法实现SecureString
的目的,因为字符串内容位于托管内存中,因此不应写入。也就是说,还有完全不使用SecureString
,因为即使在非托管内存中,字符串也以纯文本形式保存。唯一的好处是,正确使用SecureString时,本机缓冲区的生存期更短(不使用SecureString时,本机缓冲区的生存期更长)。
// Get a token for the user
const int LOGON32_LOGON_BATCH = 4;
const int LOGON32_PROVIDER_DEFAULT = 0;
//We'll use our extension method to pass the password as a normal string
LogonUser(ImpUser, ImpDomain, ImpPassword.ToUnSecureString(), LOGON32_LOGON_BATCH, LOGON32_PROVIDER_DEFAULT, ref Token);
// Load user profile
ProfileInfo profileInfo = new ProfileInfo();
profileInfo.dwSize = Marshal.SizeOf(profileInfo);
profileInfo.lpUserName = ImpUser;
profileInfo.dwFlags = 1;
bool loadSuccess = LoadUserProfile(Token, ref profileInfo);
//Detect and handle failure gracefully
if (!loadSuccess)
{
errCode = Marshal.GetLastWin32Error();
Win32Exception ex = new Win32Exception(errCode);
throw new Exception($"Failed to load profile for {ImpUser}. Error code: {errCode}", ex);
}
if (profileInfo.hProfile == IntPtr.Zero)
{
errCode = Marshal.GetLastWin32Error();
Win32Exception ex = new Win32Exception(errCode);
throw new Exception($"Failed accessing HKCU registry for {ImpUser}. Error code: {errCode}", ex);
}
//Note that UserCredentials() constructor I chose requires the
//password to be passed as a SecureString object.
var Credentials = new UserCredentials(impDomain, impUser, impPassword);
Impersonation.RunAsUser(Credentials, LogonType.Batch, () =>
{
//Within this bock, you can call methods such as
//Registry.CurrentUser.OpenSubKey()
//and they use the impersonated account's registry hive
}