使用LogonUser,CredUIPromptForWindowsCredentials和CredUnPackAuthenticationBuffer从用户输入的凭据安全地获取登录令牌

时间:2014-09-08 23:25:43

标签: c# winapi pinvoke access-violation

我正在尝试更新现有的一段库代码,提示最终用户输入用户名和密码(使用普通的Windows用户界面),然后将这些凭据传递给LogonUser以获取网络资源的模拟令牌。 / p>

现有代码使用CredUIPromptForCredentials并按预期工作,但UI是旧式WinXP对话框。这个方法已经被一堆箍跳取代,使用CredUIPromptForWindowsCredentials显示对话框,然后CredUnPackAuthenticationBuffer将凭证blob解码为用户名,域名和密码字符串。

P / Invoking CredUnPackAuthenticationBufferfrom around the web的规范示例,使用LPTSTR封送StringBuilder字段。这适用于用户名和域名,但不保证密码不会被分页,移动或以其他方式插入,除非StringBuilder指向内存(我不能强迫它做),当我完成时,我不能以编程方式强制对每个角色进行归零。

在旧式代码中,我们使用LogonUser密码参数声明CredUIPromptForCredentialsIntPtr。我们手动创建char[],将其打包在GCHandleGCHandleType.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)
    }
}

0 个答案:

没有答案