• Курсы Академии Кодебай, стартующие в мае - июне, от команды The Codeby

    1. Цифровая криминалистика и реагирование на инциденты
    2. ОС Linux (DFIR) Старт: 16 мая
    3. Анализ фишинговых атак Старт: 16 мая Устройства для тестирования на проникновение Старт: 16 мая

    Скидки до 10%

    Полный список ближайших курсов ...

Статья ASM – интерфейс защиты данных DPAPI

Все, кто хоть немного дорожит своей информацией, направляют фокус на её защиту. Учитывая эти обстоятельства, начиная ещё с Win-2k инженеры Microsoft ввели в состав своей ОС специальный интерфейс и назвали его "Data-Protection Programming Interface", или коротко DPAPI. В результате, не прибегая к услугам внешних библиотек, у нас появилась возможность защищать свои данные вполне крипткостойким механизмом, причём эти данные могут находиться как на жёстком диске, так и непосредственно в памяти. В состав этого интерфейса входят всего 4 функции, однако практическая его реализация довольно сложна и корнями уходит в нёдра операционной системы, иначе 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 в свой бокс, но выташить потом из этого дампа пароль находясь в офлайне – задача не простая. В грубой форме это выглядит примерно так:


LSA_scheme.png


Ядерный монитор безопасности 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):

tasklist.png


Что касается баз LSA и SAM, то они не доступны в реестре смертным пользователям,
однако просмотреть их содержимое можно утилитой "PsExec" из комплекта Марка Руссиновича:

SAM_LSA.png



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" позволяет принудительно обновить системный мастер-ключ. В большинстве случаях флаг сбрасывают в нуль, чтобы функция взяла дефолтное его значение из реестра.


Flags.png


Обратная по назначению функция 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'

DPAPI_BLOB.png


Посмотрим, что мы тут имеем..
Значит обычную строку пароля из 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 возьмёт из базы текущий ключ и если попытка декрипта им не увенчается успехом, то начнёт перебирать более старые ключи, пока не найдёт валидный. Схема генерации мастер-ключа представлена ниже:


MasterKeyGen.png


В системе имеются два типа "мастер-ключей" – один привязан к учётной записи пользователя, а другой к машине в целом (хранится в 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, и на моей машине выглядят так:


UserKey.png


Всё те-же ребята из 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'

Master-Key.png


Обратите внимание на Guid мастер-ключа. В предыдущей программе мы шифровали данные при помощи CryptProtectData(), с последующим выводом результирующего BLOB'а на консоль. Если всё было сделано правильно, то Guid из блоба-данных должен совпадать с Guid'ом из блоба мастер-ключа, т.к. именно им и происходит шифрование. Как видим ошибок нет и ключ мы получили валидный:

Compare.png



5. Постскриптум

Система использует интерфейс DPAPI при защите большого зоопарка персональных данных. Это пароли и данные автозаполнения форм в браузерах IE, кукисов и паролей Chrome, учётных записей в Outlook, Win-Mail, FTP, для доступа к расшаренным папкам и ключам учёток Wi-Fi, для удаленного доступа к рабочему столу и многое другое. DPAPI активно юзают и сторонние разработчики типа Skype, GoogleTalk и др. К сожалению ограниченные объёмы статьи не позволяют охватить всего материала на эту тему (хотя и так получилось слишком много букаф), а потому за бортом осталось много интересного. В скрепку кладу два представленных здесь исполняемых файла, а так-же инклуд с описанием основных структур DPAPI. Всем удачи, пока!
 

Вложения

  • DPAPI.zip
    5,9 КБ · Просмотры: 298
Последнее редактирование:

Dervish

Green Team
11.10.2019
36
4
BIT
0
Статья хорошая но у меня почему то не получается в командной строке точно так же чтоб получилось как в самом начале . у меня Windows 7
 

Marylin

Mod.Assembler
Red Team
05.06.2019
305
1 378
BIT
318
там не нужны права админа, попробуйте ещё раз
 
  • Нравится
Реакции: Mikl___

Andrei_0x7eff0

Green Team
23.09.2023
18
1
BIT
104

DPAPI так описали статью как-будто очень не надежно сохраняют данные -эти из Майкрасофта, не внушает доверия их защита по охране личных данных​

 
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!