LoginSignup
0
1

More than 3 years have passed since last update.

IE/Edgeに保存されているログインデータを出力してみる

Last updated at Posted at 2020-05-26

はじめに

Chromeに保存されているパスワードを解読してみる
に引き続き、今回はInternet ExplorerとMicrosoft EdgeがどのようにWebサイトのログインデータを保存しているのか調べてみました。

IE/Edgeのログインデータの保存方法

調べてみたところ、ログインデータは「Windows Vaults」と呼ばれる特殊なフォルダに保存されていることがわかりました。
具体的には、C:\Windows\System32\vaultcli.dllに含まれているWindows VaultsのAPIを使用してアクセスする仕組みになっているようです。

ログインデータを読み込んでみる

ログインデータを読み込んで出力するプログラムは以下のようになります。


using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices;

namespace FetchVaultCredential
{
    public static class Program
    {
        public enum VAULT_ELEMENT_TYPE : int
        {
            Undefined = -1,
            Boolean = 0,
            Short = 1,
            UnsignedShort = 2,
            Int = 3,
            UnsignedInt = 4,
            Double = 5,
            Guid = 6,
            String = 7,
            ByteArray = 8,
            TimeStamp = 9,
            ProtectedArray = 10,
            Attribute = 11,
            Sid = 12,
            Last = 13
        }

        public enum VAULT_SCHEMA_ELEMENT_ID : int
        {
            Illegal = 0,
            Resource = 1,
            Identity = 2,
            Authenticator = 3,
            Tag = 4,
            PackageSid = 5,
            AppStart = 100,
            AppEnd = 10000
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        public struct VAULT_ITEM_WIN8
        {
            public Guid SchemaId;
            public IntPtr pszCredentialFriendlyName;
            public IntPtr pResourceElement;
            public IntPtr pIdentityElement;
            public IntPtr pAuthenticatorElement;
            public IntPtr pPackageSid;
            public ulong LastModified;
            public int dwFlags;
            public int dwPropertiesCount;
            public IntPtr pPropertyElements;
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        public struct VAULT_ITEM_WIN7
        {
            public Guid SchemaId;
            public IntPtr pszCredentialFriendlyName;
            public IntPtr pResourceElement;
            public IntPtr pIdentityElement;
            public IntPtr pAuthenticatorElement;
            public ulong LastModified;
            public int dwFlags;
            public int dwPropertiesCount;
            public IntPtr pPropertyElements;
        }

        [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi)]
        public struct VAULT_ITEM_ELEMENT
        {
            [FieldOffset(0)] public VAULT_SCHEMA_ELEMENT_ID SchemaElementId;
            [FieldOffset(8)] public VAULT_ELEMENT_TYPE Type;
        }

        [DllImport("vaultcli.dll")]
        public extern static int VaultOpenVault(ref Guid vaultGuid, int offset, ref IntPtr vaultHandle);

        [DllImport("vaultcli.dll")]
        public extern static int VaultCloseVault(ref IntPtr vaultHandle);

        [DllImport("vaultcli.dll")]
        public extern static int VaultFree(ref IntPtr vaultHandle);

        [DllImport("vaultcli.dll")]
        public extern static int VaultEnumerateVaults(int offset, ref int vaultCount, ref IntPtr vaultGuid);

        [DllImport("vaultcli.dll")]
        public extern static int VaultEnumerateItems(IntPtr vaultHandle, int chunkSize, ref int vaultItemCount, ref IntPtr vaultItem);

        [DllImport("vaultcli.dll", EntryPoint = "VaultGetItem")]
        public extern static int VaultGetItem_WIN8(IntPtr vaultHandle, ref Guid schemaId, IntPtr pResourceElement, IntPtr pIdentityElement, IntPtr pPackageSid, IntPtr zero, int arg6, ref IntPtr passwordVaultPtr);

        [DllImport("vaultcli.dll", EntryPoint = "VaultGetItem")]
        public extern static int VaultGetItem_WIN7(IntPtr vaultHandle, ref Guid schemaId, IntPtr pResourceElement, IntPtr pIdentityElement, IntPtr zero, int arg5, ref IntPtr passwordVaultPtr);

        public static void GetLogins()
        {
            // Windows Vaultsをチェック
            var OSVersion = Environment.OSVersion.Version;
            var OSMajor = OSVersion.Major;
            var OSMinor = OSVersion.Minor;

            Type VAULT_ITEM;

            if (OSMajor >= 6 && OSMinor >= 2)
            {
                // Windows 8以降
                VAULT_ITEM = typeof(VAULT_ITEM_WIN8);
            }
            else
            {
                // Windows 7以前
                VAULT_ITEM = typeof(VAULT_ITEM_WIN7);
            }

            // VAULT_ITEM_ELEMENTからItemValueを取り出す
            object GetVaultElementValue(IntPtr vaultElementPtr)
            {
                object results;
                object partialElement = Marshal.PtrToStructure(vaultElementPtr, typeof(VAULT_ITEM_ELEMENT));
                FieldInfo partialElementInfo = partialElement.GetType().GetField("Type");
                var partialElementType = partialElementInfo.GetValue(partialElement);

                IntPtr elementPtr = (IntPtr)(vaultElementPtr.ToInt64() + 16);
                switch ((int)partialElementType)
                {
                    case 7: // String(パスワード)
                        IntPtr StringPtr = Marshal.ReadIntPtr(elementPtr);
                        results = Marshal.PtrToStringUni(StringPtr);
                        break;
                    case 0: // bool
                        results = Marshal.ReadByte(elementPtr);
                        results = (bool)results;
                        break;
                    case 1: // Short
                        results = Marshal.ReadInt16(elementPtr);
                        break;
                    case 2: // Unsigned Short
                        results = Marshal.ReadInt16(elementPtr);
                        break;
                    case 3: // Int
                        results = Marshal.ReadInt32(elementPtr);
                        break;
                    case 4: // Unsigned Int
                        results = Marshal.ReadInt32(elementPtr);
                        break;
                    case 5: // Double
                        results = Marshal.PtrToStructure(elementPtr, typeof(Double));
                        break;
                    case 6: // GUID
                        results = Marshal.PtrToStructure(elementPtr, typeof(Guid));
                        break;
                    case 12: // セキュリティ識別子
                        IntPtr sidPtr = Marshal.ReadIntPtr(elementPtr);
                        var sidObject = new System.Security.Principal.SecurityIdentifier(sidPtr);
                        results = sidObject.Value;
                        break;
                    default:
                        // VAULT_ELEMENT_TYPEが実装されていないので無視
                        results = null;
                        break;
                }
                return results;
            }

            int vaultCount = 0;
            // VaultのGUIDポインタ
            IntPtr vaultGuidPtr = IntPtr.Zero;
            // 全てのVaultを取得
            var result = VaultEnumerateVaults(0, ref vaultCount, ref vaultGuidPtr);

            if (result != 0)
            {
                throw new Exception("Vaultを取得できません。(0x" + result.ToString() + ")");
            }

            // GUID対応表
            IntPtr guidAddress = vaultGuidPtr;
            Dictionary<Guid, string> vaultSchema = new Dictionary<Guid, string>();
            vaultSchema.Add(new Guid("2F1A6504-0641-44CF-8BB5-3612D865F2E5"), "Windows Secure Note");
            vaultSchema.Add(new Guid("3CCD5499-87A8-4B10-A215-608888DD3B55"), "Windows Web Password Credential");
            vaultSchema.Add(new Guid("154E23D0-C644-4E6F-8CE6-5069272F999F"), "Windows Credential Picker Protector");
            vaultSchema.Add(new Guid("4BF4C442-9B8A-41A0-B380-DD4A704DDB28"), "Web Credentials");
            vaultSchema.Add(new Guid("77BC582B-F0A6-4E15-4E80-61736B6F3B29"), "Windows Credentials");
            vaultSchema.Add(new Guid("E69D7838-91B5-4FC9-89D5-230D4D4CC2BC"), "Windows Domain Certificate Credential");
            vaultSchema.Add(new Guid("3E0E35BE-1B77-43E7-B873-AED901B6275B"), "Windows Domain Password Credential");
            vaultSchema.Add(new Guid("3C886FF3-2669-4AA2-A8FB-3F6759A77548"), "Windows Extended Credential");
            vaultSchema.Add(new Guid("00000000-0000-0000-0000-000000000000"), null);

            for (int i = 0; i < vaultCount; i++)
            {
                // Vaultを開く
                object vaultGuidString = Marshal.PtrToStructure(guidAddress, typeof(Guid));
                Guid vaultGuid = new Guid(vaultGuidString.ToString());
                guidAddress = (IntPtr)(guidAddress.ToInt64() + Marshal.SizeOf(typeof(Guid)));
                IntPtr vaultHandle = IntPtr.Zero;
                string vaultType;
                if (vaultSchema.ContainsKey(vaultGuid))
                {
                    vaultType = vaultSchema[vaultGuid];
                }
                else
                {
                    vaultType = vaultGuid.ToString();
                }
                result = VaultOpenVault(ref vaultGuid, 0, ref vaultHandle);
                if (result != 0)
                {
                    throw new Exception(vaultType + "からVaultを開けませんでした。" + "(0x" + result.ToString() + ")");
                }

                // Vault内のアイテムを列挙
                int vaultItemCount = 0;
                IntPtr vaultItemPtr = IntPtr.Zero;
                result = VaultEnumerateItems(vaultHandle, 512, ref vaultItemCount, ref vaultItemPtr);
                if (result != 0)
                {
                    throw new Exception(vaultType + "からのVaultの列挙に失敗しました。" + "(0x" + result.ToString() + ")");
                }
                var structAddress = vaultItemPtr;
                if (vaultItemCount > 0)
                {
                    for (int j = 1; j <= vaultItemCount; j++)
                    {
                        // Vaultの列挙を開始
                        var currentItem = Marshal.PtrToStructure(structAddress, VAULT_ITEM);
                        structAddress = (IntPtr)(structAddress.ToInt64() + Marshal.SizeOf(VAULT_ITEM));

                        IntPtr passwordVaultItem = IntPtr.Zero;
                        // フィールド情報の検索
                        FieldInfo schemaIdInfo = currentItem.GetType().GetField("SchemaId");
                        Guid schemaId = new Guid(schemaIdInfo.GetValue(currentItem).ToString());
                        FieldInfo pResourceElementInfo = currentItem.GetType().GetField("pResourceElement");
                        IntPtr pResourceElement = (IntPtr)pResourceElementInfo.GetValue(currentItem);
                        FieldInfo pIdentityElementInfo = currentItem.GetType().GetField("pIdentityElement");
                        IntPtr pIdentityElement = (IntPtr)pIdentityElementInfo.GetValue(currentItem);
                        FieldInfo dateTimeInfo = currentItem.GetType().GetField("LastModified");
                        ulong lastModified = (ulong)dateTimeInfo.GetValue(currentItem);

                        IntPtr pPackageSid = IntPtr.Zero;
                        if (OSMajor >= 6 && OSMinor >= 2)
                        {
                            // 新しいバージョンにはパッケージSIDがある
                            FieldInfo pPackageSidInfo = currentItem.GetType().GetField("pPackageSid");
                            pPackageSid = (IntPtr)pPackageSidInfo.GetValue(currentItem);
                            result = VaultGetItem_WIN8(vaultHandle, ref schemaId, pResourceElement, pIdentityElement, pPackageSid, IntPtr.Zero, 0, ref passwordVaultItem);
                        }
                        else
                        {
                            result = VaultGetItem_WIN7(vaultHandle, ref schemaId, pResourceElement, pIdentityElement, IntPtr.Zero, 0, ref passwordVaultItem);
                        }

                        if (result != 0)
                        {
                            throw new Exception("Error occured while retrieving vault item. Error: 0x" + result.ToString());
                        }
                        object passwordItem = Marshal.PtrToStructure(passwordVaultItem, VAULT_ITEM);
                        FieldInfo pAuthenticatorElementInfo = passwordItem.GetType().GetField("pAuthenticatorElement");
                        IntPtr pAuthenticatorElement = (IntPtr)pAuthenticatorElementInfo.GetValue(passwordItem);
                        // 認証情報を取得
                        object cred = GetVaultElementValue(pAuthenticatorElement);
                        object packageSid = null;
                        if (pPackageSid != IntPtr.Zero && pPackageSid != null)
                        {
                            packageSid = GetVaultElementValue(pPackageSid);
                        }
                        if (cred != null) // 取得に成功したデータを表示
                        {
                            Console.WriteLine("Vault Type   : {0}", vaultType);
                            object resource = GetVaultElementValue(pResourceElement);
                            if (resource != null)
                            {
                                Console.WriteLine("Resource     : {0}", resource);
                            }
                            object identity = GetVaultElementValue(pIdentityElement);
                            if (identity != null)
                            {
                                Console.WriteLine("Identity     : {0}", identity);
                            }
                            if (packageSid != null)
                            {
                                Console.WriteLine("PacakgeSid  : {0}", packageSid);
                            }
                            Console.WriteLine("Credential   : {0}", cred);
                            Console.WriteLine("LastModified : {0}", System.DateTime.FromFileTimeUtc((long)lastModified));
                            Console.WriteLine();
                        }
                    }
                }
            }
        }

        public static void Main(string[] args)
        {
            GetLogins();
            Console.ReadKey(true);
        }
    }
}

実行する際は、ビルド設定の「32 ビットを選ぶ」のチェックを外してください。

仕組みとしては、Windows VaultのAPIをDllImportで読み込み、それを使ってVaultの構造体を取得してデータを取り出しています。
取得したデータ自体に暗号化などはされていないようです。

これを実行すると、以下のようにログインデータが出力されます。

Vault Type   : Web Credentials
Resource     : https://login.microsoftonline.com/
Identity     : foobar314159@yahoo.co.jp
Credential   : TekitounaPassword
LastModified : 2020/05/23 15:50:49

Vault Type   : Web Credentials
Resource     : https://accounts.google.com/
Identity     : unknown141421356@gmail.com
Credential   : SecurePass123456
LastModified : 2020/05/26 11:52:57

おわりに

という訳で、IE/Edgeに保存されているログインデータも取得できてしまいました。
実は、Windows Vaultsに関するAPIの仕様はMicrosoft公式からは公開されていません。
だから、Microsoftは特にログインデータに暗号化処理をしなかったのだと思います。
しかし、海外のハッカー達にとってWindows Vaultsの仕様を一から自力で解析することなど朝飯前だったようです。恐ろしいですね。

参考文献

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1