Get Registry Key DACL Security Descriptor

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

You need to parse the output SDDL string in order to understand access properties for desired keys, there are plenty of articles arround explaining how to understand an SDDL string format.

You can also play with flags associated to ConvertSecurityDescriptorToStringSecurityDescriptor call to extract even more information from captured Security Descriptor Pointer after RegGetKeySecurity call.

{-------------------------------------------------------------------------------
  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 ;)

comments powered by Disqus