
Python DPAPI NG Decryptor para plataformas não Windows
Biblioteca para criptografia e descriptografia de DPAPI NG, também conhecido como CNG DPAPI, em Python. Ela é projetada para replicar o comportamento de NCryptUnprotectSecret e NCryptProtectSecret. Isso pode ser usado em hosts não Windows para criptografar/descriptografar segredos protegidos por DPAPI NG, como a senha de usuário protegida do PFX ou a senha criptografada do LAPS. Ele pode descriptografar qualquer blob DPAPI NG usando uma cópia offline da chave raiz do domínio ou criptografar/descriptografar usando as credenciais do usuário fornecido para recuperar as informações necessárias via RPC.
Atualmente, apenas estes descritores de proteção são suportados:
| Tipo | Finalidade |
|---|---|
| SID | Somente o usuário SID ou membros do grupo SID podem descriptografar o segredo |
Isto implementa o MS-GKDI Group Key Distribution Protocol.
Para instalar o dpapi-ng com todos os recursos básicos, execute
python -m pip install dpapi-ng
O suporte à autenticação Kerberos não será instalado por padrão, pois depende de bibliotecas do sistema e de um compilador válido. A biblioteca krb5 e o compilador podem ser instalados através dos seguintes pacotes:
# 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
Depois de instalados, os extras Python do Kerberos podem ser instalados com
python -m pip install dpapi-ng[kerberos]
O Kerberos também precisa ser configurado para se comunicar com o domínio, mas isso está fora do escopo desta página.
git clone https://github.com/jborean93/dpapi-ng.git
cd dpapi-ng
pip install -e .
Há tanto uma API síncrona quanto uma assíncrona (asyncio) disponíveis para criptografar/descriptografar um blob.
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)
Para descriptografar o blob, a chave especificada no blob precisa ser recuperada do controlador de domínio que gerou o blob.
Para criptografar um blob, a chave de grupo do SID alvo especificado precisa ser recuperada.
Para descriptografia, o nome do host do controlador de domínio é recuperado automaticamente por meio de uma consulta DNS SRV de _ldap._tcp.dc._msdcs.{domain_name} usando o nome de domínio encontrado no blob DPAPI-NG ou com o valor especificado no kwarg server.
Para criptografia, o nome do host do controlador de domínio é determinado usando o kwarg domain_name para uma consulta DNS SRV, o kwarg server diretamente ou usando o domínio de pesquisa do sistema (se disponível).
Ele tentará autenticar com o identificador de usuário atual, que no Linux só existirá se kinit já tiver sido chamado para recuperar o tíquete de um usuário.
Caso contrário, se nenhuma identidade estiver disponível, os kwargs username e password podem ser usados para especificar um usuário personalizado.
Os seguintes kwargs podem ser usados para ncrypt_unprotect_secret, async_ncrypt_unprotect_secret, ncrypt_protect_secret e async_ncrypt_protect_secret.
server: Use este servidor como alvo RPC se uma chave precisar ser recuperadausername: O nome de usuário para autenticar na conexão RPCpassword: A senha para autenticar na conexão RPCauth_protocol: O protocolo de autenticação (negotiate, kerberos, ntlm) a ser usado para a conexão RPCcache: Um cache para armazenar chaves recuperadas para operações futurasAlém disso, ncrypt_protect_secret e async_ncrypt_protect_secret aceitam o seguinte kwarg adicional.
domain_name: O nome do domínio/floresta a ser usado ao procurar o alvo RPC para recuperar as informações da chaveroot_key_identifier: O UUID do identificador da chave raiz, necessário para fazer o cache funcionar para essas funçõesTambém é possível criptografar e descriptografar o blob DPAPI-NG fornecendo a chave raiz armazenada no domínio. Essa chave pode ser recuperada usando um ataque offline ou por meio de uma consulta LDAP se estiver sendo executado como um usuário Administrador de Domínio. Para recuperar as chaves raiz do domínio usando PowerShell, o seguinte pode ser executado:
$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')
}
}
O seguinte comando ldapsearch pode ser usado fora do 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
Nota: o ldapsearch provavelmente precisará das opções -H e das informações de bind do usuário para ser bem-sucedido.
As informações recuperadas podem ser armazenadas em um cache e usadas para chamadas subsequentes de ncrypt_protect_secret e 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)
Atualmente, o algoritmo KDF SP800_108_CTR_HMAC e os algoritmos de acordo de segredo DH, ECDH_P256 e ECDH_P384 foram testados e funcionam.
O algoritmo de acordo de segredo ECDH_P521 também deve funcionar, mas não foi testado, pois um ambiente de teste não pode ser criado com ele no momento.
Gostaria de agradecer às seguintes pessoas (handles do GitHub ou Twitter entre parênteses) por sua ajuda neste projeto:
Sem a paciência e o conhecimento deles, isso provavelmente não teria sido possível.