rapid7/metasploit-framework

View on GitHub
lib/rex/proto/ms_dtyp.rb

Summary

Maintainability
F
4 days
Test Coverage
# -*- coding: binary -*-

require 'bindata'
require 'ruby_smb'
require 'rex/proto/secauthz/well_known_sids'

module Rex::Proto::MsDtyp
  # [2.4.3 ACCESS_MASK](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/7a53f60e-e730-4dfe-bbe9-b21b62eb790b)
  class MsDtypAccessMask < BinData::Record
    endian :little
    hide   :reserved0, :reserved1

    # the protocol field id reserved for protocol-specific access rights
    uint16 :protocol

    bit3   :reserved0
    bit1   :sy
    bit1   :wo
    bit1   :wd
    bit1   :rc
    bit1   :de

    bit1   :gr
    bit1   :gw
    bit1   :gx
    bit1   :ga
    bit2   :reserved1
    bit1   :ma
    bit1   :as
    def bit_names
      names = []
      names << :GENERIC_READ if self.gr != 0
      names << :GENERIC_WRITE if self.gw != 0
      names << :GENERIC_EXECUTE if self.gx != 0
      names << :GENERIC_ALL if self.ga != 0
      names << :MAXIMUM_ALLOWED if self.ma != 0
      names << :ACCESS_SYSTEM_SECURITY if self.as != 0
      names << :SYNCHRONIZE if self.sy != 0
      names << :WRITE_OWNER if self.wo != 0
      names << :WRITE_DACL if self.wd != 0
      names << :READ_CONTROL if self.rc != 0
      names << :DELETE if self.de != 0
      names
    end

    ALL  = MsDtypAccessMask.new({ gr: 1, gw: 1, gx: 1, ga: 1, ma: 1, as: 1, sy: 1, wo: 1, wd: 1, rc: 1, de: 1, protocol: 0xffff })
    NONE = MsDtypAccessMask.new({ gr: 0, gw: 0, gx: 0, ga: 0, ma: 0, as: 0, sy: 0, wo: 0, wd: 0, rc: 0, de: 0, protocol: 0 })
  end

  # [2.4.2.2 SID--Packet Representation](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/f992ad60-0fe4-4b87-9fed-beb478836861)
  class MsDtypSid < BinData::Primitive
    endian :little

    uint8 :revision, initial_value: 1
    uint8 :sub_authority_count, initial_value: -> { self.sub_authority.length }
    array :identifier_authority, type: :uint8, initial_length: 6
    array :sub_authority, type: :uint32, initial_length: :sub_authority_count

    def set(val)
      # allow assignment from the human-readable string representation
      raise ArgumentError.new("Invalid SID: #{val}") unless val.is_a?(String) && val =~ /^S-1-(\d+)(-\d+)*$/

      _, _, ia, sa = val.split('-', 4)
      self.identifier_authority = [ia.to_i].pack('Q>')[2..].bytes
      self.sub_authority = sa.nil? ? [] : sa.split('-').map(&:to_i)
    end

    def get
      str = 'S-1'
      str << "-#{("\x00\x00" + identifier_authority.to_binary_s).unpack1('Q>')}"
      str << '-' + sub_authority.map(&:to_s).join('-') unless sub_authority.empty?
      str
    end

    def rid
      sub_authority.last
    end
  end

  # [Universal Unique Identifier](http://pubs.opengroup.org/onlinepubs/9629399/apdxa.htm)
  # The online documentation at [2.3.4.2](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/001eec5a-7f8b-4293-9e21-ca349392db40)
  # weirdly doesn't mention this needs to be 4 byte aligned for us to read it correctly,
  # which the RubySMB::Dcerpc::Uuid definition takes care of.
  class MsDtypGuid < RubySMB::Dcerpc::Uuid
    def self.random_generate
      # Taken from the "D" format as specified in
      # https://learn.microsoft.com/en-us/dotnet/api/system.guid.tostring?view=net-7.0
      "{#{Rex::Text.rand_text_hex(8)}-#{Rex::Text.rand_text_hex(4)}-#{Rex::Text.rand_text_hex(4)}-#{Rex::Text.rand_text_hex(4)}-#{Rex::Text.rand_text_hex(12)}}".downcase
    end
  end

  # Definitions taken from [2.4.4.1 ACE_HEADER](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/628ebb1d-c509-4ea0-a10f-77ef97ca4586)
  class MsDtypAceType
    ACCESS_ALLOWED_ACE_TYPE                 = 0x0
    ACCESS_DENIED_ACE_TYPE                  = 0x1
    SYSTEM_AUDIT_ACE_TYPE                   = 0x2
    SYSTEM_ALARM_ACE_TYPE                   = 0x3 # Reserved for future use according to documentation.
    ACCESS_ALLOWED_COMPOUND_ACE_TYPE        = 0x4 # Reserved for future use according to documentation.
    ACCESS_ALLOWED_OBJECT_ACE_TYPE          = 0x5
    ACCESS_DENIED_OBJECT_ACE_TYPE           = 0x6
    SYSTEM_AUDIT_OBJECT_ACE_TYPE            = 0x7
    SYSTEM_ALARM_OBJECT_ACE_TYPE            = 0x8 # Reserved for future use according to documentation.
    ACCESS_ALLOWED_CALLBACK_ACE_TYPE        = 0x9
    ACCESS_DENIED_CALLBACK_ACE_TYPE         = 0xA
    ACCESS_ALLOWED_CALLBACK_OBJECT_ACE_TYPE = 0xB
    ACCESS_DENIED_CALLBACK_OBJECT_ACE_TYPE  = 0xC
    SYSTEM_AUDIT_CALLBACK_ACE_TYPE          = 0xD
    SYSTEM_ALARM_CALLBACK_ACE_TYPE          = 0xE # Reserved for future use according to documentation.
    SYSTEM_AUDIT_CALLBACK_OBJECT_ACE_TYPE   = 0xF
    SYSTEM_ALARM_CALLBACK_OBJECT_ACE_TYPE   = 0x10 # Reserved for future use according to documentation.
    SYSTEM_MANDATORY_LABEL_ACE_TYPE         = 0x11
    SYSTEM_RESOURCE_ATTRIBUTE_ACE_TYPE      = 0x12
    SYSTEM_SCOPED_POLICY_ID_ACE_TYPE        = 0x13

    def self.name(value)
      constants.select { |c| c.upcase == c }.find { |c| const_get(c) == value }
    end
  end

  # [2.4.4.1 ACE_HEADER](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/628ebb1d-c509-4ea0-a10f-77ef97ca4586)
  class MsDtypAceHeader < BinData::Record
    endian :little

    uint8  :ace_type
    struct :ace_flags do
      bit1 :failed_access_ace_flag
      bit1 :successful_access_ace_flag
      bit1 :critical_ace_flag  # used only with access allowed ACE types, see: https://www.codemachine.com/downloads/win10.1903/ntifs.h
      bit1 :inherited_ace
      bit1 :inherit_only_ace
      bit1 :no_propagate_inherit_ace
      bit1 :container_inherit_ace
      bit1 :object_inherit_ace
    end
    uint16 :ace_size, initial_value: -> { parent&.num_bytes || 0 }
  end

  class MsDtypAceNonObjectBody < BinData::Record
    endian :little

    ms_dtyp_access_mask :access_mask
    ms_dtyp_sid         :sid, byte_align: 4
  end

  class MsDtypAceObjectBody < BinData::Record
    endian :little

    ms_dtyp_access_mask :access_mask
    struct              :flags do
      bit1 :reserved5
      bit1 :reserved4
      bit1 :reserved3
      bit1 :reserved2
      bit1 :reserved1
      bit1 :reserved
      bit1 :ace_inherited_object_type_present
      bit1 :ace_object_type_present
    end
    ms_dtyp_guid        :object_type, onlyif: -> { flags.ace_object_type_present != 0x0 }
    ms_dtyp_guid        :inherited_object_type, onlyif: -> { flags.ace_inherited_object_type_present != 0x0 }
    ms_dtyp_sid         :sid, byte_align: 4
  end

  # [2.4.4.2 ACCESS_ALLOWED_ACE](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/72e7c7ea-bc02-4c74-a619-818a16bf6adb)
  class MsDtypAccessAllowedAceBody < MsDtypAceNonObjectBody
  end

  # [2.4.4.4 ACCESS_DENIED_ACE](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/b1e1321d-5816-4513-be67-b65d8ae52fe8)
  class MsDtypAccessDeniedAceBody < MsDtypAceNonObjectBody
  end

  # [2.4.4.10 SYSTEM_AUDIT_ACE](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/9431fd0f-5b9a-47f0-b3f0-3015e2d0d4f9)
  class MsDtypSystemAuditAceBody < MsDtypAceNonObjectBody
  end

  # [2.4.4.3 ACCESS_ALLOWED_OBJECT_ACE](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/c79a383c-2b3f-4655-abe7-dcbb7ce0cfbe)
  class MsDtypAccessAllowedObjectAceBody < MsDtypAceObjectBody
  end

  # [2.4.4.5 ACCESS_DENIED_OBJECT_ACE](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/8720fcf3-865c-4557-97b1-0b3489a6c270)
  class MsDtypAccessDeniedObjectAceBody < MsDtypAceObjectBody
  end

  # [2.4.4.11 SYSTEM_AUDIT_OBJECT_ACE](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/c8da72ae-6b54-4a05-85f4-e2594936d3d5)
  class MsDtypSystemAuditObjectAceBody < MsDtypAceObjectBody
    endian :little

    string             :application_data, read_length: -> { calc_app_data_length }

    def calc_app_data_length
      ace_header = parent&.header
      return 0 if ace_header.nil?
      ace_size = ace_header&.ace_size
      return 0 if ace_size.nil? or (ace_size == 0)

      ace_header_length = ace_header.to_binary_s.length
      body = parent&.body
      if body.nil?
        return 0 # Read no data as there is no body, so either we have done some data misalignment or we shouldn't be reading data.
      else
        ace_body_length = body.to_binary_s.length
        return ace_size - (ace_header_length + ace_body_length)
      end
    end
  end

  class MsDtypAce < BinData::Record
    endian :little

    ms_dtyp_ace_header :header
    choice             :body, selection: -> { header.ace_type } do
      ms_dtyp_access_allowed_ace_body Rex::Proto::MsDtyp::MsDtypAceType::ACCESS_ALLOWED_ACE_TYPE
      ms_dtyp_access_denied_ace_body Rex::Proto::MsDtyp::MsDtypAceType::ACCESS_DENIED_ACE_TYPE
      ms_dtyp_system_audit_ace_body Rex::Proto::MsDtyp::MsDtypAceType::SYSTEM_AUDIT_ACE_TYPE
      # Type 3 is reserved for future use
      # Type 4 is reserved for future use
      ms_dtyp_access_allowed_object_ace_body Rex::Proto::MsDtyp::MsDtypAceType::ACCESS_ALLOWED_OBJECT_ACE_TYPE
      ms_dtyp_access_denied_object_ace_body Rex::Proto::MsDtyp::MsDtypAceType::ACCESS_DENIED_OBJECT_ACE_TYPE
      ms_dtyp_system_audit_object_ace_body Rex::Proto::MsDtyp::MsDtypAceType::SYSTEM_AUDIT_OBJECT_ACE_TYPE
      # Type 8 is reserved for future use
      # Type 14 aka 0xE is reserved for future use
      # Type 16 aka 0x10 is reserved for future use
      string :default, read_length: -> { header.ace_size - body.rel_offset }
    end
  end

  # [2.4.5 ACL](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/20233ed8-a6c6-4097-aafa-dd545ed24428)
  class MsDtypAcl < BinData::Record
    ACL_REVISION = 2
    ACL_REVISION_DS = 4

    endian :little

    uint8  :acl_revision, initial_value: ACL_REVISION
    uint8  :sbz1
    uint16 :acl_size, initial_value: -> { num_bytes }
    uint16 :acl_count, initial_value: -> { aces.length }
    uint16 :sbz2
    array  :aces, type: :ms_dtyp_ace, initial_length: :acl_count
  end

  # [2.4.6 SECURITY_DESCRIPTOR](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/7d4dac05-9cef-4563-a058-f108abecce1d)
  class MsDtypSecurityDescriptor < BinData::Record
    endian :little

    uint8  :revision, initial_value: 1
    uint8  :sbz1
    struct :control do
      bit1 :ss
      bit1 :dt
      bit1 :sd
      bit1 :sp, initial_value: -> { sacl? ? 1 : 0 }
      bit1 :dd
      bit1 :dp, initial_value: -> { dacl? ? 1 : 0 }
      bit1 :gd
      bit1 :od

      bit1 :sr, initial_value: 1
      bit1 :rm
      bit1 :ps
      bit1 :pd
      bit1 :si
      bit1 :di
      bit1 :sc
      bit1 :dc
    end
    uint32 :offset_owner, value: -> { offset_for(:owner_sid) }
    uint32 :offset_group, value: -> { offset_for(:group_sid) }
    uint32 :offset_sacl, value: -> { offset_for(:sacl) }
    uint32 :offset_dacl, value: -> { offset_for(:dacl) }
    rest   :buffer, value: -> { build_buffer }
    hide   :buffer

    def self.from_sddl_text(sddl_text, domain_sid:)
      sacl_set = dacl_set = false
      sd = self.new
      sddl_text = sddl_text.dup.gsub(/\s/, '')  # start by removing all whitespace
      sddl_text.scan(/([OGDS]:(?:.(?!:))*)/).each do |part,|
        component, _, value = part.partition(':')
        case component
        when 'O'
          if sd.owner_sid.present?
            raise RuntimeError.new('SDDL parse error on extra owner SID')
          end

          sd.owner_sid = self.parse_sddl_sid(value, domain_sid: domain_sid)
        when 'G'
          if sd.group_sid.present?
            raise RuntimeError.new('SDDL parse error on extra group SID')
          end

          sd.group_sid = self.parse_sddl_sid(value, domain_sid: domain_sid)
        when 'D'
          raise RuntimeError.new('SDDL parse error on extra DACL') if dacl_set

          value.upcase!
          dacl_set = true
          access_control = true
          flags = value.split('(', 2).first || ''
          flags.split(/(P|AR|AI|NO_ACCESS_CONTROL)/).each do |flag|
            case flag
            when 'AI'
              sd.control.di = true
            when 'AR'
              sd.control.dc = true
            when 'P'
              sd.control.pd = true
            when 'NO_ACCESS_CONTROL'
              access_control = false
            when ''
            else
              raise RuntimeError.new('SDDL parse error on unknown DACL flag: ' + flag)
            end
          end

          next unless access_control

          sd.dacl = MsDtypAcl.new
          sd.dacl.aces = self.parse_sddl_aces(value.delete_prefix(flags), domain_sid: domain_sid)
        when 'S'
          raise RuntimeError.new('SDDL parse error on extra SACL') if sacl_set

          value.upcase!
          sacl_set = true
          access_control = true
          flags = value.split('(', 2).first || ''
          flags.split(/(P|AR|AI|NO_ACCESS_CONTROL)/).each do |flag|
            case flag
            when 'AI'
              sd.control.si = true
            when 'AR'
              sd.control.sc = true
            when 'P'
              sd.control.ps = true
            when 'NO_ACCESS_CONTROL'
              access_control = false
            when ''
            else
              raise RuntimeError.new('SDDL parse error on unknown SACL flag: ' + flag)
            end
          end

          next unless access_control

          sd.sacl = MsDtypAcl.new
          sd.sacl.aces = self.parse_sddl_aces(value.delete_prefix(flags), domain_sid: domain_sid)
        else
          raise RuntimeError.new('SDDL parse error on unknown directive: ' + part[0])
        end
      end

      sd
    end

    class << self
      private

      def parse_sddl_ace(ace, domain_sid:)
        parts = ace.upcase.split(';', -1)
        raise RuntimeError.new('SDDL parse error on too few ACE fields') if parts.length < 6
        raise RuntimeError.new('SDDL parse error on too many ACE fields') if parts.length > 7

        ace_type, ace_flags, rights, object_guid, inherit_object_guid, account_sid = parts[0...6]
        resource_attribute = parts[6]

        ace = MsDtypAce.new
        case ace_type
        when 'A'
          ace.header.ace_type = MsDtypAceType::ACCESS_ALLOWED_ACE_TYPE
        when 'D'
          ace.header.ace_type = MsDtypAceType::ACCESS_DENIED_ACE_TYPE
        when 'OA'
          ace.header.ace_type = MsDtypAceType::ACCESS_ALLOWED_OBJECT_ACE_TYPE
        when 'OD'
          ace.header.ace_type = MsDtypAceType::ACCESS_DENIED_OBJECT_ACE_TYPE
        when 'AU'
          ace.header.ace_type = MsDtypAceType::SYSTEM_AUDIT_ACE_TYPE
        when 'OU'
          ace.header.ace_type = MsDtypAceType::SYSTEM_AUDIT_OBJECT_ACE_TYPE
        when 'AL', 'OL', 'ML', 'XA', 'SD', 'RA', 'SP', 'XU', 'ZA', 'TL', 'FL'
          raise RuntimeError.new('SDDL parse error on unsupported ACE type: ' + ace_type)
        else
          raise RuntimeError.new('SDDL parse error on unknown ACE type: ' + ace_type)
        end

        ace_flags.split(/(CI|OI|NP|IO|ID|SA|FA|TP|CR)/).each do |flag|
          case flag
          when 'CI'
            ace.header.ace_flags.container_inherit_ace = true
          when 'OI'
            ace.header.ace_flags.object_inherit_ace = true
          when 'NP'
            ace.header.ace_flags.no_propagate_inherit_ace = true
          when 'IO'
            ace.header.ace_flags.inherit_only_ace = true
          when 'ID'
            ace.header.ace_flags.inherited_ace = true
          when 'SA'
            ace.header.ace_flags.successful_access_ace_flag = true
          when 'FA'
            ace.header.ace_flags.failed_access_ace_flag = true
          when 'TP'
            raise RuntimeError.new('SDDL parse error on unsupported ACE flag: TP')
          when 'CR'
            ace.header.ace_flags.critical_ace_flag = true
          when ''
          else
            raise RuntimeError.new('SDDL parse error on unknown ACE flag: ' + flag)
          end
        end

        rights.split(/(G[ARWX]|RC|SD|WD|WO|RP|WP|CC|DC|LC|SW|LO|DT|CR|F[ARWX]|K[ARWX]|N[RWX])/).each do |right|
          case right
          # generic access rights
          when 'GA', 'GR', 'GW', 'GX'
            ace.body.access_mask.send("#{right.downcase}=", true)
          # standard access rights
          when 'RC'
            ace.body.access_mask.rc = true
          when 'SD'
            ace.body.access_mask.de = true
          when 'WD', 'WO'
            ace.body.access_mask.send("#{right.downcase}=", true)
          # directory service object access rights
          when 'RP'
            ace.body.access_mask.protocol |= 16
          when 'WP'
            ace.body.access_mask.protocol |= 32
          when 'CC'
            ace.body.access_mask.protocol |= 1
          when 'DC'
            ace.body.access_mask.protocol |= 2
          when 'LC'
            ace.body.access_mask.protocol |= 4
          when 'SW'
            ace.body.access_mask.protocol |= 8
          when 'LO'
            ace.body.access_mask.protocol |= 128
          when 'DT'
            ace.body.access_mask.protocol |= 64
          when 'CR'
            ace.body.access_mask.protocol |= 256
          # file access rights
          when 'FA'
            ace.body.access_mask.protocol |= 0x1ff
            ace.body.access_mask.de = true
            ace.body.access_mask.rc = true
            ace.body.access_mask.wd = true
            ace.body.access_mask.wo = true
            ace.body.access_mask.sy = true
          when 'FR'
            ace.body.access_mask.protocol |= 0x89
          when 'FW'
            ace.body.access_mask.protocol |= 0x116
          when 'FX'
            ace.body.access_mask.protocol |= 0xa0
          # registry key access rights
          when 'KA'
            ace.body.access_mask.protocol |= 0x3f
            ace.body.access_mask.de = true
            ace.body.access_mask.rc = true
            ace.body.access_mask.wd = true
            ace.body.access_mask.wo = true
          when 'KR'
            ace.body.access_mask.protocol |= 0x19
          when 'KW'
            ace.body.access_mask.protocol |= 0x06
          when 'KX'
            ace.body.access_mask.protocol |= 0x19
          when 'NR', 'NW', 'NX'
            raise RuntimeError.new('SDDL parse error on unsupported ACE access right: ' + right)
          when ''
          else
            raise RuntimeError.new('SDDL parse error on unknown ACE access right: ' + right)
          end
        end

        unless object_guid.blank?
          begin
            guid = MsDtypGuid.new(object_guid)
          rescue StandardError
            raise RuntimeError.new('SDDL parse error on invalid object GUID: ' + object_guid)
          end

          unless ace.body.respond_to?('object_type=')
            raise RuntimeError.new('SDDL error on setting object type for incompatible ACE type')
          end
          ace.body.flags.ace_object_type_present = true
          ace.body.object_type = guid
        end

        unless inherit_object_guid.blank?
          begin
            guid = MsDtypGuid.new(inherit_object_guid)
          rescue StandardError
            raise RuntimeError.new('SDDL parse error on invalid object GUID: ' + inherit_object_guid)
          end

          unless ace.body.respond_to?('inherited_object_type=')
            raise RuntimeError.new('SDDL error on setting object type for incompatible ACE type')
          end
          ace.body.flags.ace_inherited_object_type_present = true
          ace.body.inherited_object_type = guid
        end

        unless account_sid.blank?
          ace.body.sid = self.parse_sddl_sid(account_sid, domain_sid: domain_sid)
        end

        unless resource_attribute.blank?
          raise RuntimeError.new('SDDL parse error on unsupported resource attribute: ' + resource_attribute)
        end

        ace
      end

      def parse_sddl_aces(aces, domain_sid:)
        ace_regex = /\([^\)]*\)/

        invalid_aces = aces.split(ace_regex).reject(&:empty?)
        unless invalid_aces.empty?
          raise RuntimeError.new('SDDL parse error on malformed ACE: ' + invalid_aces.first)
        end

        aces.scan(ace_regex).map do |ace_text|
          self.parse_sddl_ace(ace_text[1...-1], domain_sid: domain_sid)
        end
      end

      def parse_sddl_sid(sid, domain_sid:)
        # see: https://learn.microsoft.com/en-us/windows/win32/secauthz/sid-strings
        sid = sid.dup.upcase

        # these can be validated using powershell where ?? is the code
        #   (ConvertFrom-SddlString -Sddl "O:??").RawDescriptor.Owner
        case sid
        when 'AA'  # SDDL_ACCESS_CONTROL_ASSISTANCE_OPS
          sid = Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_ACCESS_CONTROL_ASSISTANCE_OPS
        when 'AC'  # SDDL_ALL_APP_PACKAGES
          sid = Rex::Proto::Secauthz::WellKnownSids::SECURITY_ALL_APP_PACKAGES
        when 'AN'  # SDDL_ANONYMOUS
          sid = Rex::Proto::Secauthz::WellKnownSids::SECURITY_ANONYMOUS_LOGON_SID
        when 'AO'  # SDDL_ACCOUNT_OPERATORS
          sid = Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_ACCOUNT_OPS
        when 'AP'  # SDDL_PROTECTED_USERS
          sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_PROTECTED_USERS}"
        when 'AU'  # SDDL_AUTHENTICATED_USERS
          sid = Rex::Proto::Secauthz::WellKnownSids::SECURITY_AUTHENTICATED_USER_SID
        when 'BA'  # SDDL_BUILTIN_ADMINISTRATORS
          sid = Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_ADMINS
        when 'BG'  # SDDL_BUILTIN_GUESTS
          sid = Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_GUESTS
        when 'BO'  # SDDL_BACKUP_OPERATORS
          sid = Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_BACKUP_OPS
        when 'BU'  # SDDL_BUILTIN_USERS
          sid = Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_USERS
        when 'CA'  # SDDL_CERT_SERV_ADMINISTRATORS
          sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_CERT_ADMINS}"
        when 'CD'  # SDDL_CERTSVC_DCOM_ACCESS
          sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_CERTSVC_DCOM_ACCESS_GROUP}"
        when 'CG'  # SDDL_CREATOR_GROUP
          sid = Rex::Proto::Secauthz::WellKnownSids::SECURITY_CREATOR_GROUP_SID
        when 'CN'  # SDDL_CLONEABLE_CONTROLLERS
          sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_CLONEABLE_CONTROLLERS}"
        when 'CO'  # SDDL_CREATOR_OWNER
          sid = Rex::Proto::Secauthz::WellKnownSids::SECURITY_CREATOR_OWNER_SID
        when 'CY'  # SDDL_CRYPTO_OPERATORS
          sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_CRYPTO_OPERATORS}"
        when 'DA'  # SDDL_DOMAIN_ADMINISTRATORS
          sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_ADMINS}"
        when 'DC'  # SDDL_DOMAIN_COMPUTERS
          sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_COMPUTERS}"
        when 'DD'  # SDDL_DOMAIN_DOMAIN_CONTROLLERS
          sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_CONTROLLERS}"
        when 'DG'  # SDDL_DOMAIN_GUESTS
          sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_GUESTS}"
        when 'DU'  # SDDL_DOMAIN_USERS
          sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_USERS}"
        when 'EA'  # SDDL_ENTERPRISE_ADMINS
          sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_ENTERPRISE_ADMINS}"
        when 'ED'  # SDDL_ENTERPRISE_DOMAIN_CONTROLLERS
          sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_ENTERPRISE_CONTROLLERS_SID}"
        when 'EK'  # SDDL_ENTERPRISE_KEY_ADMINS
          sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_ENTERPRISE_KEY_ADMINS}"
        when 'ER'  # SDDL_EVENT_LOG_READERS
          sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_EVENT_LOG_READERS_GROUP}"
        when 'ES'  # SDDL_RDS_ENDPOINT_SERVERS
          sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_RDS_ENDPOINT_SERVERS}"
        when 'HA'  # SDDL_HYPER_V_ADMINS
          sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_HYPER_V_ADMINS}"
        when 'HI'  # SDDL_ML_HIGH
          sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_MANDATORY_HIGH_RID}"
        when 'IS'  # SDDL_IIS_USERS
          sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_IUSERS}"
        when 'IU'  # SDDL_INTERACTIVE
          sid = Rex::Proto::Secauthz::WellKnownSids::SECURITY_INTERACTIVE_SID
        when 'KA'  # SDDL_KEY_ADMINS
          sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_KEY_ADMINS}"
        when 'LA'  # SDDL_LOCAL_ADMIN
          sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_USER_RID_ADMIN}"
        when 'LG'  # SDDL_LOCAL_GUEST
          sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_USER_RID_GUEST}"
        when 'LS'  # SDDL_LOCAL_SERVICE
          sid = Rex::Proto::Secauthz::WellKnownSids::SECURITY_LOCAL_SERVICE_SID
        when 'LU'  # SDDL_PERFLOG_USERS
          sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_LOGGING_USERS}"
        when 'LW'  # SDDL_ML_LOW
          sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_MANDATORY_LOW_RID}"
        when 'ME'  # SDDL_ML_MEDIUM
          sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_MANDATORY_MEDIUM_RID}"
        when 'MP'  # SDDL_ML_MEDIUM_PLUS
          sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_MANDATORY_MEDIUM_PLUS_RID}"
        when 'MU'  # SDDL_PERFMON_USERS
          sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_MONITORING_USERS}"
        when 'NO'  # SDDL_NETWORK_CONFIGURATION_OPS
          sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_NETWORK_CONFIGURATION_OPS}"
        when 'NS'  # SDDL_NETWORK_SERVICE
          sid = Rex::Proto::Secauthz::WellKnownSids::SECURITY_NETWORK_SERVICE_SID
        when 'NU'  # SDDL_NETWORK
          sid = Rex::Proto::Secauthz::WellKnownSids::SECURITY_NETWORK_SID
        when 'OW'  # SDDL_OWNER_RIGHTS
          sid = "#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_CREATOR_SID_AUTHORITY}-4"
        when 'PA'  # SDDL_GROUP_POLICY_ADMINS
          sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_POLICY_ADMINS}"
        when 'PO'  # SDDL_PRINTER_OPERATORS
          sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_PRINT_OPS}"
        when 'PS'  # SDDL_PERSONAL_SELF
          sid = Rex::Proto::Secauthz::WellKnownSids::SECURITY_PRINCIPAL_SELF_SID
        when 'PU'  # SDDL_POWER_USERS
          sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_POWER_USERS}"
        when 'RA'  # SDDL_RDS_REMOTE_ACCESS_SERVERS
          sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_RDS_REMOTE_ACCESS_SERVERS}"
        when 'RC'  # SDDL_RESTRICTED_CODE
          sid = Rex::Proto::Secauthz::WellKnownSids::SECURITY_RESTRICTED_CODE_SID
        when 'RD'  # SDDL_REMOTE_DESKTOP
          sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_REMOTE_DESKTOP_USERS}"
        when 'RE'  # SDDL_REPLICATOR
          sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_REPLICATOR}"
        when 'RM'  # SDDL_RMS__SERVICE_OPERATORS
          sid = "#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_BUILTIN_DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_REMOTE_MANAGEMENT_USERS}"
        when 'RO'  # SDDL_ENTERPRISE_RO_DCs
          sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_ENTERPRISE_READONLY_DOMAIN_CONTROLLERS}"
        when 'RS'  # SDDL_RAS_SERVERS
          sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_RAS_SERVERS}"
        when 'RU'  # SDDL_ALIAS_PREW2KCOMPACC
          sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_PREW2KCOMPACCESS}"
        when 'SA'  # SDDL_SCHEMA_ADMINISTRATORS
          sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_SCHEMA_ADMINS}"
        when 'SI'  # SDDL_ML_SYSTEM
          sid = Rex::Proto::Secauthz::WellKnownSids::SECURITY_MANDATORY_SYSTEM_SID
        when 'SO'  # SDDL_SERVER_OPERATORS
          sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_SYSTEM_OPS}"
        when 'SS'  # SDDL_SERVICE_ASSERTED
          sid = Rex::Proto::Secauthz::WellKnownSids::SECURITY_AUTHENTICATION_SERVICE_ASSERTED_SID
        when 'SU'  # SDDL_SERVICE
          sid = Rex::Proto::Secauthz::WellKnownSids::SECURITY_SERVICE_SID
        when 'SY'  # SDDL_LOCAL_SYSTEM
          sid = Rex::Proto::Secauthz::WellKnownSids::SECURITY_LOCAL_SYSTEM_SID
        when 'UD'  # SDDL_USER_MODE_DRIVERS
          sid = Rex::Proto::Secauthz::WellKnownSids::SECURITY_USERMODEDRIVERHOST_ID_BASE_SID
        when 'WD'  # SDDL_EVERYONE
          sid = "#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_WORLD_SID_AUTHORITY}-#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_WORLD_RID}"
        when 'WR'  # SDDL_WRITE_RESTRICTED_CODE
          sid = Rex::Proto::Secauthz::WellKnownSids::SECURITY_WRITE_RESTRICTED_CODE_SID
        when /^S(-\d+)+/
        else
          raise RuntimeError, 'SDDL parse error on invalid SID string: ' + sid
        end


        MsDtypSid.new(sid)
      end
    end

    def initialize_shared_instance
      # define accessor methods for the custom fields to expose the same API as BinData
      define_field_accessors_for2(:owner_sid)
      define_field_accessors_for2(:group_sid)
      define_field_accessors_for2(:sacl)
      define_field_accessors_for2(:dacl)
      super
    end

    def initialize_instance
      value = super
      @owner_sid = get_parameter(:owner_sid)
      @group_sid = get_parameter(:group_sid)
      @sacl = get_parameter(:sacl)
      @dacl = get_parameter(:dacl)
      value
    end

    def do_read(val)
      value = super
      if offset_owner != 0
        @owner_sid = MsDtypSid.read(buffer[offset_owner - buffer.rel_offset..])
      end
      if offset_group != 0
        @group_sid = MsDtypSid.read(buffer[offset_group - buffer.rel_offset..])
      end
      if offset_sacl != 0
        @sacl = MsDtypAcl.read(buffer[offset_sacl - buffer.rel_offset..])
      end
      if offset_dacl != 0
        @dacl = MsDtypAcl.read(buffer[offset_dacl - buffer.rel_offset..])
      end
      value
    end

    def snapshot
      snap = super
      snap[:owner_sid] ||= owner_sid&.snapshot
      snap[:group_sid] ||= group_sid&.snapshot
      snap[:sacl] ||= sacl&.snapshot
      snap[:dacl] ||= dacl&.snapshot
      snap
    end

    attr_accessor :owner_sid, :group_sid, :sacl, :dacl

    private

    def build_buffer
      buf = ''
      buf << owner_sid.to_binary_s if owner_sid
      buf << group_sid.to_binary_s if group_sid
      buf << sacl.to_binary_s if sacl
      buf << dacl.to_binary_s if dacl
      buf
    end

    def define_field_accessors_for2(name)
      define_singleton_method("#{name}?") do
        !send(name).nil?
      end
    end

    def offset_for(field)
      return 0 unless instance_variable_get("@#{field}")

      offset = buffer.rel_offset
      %i[ owner_sid group_sid sacl dacl ].each do |cursor|
        break if cursor == field

        cursor = instance_variable_get("@#{cursor}")
        offset += cursor.num_bytes if cursor
      end

      offset
    end
  end

  # [2.3.7 LUID](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/48cbee2a-0790-45f2-8269-931d7083b2c3)
  class MsDtypLuid < BinData::Record
    endian :little

    uint32 :low_part
    int32  :high_part

    def to_s
      "0x#{high_part.to_i.to_s(16)}#{low_part.to_i.to_s(16).rjust(8, '0')}"
    end
  end

  # [2.3.5 LARGE_INTEGER](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/e904b1ba-f774-4203-ba1b-66485165ab1a)
  class MsDtypLargeInteger < BinData::Record
    endian :big_and_little

    uint32 :low_part
    int32  :high_part

    def to_datetime
      RubySMB::Field::FileTime.new(to_i).to_datetime
    end

    def to_i
      (high_part.to_i << 32) | low_part.to_i
    end
  end
end