
13.06.2021, 17:12
|
|
Постоянный
Регистрация: 01.09.2019
Сообщений: 378
С нами:
3526561
Репутация:
0
|
|
Все, кто хоть немного дорожит своей информацией, направляют фокус на её защиту. Учитывая эти обстоятельства, начиная ещё с Win-2k инженеры Microsoft ввели в состав своей ОС специальный интерфейс и назвали его "Data-Protection Programming Interface", или коротко DPAPI. В результате, не прибегая к услугам внешних библиотек, у нас появилась возможность защищать свои данные вполне крипткостойким механизмом, причём эти данные могут находиться как на жёстком диске, так и непосредственно в памяти. В состав этого интерфейса входят всего 4 функции, однако практическая его реализация довольно сложна и корнями уходит в нёдра операционной системы, иначе DPAPI не получил-бы столь высокий кредит доверия в высшем обществе. В данной статье рассматриваются технические детали этого интерфейса, и некоторые советы к области его применения.
Содержание:
1. Data Protect – основная идея;
2. Функции DPAPI из библиотеки crypt32.dll;
3. Практика – реверс интерфейса DPAPI;
4. Алгоритм поиска Master-Key;
5. Постскриптум.
-------------------------------------------------------
1. DataProtect– основная идея
В программист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" из комплекта "SysInternals" Марка Руссиновича:
2. Функции DPAPIиз библиотеки crypt32.dll
Выше упоминалось, что в интерфейс "Data-Protect-API" входят всего 4 функции – это CryptProtectData() для шифрования данных, и CryptUnprotectData() для их-же расшифровки. Есть ещё пара аналогичных функций, только для шифрования данных прямо в памяти CryptProtectMemory(), но они применяются довольно редко, поэтому останавливаться на них не будем. У всех этих функций прототипы одинаковы и выглядят так:
C-подобный:
Код:
BOOL
CryptProtectData
(
;
//
;
//-- Запрос на ввод пароля и узнаём его длину
cinvoke printf
,
cinvoke scanf
,
,
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
,
,
ecx
,
esi
pop ecx esi
call PrintHexDump
;
//************************************************************
;
//-- Проецируем структуру DPAPI_DATABLOB на полученные данные,
;
//-- и выводим их на консоль.
cinvoke printf
,
mov esi
,
[
outBlob
.
pData
]
mov eax
,
[
esi
+
DPAPI_DATABLOB
.
dwVersion
]
cinvoke printf
,
,
eax
mov esi
,
[
outBlob
.
pData
]
push esi esi
add esi
,
DPAPI_DATABLOB
.
ProvGuid
invoke StringFromGUID2
,
esi
,
buff
,
dwRet
cinvoke printf
,
,
buff
pop esi
add esi
,
DPAPI_DATABLOB
.
MasterKeyGuid
invoke StringFromGUID2
,
esi
,
buff
,
dwRet
cinvoke printf
,
,
buff
pop esi
mov eax
,
[
esi
+
DPAPI_DATABLOB
.
dwFlags
]
cinvoke printf
,
,
eax
mov esi
,
[
outBlob
.
pData
]
mov eax
,
[
esi
+
DPAPI_DATABLOB
.
dwDataDescLen
]
push esi eax
cinvoke printf
,
,
eax
pop ecx esi
add esi
,
DPAPI_DATABLOB
.
szDataDesc
push esi ecx
call PrintHexString
pop ecx esi
add esi
,
ecx
;
//
,
ebx
,
edx
pop esi
add esi
,
4
lodsd
push eax esi eax esi
cinvoke printf
,
,
eax
pop esi ecx
or ecx
,
ecx
je @f
;
//
,
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
,
,
ebx
,
edx
pop esi
add esi
,
4
lodsd
push eax esi eax esi
cinvoke printf
,
,
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
,
,
eax
pop esi ecx
or ecx
,
ecx
je @f
call PrintHexString
@@
:
pop esi eax
add esi
,
eax
lodsd
push eax esi
cinvoke printf
,
,
eax
pop esi ecx
or ecx
,
ecx
je @exit
call PrintHexString
@exit
:
cinvoke _getch
;
//
pop ecx
mov ebp
,
32
@miss0
:
xor eax
,
eax
lodsb
push ecx esi
cinvoke printf
,
,
eax
;
//
pop ecx
mov ebp
,
16
@miss1
:
xor eax
,
eax
lodsb
push ecx esi ebp
cinvoke printf
,
,
eax
;
//
;
//-- Формируем путь до файла "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
,
jmp @exit
;
//-- Каталог нашли – покажем SID юзера (имя папки)
@found0
:
invoke FindClose
,
[
hndl
]
cinvoke printf
,
,
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
,
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
,
,
\
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
,
jmp @exit
@open
:
push eax
invoke _lread
,
eax
,
buff
,
468
pop eax
invoke _lclose
,
eax
;
//*******************************************************
;
//-- Проецируем структуру MASTERKEY_HEADER на данные файла-ключа,
;
//-- и выводим всю инфу на консоль.
cinvoke printf
,
mov esi
,
buff
push esi
mov eax
,
[
esi
+
MASTERKEY_HEADER
.
dwVersion
]
cinvoke printf
,
,
eax
,
eax
pop esi
add esi
,
MASTERKEY_HEADER
.
szGuid
cinvoke printf
,
,
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
,
,
\
eax
,
eax
,
ebx
,
ebx
,
ecx
,
ecx
,
edx
,
edx
,
edi
,
edi
cinvoke printf
,
mov esi
,
buff
push esi
mov eax
,
[
esi
+
MASTERKEY_HEADER
.
Version
]
cinvoke printf
,
,
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
,
,
eax
,
eax
mov esi
,
buff
mov ebx
,
[
esi
+
MASTERKEY_HEADER
.
HMACAlgId
]
mov ecx
,
13
mov esi
,
HashTable
call GetAlgorithm
cinvoke printf
,
,
ebx
,
edx
mov esi
,
buff
mov ebx
,
[
esi
+
MASTERKEY_HEADER
.
CryptAlgId
]
mov ecx
,
4
mov esi
,
CryptTable
call GetAlgorithm
cinvoke printf
,
,
,
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
,
pop ecx
mov ebp
,
16
@miss
:
xor eax
,
eax
lodsb
push ecx esi ebp
cinvoke printf
,
,
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. Всем удачи, пока!
|
|
|
|
Предыдущая тема
Следующая тема
|
Здесь присутствуют: 1 (пользователей: 0 , гостей: 1)
|
|
|
|