Все, кто хоть немного дорожит своей информацией, направляют фокус на её защиту. Учитывая эти обстоятельства, начиная ещё с Win-2k инженеры Microsoft ввели в состав своей ОС специальный интерфейс и назвали его "Data-Protection Programming Interface", или коротко DPAPI. В результате, не прибегая к услугам внешних библиотек, у нас появилась возможность защищать свои данные вполне крипткостойким механизмом, причём эти данные могут находиться как на жёстком диске, так и непосредственно в памяти. В состав этого интерфейса входят всего 4 функции, однако практическая его реализация довольно сложна и корнями уходит в нёдра операционной системы, иначе DPAPI не получил-бы столь высокий кредит доверия в высшем обществе. В данной статье рассматриваются технические детали этого интерфейса, и некоторые советы к области его применения.
Содержание:
1. Data Protect – основная идея
В программистcких кругах бытует мнение, что для защиты критически важных данных лучше применять не стандартные методы (которые предлагает нам система), а выстраивать крепость вручную, щедро разбавляя программный код обратимыми математическими формулами от-фонаря. Аргументируются доводы тем, что мол все системные алгоритмы шифрования уже давно изучены, и с их взломом не сталкивался только ленивый. Если учесть вычислительные возможности современных процессоров, то доля правды в этом есть – элементарный брутфорс с применением радужных таблиц рано или поздно всё-же даст результат, не говоря уже о более продуманном подходе.
Но проблема в том, что какую-бы высшую математику мы не применили, сам алгоритм остаётся всегда на поверхности и при интерактивной отладке сразу-же всплывёт на поверхность. На вопрос "как спрятать его от посторонних глаз?" можно смело ответить – никак! Это напоминает эффект-плацебо, когда мы тупо верим в пользу некоторого действия, в реале нейтрального, прямой эффект которого не наблюдается.
Однако любую критику выдерживает герой этой статьи – механизм шифрования данных DPAPI. Только столкнувшись с его реверсом понимаешь, что разрабатывался он с уклоном на то, чтобы закрыть данные не только от различного рода кодокопателей, но при некоторых обстоятельствах и от самой системы. Достичь такого уровня позволяет передача данных по закрытым каналам ALPC/RPC, что подразумевает локальный (Advanced Local) и удалённый вызов системных процедур из пользовательского режима (Remote Procedure Call). Другими словами, при вызове функций DPAPI происходит переключение контекста из юзера в кернел, где и осуществляется вся магия непосредственного шифрования.
Такой расклад привлёк внимание сразу нескольких независимых групп, которые занялись анализом интерфейса DPAPI. Первыми, результатов добилась команда из фирмы "Passcape Software" ещё в 2003 году, которым удалось полностью обратить никак недокументированный на тот момент алгоритм защиты Microsoft. По их докладу стало известно, что в процессе шифрования, DPAPI использует три составляющие – это т.н. "мастер-ключ", идентификатор безопасности пользователя SID (Security Identifiers), и хеш от пароля юзера на вход в систему. Гремучая смесь этих составляющих даёт на выходе невероятно прочную стену, пробить которую юзермодным отладчикам становится не под силу.
Как мы узнаем позже, под своей учётной записью, системный мастер-ключ и SID юзера добыть не проблема, а вот с паролем могут возникнуть трудности, т.к. его хеш зарыт в недоступной никому области памяти системного процесса lsass.exe. Именно для доступа к паролю, код интерфейса DPAPI использует закрытые каналы Lsass и SRM, по которым гоняет служебные запросы и данные. Пройдя этап переработки и метаболизма, шифруемые данные из ядра через эти-же каналы возвращаются опять в приложение юзера. Конечно можно сдампить память процесса lsass в свой бокс, но выташить потом из этого дампа пароль находясь в офлайне – задача не простая. В грубой форме это выглядит примерно так:
Ядерный монитор безопасности SRM и пользовательский процесс Lsass обмениваются данными при помощи механизма ALPC (см.рис.выше). В ходе инициализации системы, SRM и Lsass создают свои порты (SeRmCmdPort и SeLsaCmdPort соответственно), после чего присовокупляются друг к другу. Это приводит к созданию закрытого коммуникационного канала связи. Один раз создав соединение при загрузке системы, как SRM так и Lsass больше не разрывают его, поэтому "любопытные" процессы юзера не имеют возможности подключиться к ним – запросы на коннект будут в мёртвом цикле крутится в кольцевом буфере АLPC, или попросту отвергаться.
Система безопасности Win разделяет права своих пользователей при помощи трёх составляющих – это учётные записи юзеров, их пароли, и защита принадлежащих этим юзерам личных файлов. Рассмотрим, каким образом каждый из перечисленных аспектов архитектуры влияет на выполнение жёстких требований безопасности. В неё входят следующие компоненты и привязанные к ним базы-данных:
Системный транслет Lsass.exe довольно творческая единица, и загружает в своё пространство множество библиотек для решения различного круга задач. В контексте данной статьи нас будут интересовать: lsasrv, samsrv, authz и crypt32.dll (где и сосредоточены собственно функции DPAPI). Консольная утилита tasklist.exe с одноимённым фильтром(/fi) и аргументом(/m) возвращает список всех загруженных в процесс библиотек, что продемонстрированно на скрине ниже. Обратите внимание, сколько имеется модулей, где фигурирует слово "crypt" (я насчитал их 7):
Что касается баз LSA и SAM, то они не доступны в реестре смертным пользователям,
однако просмотреть их содержимое можно утилитой "PsExec" из комплекта
2. Функции DPAPI из библиотеки crypt32.dll
Выше упоминалось, что в интерфейс "Data-Protect-API" входят всего 4 функции – это CryptProtectData() для шифрования данных, и CryptUnprotectData() для их-же расшифровки. Есть ещё пара аналогичных функций, только для шифрования данных прямо в памяти CryptProtectMemory(), но они применяются довольно редко, поэтому останавливаться на них не будем. У всех этих функций прототипы одинаковы и выглядят так:
Как видим, исходные данные этой функции нужно передавать через структуру DATA_BLOB, которая имеет всего два поля – в первом нужно указать размер данных, а во-втором их адрес. Когда функция отработает, она вернёт результат своей работы так-же в структуру DATA_BLOB (см.последний аргумент), при этом сами данные могут находиться хоть у чёрта на куличках (например в хипе) – главное мы получим их размер и адрес. BLOB подразумевает "Binary-Long OBject", или большой объект бинарных данных. Везде, где встречается этот термин нужно иметь в виду, что перед нами не строка или hex-значение, а массив двоичных данных переменной длины. Для описания таких массивов и служит структура DATA_BLOB.
Отдельного внимания заслуживает и опциональный третий параметр, в котором при желании можно передать данные с энтропией. В криптологии, энтропией называют рандомную "соль". Добавленная к полезным данным, она на порядок усложняет взлом паролей перебором. Как и шифруемые данные, энтропия так-же передаётся структурой DATA_BLOB, и на практике настоятельно рекомендуется к использованию. Поскольку она напрямую влияет на безопасность данных, то не хранится в выходном объекте зашифрованных данных DPAPI_DATA_BLOB. Мы должны сами обеспечить надлежащее хранение этого секрета, и на этапе расшифровки данных обязательно передать её в том-же виде обратно функции CryptUnprotectData(). Будьте осторожны! При потере или искажении энтропии хоть на 1-бит, восстановить данные будет не возможно.
Предпоследний аргумент функции ожидает указания флагов, которые играют немаловажную роль в процессе шифрования. Уже упоминалось, что в театре действий DPAPI принимают активное участие: системный "мастер-ключ", SID текущего пользователя, и хеш от его пароля на вход в систему. Так, если мы укажем при шифровании данных флаг "LOCAL_MACHINE", то SID и хеш пароля уже отправляются на скамейку запасных, и функция шифрует только мастер-ключом. В результате, зашифрованные нами личные данные сможет расшифровать любой пользователь данного компьютера, что сведёт на нет саму идею DPAPI. Ещё один флаг "CRED_SYNC" позволяет принудительно обновить системный мастер-ключ. В большинстве случаях флаг сбрасывают в нуль, чтобы функция взяла дефолтное его значение из реестра.
Обратная по назначению функция CryptUnprotectData() сначала проверяет целостность данных в структуре DPAPI_DATABLOB, и только потом расшифровывает их. Единственный пользователь, который может расшифровать закриптованные в DPAPI данные – это пользователь с теми-же учётными данными, что и пользователь, зашифровавший их. Более того, шифрование и дешифрование должны выполняться на одном компьютере, чтобы совпадал их мастер-ключ.
3. Практика – реверс интерфейса DPAPI
Теперь проведём небольшой эксперимент, и заглянем внутрь выходного объекта DPAPI_DATABLOB.
Учитывая, что ребята из фирмы "Passcape Software" уже всё сделали за нас и предоставили на всеобщее обозрение детальное описание всех полей этой структуры, нам остаётся лишь написать программу шифрования данных функцией CryptProtectData(), после чего проекцией наложить на полученный BLOB данную структуру. Вот как они её описывают:
Это именно то, что возвращает функция после шифрования переданных ей данных. При этом непосредственно сами полезные данные расположены в хвосте структуры (поле dwDataLen, и pData), а остальное – служебная информация, чтобы была возможность в последующем расшифровать этот BLOB, типа: идентификатор мастер-ключа, соль, флаги, алгоритмы шифрования и хеширования, и т.п.
Обратите внимание на поле HMAC "Message Authentification Code", или код аутентификации сообщения. МАС применяется для проверки целостности данных в этой структуре, а префикс(Н) говорит о том, что это хеш от Auth-кода. Его можно сравнить с контрольной суммой данных, только размер HМАС как-правило 16 или 32 байта, а не 4 как в CRC-32.
Чтобы исходник не потерял наглядность, я просто втяну пользователя в авантюру и запрошу у него пароль, который тут-же зашифрую при помощи CryptProtectData(). После того-как эта функция через процесс Lsass.exe залезет в ядро, и "подёргав его за вымя" вернёт мне DPAPI_DATABLOB зашифрованного пароля, я выведу на консоль все его поля. Это позволит узнать, какой именно алгоритм шифрования/хеширования использует интерфейс DPAPI, кто его провайдер и всё остальное.
Чтобы сбоксить на консоль Guid'ы поставщика и мастер-ключа, позовём на помощь вспомогательную функцию StringFromGUID2() из библиотеки ole32.dll. Всё-что ей нужно, это указатель на двоичный Guid в структуре BLOB, и указатель на приёмный буфер, куда функция вернёт его строковое представление.
Каждый из алгоритмов шифрования и хеширования имеет свои константы, которые определены в сишном заголовочном хидере WinCrypt.h. Я собрал их все в инклуд и забросил в скрепку. Вот их значения, привязав к которым строки, можно выводить их на консоль:
Остальное всё в штатном режиме. Значит запрашиваем пароль и загнав его данные в структуру "inBlob", передаём его для шифрования в CryptProtectData(). Можно было в конце программы опять расшифровать пароль, но место в консольном окне уже не хватило, поэтому оставлю это действо вам в качестве дз.
Поскольку мы заранее не знаем длину поля, (например, pSalt в структуре BLOB) то вычислять следующее от него смещение придётся динамически. Для этого, нужно запомнить указатель на предыдущее, и добавить к нему благо-известную длину. Такими короткими перебежками можно будет обойти всю структуру данных. Вот пример реализации:
Посмотрим, что мы тут имеем..
Значит обычную строку пароля из 14-ти символов, DPAPI зашифровал в BLOB размером аж 230-байт. В качестве криптографического провайдера задействован поставщик с GUID'ом
Члены структуры algCrypt и algHash прямым текстом сообщают нам алгоритмы шифрования и хеширования данных, а так-же значение рандомной соли (не путать с энтропией, которая уязвима и не хранится в данной структуре), кода-аутентификации МАС, и сигнатуры (электронная подпись).
Практика показала, что изменение длины шифруемых входных данных, никак не влияет на размеры полей Salt\HMAC\Sign, кроме непосредственно самих данных в зелёном блоке "CryptData". При вводе до 15-символов включительно, размер данных остаётся 16-байт, а если ввести 16-символов, то размер зашифрованных данных сразу-же увеличивается в два раза = 32-байта. Связано это с блочным шифрованием данных AES-256 в режиме CBC (Cipher-Block-Chaining), который оперирует информационной матрицей 4х4 байта.
Важным моментом во-всей этой кухне является то, что ознакомившись с структурой DPAPI_DATABLOB мы получили штамм, по которому можем находить в виртуальной памяти ОЗУ компьютера не только свои, но и чужие объекты DPAPI. В качестве такой сигнатуры поиска, могут послужить два первых поля данного объекта BLOB, где указывается версия(1) структуры, и Guid виндозного крипто-провайдера. За некоторым исключением, во-всех объектах BLOB и операционных системах, эти первые 20-байт будут одинаковы.
Таким образом, для декрипта зашифрованных данных DPAPI нам остаётся найти лишь хеш от юзерского пасса в памяти Lsass.exe, и мастер-ключ системы. Проблему предвещает пароль, добыть который можно только сохранив память процесса Lsass.exe, после чего искать его в дампе отталкиваясь, например, от аргументов функций шифрования. Но для этого во-первых нужны права админа (иначе Lsass не сдампить), а во-вторых – необходимо быть осведомлённым о самом алгоритме шифрования. Не сказать, что задача не решаема в принципе, однако займёт уйму времени. Так-что оставим это дело для потомков, а сами попробуем обзавестись мастер-ключом.
4. Алгоритм поиска Master-Key
Для начала определимся, что такое мастер-ключ и зачем он нужен.
Это основной ключ интерфейса DPAPI, при помощи которого он шифрует данные алгоритмом AES-256/СВС. Изначально, в открытом виде ключ имеет длину 64-байта, но после его хеширования алгоритмом SHA-512 размер увеличивается до 176-байт. В создании мастер-ключа принимает участие предварительный ключ под названием "PreKey", который создаётся из пароля юзера на вход в систему (из защищённой памяти Lsass.exe), и идентификатора SID этого юзера. Далее генерируется 64-байтный рандом (основной материал для мастер-ключа), к нему добавляется соль размером 16-байт, и вся эта братия отправляется в "миксер" AES-256.
На выходе из блока шифрования AES получаем структуру "MasterKey_BLOB", которой система присваивает уникальный Guid. По-умолчанию, процедура генерации мастер-ключей в системе повторяется каждые 90-дней, чтобы периодически обновлять их. При этом отработанные/старые ключи не удаляются, а отправляются в бэкап. Резерв нужен для тех случаев, когда мы зашифруем данные, например, за день до обновления системой старого ключа, и попытаемся расшифровать их после. В этом случае, DPAPI возьмёт из базы текущий ключ и если попытка декрипта им не увенчается успехом, то начнёт перебирать более старые ключи, пока не найдёт валидный. Схема генерации мастер-ключа представлена ниже:
В системе имеются два типа "мастер-ключей" – один привязан к учётной записи пользователя, а другой к машине в целом (хранится в System32\Microsoft\Protect). Когда мы вызываем функцию CryptProtectData() с флагом LOCAL_MACHINE, механизм DPAPI шифрует данные ключом компьютера и любой пользователь данной машины сможет расшифровать их. Если-же этот флаг сброшен, то применяется мастер-ключ текущего пользователя, и юзер с другой учётной записью не сможет уже осуществить обратный декрипт, поскольку его SID (и если есть пароль) окажется уткой.
Мастер-ключи юзера хранятся в папке его профиля:
Всё те-же ребята из Passcape отреверсили структуру мастер-ключей, присвоив ей имя MASTERKEY_BLOB. В этих 468-байтах достаточно информации, чтобы получить всю подноготную мастер-ключа, включая значение соли, идентификаторы алгоритмов, раундов хеширования и прочее. Кстати о раундах.. Термин применяется к функции PBKDF2 (Password-Based Key Derivation Function), криптографическому стандарту формирования ключей на основе пароля. Фактически, это цикл хэширования ключей алгоритмом SHA-512, где раунды определяют кол-во итераций (повторений). К примеру на моей Win-7 таких раундов 5400, что в миллионы раз увеличивает время перебора ключей брутфорсом.
Что касается самой 468-байтной структуры MASTERKEY_BLOB, то она состоит из 5-ти частей. Сначала идёт 128-байтный заголовок Header, после которого следуют описатели 4-х ключей – это непосредственно паспорт ключа MasterKey, далее BackupKey, CredHist и DomainKey. Нам интересен только первый мастер-ключ, поэтому структуру оформляем так:
В демонстрационном примере ниже попытаемся найти свой мастер-ключ на диске, и вывести из него всю информацию. Основную проблему здесь представляет только динамический поиск директории "MasterKey-Storage-Folder", поскольку SID пользователя заранее нам не известен. Для этого воспользуемся парой функций FindFirstFile() + FindNextFile(), которые перечисляют файлы и папки в указанном дире. Они возвращают информацию о найденном файле в сл.структуру:
Таким образом, в поле "cFileName" мы получим имя папки. Поскольку SID пользователя всегда начинается с символов
Обратите внимание на Guid мастер-ключа. В предыдущей программе мы шифровали данные при помощи CryptProtectData(), с последующим выводом результирующего BLOB'а на консоль. Если всё было сделано правильно, то Guid из блоба-данных должен совпадать с Guid'ом из блоба мастер-ключа, т.к. именно им и происходит шифрование. Как видим ошибок нет и ключ мы получили валидный:
5. Постскриптум
Система использует интерфейс DPAPI при защите большого зоопарка персональных данных. Это пароли и данные автозаполнения форм в браузерах IE, кукисов и паролей Chrome, учётных записей в Outlook, Win-Mail, FTP, для доступа к расшаренным папкам и ключам учёток Wi-Fi, для удаленного доступа к рабочему столу и многое другое. DPAPI активно юзают и сторонние разработчики типа Skype, GoogleTalk и др. К сожалению ограниченные объёмы статьи не позволяют охватить всего материала на эту тему (хотя и так получилось слишком много букаф), а потому за бортом осталось много интересного. В скрепку кладу два представленных здесь исполняемых файла, а так-же инклуд с описанием основных структур DPAPI. Всем удачи, пока!
Содержание:
1. Data Protect – основная идея;
2. Функции DPAPI из библиотеки crypt32.dll;
3. Практика – реверс интерфейса DPAPI;
4. Алгоритм поиска Master-Key;
5. Постскриптум.
-------------------------------------------------------1. Data Protect – основная идея
В программистcких кругах бытует мнение, что для защиты критически важных данных лучше применять не стандартные методы (которые предлагает нам система), а выстраивать крепость вручную, щедро разбавляя программный код обратимыми математическими формулами от-фонаря. Аргументируются доводы тем, что мол все системные алгоритмы шифрования уже давно изучены, и с их взломом не сталкивался только ленивый. Если учесть вычислительные возможности современных процессоров, то доля правды в этом есть – элементарный брутфорс с применением радужных таблиц рано или поздно всё-же даст результат, не говоря уже о более продуманном подходе.
Но проблема в том, что какую-бы высшую математику мы не применили, сам алгоритм остаётся всегда на поверхности и при интерактивной отладке сразу-же всплывёт на поверхность. На вопрос "как спрятать его от посторонних глаз?" можно смело ответить – никак! Это напоминает эффект-плацебо, когда мы тупо верим в пользу некоторого действия, в реале нейтрального, прямой эффект которого не наблюдается.
Однако любую критику выдерживает герой этой статьи – механизм шифрования данных DPAPI. Только столкнувшись с его реверсом понимаешь, что разрабатывался он с уклоном на то, чтобы закрыть данные не только от различного рода кодокопателей, но при некоторых обстоятельствах и от самой системы. Достичь такого уровня позволяет передача данных по закрытым каналам ALPC/RPC, что подразумевает локальный (Advanced Local) и удалённый вызов системных процедур из пользовательского режима (Remote Procedure Call). Другими словами, при вызове функций DPAPI происходит переключение контекста из юзера в кернел, где и осуществляется вся магия непосредственного шифрования.
Такой расклад привлёк внимание сразу нескольких независимых групп, которые занялись анализом интерфейса DPAPI. Первыми, результатов добилась команда из фирмы "Passcape Software" ещё в 2003 году, которым удалось полностью обратить никак недокументированный на тот момент алгоритм защиты Microsoft. По их докладу стало известно, что в процессе шифрования, DPAPI использует три составляющие – это т.н. "мастер-ключ", идентификатор безопасности пользователя SID (Security Identifiers), и хеш от пароля юзера на вход в систему. Гремучая смесь этих составляющих даёт на выходе невероятно прочную стену, пробить которую юзермодным отладчикам становится не под силу.
Как мы узнаем позже, под своей учётной записью, системный мастер-ключ и SID юзера добыть не проблема, а вот с паролем могут возникнуть трудности, т.к. его хеш зарыт в недоступной никому области памяти системного процесса lsass.exe. Именно для доступа к паролю, код интерфейса DPAPI использует закрытые каналы Lsass и SRM, по которым гоняет служебные запросы и данные. Пройдя этап переработки и метаболизма, шифруемые данные из ядра через эти-же каналы возвращаются опять в приложение юзера. Конечно можно сдампить память процесса lsass в свой бокс, но выташить потом из этого дампа пароль находясь в офлайне – задача не простая. В грубой форме это выглядит примерно так:
Ядерный монитор безопасности SRM и пользовательский процесс Lsass обмениваются данными при помощи механизма ALPC (см.рис.выше). В ходе инициализации системы, SRM и Lsass создают свои порты (SeRmCmdPort и SeLsaCmdPort соответственно), после чего присовокупляются друг к другу. Это приводит к созданию закрытого коммуникационного канала связи. Один раз создав соединение при загрузке системы, как SRM так и Lsass больше не разрывают его, поэтому "любопытные" процессы юзера не имеют возможности подключиться к ним – запросы на коннект будут в мёртвом цикле крутится в кольцевом буфере АLPC, или попросту отвергаться.
Система безопасности Win разделяет права своих пользователей при помощи трёх составляющих – это учётные записи юзеров, их пароли, и защита принадлежащих этим юзерам личных файлов. Рассмотрим, каким образом каждый из перечисленных аспектов архитектуры влияет на выполнение жёстких требований безопасности. В неё входят следующие компоненты и привязанные к ним базы-данных:
• SRM – Security Reference Monitor, или глобальный монитор безопасности. Находится в исполняющей подсистеме ядра Ntoskrnl.exe, и отвечает за: (1) оформление "маркеров доступа" (токенов) для предоставления контекста безопасности, (2) выполнение проверок доступа к объектам, (3) работу с привилегиями (правами) пользователей.
• LSASS – Local Security Authority Sub-System. Это процесс пользовательского режима Lsass.exe, который отвечает за политику безопасности системы и аутентификацию всех пользователей. Функционал реализован в библиотеке Lsasrv.dll. Более того, в Lsass хранится зашифрованный двоичный объект хеша SHA-512 от пароля юзверя, на который опирается весь интерфейс DPAPI. Кстати такие процессы называют ещё "транслеты" – изолированные процессы ядра в пользовательском режиме.
• Lsa-база, где хранятся настройки политики безопасности системы. Она прописана в ACL-закрытой ветке реестра HKLM\SECURITY (Access Control List, список управления доступом). По информации из этой базы система определяет, у кого есть права на доступ к нёдрам системы, кому из них и какие конкретно привилегии назначены.
• SAM – Security Accounts Manager, или администратор учётных записей. Сервис samsrv.dll загружается в процесс Lsass и отвечает за управление базой с именами пользователей, и поддержки групп на локальной машине. Привязанная к нему база содержит данные локальных юзеров и групп, наряду с их паролями и другими атрибутами безопасности. База хранится в защищённой ветке реестра HKLM\SAM.
Системный транслет Lsass.exe довольно творческая единица, и загружает в своё пространство множество библиотек для решения различного круга задач. В контексте данной статьи нас будут интересовать: lsasrv, samsrv, authz и crypt32.dll (где и сосредоточены собственно функции DPAPI). Консольная утилита tasklist.exe с одноимённым фильтром(/fi) и аргументом(/m) возвращает список всех загруженных в процесс библиотек, что продемонстрированно на скрине ниже. Обратите внимание, сколько имеется модулей, где фигурирует слово "crypt" (я насчитал их 7):
Что касается баз LSA и SAM, то они не доступны в реестре смертным пользователям,
однако просмотреть их содержимое можно утилитой "PsExec" из комплекта
Ссылка скрыта от гостей
Марка Руссиновича:2. Функции DPAPI из библиотеки crypt32.dll
Выше упоминалось, что в интерфейс "Data-Protect-API" входят всего 4 функции – это CryptProtectData() для шифрования данных, и CryptUnprotectData() для их-же расшифровки. Есть ещё пара аналогичных функций, только для шифрования данных прямо в памяти CryptProtectMemory(), но они применяются довольно редко, поэтому останавливаться на них не будем. У всех этих функций прототипы одинаковы и выглядят так:
C-подобный:
BOOL CryptProtectData( ;//<------- 0 = ошибка!
pDataIn dd 0 ;// линк на структуру DATA_BLOB, которая описывает данные для шифрования
ppszDataDescr dd 0 ;// 0, или линк на строковое описание шифруемых данных
pOptionalEntropy dd 0 ;// 0, или линк на DATA_BLOB, с паролем или доп.энтропией (соль)
pvReserved dd 0 ;// 0
pPromptStruct dd 0 ;// 0, или линк на структуру CRYPTPROTECT_PROMPTSTRUCT (подсказки)
dwFlags dd 0 ;// 0, или флаги управления процессом шифрования
pDataOut dd 0 ;// линк на структуру DATA_BLOB, в которую функция вернёт зашифрованные данные
);
;//************************************************
struct DATA_BLOB
dwDataSize dd 0 ;// размер данных
pData dd 0 ;// указатель (pointer) на данные
ends
Как видим, исходные данные этой функции нужно передавать через структуру DATA_BLOB, которая имеет всего два поля – в первом нужно указать размер данных, а во-втором их адрес. Когда функция отработает, она вернёт результат своей работы так-же в структуру DATA_BLOB (см.последний аргумент), при этом сами данные могут находиться хоть у чёрта на куличках (например в хипе) – главное мы получим их размер и адрес. BLOB подразумевает "Binary-Long OBject", или большой объект бинарных данных. Везде, где встречается этот термин нужно иметь в виду, что перед нами не строка или hex-значение, а массив двоичных данных переменной длины. Для описания таких массивов и служит структура DATA_BLOB.
Отдельного внимания заслуживает и опциональный третий параметр, в котором при желании можно передать данные с энтропией. В криптологии, энтропией называют рандомную "соль". Добавленная к полезным данным, она на порядок усложняет взлом паролей перебором. Как и шифруемые данные, энтропия так-же передаётся структурой DATA_BLOB, и на практике настоятельно рекомендуется к использованию. Поскольку она напрямую влияет на безопасность данных, то не хранится в выходном объекте зашифрованных данных DPAPI_DATA_BLOB. Мы должны сами обеспечить надлежащее хранение этого секрета, и на этапе расшифровки данных обязательно передать её в том-же виде обратно функции CryptUnprotectData(). Будьте осторожны! При потере или искажении энтропии хоть на 1-бит, восстановить данные будет не возможно.
Предпоследний аргумент функции ожидает указания флагов, которые играют немаловажную роль в процессе шифрования. Уже упоминалось, что в театре действий DPAPI принимают активное участие: системный "мастер-ключ", SID текущего пользователя, и хеш от его пароля на вход в систему. Так, если мы укажем при шифровании данных флаг "LOCAL_MACHINE", то SID и хеш пароля уже отправляются на скамейку запасных, и функция шифрует только мастер-ключом. В результате, зашифрованные нами личные данные сможет расшифровать любой пользователь данного компьютера, что сведёт на нет саму идею DPAPI. Ещё один флаг "CRED_SYNC" позволяет принудительно обновить системный мастер-ключ. В большинстве случаях флаг сбрасывают в нуль, чтобы функция взяла дефолтное его значение из реестра.
Обратная по назначению функция CryptUnprotectData() сначала проверяет целостность данных в структуре DPAPI_DATABLOB, и только потом расшифровывает их. Единственный пользователь, который может расшифровать закриптованные в DPAPI данные – это пользователь с теми-же учётными данными, что и пользователь, зашифровавший их. Более того, шифрование и дешифрование должны выполняться на одном компьютере, чтобы совпадал их мастер-ключ.
3. Практика – реверс интерфейса DPAPI
Теперь проведём небольшой эксперимент, и заглянем внутрь выходного объекта DPAPI_DATABLOB.
Учитывая, что ребята из фирмы "Passcape Software" уже всё сделали за нас и предоставили на всеобщее обозрение детальное описание всех полей этой структуры, нам остаётся лишь написать программу шифрования данных функцией CryptProtectData(), после чего проекцией наложить на полученный BLOB данную структуру. Вот как они её описывают:
C-подобный:
struct DPAPI_DATABLOB
dwVersion dd 0 ;// версия структуры
ProvGuid db 16 dup(0) ;// Guid крипто-провайдера
MasterKeyVersion dd 0 ;// версия ключа
MasterKeyGuid db 16 dup(0) ;// Guid ключа
dwFlags dd 0 ;// флаги
dwDataDescLen dd 0 ;// размер аргумента(2) "DataDescr" функции CryptProtectData()
szDataDesc db ? ;// ^^значение аргумента.
algCrypt dd 0 ;// алгоритм шифрования (например AES)
cryptAlgLen dd 0 ;// ^^размер ключа (например 256, итого AES-256)
dwSaltLen dd 0 ;// размер соли хеширования
pSalt db ? ;// ^^значение соли
dwHmacKeyLen dd 0 ;// размер "кода-аутентификации" HMAC
pHmac db ? ;// ^^значение HMAC
algHash dd 0 ;// алгоритм хеширования (например SHA)
hashAlgLen dd 0 ;// ^^размер блока (например 512, итого SHA-512)
dwHmac2KeyLen dd 0 ;// см.выше
pHmac2 db ? ;// ^^^
dwDataLen dd 0 ;// размер зашифрованных данных
pData db ? ;// ^^данные
dwSignLen dd 0 ;// размер сигнатуры
pSign db ? ;// ^^сигнатура
ends
Это именно то, что возвращает функция после шифрования переданных ей данных. При этом непосредственно сами полезные данные расположены в хвосте структуры (поле dwDataLen, и pData), а остальное – служебная информация, чтобы была возможность в последующем расшифровать этот BLOB, типа: идентификатор мастер-ключа, соль, флаги, алгоритмы шифрования и хеширования, и т.п.
Обратите внимание на поле HMAC "Message Authentification Code", или код аутентификации сообщения. МАС применяется для проверки целостности данных в этой структуре, а префикс(Н) говорит о том, что это хеш от Auth-кода. Его можно сравнить с контрольной суммой данных, только размер HМАС как-правило 16 или 32 байта, а не 4 как в CRC-32.
Чтобы исходник не потерял наглядность, я просто втяну пользователя в авантюру и запрошу у него пароль, который тут-же зашифрую при помощи CryptProtectData(). После того-как эта функция через процесс Lsass.exe залезет в ядро, и "подёргав его за вымя" вернёт мне DPAPI_DATABLOB зашифрованного пароля, я выведу на консоль все его поля. Это позволит узнать, какой именно алгоритм шифрования/хеширования использует интерфейс DPAPI, кто его провайдер и всё остальное.
Чтобы сбоксить на консоль Guid'ы поставщика и мастер-ключа, позовём на помощь вспомогательную функцию StringFromGUID2() из библиотеки ole32.dll. Всё-что ей нужно, это указатель на двоичный Guid в структуре BLOB, и указатель на приёмный буфер, куда функция вернёт его строковое представление.
Каждый из алгоритмов шифрования и хеширования имеет свои константы, которые определены в сишном заголовочном хидере WinCrypt.h. Я собрал их все в инклуд и забросил в скрепку. Вот их значения, привязав к которым строки, можно выводить их на консоль:
C-подобный:
;// Константы алгоритмов
;//----------------------------------
CALG_DES = 0x00006601 ;//<--- шифрование
CALG_3DES = 0x00006603
CALG_AES = 0x00006611
CALG_AES_128 = 0x0000660e
CALG_AES_192 = 0x0000660f
CALG_AES_256 = 0x00006610
CALG_RC2 = 0x00006602
CALG_RC4 = 0x00006801
CALG_RC5 = 0x0000660d
CALG_HASH_REPLACE_OWF = 0x0000800b ;//<--- хеширование
CALG_HMAC = 0x00008009
CALG_MAC = 0x00008005
CALG_MD2 = 0x00008001
CALG_MD4 = 0x00008002
CALG_MD5 = 0x00008003
CALG_SHA = 0x00008004
CALG_SHA1 = 0x00008004
CALG_SHA256 = 0x0000800c
CALG_SHA384 = 0x0000800d
CALG_SHA512 = 0x0000800e
CALG_SSL3_SHAMD5 = 0x00008008
CALG_TLS1_PRF = 0x0000800a
;//----------------------------------
;// Таблица алгоритмов шифрования
;//----------------------------------
CryptTable dd 0x6603,calgDes,0x6610,calgAes
dd 0x6801,calgRc4,0x660d,calgRc5
calgDes db '3DES',0
calgAes db 'AES.256',0
calgRc4 db 'RC4',0
calgRc5 db 'RC5',0
Остальное всё в штатном режиме. Значит запрашиваем пароль и загнав его данные в структуру "inBlob", передаём его для шифрования в CryptProtectData(). Можно было в конце программы опять расшифровать пароль, но место в консольном окне уже не хватило, поэтому оставлю это действо вам в качестве дз.
Поскольку мы заранее не знаем длину поля, (например, pSalt в структуре BLOB) то вычислять следующее от него смещение придётся динамически. Для этого, нужно запомнить указатель на предыдущее, и добавить к нему благо-известную длину. Такими короткими перебежками можно будет обойти всю структуру данных. Вот пример реализации:
C-подобный:
format pe console
entry start
;//------------
section '.inc' data readable
include 'win32ax.inc'
include 'equates\dpapi.inc'
;//------------
.data
inBlob CRYPT_BLOB ;// структура с исходными данными
outBlob CRYPT_BLOB ;// сюда получим результат
hndl dd 0
dwRet dd 0
buff db 0
;//------------
.code
start: invoke SetConsoleTitle,<'*** DPAPI Crypt/Decrypt ***',0>
;//-- Запрос на ввод пароля и узнаём его длину
cinvoke printf,<10,' Type password....: ',0>
cinvoke scanf,<'%s',0>,buff
invoke lstrlen,buff
;//-- Оформляем входную структуру DATA_BLOB и шифруем пароль!
mov [inBlob.dataSize],eax
mov [inBlob.pData],buff
invoke CryptProtectData,inBlob,0,0,0,0,0,outBlob
;//-- Получили выходную структуру DATA_BLOB – покажем инфу из неё
mov ecx,[outBlob.dataSize]
mov esi,[outBlob.pData]
push esi ecx
cinvoke printf,<10,' BLOB data size...: %d byte',\
10,' BLOB data offset.: 0x%08x',10,\
10,' DPAPI BLOB dump...............',10,10,0>,ecx,esi
pop ecx esi
call PrintHexDump
;//************************************************************
;//-- Проецируем структуру DPAPI_DATABLOB на полученные данные,
;//-- и выводим их на консоль.
cinvoke printf,<10,10,' Decode BLOB dump..............',0>
mov esi,[outBlob.pData]
mov eax,[esi + DPAPI_DATABLOB.dwVersion]
cinvoke printf,<10,10,' DPAPI version...: %d',0>,eax
mov esi,[outBlob.pData]
push esi esi
add esi,DPAPI_DATABLOB.ProvGuid
invoke StringFromGUID2,esi,buff,dwRet
cinvoke printf,<10,' CryptProv Guid..: %ls',0>,buff
pop esi
add esi,DPAPI_DATABLOB.MasterKeyGuid
invoke StringFromGUID2,esi,buff,dwRet
cinvoke printf,<10,' MasterKey Guid..: %ls',0>,buff
pop esi
mov eax,[esi + DPAPI_DATABLOB.dwFlags]
cinvoke printf,<10,' dwProtect Flag..: 0x%08x',10,0>,eax
mov esi,[outBlob.pData]
mov eax,[esi + DPAPI_DATABLOB.dwDataDescLen]
push esi eax
cinvoke printf,<10,' dwDataDesc len..: %d',\
10,' DataDesc......: ',0>,eax
pop ecx esi
add esi,DPAPI_DATABLOB.szDataDesc
push esi ecx
call PrintHexString
pop ecx esi
add esi,ecx ;//<--- вычисляем динамически адрес сл.поля
lodsd
push esi
mov ebx,eax
mov esi,CryptTable
mov ecx,4
call GetAlgorithm
cinvoke printf,<10,' algCrypt........: 0x%08x = %s',0>,ebx,edx
pop esi
add esi,4
lodsd
push eax esi eax esi
cinvoke printf,<10,' dwSalt len......: %d',\
10,' Salt..........: ',0>,eax
pop esi ecx
or ecx,ecx
je @f ;//<---- пропустить, если поле пустое
call PrintHexString
@@: pop esi eax
add esi,eax
lodsd
push eax esi eax esi
cinvoke printf,<10,' dwHmacKey len...: %d',\
10,' HmacKey.......: ',0>,eax
pop esi ecx
or ecx,ecx
je @f
call PrintHexString
@@: pop esi eax
add esi,eax
lodsd
push esi
mov ebx,eax
mov esi,HashTable
mov ecx,13
call GetAlgorithm
cinvoke printf,<10,' algHash.........: 0x%08x = %s',0>,ebx,edx
pop esi
add esi,4
lodsd
push eax esi eax esi
cinvoke printf,<10,\
10,' dwHmac2Key len..: %d',\
10,' Hmac2Key......: ',0>,eax
pop esi ecx
or ecx,ecx
je @f
call PrintHexString
@@: pop esi eax
add esi,eax
lodsd
push eax esi eax esi
cinvoke printf,<10,\
10,' dwCryptData len.: %d',\
10,' CryptData.....: ',0>,eax
pop esi ecx
or ecx,ecx
je @f
call PrintHexString
@@: pop esi eax
add esi,eax
lodsd
push eax esi
cinvoke printf,<10,\
10,' dwSign len......: %d',\
10,' Signature.....: ',0>,eax
pop esi ecx
or ecx,ecx
je @exit
call PrintHexString
@exit: cinvoke _getch ;//<---- GAME OVER!!! ------------//
cinvoke exit,0
;//------------
;//----- Процедуры вывода дампа на консоль --------------
;//----- на входе: ESI = указатель на данные, ECX = длина
proc PrintHexString
mov ebp,32 ;// кол-во байт в строке
@@: or ebp,ebp
jnz @miss0
push ecx
cinvoke printf,<10,20 dup(' '),0>
pop ecx
mov ebp,32
@miss0: xor eax,eax
lodsb
push ecx esi
cinvoke printf,<'%02x',0>,eax ;//<--- без разделителя байт
pop esi ecx
dec ebp
loop @b
ret
endp
proc PrintHexDump
mov ebp,16
@@: or ebp,ebp
jnz @miss1
push ecx
cinvoke printf,<10,0>
pop ecx
mov ebp,16
@miss1: xor eax,eax
lodsb
push ecx esi ebp
cinvoke printf,<' %02x',0>,eax ;//<--- с разделителем байт
pop ebp esi ecx
dec ebp
loop @b
ret
endp
;//----- Процедура поиска алгоритмов --------------
;//----- на входе: EBX = код алгоритма, ESI = указатель на таблицу, ECX = длина таблицы
;//----- на выходе: EDX = указатель на строку с именем (см.инклуд DPAPI.INC)
proc GetAlgorithm
mov edx,unk
@findAlgo:
lodsd
cmp ebx,eax
jne @f
mov edx,[esi]
jmp @foundAlgo
@@: add esi,4
loop @findAlgo
@foundAlgo: ret
endp
;//------------
section '.idata' import data readable
library kernel32,'kernel32.dll',msvcrt,'msvcrt.dll',\
crypt32,'crypt32.dll',ole32,'ole32.dll'
import ole32,StringFromGUID2,'StringFromGUID2'
include 'api\kernel32.inc'
include 'api\crypt32.inc'
include 'api\msvcrt.inc'
Посмотрим, что мы тут имеем..
Значит обычную строку пароля из 14-ти символов, DPAPI зашифровал в BLOB размером аж 230-байт. В качестве криптографического провайдера задействован поставщик с GUID'ом
{df9d8cd0-1501-11d1-8c7a-00c04fc297eb}
– это системный провайдер Win по-умолчанию, реализованный в библиотеке Psbase.dll. Если этот провайдер нас чем-то не устраивает, другого можно выбрать в ветке реестра HKLM\Software\Microsoft\Cryptography\Protect\Providers
. Далее следует идентификатор мастер-ключа, сброшенный флаг в предпоследнем аргументе функции CryptProtectData(), и дефолтное значение Unicode-нуль второго аргумента "pszDataDesc".Члены структуры algCrypt и algHash прямым текстом сообщают нам алгоритмы шифрования и хеширования данных, а так-же значение рандомной соли (не путать с энтропией, которая уязвима и не хранится в данной структуре), кода-аутентификации МАС, и сигнатуры (электронная подпись).
Практика показала, что изменение длины шифруемых входных данных, никак не влияет на размеры полей Salt\HMAC\Sign, кроме непосредственно самих данных в зелёном блоке "CryptData". При вводе до 15-символов включительно, размер данных остаётся 16-байт, а если ввести 16-символов, то размер зашифрованных данных сразу-же увеличивается в два раза = 32-байта. Связано это с блочным шифрованием данных AES-256 в режиме CBC (Cipher-Block-Chaining), который оперирует информационной матрицей 4х4 байта.
Важным моментом во-всей этой кухне является то, что ознакомившись с структурой DPAPI_DATABLOB мы получили штамм, по которому можем находить в виртуальной памяти ОЗУ компьютера не только свои, но и чужие объекты DPAPI. В качестве такой сигнатуры поиска, могут послужить два первых поля данного объекта BLOB, где указывается версия(1) структуры, и Guid виндозного крипто-провайдера. За некоторым исключением, во-всех объектах BLOB и операционных системах, эти первые 20-байт будут одинаковы.
Таким образом, для декрипта зашифрованных данных DPAPI нам остаётся найти лишь хеш от юзерского пасса в памяти Lsass.exe, и мастер-ключ системы. Проблему предвещает пароль, добыть который можно только сохранив память процесса Lsass.exe, после чего искать его в дампе отталкиваясь, например, от аргументов функций шифрования. Но для этого во-первых нужны права админа (иначе Lsass не сдампить), а во-вторых – необходимо быть осведомлённым о самом алгоритме шифрования. Не сказать, что задача не решаема в принципе, однако займёт уйму времени. Так-что оставим это дело для потомков, а сами попробуем обзавестись мастер-ключом.
4. Алгоритм поиска Master-Key
Для начала определимся, что такое мастер-ключ и зачем он нужен.
Это основной ключ интерфейса DPAPI, при помощи которого он шифрует данные алгоритмом AES-256/СВС. Изначально, в открытом виде ключ имеет длину 64-байта, но после его хеширования алгоритмом SHA-512 размер увеличивается до 176-байт. В создании мастер-ключа принимает участие предварительный ключ под названием "PreKey", который создаётся из пароля юзера на вход в систему (из защищённой памяти Lsass.exe), и идентификатора SID этого юзера. Далее генерируется 64-байтный рандом (основной материал для мастер-ключа), к нему добавляется соль размером 16-байт, и вся эта братия отправляется в "миксер" AES-256.
На выходе из блока шифрования AES получаем структуру "MasterKey_BLOB", которой система присваивает уникальный Guid. По-умолчанию, процедура генерации мастер-ключей в системе повторяется каждые 90-дней, чтобы периодически обновлять их. При этом отработанные/старые ключи не удаляются, а отправляются в бэкап. Резерв нужен для тех случаев, когда мы зашифруем данные, например, за день до обновления системой старого ключа, и попытаемся расшифровать их после. В этом случае, DPAPI возьмёт из базы текущий ключ и если попытка декрипта им не увенчается успехом, то начнёт перебирать более старые ключи, пока не найдёт валидный. Схема генерации мастер-ключа представлена ниже:
В системе имеются два типа "мастер-ключей" – один привязан к учётной записи пользователя, а другой к машине в целом (хранится в System32\Microsoft\Protect). Когда мы вызываем функцию CryptProtectData() с флагом LOCAL_MACHINE, механизм DPAPI шифрует данные ключом компьютера и любой пользователь данной машины сможет расшифровать их. Если-же этот флаг сброшен, то применяется мастер-ключ текущего пользователя, и юзер с другой учётной записью не сможет уже осуществить обратный декрипт, поскольку его SID (и если есть пароль) окажется уткой.
Мастер-ключи юзера хранятся в папке его профиля:
C:\Users\%USER%\AppData\Roaming\Microsoft\Protect\%SID%\
, где SID – это директория, имя которой совпадает с описателем безопасности SID пользователя. В доках эту папку называют ещё "MasterKey-Storage-Folder" и в ней складируются буквально все, когда-либо сгенерированные системой ключи. Имя активного на данный момент ключа и срок его действия хранится в файле Preferred, который лежит рядом с ключами – первые 16-байт в нём Guid ключа, а оставшиеся 8-байт – дата в формате FILETIME. Файлы ключей именуются без расширения согласно присвоенным им Guid, и на моей машине выглядят так:Всё те-же ребята из Passcape отреверсили структуру мастер-ключей, присвоив ей имя MASTERKEY_BLOB. В этих 468-байтах достаточно информации, чтобы получить всю подноготную мастер-ключа, включая значение соли, идентификаторы алгоритмов, раундов хеширования и прочее. Кстати о раундах.. Термин применяется к функции PBKDF2 (Password-Based Key Derivation Function), криптографическому стандарту формирования ключей на основе пароля. Фактически, это цикл хэширования ключей алгоритмом SHA-512, где раунды определяют кол-во итераций (повторений). К примеру на моей Win-7 таких раундов 5400, что в миллионы раз увеличивает время перебора ключей брутфорсом.
Что касается самой 468-байтной структуры MASTERKEY_BLOB, то она состоит из 5-ти частей. Сначала идёт 128-байтный заголовок Header, после которого следуют описатели 4-х ключей – это непосредственно паспорт ключа MasterKey, далее BackupKey, CredHist и DomainKey. Нам интересен только первый мастер-ключ, поэтому структуру оформляем так:
C-подобный:
struct MASTERKEY_HEADER
dwVersion dd 0 ;// версия файла с ключом
dwReserved1 dq 0 ;//
szGuid db 72 dup(0) ;// Юникоде-строка с GUID'ом ключа
dwUnused dq 0 ;//
dwPolicy dd 0 ;// поле с флагами
dwUserKeySize dd 0 ;// размер мастер-ключа юзера
dwLocalEncKeySize dd 0 ;// размер резервного ключа Backup
dwLocalKeySize dd 0 ;// размер локального ключа, или поля CREDHIST
dwDomainKeySize dd 0 ;// резервная копия ключа домена
dwReserved2 db 16 dup(0) ;//
;//-- MasterKey info
Version dd 0 ;// версия слота
pSalt db 16 dup(0) ;// значение соли, добавленной к ключу
dwRounds dd 0 ;// счётчик итераций в алго генерации ключа PBKDF2
HmacAlgId dd 0 ;// алго проверки целостности данных
CryptAlgId dd 0 ;// алго шифрования ключа
pKey db ? ;// зашифрованный мастер-ключ!
ends
В демонстрационном примере ниже попытаемся найти свой мастер-ключ на диске, и вывести из него всю информацию. Основную проблему здесь представляет только динамический поиск директории "MasterKey-Storage-Folder", поскольку SID пользователя заранее нам не известен. Для этого воспользуемся парой функций FindFirstFile() + FindNextFile(), которые перечисляют файлы и папки в указанном дире. Они возвращают информацию о найденном файле в сл.структуру:
C-подобный:
struct WIN32_FIND_DATA
dwFileAttributes dd 0 ;// атрибуты файла/папки
ftCreationTime FILETIME ;// время создания
ftLastAccessTime FILETIME ;// время последнего доступа
ftLastWriteTime FILETIME ;// время последнего изменения
nFileSizeHigh dd 0 ;// размер файла
nFileSizeLow dd 0 ;// ^^^
dwReserved dq 0 ;//
cFileName rb 256 ;// Win-имя файла/папки <----- наш клиент!
cAlternateFileName rb 14 ;// DOS-имя в формате 8.3
dwFileType dd 0 ;// 0
dwCreatorType dd 0 ;// 0
wFinderFlags dw 0 ;// 0
ends
Таким образом, в поле "cFileName" мы получим имя папки. Поскольку SID пользователя всегда начинается с символов
S-1-5-21-xxxxx
, то первые четыре символа "S-1-" можно использовать в качестве маски для поиска. Дружелюбную дату и время из 8-байтной структуры FILETIME можно получить функцией FileTimeToSystemTime(), а папку юзера Roaming – функцией SHGetFolderPathA() с флагом CSIDL_APPDATA. Вот пример такого алго:
C-подобный:
format pe console
entry start
;//------------
section '.inc' data readable
include 'win32ax.inc'
include 'equates\dpapi.inc'
;//------------
.data
userPath db 512 dup(0)
keyPath db '\Microsoft\Protect\*.*',0
preferred db '\preferred',0
fd WIN32_FIND_DATA
sysTime SYSTEMTIME
hndl dd 0
dwRet dd 0
fName db 64 dup(0)
buff db 0
;//------------
.code
start: invoke SetConsoleTitle,<'*** DPAPI MasterKey info ***',0>
;//-- Формируем путь до файла "Master-Key"
invoke SHGetFolderPathA,0,CSIDL_APPDATA,0,0,userPath
invoke lstrcat,userPath,keyPath
;//-- Вычисляем имя папки SID, с ключами пользователя
invoke FindFirstFile,userPath,fd
mov [hndl],eax
@find0: cmp dword[fd.cFileName],'S-1-'
je @found0
invoke FindNextFile,[hndl],fd
or eax,eax
jnz @find0
cinvoke printf,<10,' ERROR! KeyDir not found.',0>
jmp @exit
;//-- Каталог нашли – покажем SID юзера (имя папки)
@found0: invoke FindClose,[hndl]
cinvoke printf,<10,' User SID......: %s',0>,fd.cFileName
;//-- Добавляем к пути имя папки, чтобы открыть её
mov edi,userPath
xor ecx,ecx
dec ecx
mov al,'*'
repne scasb
dec edi
mov byte[edi],0
invoke lstrcat,userPath,fd.cFileName
invoke lstrcat,userPath,preferred
;//-- Прочитать из папки SID файл "Preferred"
invoke _lopen,userPath,0
or eax,eax ;// ошибка?
jns @next
cinvoke printf,<10,' ERROR! Preferred file not found.',0>
jmp @exit
@next: push eax
invoke _lread,eax,buff,24 ;// читаем файл в буфер
pop eax
invoke _lclose,eax
;//-- Покажем время действия мастер-ключа, из последних 8-байт файла "Preferred"
mov eax,buff
add eax,24-8
invoke FileTimeToSystemTime,eax,sysTime
movzx ecx,word[sysTime.wYear]
movzx ebx,word[sysTime.wMonth]
movzx eax,word[sysTime.wDay]
movzx edx,word[sysTime.wHour]
movzx esi,word[sysTime.wMinute]
cinvoke printf,<10,' Key valid time: %02d.%02d.%d %02d:%02d',0>,\
eax,ebx,ecx,edx,esi
;//-- Вытащим из файла "Preferred" GUID активного ключа,
;//-- и переведём его из Unicode в ANSI
invoke StringFromGUID2,buff,fName,dwRet
mov esi,fName
mov edi,esi
mov byte[esi],'\'
@@: lodsw
cmp al,'}'
je @f
stosb
jmp @b
@@: mov byte[edi],0
;//-- Наконец добавим к пути Guid в виде имени файла-ключа
invoke lstrlen,userPath
mov esi,userPath
add esi,eax
sub esi,10
mov byte[esi],0
invoke lstrcat,userPath,fName
;//-- Читаем файл мастер-ключа в свой буфер
invoke _lopen,userPath,0
or eax,eax ;// ошибка?
jns @open
cinvoke printf,<10,' ERROR! MasterKey file not openned.',0>
jmp @exit
@open: push eax
invoke _lread,eax,buff,468
pop eax
invoke _lclose,eax
;//*******************************************************
;//-- Проецируем структуру MASTERKEY_HEADER на данные файла-ключа,
;//-- и выводим всю инфу на консоль.
cinvoke printf,<10,10,' MasterKey HEADER ****',0>
mov esi,buff
push esi
mov eax,[esi + MASTERKEY_HEADER.dwVersion]
cinvoke printf,<10,' dwVersion........: 0x%08x = %d',0>,eax,eax
pop esi
add esi,MASTERKEY_HEADER.szGuid
cinvoke printf,<10,' szMasterKeyGuid..: {%ls}',0>,esi
mov esi,buff
mov eax,[esi + MASTERKEY_HEADER.dwPolicy]
mov ebx,[esi + MASTERKEY_HEADER.dwUserKeySize]
mov ecx,[esi + MASTERKEY_HEADER.dwLocalEncKeySize]
mov edx,[esi + MASTERKEY_HEADER.dwLocalKeySize]
mov edi,[esi + MASTERKEY_HEADER.dwDomainKeySize]
cinvoke printf,<10,' dwFlags..........: 0x%08x = %d',\
10,' dwMasterKey len..: 0x%08x = %d',\
10,' dwBackupKey len..: 0x%08x = %d',\
10,' dwCredHist len..: 0x%08x = %d',\
10,' dwDomainKey len..: 0x%08x = %d',0>,\
eax,eax,ebx,ebx,ecx,ecx,edx,edx,edi,edi
cinvoke printf,<10,10,' MasterKey INFO ******',0>
mov esi,buff
push esi
mov eax,[esi + MASTERKEY_HEADER.Version]
cinvoke printf,<10,' dwVersion........: 0x%08x = %d',\
10,' Salt (16-bytes)..: ',0>,eax,eax
pop esi
add esi,MASTERKEY_HEADER.pSalt
mov ecx,16
call PrintHexString
mov esi,buff
mov eax,[esi + MASTERKEY_HEADER.dwRounds]
cinvoke printf,<10,' PBKDF2 rounds....: 0x%08x = %d',0>,eax,eax
mov esi,buff
mov ebx,[esi + MASTERKEY_HEADER.HMACAlgId]
mov ecx,13
mov esi,HashTable
call GetAlgorithm
cinvoke printf,<10,' Hash algorithm..: 0x%08x = %s',0>,ebx,edx
mov esi,buff
mov ebx,[esi + MASTERKEY_HEADER.CryptAlgId]
mov ecx,4
mov esi,CryptTable
call GetAlgorithm
cinvoke printf,<10,' Crypt algorithm..: 0x%08x = %s',\
10,10,' Master-Key hash value............. ',10,10,0>,,ebx,edx
mov esi,buff
mov ecx,[esi + MASTERKEY_HEADER.dwUserKeySize]
add esi,MASTERKEY_HEADER.pKey
call PrintHexString
@exit: cinvoke _getch
cinvoke exit,0
;//------------
;//----- Процедура вывода дампа на консоль --------------
;//----- на входе: ESI = указатель на данные, ECX = длина
proc PrintHexString
mov ebp,16
@@: or ebp,ebp
jnz @miss
push ecx
cinvoke printf,<10,0>
pop ecx
mov ebp,16
@miss: xor eax,eax
lodsb
push ecx esi ebp
cinvoke printf,<' %02x',0>,eax
pop ebp esi ecx
dec ebp
loop @b
ret
endp
;//----- Процедура поиска алгоритмов --------------
;//----- на входе: EBX = код алгоритма, ESI = указатель на таблицу, ECX = длина таблицы
;//----- на выходе: EDX = указатель на строку с именем (см.инклуд DPAPI.INC)
proc GetAlgorithm
mov edx,unk
@findAlgo:
lodsd
cmp ebx,eax
jne @f
mov edx,[esi]
jmp @foundAlgo
@@: add esi,4
loop @findAlgo
@foundAlgo: ret
endp
;//------------
section '.idata' import data readable
library kernel32,'kernel32.dll',shell32,'shell32.dll',\
ole32,'ole32.dll',msvcrt,'msvcrt.dll'
import ole32,StringFromGUID2,'StringFromGUID2'
include 'api\kernel32.inc'
include 'api\shell32.inc'
include 'api\msvcrt.inc'
Обратите внимание на Guid мастер-ключа. В предыдущей программе мы шифровали данные при помощи CryptProtectData(), с последующим выводом результирующего BLOB'а на консоль. Если всё было сделано правильно, то Guid из блоба-данных должен совпадать с Guid'ом из блоба мастер-ключа, т.к. именно им и происходит шифрование. Как видим ошибок нет и ключ мы получили валидный:
5. Постскриптум
Система использует интерфейс DPAPI при защите большого зоопарка персональных данных. Это пароли и данные автозаполнения форм в браузерах IE, кукисов и паролей Chrome, учётных записей в Outlook, Win-Mail, FTP, для доступа к расшаренным папкам и ключам учёток Wi-Fi, для удаленного доступа к рабочему столу и многое другое. DPAPI активно юзают и сторонние разработчики типа Skype, GoogleTalk и др. К сожалению ограниченные объёмы статьи не позволяют охватить всего материала на эту тему (хотя и так получилось слишком много букаф), а потому за бортом осталось много интересного. В скрепку кладу два представленных здесь исполняемых файла, а так-же инклуд с описанием основных структур DPAPI. Всем удачи, пока!
Вложения
Последнее редактирование: