我正在尝试更新现有的一段库代码,提示最终用户输入用户名和密码(使用普通的Windows用户界面),然后将这些凭据传递给LogonUser以获取网络资源的模拟令牌。 / p>
现有代码使用CredUIPromptForCredentials
并按预期工作,但UI是旧式WinXP对话框。这个方法已经被一堆箍跳取代,使用CredUIPromptForWindowsCredentials
显示对话框,然后CredUnPackAuthenticationBuffer
将凭证blob解码为用户名,域名和密码字符串。
P / Invoking CredUnPackAuthenticationBuffer
,from around the web的规范示例,使用LPTSTR
封送StringBuilder
字段。这适用于用户名和域名,但不保证密码不会被分页,移动或以其他方式插入,除非StringBuilder
指向内存(我不能强迫它做),当我完成时,我不能以编程方式强制对每个角色进行归零。
在旧式代码中,我们使用LogonUser
密码参数声明CredUIPromptForCredentials
和IntPtr
。我们手动创建char[]
,将其打包在GCHandle
(GCHandleType.Pinned
}中并将gch.AddrOfPinnedObject()
传递给CredUIPromptForCredentials
;然后将相同的gch.AddrOfPinnedObject()
传递给LogonUser
。最后,我们在固定Array.Clear
上致电char[]
并处置GCHandle
。
但是,在新式代码中,CredUnPackAuthenticationBuffer
的密码参数是一个只有参数,因此我创建的任何缓冲区都会被忽略。我已将密码参数声明为out IntPtr
,并打算将此IntPtr
传递给LogonUser
(与旧式相同)。一旦返回,我打算将IntPtr
传递给SecureZeroMemory
以清除它。我相信(正确或错误,但我有什么选择?)CredUnPackAuthenticationBuffer
将相应地固定内存。
运行代码时,CredUIPromptForWindowsCredentials
调用会正确显示对话框,CredUnPackAuthenticationBuffer
会运行。如果我在这里放置断点/监视,我可以看到用户名和域已被正确解析为StringBuilder
参数(虽然用户名长度很大),密码IntPtr
设置为有效值和密码长度参数是否正确。
但是,当我将其传递给LogonUser
过程时,调用因AccessViolationException而失败:尝试读取或写入受保护的内存。这通常表明其他内存已损坏。
为什么会这样?
是因为我试图读取缓冲区的末尾,还是因为缓冲区已加密(例如DPAPI)?
请注意,用户名/密码已确认正确无误。如果我将所有IntPtr
内容全部删除并将其替换为StringBuilder
,它就能正常运行。但是,这可能会导致我的程序泄露密码,该密码针对所有recommendations I have read并尝试努力。
StringBuilder的内置编组工作做了什么,我也可以做什么?
一些示例代码(为了简洁而裁剪了一些补贴方法/枚举,但DllImport
/ extern
定义和后续用法是相同的):
private class Native
{
[DllImport("credui", CharSet = CharSet.Unicode)]
public static extern CredUIReturnCodes CredUIPromptForWindowsCredentials(
ref CredUIInfo credUIInfo,
int errorCode,
ref uint authPackage,
IntPtr sourceAuthBuffer,
uint sourceAuthBufferSize,
out IntPtr targetAuthBuffer,
out uint targetAuthBufferSize,
[MarshalAs(UnmanagedType.Bool)] ref bool save,
CredWinUIFlags flags
);
[DllImport("credui", CharSet = CharSet.Unicode, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CredUnPackAuthenticationBuffer(
CredPackFlags flags,
IntPtr authBuffer,
uint authBufferSize,
StringBuilder username,
out int usernameLength,
StringBuilder domain,
out int domainLength,
out IntPtr password,
out int passwordLength
);
[DllImport("credui", CharSet = CharSet.Unicode, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CredUnPackAuthenticationBuffer(
CredPackFlags flags,
IntPtr authBuffer,
uint authBufferSize,
StringBuilder username,
out int usernameLength,
StringBuilder domain,
out int domainLength,
StringBuilder password,
out int passwordLength
);
[DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool LogonUser(
string username,
string domain,
IntPtr password,
LogonType logonType,
LogonProvider logonProvider,
out IntPtr handle
);
[DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CloseHandle(IntPtr handle);
}
public void Test()
{
var info = GetCredUIInfo();
uint authPackage = 0;
bool saveChecked = false;
IntPtr buffer;
uint bufferLength;
var ui = Native.CredUIPromptForWindowsCredentials(
ref info,
0,
ref authPackage,
IntPtr.Zero,
0,
out buffer,
out bufferLength,
CredWinUIFlags.GenericCredentials
);
if (uiResult == CredUIReturnCodes.NoError)
{
int usernameLength = 513,
domainLength = 337,
passwordLength = 256;
StringBuilder username = new StringBuilder(usernameLength);
StringBuilder domain = new StringBuilder(domainLength);
IntPtr password;
if (Native.CredUnPackAuthenticationBuffer(
CredPackFlags.PackProtectedCredentials,
buffer, bufferLength,
username, ref usernameLength,
domain, ref domainLength,
password, ref passwordLength
))
{
IntPtr logonToken;
if (Native.LogonUser(
username,
domain,
password,
LogonType.NewCredentials,
LogonProvider.WindowsNT50,
out logonToken
)) /*** fails here with AccessViolationException ***/
{
using (WindowsIdentity.Impersonate(logonToken))
{
TestNetworkAccess();
}
CloseHandle(logonToken);
}
// TODO: Native.SecureZeroMemory(password, passwordLength);
// - possibly Marshal.ZeroFreeGlobalAllocUnicode(password)
}
// TODO: Native.SecureZeroMemory(buffer, bufferLength);
// - possibly Marshal.ZeroFreeGlobalAllocUnicode(buffer)
}
}