Get Registry Key DACL Security Descriptor

Below code snippet demonstrate how to get DACL Security Descriptor in SDDL format for a targeted registry key.

(*
  Check if target registry key have write access.

  Return Code:
    0 : Success.
    1 : Could not open registry key.
    2 : Could not get registry key security requirements.
    3 : Could not get registry key security information.
    4 : Could not translate security descriptor.

    99 : Unknown
*)
function GetRegKeySecurityDescriptor(AKeyHive : HKEY; AKeyPath : String; var ASDDL : String) : Cardinal;
var AKey                   : HKEY;
    pSecDesc               : PSecurityDescriptor;
    ASize                  : DWORD;
    ARet                   : Cardinal;
    AFlags                 : Cardinal;
    AToken                 : THandle;
    APrivilegeSet          : TPrivilegeSet;
    AGenericMapping        : TGenericMapping;
    AGrantedAccess         : DWORD;
    AResult                : BOOL;
    AMask                  : DWORD;
    hADVAPI                : THandle;
    ASecurityDescriptorStr : PWideChar;
    AReturnedLength        : ULONG;
    APos                   : Integer;

    // https://docs.microsoft.com/en-us/windows/win32/api/sddl/nf-sddl-convertsecuritydescriptortostringsecuritydescriptorw
    ConvertSecurityDescriptorToStringSecurityDescriptor : function(
                                                                    SecurityDescriptor : PSECURITY_DESCRIPTOR;
                                                                    RequestedStringSDRevision : DWORD;
                                                                    SecurityInformation : SECURITY_INFORMATION;
                                                                    var StringSecurityDescriptor : LPWSTR;
                                                                    StringSecurityDescriptorLen : PULONG
                                                          ) : BOOL; stdcall;
const SDDL_REVISION_1     = 1;
begin
  result := 99;
  ASDDL := '';
  ///

  ARet := RegOpenKeyExW(AKeyHive, PWideChar(AKeyPath), 0, KEY_READ, AKey);
  if (ARet <> ERROR_SUCCESS) then
    Exit(1);
  try
    ASize := 0;
    AFlags := (
                DACL_SECURITY_INFORMATION  or
                OWNER_SECURITY_INFORMATION or
                GROUP_SECURITY_INFORMATION
    );
    ///

    ARet := RegGetKeySecurity(AKey, AFlags, nil, ASize);
    if (ARet = ERROR_INSUFFICIENT_BUFFER) and (ASize > 0) then begin
      GetMem(pSecDesc, ASize);
      try
        ARet := RegGetKeySecurity(AKey, AFlags, pSecDesc, ASize);
        if (ARet <> ERROR_SUCCESS) then
          Exit(3);
        ///

        hADVAPI := LoadLibrary('ADVAPI32.DLL');
        if (hADVAPI = 0) then
          Exit();
        try
          @ConvertSecurityDescriptorToStringSecurityDescriptor := GetProcAddress(hADVAPI, 'ConvertSecurityDescriptorToStringSecurityDescriptorW');
          if Assigned(ConvertSecurityDescriptorToStringSecurityDescriptor) then begin

            if NOT ConvertSecurityDescriptorToStringSecurityDescriptor(
                                                                  pSecDesc,
                                                                  SDDL_REVISION_1,
                                                                  DACL_SECURITY_INFORMATION,
                                                                  ASecurityDescriptorStr,
                                                                  @AReturnedLength
            ) then
              Exit(4);

            ///
            ASDDL := UnicodeString(ASecurityDescriptorStr);
          end;
        finally
          FreeLibrary(hADVAPI);
        end;
      finally
        FreeMem(pSecDesc, ASize);
      end;
    end else
      Exit(2);
  finally
    RegCloseKey(AKey);
  end;
end;

Example of usage

//...
var ASDDLOut : String;
//...
GetRegKeySecurityDescriptor(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Services\arcsas\', ASDDLOut);

WriteLn(ASDDLOut);

Will output something similar to this

D:AI(A;;KA;;;BA)(A;CI;KR;;;S-1-5-21-1216997795-677674728-3816802077-1001)(A;CIID;KR;;;BU)(A;CIID;KA;;;BA)(A;CIID;KA;;;SY)(A;CIIOID;KA;;;CO)(A;CIID;KR;;;AC)(A;CIID;KR;;;S-1-15-3-1024-1065365936-1281604716-3511738428-1654721687-432734479-3232135806-4053264122-3456934681)

Parser

I will probably in a near future work on a delphi parser for SDDL Format (Some probably already exists).

Here is a tiny example using only basic delphi string manipulation techniques (Not the cleanest way) to extract the six properties.

function ParseSDDL_DACL(ASDDL : String) : Boolean;
var APos, APosEnd : Integer;
    AChunk        : String;
    AF1, AF2, AF3 : String;
    AF4, AF5, AF6 : String;

    function GetNextChunkProperty() : String;
    begin
      result := Copy(AChunk, 1, Pos(';', AChunk)-1);
      Delete(AChunk, 1, Pos(';', AChunk));
    end;

begin
  ASDDL := UpperCase(ASDDL);
  APos := 0;
  while true do begin
    if (APos > 0) then
      Delete(ASDDL, 1, APos);

    APos := Pos(':', ASDDL);

    if (APos = 0) then
      break;

    {
      Attempt to find DACL Security Descriptor
    }
    if (Copy(ASDDL, (APos -1), 1) = 'D') then
      break;
  end;

  {
    Read DACL Security Descriptor
  }
  if (APos > 0) then begin
    Delete(ASDDL, 1, APos);

    // Clean other Security Descriptor
    APos := Pos(':', ASDDL);
    if (APos > 0) then
      Delete(ASDDL, (APos -1), Length(ASDDL));

    {
      Iterate through each descriptor items
    }
    while True do begin
      if (Length(ASDDL) = 0) then
        break;

      APos := Pos('(', ASDDL);
      APosEnd := Pos(')', ASDDL);

      if (APos = 0) or (APosEnd = 0) then
        break;
      try
        Inc(APos);
        AChunk := Copy(ASDDL, APos, (APosEnd - APos));

        {
          Parse chunk
        }
        AF1 := GetNextChunkProperty(); // ACE Type
        AF2 := GetNextChunkProperty(); // ACE Flags
        AF3 := GetNextChunkProperty(); // Permissions
        AF4 := GetNextChunkProperty(); // Object Type (GUID)
        AF5 := GetNextChunkProperty(); // Object Type (GUID)
        AF6 := GetNextChunkProperty(); // Trustee (SID)

        {
          ...

          ...
        }
      finally
        Delete(ASDDL, 1, APosEnd); // Clean
      end;
    end;
  end;
end;

Alternative

I recently worked on parsing SDDL string from a bunch of registry keys to solve a HackTheBox box without using third part tools (such as AccessChk.exe from SysInternals)

Parsing SDDL string is a very complex task, there is a lot of things to take in consideration in some cases.

If for example you want to know if current user have Write Access to a target registry key, instead of using above function and parse output SDDL string (and take in consideration all attributes) we could simply attempt to open target key with KEY_WRITE flag and check whether or not you receive an Access Denied (5) error.

Example

// ...
type
  TAccessStatus = (
    asTrue,
    asFalse,
    asError
  );
// ...

function CheckCurrentUserKeyAccess(AKeyHive : HKEY; AKeyPath : String; var AStatus : TAccessStatus): Boolean;
var AKey : HKEY;
    ARet : Integer;
begin
  result := False;
  AStatus := asError;
  ///

  ARet := RegOpenKeyExW(AKeyHive, PWideChar(AKeyPath), 0, KEY_WRITE, AKey);
  try
    result := ((ARet = ERROR_SUCCESS) or (ARet = ERROR_ACCESS_DENIED));
    if NOT result then
      Exit();

    if (ARet = ERROR_ACCESS_DENIED) then
      AStatus := asFalse
    else
      AStatus := asTrue;

    ///
    result := True;
  finally
    if (ARet = ERROR_SUCCESS) then
      RegCloseKey(AKey);
  end;
end;

// ...

var AStatus : TAccessStatus;
begin
  try
    if CheckCurrentUserKeyAccess(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Services\arcsas\', AStatus) then begin
      case AStatus of
        asTrue  : WriteLn('Access Granted');
        asFalse : WriteLn('Access Denied');
        asError : WriteLn('Could not determine access status');
      end;
    end;

    // ...

If you have any alternative in minds, just let me know ;)

Written the Nov. 23, 2020, 9:52 a.m. by Jean-Pierre LESUEUR

Updated: 1 month, 4 weeks ago.