C#.NET 中的完整模拟演示
这是一个 C# 中的完整模拟演示,模拟一个用户并访问其文件和 HKCU 注册表项。
引言
模拟是指线程使用与拥有该线程的进程不同的安全信息来执行的能力。通常,服务器应用程序中的线程会模拟客户端。这允许服务器线程代表该客户端执行操作以访问服务器上的对象或验证对客户端自己的对象的访问权限。
背景
在某些情况下,我们需要模拟另一个 Windows 帐户,并在该用户的会话下进行一些工作,例如
- 企业 ASP.NET Web 应用程序提供服务器管理员访问服务器的权限,具有某些特定权限;服务器管理员在页面上输入他们的 NT 帐户信息(域\帐户 + 密码),我们需要获取 WinNT 访问令牌,然后模拟该服务器用户,以便我们获得其特定权限并执行只有该帐户才能做的事情。
- 我们开发了一个 Windows 服务,它需要定期访问 Internet,但如果特定用户设置了 Sock5 代理来访问 Internet,那么您的 Windows 服务需要知道 Sock5 代理信息以便它可以访问 Internet,并且您必须模拟此用户并读取设置。
功能
我创建了一个本地用户 TempUser,它属于“Administrators”(确保至少使用 TempUser 登录一次)。我使用我自己的帐户登录,并模拟 TempUser 并做两件事
- 创建文件夹 C:\TempFolder,修改其默认权限;只有 TempUser 拥有它的完全控制权。我将在模拟后在此文件夹下创建一个文本文件以证明模拟成功。
注意:将 TempUser 设置为唯一所有者后,我的当前帐户无法访问此文件夹,除非进行权限提升(我禁用 UAC;如果启用 UAC,则会弹出一个提示窗口并要求管理员确认)。
此外,我尝试在我的帐户下以编程方式访问此文件夹,并且抛出了一个
UnauthorizedAccessException
异常! - 然后,我访问 TempUser 的 HKEY_CURRENT_USER 并进行注册表项创建、读取和删除。
代码片段
我们需要调用三个非常流行的 Win32 API:LogonUser
,DuplicateToken
和 RevertToSelf
。
[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);
///
/// A process should call the RevertToSelf function after finishing
/// any impersonation begun by using the DdeImpersonateClient,
/// ImpersonateDdeClientWindow, ImpersonateLoggedOnUser,
/// ImpersonateNamedPipeClient, ImpersonateSelf,
/// ImpersonateAnonymousToken or SetThreadToken function.
/// If RevertToSelf fails, your application continues to run in the context
/// of the client, which is not appropriate.
/// You should shut down the process if RevertToSelf fails.
/// RevertToSelf Function:
/// http://msdn.microsoft.com/en-us/library/aa379317(VS.85).aspx
///
/// A boolean value indicates the function succeeded or not.
[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);
一个重要的提示:为了访问 HKCU,我们需要调用另一个 Win32 API,LoadUserProfile
,以获取 TempUser 下 HKCU 的句柄。下面的代码,如突出显示的第 45 和 49 行所示,在调用 LoadUserProfile
之后,hProfile
将被设置为 HKCU 的句柄
[StructLayout(LayoutKind.Sequential)]
public struct ProfileInfo
{
///
/// Specifies the size of the structure, in bytes.
///
public int dwSize;
///
/// This member can be one of the following flags: PI_NOUI or PI_APPLYPOLICY
///
public int dwFlags;
///
/// Pointer to the name of the user.
/// This member is used as the base name
/// of the directory in which to store a new profile.
///
public string lpUserName;
///
/// Pointer to the roaming user profile path.
/// If the user does not have a roaming profile, this member can be NULL.
///
public string lpProfilePath;
///
/// Pointer to the default user profile path. This member can be NULL.
///
public string lpDefaultPath;
///
/// Pointer to the name of the validating domain controller, in NetBIOS format.
/// If this member is NULL, the Windows NT 4.0-style policy will not be applied.
///
public string lpServerName;
///
/// Pointer to the path of the Windows NT 4.0-style policy file. This member can be NULL.
///
public string lpPolicyPath;
///
/// Handle to the HKEY_CURRENT_USER registry key.
///
public IntPtr hProfile;
}
[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);
执行模拟的代码:
WindowsIdentity m_ImpersonatedUser;
IntPtr token = IntPtr.Zero;
IntPtr tokenDuplicate = IntPtr.Zero;
const int SecurityImpersonation = 2;
const int TokenType = 1;
try
{
if (RevertToSelf())
{
Console.WriteLine("Before impersonation: " +
WindowsIdentity.GetCurrent().Name);
String userName = "TempUser";
IntPtr password = GetPassword();
if (LogonUser(userName, Environment.MachineName,
"!@#$QWERasdf", LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT, ref token) != 0)
{
if (DuplicateToken(token, SecurityImpersonation, ref tokenDuplicate) != 0)
{
m_ImpersonatedUser = new WindowsIdentity(tokenDuplicate);
using (m_ImpersonationContext = m_ImpersonatedUser.Impersonate())
{
if (m_ImpersonationContext != null)
{
Console.WriteLine("After Impersonation succeeded: " +
Environment.NewLine +
"User Name: " +
WindowsIdentity.GetCurrent(
TokenAccessLevels.MaximumAllowed).Name +
Environment.NewLine +
"SID: " +
WindowsIdentity.GetCurrent(
TokenAccessLevels.MaximumAllowed).User.Value);
#region LoadUserProfile
// Load user profile
ProfileInfo profileInfo = new ProfileInfo();
profileInfo.dwSize = Marshal.SizeOf(profileInfo);
profileInfo.lpUserName = userName;
profileInfo.dwFlags = 1;
Boolean loadSuccess =
LoadUserProfile(tokenDuplicate, ref profileInfo);
if (!loadSuccess)
{
Console.WriteLine("LoadUserProfile() failed with error code: " +
Marshal.GetLastWin32Error());
throw new Win32Exception(Marshal.GetLastWin32Error());
}
if (profileInfo.hProfile == IntPtr.Zero)
{
Console.WriteLine(
"LoadUserProfile() failed - HKCU handle " +
"was not loaded. Error code: " +
Marshal.GetLastWin32Error());
throw new Win32Exception(Marshal.GetLastWin32Error());
}
#endregion
CloseHandle(token);
CloseHandle(tokenDuplicate);
// Do tasks after impersonating successfully
AccessFileSystem();
// Access HKCU after loading user's profile
AccessHkcuRegistry(profileInfo.hProfile);
// Unload user profile
// MSDN remarks
// http://msdn.microsoft.com/en-us/library/bb762282(VS.85).aspx
// Before calling UnloadUserProfile you should
// ensure that all handles to keys that you have opened in the
// user's registry hive are closed. If you do not
// close all open registry handles, the user's profile fails
// to unload. For more information, see Registry Key
// Security and Access Rights and Registry Hives.
UnloadUserProfile(tokenDuplicate, profileInfo.hProfile);
// Undo impersonation
m_ImpersonationContext.Undo();
}
}
}
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);
}
AccessFileSystem 方法
private static void AccessFileSystem()
{
// Access file system %appdata% will be "C:\Users\TempUser\appdata\Roaming"
String appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
File.AppendAllText("C:\\TempFolder\\Temp.txt", "some text...");
}
AccessHkcuRegistry 方法:
private static void AccessHkcuRegistry(IntPtr hkcuHandle)
{
// Access registry HKCU
using (SafeRegistryHandle safeHandle =
new SafeRegistryHandle(hkcuHandle, true))
{
using (RegistryKey tempUserHKCU = RegistryKey.FromHandle(safeHandle))
{
// Unum all sub keys under tempuser's HKCU
String[] keys = tempUserHKCU.GetSubKeyNames();
// Create a new sub key under tempuser's HKCU
using (RegistryKey tempKeyByWayne =
tempUserHKCU.CreateSubKey("TempKeyByWayne"))
{
// Ensure priviledge
//RegistrySecurity registrySecurity = new RegistrySecurity();
//RegistryAccessRule accessRule =
// new RegistryAccessRule(Environment.MachineName + "\\" + userName,
// RegistryRights.TakeOwnership,
// InheritanceFlags.ContainerInherit,
// PropagationFlags.None,
// AccessControlType.Allow);
//registrySecurity.SetAccessRule(accessRule);
//tempKeyByWayne.SetAccessControl(registrySecurity);
// Create a new String value under created TempKeyByWayne subkey
tempKeyByWayne.SetValue("StrType",
"TempContent", RegistryValueKind.String);
// Read the value
using (RegistryKey regKey =
tempUserHKCU.OpenSubKey("TempKeyByWayne"))
{
String valueContent = regKey.GetValue("StrType") as String;
Console.WriteLine(valueContent);
}
// Delete created TempKeyByWayne subkey
tempUserHKCU.DeleteSubKey("TempKeyByWayne");
tempKeyByWayne.Close();
}
}
}
}
模拟结果和验证
Temp.txt 创建
"TempKeyByWayne" 在 HKCU 下创建
参考文献
- 如何在 ASP.NET 应用程序中实现模拟:http://support.microsoft.com/kb/306158
- LogonUser Win32 API:http://msdn.microsoft.com/en-us/library/aa378184(VS.85).aspx
- 如何在 Microsoft ASP.NET 页面中生成在被模拟用户的上下文中运行的进程:http://support.microsoft.com/kb/889251
- ASP.NET 模拟:http://msdn.microsoft.com/en-us/library/xh507fc5(v=VS.100).aspx
- 安全地模拟另一个用户:http://blogs.msdn.com/b/shawnfa/archive/2005/03/22/400749.aspx
编码愉快 :)