Closed #26: KDS Root Key extraction from ntds.dit

This commit is contained in:
MichaelGrafnetter 2016-10-02 16:20:21 +02:00
parent 1c8d6dbde8
commit 26f91ca5df
18 changed files with 569 additions and 8 deletions

View File

@ -1,9 +1,9 @@
Version 2.18
- [Module] Added the Get-ADDBKdsRootKey cmdlet.
- [Module] The Get-ADReplAccount cmdlet now correctly reports the access denied error.
- [Framework] Replication errors are now reported using more suitable exception types.
Version 2.17.1
- [Module] Fixed a bug in progress reporting of the Get-ADReplAccount cmdlet.
- [Framework] Added support for KdsRootKey retrieval.
- [Framework] Replication errors are now reported using more suitable exception types.
Version 2.17
- [Module] The Get-ADReplAccount -All command now reports replication progress.

View File

@ -60,6 +60,7 @@
<Compile Include="Cryptography\OrgIdHashTester.cs" />
<Compile Include="Cryptography\HashEqualityComparerTester.cs" />
<Compile Include="DistinguishedNameTester.cs" />
<Compile Include="KdsRootKeyTester.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Cryptography\SecureStringExtensionsTester.cs" />
<Compile Include="SecurityIdentifierExtensionsTester.cs" />

View File

@ -0,0 +1,56 @@
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using DSInternals.Common.Data;
using DSInternals.Common.Cryptography;
namespace DSInternals.Common.Test
{
[TestClass]
public class KdsRootKeyTester
{
[TestMethod]
public void ParseKdfParameters_Vector1()
{
byte[] blob = "00000000010000000e000000000000005300480041003500310032000000".HexToBinary();
var result = KdsRootKey.ParseKdfParameters(blob);
Assert.AreEqual(1, result.Count);
Assert.AreEqual("SHA512", result[0]);
}
[TestMethod]
public void ParseKdfParameters_NullInput()
{
var result = KdsRootKey.ParseKdfParameters(null);
Assert.IsNull(result);
}
[TestMethod]
public void ParseKdfParameters_EmptyInput()
{
var result = KdsRootKey.ParseKdfParameters(new byte[0]{});
Assert.IsNull(result);
}
[TestMethod]
public void ParseSecretAgreementParameters_Vector1()
{
byte[] blob = "0c0200004448504d0001000087a8e61db4b6663cffbbd19c651959998ceef608660dd0f25d2ceed4435e3b00e00df8f1d61957d4faf7df4561b2aa3016c3d91134096faa3bf4296d830e9a7c209e0c6497517abd5a8a9d306bcf67ed91f9e6725b4758c022e0b1ef4275bf7b6c5bfc11d45f9088b941f54eb1e59bb8bc39a0bf12307f5c4fdb70c581b23f76b63acae1caa6b7902d52526735488a0ef13c6d9a51bfa4ab3ad8347796524d8ef6a167b5a41825d967e144e5140564251ccacb83e6b486f6b3ca3f7971506026c0b857f689962856ded4010abd0be621c3a3960a54e710c375f26375d7014103a4b54330c198af126116d2276e11715f693877fad7ef09cadb094ae91e1a15973fb32c9b73134d0b2e77506660edbd484ca7b18f21ef205407f4793a1a0ba12510dbc15077be463fff4fed4aac0bb555be3a6c1b0c6b47b1bc3773bf7e8c6f62901228f8c28cbb18a55ae31341000a650196f931c77a57f2ddf463e5e9ec144b777de62aaab8a8628ac376d282d6ed3864e67982428ebc831d14348f6f2f9193b5045af2767164e1dfc967c1fb3f2e55a4bd1bffe83b9c80d052b985d182ea0adb2a3b7313d3fe14c8484b1e052588b9b7d2bbd2df016199ecd06e1557cd0915b3353bbb64e0ec377fd028370df92b52c7891428cdc67eb6184b523d1db246c32f63078490f00ef8d647d148d47954515e2327cfef98c582664b4c0f6cc41659".HexToBinary();
KdsRootKey.ParseSecretAgreementParameters(blob);
throw new AssertInconclusiveException();
}
[TestMethod]
public void ParseSecretAgreementParameters_NullInput()
{
KdsRootKey.ParseSecretAgreementParameters(null);
throw new AssertInconclusiveException();
}
[TestMethod]
public void ParseSecretAgreementParameters_EmptyInput()
{
KdsRootKey.ParseSecretAgreementParameters(new byte[0] { });
throw new AssertInconclusiveException();
}
}
}

View File

@ -41,6 +41,7 @@
<ItemGroup>
<Compile Include="Cryptography\HashEqualityComparer.cs" />
<Compile Include="Cryptography\Pbkdf2.cs" />
<Compile Include="Data\KdsRootKey.cs" />
<Compile Include="Extensions\ByteArrayExtensions.cs" />
<Compile Include="Cryptography\Crc32.cs" />
<Compile Include="Cryptography\GPPrefPwdObfuscator.cs" />

View File

@ -56,6 +56,8 @@
value = binarySid.ToSecurityIdentifier(this.HasBigEndianRid);
}
public abstract void ReadAttribute(string name, out DistinguishedName value);
public void ReadAttribute(string name, out SecurityIdentifier[] value)
{
byte[][] binarySids;

View File

@ -0,0 +1,266 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace DSInternals.Common.Data
{
/// <summary>
/// Root key for the Group Key Distribution Service.
/// </summary>
public class KdsRootKey
{
private int? version;
private DateTime ?creationTime;
private DateTime ?effectiveTime;
private byte[] privateKey;
private string kdfAlgorithmName;
private string secretAgreementAlgorithmName;
private byte[] secretAgreementAlgorithmParam;
private int? privateKeyLength;
private int? publicKeyLength;
public KdsRootKey(DirectoryObject dsObject)
{
// Parameter validation
Validator.AssertNotNull(dsObject, "dsObject");
// TODO: Validate object type
// Key format version
// TODO: Check that format == 1
dsObject.ReadAttribute(CommonDirectoryAttributes.KdsVersion, out this.version);
// Domain controller DN
DistinguishedName dcDN;
dsObject.ReadAttribute(CommonDirectoryAttributes.KdsDomainController, out dcDN);
this.DomainController = dcDN.ToString();
// Private key
dsObject.ReadAttribute(CommonDirectoryAttributes.KdsPrivateKey, out this.privateKey);
// Creation time
dsObject.ReadAttribute(CommonDirectoryAttributes.KdsCreationTime, out this.creationTime);
// Effective time
dsObject.ReadAttribute(CommonDirectoryAttributes.KdsEffectiveTime, out this.effectiveTime);
// Guid
string cn;
dsObject.ReadAttribute(CommonDirectoryAttributes.CommonName, out cn);
this.KeyId = Guid.Parse(cn);
// KDF algorithm
dsObject.ReadAttribute(CommonDirectoryAttributes.KdsKdfAlgorithm, out this.kdfAlgorithmName);
// KDF algorithm parameters (only 1 in current implementation)
byte[] rawKdfParams;
dsObject.ReadAttribute(CommonDirectoryAttributes.KdsKdfParameters, out rawKdfParams);
this.KdfParameters = ParseKdfParameters(rawKdfParams);
// Secret agreement algorithm
dsObject.ReadAttribute(CommonDirectoryAttributes.KdsSecretAgreementAlgorithm, out this.secretAgreementAlgorithmName);
// Secret agreement algorithm parameters
dsObject.ReadAttribute(CommonDirectoryAttributes.KdsSecretAgreementParameters, out this.secretAgreementAlgorithmParam);
// Secret agreement private key length
dsObject.ReadAttribute(CommonDirectoryAttributes.KdsSecretAgreementPrivateKeyLength, out this.privateKeyLength);
// Secret agreement public key length
dsObject.ReadAttribute(CommonDirectoryAttributes.KdsSecretAgreementPublicKeyLength, out this.publicKeyLength);
}
/// <summary>
/// Gets the unique identifier of this root key.
/// </summary>
public Guid KeyId
{
get;
private set;
}
/// <summary>
/// Gets the version number of this root key.
/// </summary>
public int VersionNumber
{
get
{
return this.version.Value;
}
}
/// <summary>
/// Gets the root key.
/// </summary>
public byte[] KeyValue
{
get
{
return this.privateKey;
}
}
/// <summary>
/// Gets the time after which this root key may be used.
/// </summary>
public DateTime EffectiveTime
{
get
{
return this.effectiveTime.Value;
}
}
/// <summary>
/// Gets the time when this root key was created.
/// </summary>
public DateTime CreationTime
{
get
{
return this.creationTime.Value;
}
}
/// <summary>
/// Gets distinguished name of the Domain Controller which generated this root key.
/// </summary>
public string DomainController
{
get;
private set;
}
/// <summary>
/// Gets the algorithm name of the key derivation function used to compute keys.
/// </summary>
public string KdfAlgorithm
{
get
{
return this.kdfAlgorithmName;
}
}
/// <summary>
/// Parameters for the key derivation algorithm.
/// </summary>
public Dictionary<uint, string> KdfParameters
{
get;
private set;
}
/// <summary>
/// Gets the name of the secret agreement algorithm to be used with public keys.
/// </summary>
public string SecretAgreementAlgorithm
{
get
{
return this.secretAgreementAlgorithmName;
}
}
/// <summary>
/// Gets the parameters for the secret agreement algorithm.
/// </summary>
public byte[] SecretAgreementParameters
{
get
{
return this.secretAgreementAlgorithmParam;
}
}
/// <summary>
/// Gets the length of the secret agreement public key.
/// </summary>
public int SecretAgreementPublicKeyLength
{
get
{
return this.publicKeyLength.Value;
}
}
/// <summary>
/// Gets the length of the secret agreement private key.
/// </summary>
public int SecretAgreementPrivateKeyLength
{
get
{
return this.privateKeyLength.Value;
}
}
public static Dictionary<uint,string> ParseKdfParameters(byte[] blob)
{
if(blob == null || blob.Length == 0)
{
return null;
}
// TODO: Validate length
var result = new Dictionary<uint,string>();
using (Stream stream = new MemoryStream(blob))
{
using (BinaryReader reader = new BinaryReader(stream))
{
uint version = reader.ReadUInt32();
// TODO: Test that version == 0
uint parameterCount = reader.ReadUInt32();
for (uint i = 0; i < parameterCount; i++)
{
int valueLength = reader.ReadInt32();
uint parameterId = reader.ReadUInt32();
// TODO: Test that paramId == 0
byte[] binaryValue = reader.ReadBytes(valueLength);
// Remove the trailing 0 at the end.
string value = UnicodeEncoding.Unicode.GetString(binaryValue, 0, valueLength - sizeof(char));
result.Add(parameterId, value);
}
}
}
return result;
}
public static void ParseSecretAgreementParameters(byte[] blob)
{
if (blob == null || blob.Length == 0)
{
return;
}
// TODO: Validate minimum length
using (Stream stream = new MemoryStream(blob))
{
using (BinaryReader reader = new BinaryReader(stream))
{
var length = reader.ReadInt32();
// TODO: validate actual length
var binaryMagic = reader.ReadBytes(sizeof(int));
string magic = ASCIIEncoding.ASCII.GetString(binaryMagic);
// TODO: Test that magic is DHPM
// DH:
int keySize = reader.ReadInt32();
var p = reader.ReadBytes(keySize);
var g = reader.ReadBytes(keySize);
}
}
}
}
}

View File

@ -35,6 +35,17 @@
public const int IsDeletedId = 131120;
public const string IsInGlobalCatalog = "isMemberOfPartialAttributeSet";
public const string IsSingleValued = "isSingleValued";
public const string KdsCreationTime = "msKds-CreateTime";
public const string KdsDomainController = "msKds-DomainID";
public const string KdsKdfAlgorithm = "msKds-KDFAlgorithmID";
public const string KdsKdfParameters = "msKds-KDFParam";
public const string KdsSecretAgreementPrivateKeyLength = "msKds-PrivateKeyLength";
public const string KdsSecretAgreementPublicKeyLength = "msKds-PublicKeyLength";
public const string KdsPrivateKey = "msKds-RootKeyData";
public const string KdsSecretAgreementAlgorithm = "msKds-SecretAgreementAlgorithmID";
public const string KdsSecretAgreementParameters = "msKds-SecretAgreementParam";
public const string KdsEffectiveTime = "msKds-UseStartTime";
public const string KdsVersion = "msKds-Version";
public const string LastLogon = "lastLogon";
public const int LastLogonId = 589876;
public const string LastLogonTimestamp = "lastLogonTimestamp";

View File

@ -9,5 +9,6 @@
public const string AttributeSchema = "attributeSchema";
public const int AttributeSchemaId = 196622;
public const string Schema = "dMD";
public const string KdsRootKey = "msKds-ProvRootKey";
}
}

View File

@ -111,6 +111,13 @@
value = this.cursor.RetrieveColumnAsLong(columnId);
}
public override void ReadAttribute(string name, out DistinguishedName value)
{
Columnid columnId = this.context.Schema.FindColumnId(name);
var dnt = this.cursor.RetrieveColumnAsDNTag(columnId);
value = this.context.DistinguishedNameResolver.Resolve(dnt.Value);
}
public override void ReadAttribute(string name, out RawSecurityDescriptor value)
{
byte[] binarySecurityDescriptorId;

View File

@ -198,6 +198,16 @@
}
}
public IEnumerable<KdsRootKey> GetKdsRootKeys()
{
// TODO: Refactor using Linq
// TODO: Test if schema contains the ms-Kds-Prov-RootKey class.
foreach (var keyObject in this.FindObjectsByCategory(CommonDirectoryClasses.KdsRootKey))
{
yield return new KdsRootKey(keyObject);
}
}
public IEnumerable<DirectoryObject> FindObjectsByCategory(string className, bool includeDeleted = false)
{
// Find all objects with the right objectCategory

View File

@ -125,7 +125,8 @@
else
{
// TODO: Class not found exception
throw new Exception("Class not found.");
string message = String.Format("Class {0} has not been found in the schema.", className);
throw new InvalidOperationException(message);
}
}

View File

@ -2,13 +2,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DSInternals.DataStore
{
public class LinkResolver : IDisposable
{
// Column names:
private const string linkDNCol = "link_DNT";
private const string backlinkDNCol = "backlink_DNT";
private const string linkBaseCol = "link_base";

View File

@ -0,0 +1,30 @@
namespace DSInternals.PowerShell.Commands
{
using System;
using System.Management.Automation;
using DSInternals.Common;
using DSInternals.Common.Cryptography;
using DSInternals.DataStore;
[Cmdlet(VerbsCommon.Get, "ADDBKdsRootKey")]
[OutputType(typeof(DSInternals.Common.Data.KdsRootKey))]
public class GetADDBKdsRootKeyCommand : ADDBCommandBase
{
// TODO: Add optional Guid parameter
protected override void BeginProcessing()
{
base.BeginProcessing();
// TODO: Remove this from the final commit:
this.Host.UI.WriteLine("DSInternals 2.17 Preview [In cooperation with CQURE Team for DPAPI-NG decryption]");
using(var directoryAgent = new DirectoryAgent(this.DirectoryContext))
{
foreach(var rootKey in directoryAgent.GetKdsRootKeys())
{
this.WriteObject(rootKey);
}
}
// TODO: Exception handling
}
}
}

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8" ?>
<Configuration>
<Controls>
<Control>
<Name>HashCollection</Name>
<CustomControl>
<CustomEntries>
<CustomEntry>
<CustomItem>
<Frame>
<LeftIndent>2</LeftIndent>
<CustomItem>
<ExpressionBinding>
<EnumerateCollection />
<ScriptBlock>$_ | ConvertTo-Hex | foreach -Begin { [int] $i = 1 } -Process { if($i -gt 1) { "`n" }; "Hash {0:d2}: {1}" -f $i,$_ ; $i++ }</ScriptBlock>
</ExpressionBinding>
<NewLine />
</CustomItem>
</Frame>
</CustomItem>
</CustomEntry>
</CustomEntries>
</CustomControl>
</Control>
<Control>
<Name>Hash</Name>
<!-- Converts binary hash into a hex string -->
<CustomControl>
<CustomEntries>
<CustomEntry>
<CustomItem>
<ExpressionBinding>
<EnumerateCollection />
<ScriptBlock>ConvertTo-Hex $_</ScriptBlock>
</ExpressionBinding>
</CustomItem>
</CustomEntry>
</CustomEntries>
</CustomControl>
</Control>
</Controls>
</Configuration>

View File

@ -0,0 +1,107 @@
<?xml version="1.0" encoding="utf-8" ?>
<Configuration>
<ViewDefinitions>
<View>
<Name>KdsRootKey</Name>
<ViewSelectedBy>
<TypeName>DSInternals.Common.Data.KdsRootKey</TypeName>
</ViewSelectedBy>
<CustomControl>
<CustomEntries>
<CustomEntry>
<CustomItem>
<Text>Id: </Text>
<ExpressionBinding>
<PropertyName>KeyId</PropertyName>
</ExpressionBinding>
<NewLine />
<Text>Version: </Text>
<ExpressionBinding>
<PropertyName>VersionNumber</PropertyName>
</ExpressionBinding>
<NewLine />
<Text>Creation Time: </Text>
<ExpressionBinding>
<PropertyName>CreationTime</PropertyName>
</ExpressionBinding>
<NewLine />
<Text>Effective Time: </Text>
<ExpressionBinding>
<PropertyName>EffectiveTime</PropertyName>
</ExpressionBinding>
<NewLine />
<Text>Domain Controller: </Text>
<ExpressionBinding>
<PropertyName>DomainController</PropertyName>
</ExpressionBinding>
<NewLine />
<Text>Key</Text>
<NewLine />
<Frame>
<LeftIndent>2</LeftIndent>
<CustomItem>
<ExpressionBinding>
<PropertyName>KeyValue</PropertyName>
<CustomControlName>Hash</CustomControlName>
</ExpressionBinding>
<NewLine />
</CustomItem>
</Frame>
<Text>Key Derivation Function</Text>
<NewLine />
<Frame>
<LeftIndent>2</LeftIndent>
<CustomItem>
<Text>Algorithm: </Text>
<ExpressionBinding>
<PropertyName>KdfAlgorithm</PropertyName>
</ExpressionBinding>
<NewLine />
<Text>Parameters: </Text>
<ExpressionBinding>
<PropertyName>KdfParameters</PropertyName>
</ExpressionBinding>
<NewLine />
</CustomItem>
</Frame>
<Text>Secret Agreement</Text>
<NewLine />
<Frame>
<LeftIndent>2</LeftIndent>
<CustomItem>
<Text>Algorithm: </Text>
<ExpressionBinding>
<PropertyName>SecretAgreementAlgorithm</PropertyName>
</ExpressionBinding>
<NewLine />
<Text>Public Key Length: </Text>
<ExpressionBinding>
<PropertyName>SecretAgreementPublicKeyLength</PropertyName>
</ExpressionBinding>
<NewLine />
<Text>Private Key Length: </Text>
<ExpressionBinding>
<PropertyName>SecretAgreementPrivateKeyLength</PropertyName>
</ExpressionBinding>
<NewLine />
<Text>Parameters</Text>
<NewLine />
<Frame>
<LeftIndent>2</LeftIndent>
<CustomItem>
<ExpressionBinding>
<PropertyName>SecretAgreementParameters</PropertyName>
<CustomControlName>Hash</CustomControlName>
</ExpressionBinding>
<NewLine />
</CustomItem>
</Frame>
</CustomItem>
</Frame>
</CustomItem>
</CustomEntry>
</CustomEntries>
</CustomControl>
</View>
</ViewDefinitions>
</Configuration>

View File

@ -51,6 +51,7 @@
<Compile Include="Commands\Base\ADReplObjectCommandBase.cs" />
<Compile Include="Commands\Base\ADReplCommandBase.cs" />
<Compile Include="Commands\Base\PSCmdletEx.cs" />
<Compile Include="Commands\Datastore\GetADDBKdsRootKeyCommand.cs" />
<Compile Include="Commands\Hash\ConvertToNTHashDictionaryCommand.cs" />
<Compile Include="Commands\Misc\ConvertFromADManagedPasswordBlobCommand.cs" />
<Compile Include="Commands\Misc\TestPasswordQualityCommand.cs" />
@ -143,6 +144,14 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<None Include="ADDBCommandHierarchy.cd" />
<None Include="DSInternals.Hash.format.ps1xml">
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="DSInternals.KdsRootKey.format.ps1xml">
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="DSInternals.PasswordQualityTestResult.format.ps1xml">
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>

View File

@ -57,7 +57,9 @@ ProcessorArchitecture = 'None'
# Format files (.ps1xml) to be loaded when importing this module
FormatsToProcess = 'DSInternals.DSAccount.format.ps1xml',
'DSInternals.PasswordQualityTestResult.format.ps1xml'
'DSInternals.PasswordQualityTestResult.format.ps1xml',
'DSInternals.KdsRootKey.format.ps1xml',
'DSInternals.Hash.format.ps1xml'
# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
NestedModules = @('DSInternals.PowerShell.dll')
@ -76,7 +78,8 @@ CmdletsToExport = 'ConvertTo-NTHash', 'ConvertTo-LMHash', 'Set-SamAccountPasswor
'Get-ADReplAccount', 'ConvertTo-Hex',
'ConvertFrom-ADManagedPasswordBlob',
'Get-ADDBBackupKey', 'Get-ADReplBackupKey', 'Save-DPAPIBlob',
'Set-ADDBBootKey','ConvertTo-NTHashDictionary', 'Test-PasswordQuality'
'Set-ADDBBootKey','ConvertTo-NTHashDictionary', 'Test-PasswordQuality',
'Get-ADDBKdsRootKey'
# Variables to export from this module
# VariablesToExport = @()
@ -117,6 +120,8 @@ PrivateData = @{
# ReleaseNotes of this module
ReleaseNotes = @"
- Added the Get-ADDBKdsRootKey cmdlet.
- The Get-ADReplAccount cmdlet now correctly reports the access denied error.
- Fixed a bug in progress reporting of the Get-ADReplAccount cmdlet.
"@
} # End of PSData hashtable

View File

@ -108,6 +108,13 @@
this.ReadAttribute(attributeId, out binaryValue);
value = (binaryValue != null) ? Encoding.Unicode.GetString(binaryValue) : null;
}
protected void ReadAttribute(int attributeId, out DistinguishedName value)
{
// TODO: Implement
throw new NotImplementedException();
}
protected void ReadAttribute(int attributeId, out SecurityIdentifier value)
{
byte[] binaryValue;
@ -163,6 +170,12 @@
this.ReadAttribute(attributeId, out value);
}
public override void ReadAttribute(string name, out DistinguishedName value)
{
int attributeId = this.Schema.FindAttributeId(name);
this.ReadAttribute(attributeId, out value);
}
public override void ReadAttribute(string name, out RawSecurityDescriptor value)
{
int attributeId = this.Schema.FindAttributeId(name);