Skip to content

Instantly share code, notes, and snippets.

@xpn
Last active October 26, 2023 01:43
Show Gist options
  • Star 51 You must be signed in to star a gist
  • Fork 18 You must be signed in to fork a gist
  • Save xpn/23dc5b6c260a7571763ca8ca745c32f4 to your computer and use it in GitHub Desktop.
Save xpn/23dc5b6c260a7571763ca8ca745c32f4 to your computer and use it in GitHub Desktop.
Quick POC looking at how encryption works for LAPS (v2)
using System;
using System.Collections.Generic;
using System.DirectoryServices.Protocols;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Security.Policy;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using static LAPSDecrypt.Win32;
using static System.Net.Mime.MediaTypeNames;
namespace LAPSDecrypt
{
internal class Win32
{
[Flags]
public enum ProtectFlags
{
NCRYPT_SILENT_FLAG = 0x00000040,
}
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
public delegate int PFNCryptStreamOutputCallback(IntPtr pvCallbackCtxt, IntPtr pbData, int cbData, [MarshalAs(UnmanagedType.Bool)] bool fFinal);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct NCRYPT_PROTECT_STREAM_INFO
{
public PFNCryptStreamOutputCallback pfnStreamOutput;
public IntPtr pvCallbackCtxt;
}
[Flags]
public enum UnprotectSecretFlags
{
NCRYPT_UNPROTECT_NO_DECRYPT = 0x00000001,
NCRYPT_SILENT_FLAG = 0x00000040,
}
[DllImport("ncrypt.dll")]
public static extern uint NCryptStreamOpenToUnprotect(in NCRYPT_PROTECT_STREAM_INFO pStreamInfo, ProtectFlags dwFlags, IntPtr hWnd, out IntPtr phStream);
[DllImport("ncrypt.dll")]
public static extern uint NCryptStreamUpdate(IntPtr hStream, IntPtr pbData, int cbData, [MarshalAs(UnmanagedType.Bool)] bool fFinal);
[DllImport("ncrypt.dll")]
public static extern uint NCryptUnprotectSecret(out IntPtr phDescriptor, Int32 dwFlags, IntPtr pbProtectedBlob, uint cbProtectedBlob, IntPtr pMemPara, IntPtr hWnd, out IntPtr ppbData, out uint pcbData);
[DllImport("ncrypt.dll", CharSet = CharSet.Unicode)]
public static extern uint NCryptGetProtectionDescriptorInfo(IntPtr hDescriptor, IntPtr pMemPara, int dwInfoType, out string ppvInfo);
}
internal class Program
{
static int delegateCallback(IntPtr pvCallbackCtxt, IntPtr pbData, int cbData, [MarshalAs(UnmanagedType.Bool)] bool fFinal)
{
byte[] data = new byte[cbData];
Marshal.Copy(pbData, data, 0, cbData);
string str = Encoding.Unicode.GetString(data);
Console.WriteLine("[*] Password is: {0}", str);
return 0;
}
static void Main(string[] args)
{
string[] attributeList = new string[]
{
"msLAPS-PasswordExpirationTime",
"msLAPS-Password",
"msLAPS-EncryptedPassword",
"msLAPS-EncryptedPasswordHistory",
"msLAPS-EncryptedDSRMPassword",
"msLAPS-EncryptedDSRMPasswordHistory",
"ms-Mcs-AdmPwd",
"ms-Mcs-AdmPwdExpirationTime"
};
// Parse arguments for DN and DC
Console.WriteLine("LAPSDecrypt POC by @_xpn_");
if (args.Length != 2)
{
Console.WriteLine("Usage: LAPSDecrypt.exe <DN> <DC>");
Console.WriteLine("Example: LAPSDecrypt.exe \"CN=CA01,OU=LAPSManaged,DC=lab,DC=local\" \"dc01.lab.local\"");
return;
}
string dn = args[0];
string dc = args[1];
string filter = string.Format("(&(objectClass={0})({1}={2}))", "computer", "distinguishedName", dn);
// Create a new ldap connection
LdapConnection ldapConnection = new LdapConnection(dc);
ldapConnection.SessionOptions.ProtocolVersion = 3;
ldapConnection.Bind();
SearchRequest searchRequest = new SearchRequest(dn, filter, SearchScope.Base, attributeList);
SearchResponse searchResponse = ldapConnection.SendRequest(searchRequest) as SearchResponse;
SearchResultEntry searchResultEntry = searchResponse.Entries[0];
if (searchResponse.Entries.Count != 1)
{
Console.WriteLine("[!] Could not find computer object");
return;
}
foreach (string attVal in searchResultEntry.Attributes.AttributeNames)
{
if (StringComparer.InvariantCultureIgnoreCase.Equals(attVal, "msLAPS-PasswordExpirationTime"))
{
var expiry = (searchResultEntry.Attributes["msLAPS-PasswordExpirationTime"].GetValues(typeof(string))[0] as string);
Console.WriteLine("[*] Expiry time is: {0}", expiry);
}
else if (StringComparer.InvariantCultureIgnoreCase.Equals(attVal, "msLAPS-Password"))
{
var unencryptedPass = (searchResultEntry.Attributes["msLAPS-Password"].GetValues(typeof(string))[0] as string);
Console.WriteLine("[*] Unencrypted Password: {0}", unencryptedPass);
} else if (StringComparer.InvariantCultureIgnoreCase.Equals(attVal, "msLAPS-EncryptedPassword"))
{
byte[] encryptedPass = (searchResultEntry.Attributes["msLAPS-EncryptedPassword"].GetValues(typeof(byte[]))[0] as byte[]);
Console.WriteLine("[*] Found encrypted password of length: {0}", encryptedPass.Length);
Win32.NCRYPT_PROTECT_STREAM_INFO info = new NCRYPT_PROTECT_STREAM_INFO
{
pfnStreamOutput = new PFNCryptStreamOutputCallback(delegateCallback),
pvCallbackCtxt = IntPtr.Zero
};
IntPtr handle;
IntPtr handle2;
IntPtr secData;
uint secDataLen;
NTAccount ntaccount;
uint ret = Win32.NCryptStreamOpenToUnprotect(info, ProtectFlags.NCRYPT_SILENT_FLAG, IntPtr.Zero, out handle);
if (ret == 0)
{
IntPtr alloc = Marshal.AllocHGlobal(encryptedPass.Length);
Marshal.Copy(encryptedPass, 16, alloc, encryptedPass.Length - 16);
// Get the authorized decryptor of the blob
ret = Win32.NCryptUnprotectSecret(out handle2, 0x41, alloc, (uint)encryptedPass.Length - 16, IntPtr.Zero, IntPtr.Zero, out secData, out secDataLen);
if (ret == 0)
{
string sid;
ret = NCryptGetProtectionDescriptorInfo(handle2, IntPtr.Zero, 1, out sid);
if (ret == 0)
{
SecurityIdentifier securityIdentifier = new SecurityIdentifier(sid.Substring(4, sid.Length - 4));
try
{
ntaccount = (securityIdentifier.Translate(typeof(NTAccount)) as NTAccount);
Console.WriteLine("[*] Authorized Decryptor: {0}", ntaccount.ToString());
} catch
{
Console.WriteLine("[*] Authorized Decryptor SID: {0}", securityIdentifier.ToString());
}
}
}
// Decrypt the blob
ret = Win32.NCryptStreamUpdate(handle, alloc, encryptedPass.Length - 16, true);
Console.WriteLine("[*] Decrypted Password");
}
}
}
}
}
}
@xpn
Copy link
Author

xpn commented Apr 18, 2023

Updated to print the authorized decryptor field

@schorschii
Copy link

@xpn do you know what these 16 bytes are which you are cutting off?

@xpn
Copy link
Author

xpn commented May 17, 2023

@xpn do you know what these 16 bytes are which you are cutting off?

It’s a header that from memory contains a length and a timestamp field (might be something else too). Next time I’m in front of the decompilation I’ll double check :)

@schorschii
Copy link

schorschii commented May 17, 2023

Thanks. I'm currently implementing encryption in the dpapi-ng python library for my LAPS4LINUX project and I found out that everything seems to work even with 16 zero bytes.
Edit: the initiator of the dpapi-ng library seems to know.

@xpn
Copy link
Author

xpn commented May 17, 2023

Ah here we go, so bytes:

  • 0-3 = UpperDateTimeStamp
  • 4-7 = LowerDateTimeStamp
  • 8-11 = EncryptedBufferSize
  • 12-15 = FlagsReserved

image

@schorschii
Copy link

Thanks :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment