
Python DPAPI NG Decryptor for non-Windows Platforms
Библиотека для де-/зашифрования DPAPI NG, также известного как CNG DPAPI, в Python. Она предназначена для воспроизведения поведения функций NCryptUnprotectSecret и NCryptProtectSecret. Её можно использовать на хостах, отличных от Windows, для де-/зашифрования защищённых секретов DPAPI NG, таких как защищённый пароль PFX-пользователя или зашифрованный пароль LAPS. Она может либо расшифровать любые BLOB-объекты DPAPI NG с помощью автономной копии корневого ключа домена, либо де-/зашифровать их, используя учётные данные указанного пользователя для получения необходимой информации по RPC.
В настоящее время поддерживаются только следующие дескрипторы защиты:
| Тип | Назначение |
|---|---|
| SID | Расшифровать секрет может только пользователь SID или члены группы SID |
Данная библиотека реализует протокол распространения групповых ключей MS-GKDI.
Чтобы установить dpapi-ng со всеми базовыми возможностями, выполните
python -m pip install dpapi-ng
Поддержка аутентификации Kerberos не устанавливается по умолчанию, поскольку она зависит от наличия системных библиотек и рабочего компилятора. Библиотеку krb5 и компилятор можно установить, установив следующие пакеты:
# Debian/Ubuntu
apt-get install gcc python3-dev libkrb5-dev
# Centos/RHEL
yum install gcc python-devel krb5-devel
# Fedora
dnf install gcc python-devel krb5-devel
# Arch Linux
pacman -S gcc krb5
После установки дополнительные зависимости Kerberos для Python можно установить с помощью
python -m pip install dpapi-ng[kerberos]
Kerberos также необходимо настроить для взаимодействия с доменом, но это выходит за рамки данной страницы.
git clone https://github.com/jborean93/dpapi-ng.git
cd dpapi-ng
pip install -e .
Для де-/зашифрования BLOB-объекта доступен как синхронный, так и асинхронный API (asyncio).
import dpapi_ng
### DECRYPTION ###
dpapi_ng_blob = b"..."
decrypted_blob = dpapi_ng.ncrypt_unprotect_secret(dpapi_ng_blob)
# async equivalent to the above
decrypted_blob = await dpapi_ng.async_ncrypt_unprotect_secret(dpapi_ng_blob)
### ENCRYPTION ###
data = b"..."
target_sid = "S-1-5-21-XXXXXXXXXX-XXXXXXXXXX-XXXXXXXXXX-XXXX"
dpapi_ng_blob = dpapi_ng.ncrypt_protect_secret(data, target_sid)
# async equivalent to the above
dpapi_ng_blob = await dpapi_ng.async_ncrypt_protect_secret(data, target_sid)
Чтобы расшифровать BLOB-объект, необходимо получить ключ, указанный в BLOB-объекте, от контроллера домена, который создал этот BLOB-объект.
Чтобы зашифровать BLOB-объект, необходимо получить групповой ключ указанного целевого SID.
Для расшифрования имя хоста контроллера домена автоматически определяется с помощью DNS-запроса SRV для _ldap._tcp.dc._msdcs.{domain_name}, где имя домена берётся из BLOB-объекта DPAPI-NG или из значения, указанного в аргументе server.
Для зашифрования имя хоста контроллера домена определяется либо с помощью аргумента domain_name для DNS-запроса SRV, либо напрямую через аргумент server, либо с использованием поискового домена системы (если он доступен).
Будет предпринята попытка аутентификации с использованием идентификатора текущего пользователя, который в Linux существует только в том случае, если для получения билета пользователя уже был вызван kinit.
Если же идентификатор недоступен, для указания другого пользователя можно использовать аргументы username и password.
Следующие аргументы можно использовать для ncrypt_unprotect_secret, async_ncrypt_unprotect_secret, ncrypt_protect_secret и async_ncrypt_protect_secret.
server: используйте этот сервер в качестве цели RPC, если необходимо получить ключusername: имя пользователя для аутентификации при RPC-подключенииpassword: пароль для аутентификации при RPC-подключенииauth_protocol: протокол аутентификации (negotiate, kerberos, ntlm), используемый для RPC-подключенияcache: кэш для хранения полученных ключей для последующих операцийКроме того, ncrypt_protect_secret и async_ncrypt_protect_secret принимают следующий дополнительный аргумент.
domain_name: имя домена/леса, используемое при поиске цели RPC для получения информации о ключеroot_key_identifier: UUID идентификатора корневого ключа, необходимый для работы кэша с этими функциямиТакже можно зашифровать и расшифровать BLOB-объект DPAPI-NG, указав корневой ключ, хранящийся в домене. Его можно получить либо с помощью автономной атаки, либо через LDAP-запрос, если вы работаете от имени администратора домена. Чтобы получить корневые ключи домена с помощью PowerShell, можно выполнить следующее:
$configurationContext = (Get-ADRootDSE).configurationNamingContext
$getParams = @{
LDAPFilter = '(objectClass=msKds-ProvRootKey)'
SearchBase = "CN=Master Root Keys,CN=Group Key Distribution Service,CN=Services,$configurationContext"
SearchScope = 'OneLevel'
Properties = @(
'cn'
'msKds-KDFAlgorithmID'
'msKds-KDFParam'
'msKds-SecretAgreementAlgorithmID'
'msKds-SecretAgreementParam'
'msKds-PrivateKeyLength'
'msKds-PublicKeyLength'
'msKds-RootKeyData'
)
}
Get-ADObject @getParams | ForEach-Object {
[PSCustomObject]@{
Version = 1
RootKeyId = [Guid]::new($_.cn)
KdfAlgorithm = $_.'msKds-KDFAlgorithmID'
KdfParameters = [System.Convert]::ToBase64String($_.'msKds-KDFParam')
SecretAgreementAlgorithm = $_.'msKds-SecretAgreementAlgorithmID'
SecretAgreementParameters = [System.Convert]::ToBase64String($_.'msKds-SecretAgreementParam')
PrivateKeyLength = $_.'msKds-PrivateKeyLength'
PublicKeyLength = $_.'msKds-PublicKeyLength'
RootKeyData = [System.Convert]::ToBase64String($_.'msKds-RootKeyData')
}
}
Следующую команду ldapsearch можно использовать за пределами Windows:
ldapsearch \
-b 'CN=Master Root Keys,CN=Group Key Distribution Service,CN=Services,CN=Configuration,DC=domain,DC=test' \
-s one \
'(objectClass=msKds-ProvRootKey)' \
cn \
msKds-KDFAlgorithmID \
msKds-KDFParam \
msKds-SecretAgreementAlgorithmID \
msKds-SecretAgreementParam \
msKds-PrivateKeyLength \
msKds-PublicKeyLength \
msKds-RootKeyData
Примечание: для успешного выполнения ldapsearch, скорее всего, потребуются параметр -H и информация о привязке пользователя.
Полученную информацию можно сохранить в кэше и использовать для последующих вызовов ncrypt_protect_secret и ncrypt_unprotect_secret:
import uuid
import dpapi_ng
cache = dpapi_ng.KeyCache()
root_key_id = uuid.UUID("76ec8b2d-d444-4f67-9db7-2f62b4358b35")
cache.load_key(
b"...", # msKds-RootKeydata
root_key_id, # cn
version=1,
kdf_algorithm="SP800_108_CTR_HMAC", # msKds-KDFAlgorithmID
kdf_parameters=b"...", # msKds-KDFParam
secret_algorithm="DH", # mskds-SecretAgreementAlgorithmID
secret_parameters=b"...", # msKds-SecretAgreementParam
private_key_length=512, # msKds-PrivateKeyLength
public_key_length=2048, # msKds-PublicKeyLength
)
dpapi_ng.ncrypt_unprotect_secret(b"...", cache=cache)
В настоящее время проверены на работоспособность алгоритм KDF SP800_108_CTR_HMAC и алгоритмы согласования ключей DH, ECDH_P256 и ECDH_P384.
Алгоритм согласования ключей ECDH_P521 также должен работать, но он не тестировался, поскольку сейчас для него невозможно создать тестовую среду.
Я хотел бы поблагодарить следующих людей (указаны ники GitHub или Twitter в скобках) за их помощь в этом проекте:
Без их терпения и знаний это, вероятно, было бы невозможно.