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 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 ;)
Written the Nov. 23, 2020, 9:52 a.m. by Jean-Pierre LESUEUR
Updated: 1 month, 3 weeks ago.