読者です 読者をやめる 読者になる 読者になる

【C#】Windows資格情報を列挙する

もうちょっと複雑なことができるものを作りました。

[2014/07/03追記ここまで]

こんなのC#じゃないわ!ただのWin32 APIよ!

前書き

ちょっと訳あって資格情報マネージャを確認しようとしたらコントロールパネルから見れませんでした。多分、権限とかなんかそんなんがあって、非表示になってるんでしょう。機能はしているのでまったく見れないというわけではなさそうです。

別にそれでも困らないんだけど、ちょっとテストしたいこともあるので、とりあえず今登録されている資格情報が見たいなー、と思い、ちまちまとコードを書きました。

コード

using System;
using System.Runtime.InteropServices;

namespace ManagedCred
{
    class Program
    {
        [DllImport("advapi32", SetLastError = true, CharSet = CharSet.Unicode)]
        private static extern bool CredEnumerate(string filter, int flag, out int count, out IntPtr pCredentials);

        public enum CRED_TYPE : uint
        {
            GENERIC = 1,
            DOMAIN_PASSWORD = 2,
            DOMAIN_CERTIFICATE = 3,
            DOMAIN_VISIBLE_PASSWORD = 4,
            GENERIC_CERTIFICATE = 5,
            DOMAIN_EXTENDED = 6,
            MAXIMUM = 7,
            MAXIMUM_EX = (MAXIMUM + 1000),
        }

        public enum CRED_PERSIST : uint
        {
            SESSION = 1,
            LOCAL_MACHINE = 2,
            ENTERPRISE = 3,
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        public struct CREDENTIAL_ATTRIBUTE
        {
            string Keyword;
            uint Flags;
            uint ValueSize;
            IntPtr Value;
        }

        /// <summary>
        /// アンマネージドなCredential
        /// </summary>
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public struct Credential
        {
            public uint Flags;
            public uint Type;
            public string TargetName;
            public string Comment;
            public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten;
            public uint CredentialBlobSize;
            public IntPtr CredentialBlob;
            public uint Persist;
            public uint AttributeCount;
            public IntPtr Attributes;
            public string TargetAlias;
            public string UserName;
        }

        /// <summary>
        /// マネージコードに置き換えたCredential
        /// </summary>
        public class ManagedCredential
        {
            public uint Flags { get; set; }
            public CRED_TYPE Type { get; set; }
            public string TargetName { get; set; }
            public string Comment { get; set; }
            public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten { get; set; }
            public byte[] CredentialBlob { get; set; }
            public CRED_PERSIST Persist { get; set; }
            public CREDENTIAL_ATTRIBUTE[] Attributes { get; set; }
            public string TargetAlias { get; set; }
            public string UserName { get; set; }
        }


        public static void Main(string[] args)
        {
            int count = 0;
            IntPtr pCredentials = IntPtr.Zero;
            IntPtr[] credentials = null;

            //CredEnumerate呼び出し
            bool ret = CredEnumerate(null, 0, out count, out pCredentials);
            if (ret != false)
            {
                credentials = new IntPtr[count];
                for (int n = 0; n < count; n++)
                {
                    credentials[n] = Marshal.ReadIntPtr(pCredentials, n * Marshal.SizeOf(typeof(IntPtr)));
                }
            }

            foreach (var ptr in credentials)
            {
                //CredEnumerateで取得したIntPtrを構造体に変換する
                var unCred = (Credential)Marshal.PtrToStructure(ptr, typeof(Credential));

                var cred = new ManagedCredential
                {
                    Flags = unCred.Flags,
                    Type = (CRED_TYPE)unCred.Type,
                    TargetName = unCred.TargetName,
                    Comment = unCred.Comment,
                    LastWritten = unCred.LastWritten,
                    CredentialBlob = new byte[unCred.CredentialBlobSize],
                    Persist = (CRED_PERSIST)unCred.Persist,
                    Attributes = new CREDENTIAL_ATTRIBUTE[unCred.AttributeCount],
                    TargetAlias = unCred.TargetAlias,
                    UserName = unCred.UserName,
                };

                var attrPtr = unCred.Attributes;

                for (int i = 0; i < unCred.AttributeCount; i++)
                {
                    cred.Attributes[i] = (CREDENTIAL_ATTRIBUTE)Marshal.PtrToStructure(ptr, typeof(CREDENTIAL_ATTRIBUTE));
                    attrPtr = (IntPtr)((int)ptr + Marshal.SizeOf(typeof(CREDENTIAL_ATTRIBUTE)));
                }

                if (unCred.CredentialBlobSize != 0)
                    Marshal.Copy(unCred.CredentialBlob, cred.CredentialBlob, 0, (int)unCred.CredentialBlobSize);


                Console.WriteLine("Flags:{0}", cred.Flags);
                Console.WriteLine("Type:{0}", cred.Type);
                Console.WriteLine("TargetName:{0}", cred.TargetName);
                Console.WriteLine("Comment:{0}", cred.Comment);
                Console.WriteLine("Persist:{0}", cred.Persist);
                Console.WriteLine("TargetAlias:{0}", cred.TargetAlias);
                Console.WriteLine("UserName:{0}", cred.UserName);
                Console.WriteLine("* * * * * * * * * *");
            }

            Console.ReadKey();
        }

    }
}

補足

残念ながら資格情報を取得するにはWin32 APIをぶったたく以外にないので、とりあえずCredEnumerateのドキュメントを読んでみましょう。

C++シグネチャですが、こんな感じになっているみたいです。

BOOL CredEnumerate(
  _In_   LPCTSTR Filter,
  _In_   DWORD Flags,
  _Out_  DWORD *Count,
  _Out_  PCREDENTIAL **Credentials
);

どれに何を渡せばいいのかはここを読んでもらうとして、面倒なのは「_Out_ PCREDENTIAL **Credentials」です。構造体の配列がポインタで返って来ます。ぴえー。

PCREDENTIALはこうなってます。

typedef struct _CREDENTIAL {
  DWORD                 Flags;
  DWORD                 Type;
  LPTSTR                TargetName;
  LPTSTR                Comment;
  FILETIME              LastWritten;
  DWORD                 CredentialBlobSize;
  LPBYTE                CredentialBlob;
  DWORD                 Persist;
  DWORD                 AttributeCount;
  PCREDENTIAL_ATTRIBUTE Attributes;
  LPTSTR                TargetAlias;
  LPTSTR                UserName;
}CREDENTIAL, *PCREDENTIAL;

TargetNameがサーバ名、UserNameが「ドメイン\ユーザ名」になっています。パスワードはCredentialBlobにゴニョゴニョされて入っているみたいですが、今回は特に必要ないので無視します。

逆に言えば、Windowsの資格情報なんてこれぐらい簡単に引っこ抜けるので、あんまり無防備に保存しないほうがいいですよ。

まとめ

資格情報マネージャは開けなくとも、このコードで普通に見れました。

でもよく考えたら資格情報消さないと目的のテストが出来ないので、その辺のロジックも実装しないといけません。もうアンマネージドなコードは触りたくないんだけど…。

参考