Path: blob/master/lib/rex/proto/ms_dtyp.rb
19669 views
# -*- coding: binary -*-12require 'bindata'3require 'ruby_smb'4require 'rex/proto/secauthz/well_known_sids'56module Rex::Proto::MsDtyp7class SDDLParseError < Rex::RuntimeError8end910# [2.4.3 ACCESS_MASK](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/7a53f60e-e730-4dfe-bbe9-b21b62eb790b)11class MsDtypAccessMask < BinData::Record12endian :little13hide :reserved0, :reserved11415# the protocol field id reserved for protocol-specific access rights16uint16 :protocol1718bit3 :reserved019bit1 :sy20bit1 :wo21bit1 :wd22bit1 :rc23bit1 :de2425bit1 :gr26bit1 :gw27bit1 :gx28bit1 :ga29bit2 :reserved130bit1 :ma31bit1 :as3233ALL = 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 })34NONE = 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 })3536# Obtain an array of the abbreviated names of permissions that the access mask specifies.37#38# @return Returns nil if the permissions can't be represented as an array of abbreviations.39# @rtype [Array<Symbol>, nil]40def permissions41if (protocol & 0b1111111000000000) != 0 || ma == 1 || as == 142return nil43end4445permissions = []46permissions << :GA if ga == 147permissions << :GR if gr == 148permissions << :GW if gw == 149permissions << :GX if gx == 15051file_access_mask = protocol & 0b00011111111152permissions << :FA if file_access_mask == 0b000111111111 && de == 1 && rc == 1 && wd == 1 && wo == 1 && sy == 153permissions << :FR if file_access_mask == 0b00001000100154permissions << :FW if file_access_mask == 0b00010001011055permissions << :FX if file_access_mask == 0b0000101000005657# windows does not reduce registry access flags (i.e. KA, KR, KW) so ignore them here to match it5859permissions << :CC if (protocol & 0b000000000001) != 0 && !permissions.include?(:FA) && !permissions.include?(:FR)60permissions << :DC if (protocol & 0b000000000010) != 0 && !permissions.include?(:FA) && !permissions.include?(:FW)61permissions << :LC if (protocol & 0b000000000100) != 0 && !permissions.include?(:FA) && !permissions.include?(:FW)62permissions << :SW if (protocol & 0b000000001000) != 0 && !permissions.include?(:FA) && !permissions.include?(:FR)63permissions << :RP if (protocol & 0b000000010000) != 0 && !permissions.include?(:FA) && !permissions.include?(:FW)64permissions << :WP if (protocol & 0b000000100000) != 0 && !permissions.include?(:FA) && !permissions.include?(:FX)65permissions << :DT if (protocol & 0b000001000000) != 0 && !permissions.include?(:FA)66permissions << :LO if (protocol & 0b000010000000) != 0 && !permissions.include?(:FA)67permissions << :CR if (protocol & 0b000100000000) != 0 && !permissions.include?(:FA)6869permissions << :SD if de == 1 && !permissions.include?(:FA)70permissions << :RC if rc == 1 && !permissions.include?(:FA)71permissions << :WD if wd == 1 && !permissions.include?(:FA)72permissions << :WO if wo == 1 && !permissions.include?(:FA)7374permissions75end7677def to_sddl_text78perms = permissions7980if perms.nil?81# if one of these conditions are true, we can't reduce this to a set of flags so dump it as hex82return "0x#{to_binary_s.unpack1('L<').to_s(16).rjust(8, '0')}"83end848586permissions.map(&:to_s).join87end8889def self.from_sddl_text(sddl_text)90if sddl_text =~ /\A0x[0-9a-fA-F]{1,8}\Z/91return self.read([sddl_text.delete_prefix('0x').to_i(16)].pack('L<'))92end9394access_mask = self.new95sddl_text.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|96case right97# generic access rights98when 'GA', 'GR', 'GW', 'GX'99access_mask.send("#{right.downcase}=", true)100# standard access rights101when 'RC'102access_mask.rc = true103when 'SD'104access_mask.de = true105when 'WD', 'WO'106access_mask.send("#{right.downcase}=", true)107# directory service object access rights108when 'RP'109access_mask.protocol |= 16110when 'WP'111access_mask.protocol |= 32112when 'CC'113access_mask.protocol |= 1114when 'DC'115access_mask.protocol |= 2116when 'LC'117access_mask.protocol |= 4118when 'SW'119access_mask.protocol |= 8120when 'LO'121access_mask.protocol |= 128122when 'DT'123access_mask.protocol |= 64124when 'CR'125access_mask.protocol |= 256126# file access rights127when 'FA'128access_mask.protocol |= 0x1ff129access_mask.de = true130access_mask.rc = true131access_mask.wd = true132access_mask.wo = true133access_mask.sy = true134when 'FR'135access_mask.protocol |= 0x89136when 'FW'137access_mask.protocol |= 0x116138when 'FX'139access_mask.protocol |= 0xa0140# registry key access rights141when 'KA'142access_mask.protocol |= 0x3f143access_mask.de = true144access_mask.rc = true145access_mask.wd = true146access_mask.wo = true147when 'KR'148access_mask.protocol |= 0x19149when 'KW'150access_mask.protocol |= 0x06151when 'KX'152access_mask.protocol |= 0x19153when 'NR', 'NW', 'NX'154raise SDDLParseError.new('unsupported ACE access right: ' + right)155when ''156else157raise SDDLParseError.new('unknown ACE access right: ' + right)158end159end160161access_mask162end163end164165# [2.4.2.2 SID--Packet Representation](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/f992ad60-0fe4-4b87-9fed-beb478836861)166class MsDtypSid < BinData::Primitive167endian :little168169uint8 :revision, initial_value: 1170uint8 :sub_authority_count, initial_value: -> { self.sub_authority.length }171array :identifier_authority, type: :uint8, initial_length: 6172array :sub_authority, type: :uint32, initial_length: :sub_authority_count173174def set(val)175# allow assignment from the human-readable string representation176raise ArgumentError.new("Invalid SID: #{val}") unless val.is_a?(String) && val =~ /^S-1-(\d+)(-\d+)*$/177178_, _, ia, sa = val.split('-', 4)179self.identifier_authority = [ia.to_i].pack('Q>')[2..].bytes180self.sub_authority = sa.nil? ? [] : sa.split('-').map(&:to_i)181end182183def get184str = 'S-1'185str << "-#{("\x00\x00" + identifier_authority.to_binary_s).unpack1('Q>')}"186str << '-' + sub_authority.map(&:to_s).join('-') unless sub_authority.empty?187str188end189190def rid191sub_authority.last192end193194# these can be validated using powershell where ?? is the code195# (ConvertFrom-SddlString -Sddl "O:??").RawDescriptor.Owner196SDDL_SIDS = {197'AA' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_ACCESS_CONTROL_ASSISTANCE_OPS, # SDDL_ACCESS_CONTROL_ASSISTANCE_OPS198'AC' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_ALL_APP_PACKAGES, # SDDL_ALL_APP_PACKAGES199'AN' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_ANONYMOUS_LOGON_SID, # SDDL_ANONYMOUS200'AO' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_ACCOUNT_OPS, # SDDL_ACCOUNT_OPERATORS201'AP' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_PROTECTED_USERS}", # SDDL_PROTECTED_USERS202'AU' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_AUTHENTICATED_USER_SID, # SDDL_AUTHENTICATED_USERS203'BA' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_ADMINS, # SDDL_BUILTIN_ADMINISTRATORS204'BG' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_GUESTS, # SDDL_BUILTIN_GUESTS205'BO' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_BACKUP_OPS, # SDDL_BACKUP_OPERATORS206'BU' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_USERS, # SDDL_BUILTIN_USERS207'CA' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_CERT_ADMINS}", # SDDL_CERT_SERV_ADMINISTRATORS208'CD' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_CERTSVC_DCOM_ACCESS_GROUP, # SDDL_CERTSVC_DCOM_ACCESS209'CG' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_CREATOR_GROUP_SID, # SDDL_CREATOR_GROUP210'CN' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_CLONEABLE_CONTROLLERS}", # SDDL_CLONEABLE_CONTROLLERS211'CO' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_CREATOR_OWNER_SID, # SDDL_CREATOR_OWNER212'CY' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_CRYPTO_OPERATORS, # SDDL_CRYPTO_OPERATORS213'DA' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_ADMINS}", # SDDL_DOMAIN_ADMINISTRATORS214'DC' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_COMPUTERS}", # SDDL_DOMAIN_COMPUTERS215'DD' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_CONTROLLERS}", # SDDL_DOMAIN_DOMAIN_CONTROLLERS216'DG' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_GUESTS}", # SDDL_DOMAIN_GUESTS217'DU' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_USERS}", # SDDL_DOMAIN_USERS218'EA' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_ENTERPRISE_ADMINS}", # SDDL_ENTERPRISE_ADMINS219'ED' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_ENTERPRISE_CONTROLLERS_SID, # SDDL_ENTERPRISE_DOMAIN_CONTROLLERS220'EK' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_ENTERPRISE_KEY_ADMINS}", # SDDL_ENTERPRISE_KEY_ADMINS221'ER' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_EVENT_LOG_READERS_GROUP, # SDDL_EVENT_LOG_READERS222'ES' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_RDS_ENDPOINT_SERVERS, # SDDL_RDS_ENDPOINT_SERVERS223'HA' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_HYPER_V_ADMINS, # SDDL_HYPER_V_ADMINS224'HI' => "S-1-16-#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_MANDATORY_HIGH_RID}", # SDDL_ML_HIGH225'IS' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_IUSERS, # SDDL_IIS_USERS226'IU' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_INTERACTIVE_SID, # SDDL_INTERACTIVE227'KA' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_KEY_ADMINS}", # SDDL_KEY_ADMINS228'LA' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_USER_RID_ADMIN}", # SDDL_LOCAL_ADMIN229'LG' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_USER_RID_GUEST}", # SDDL_LOCAL_GUEST230'LS' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_LOCAL_SERVICE_SID, # SDDL_LOCAL_SERVICE231'LU' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_LOGGING_USERS, # SDDL_PERFLOG_USERS232'LW' => "S-1-16-#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_MANDATORY_LOW_RID}", # SDDL_ML_LOW233'ME' => "S-1-16-#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_MANDATORY_MEDIUM_RID}", # SDDL_ML_MEDIUM234'MP' => "S-1-16-#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_MANDATORY_MEDIUM_PLUS_RID}", # SDDL_ML_MEDIUM_PLUS235'MU' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_MONITORING_USERS, # SDDL_PERFMON_USERS236'NO' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_NETWORK_CONFIGURATION_OPS, # SDDL_NETWORK_CONFIGURATION_OPS237'NS' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_NETWORK_SERVICE_SID, # SDDL_NETWORK_SERVICE238'NU' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_NETWORK_SID, # SDDL_NETWORK239'OW' => "#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_CREATOR_SID_AUTHORITY}-4", # SDDL_OWNER_RIGHTS240'PA' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_POLICY_ADMINS}", # SDDL_GROUP_POLICY_ADMINS241'PO' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_PRINT_OPS, # SDDL_PRINTER_OPERATORS242'PS' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_PRINCIPAL_SELF_SID, # SDDL_PERSONAL_SELF243'PU' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_POWER_USERS, # SDDL_POWER_USERS244'RA' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_RDS_REMOTE_ACCESS_SERVERS, # SDDL_RDS_REMOTE_ACCESS_SERVERS245'RC' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_RESTRICTED_CODE_SID, # SDDL_RESTRICTED_CODE246'RD' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_REMOTE_DESKTOP_USERS, # SDDL_REMOTE_DESKTOP247'RE' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_REPLICATOR, # SDDL_REPLICATOR248'RM' => "#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_BUILTIN_DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_REMOTE_MANAGEMENT_USERS}", # SDDL_RMS__SERVICE_OPERATORS249'RO' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_ENTERPRISE_READONLY_DOMAIN_CONTROLLERS}", # SDDL_ENTERPRISE_RO_DCs250'RS' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_RAS_SERVERS}", # SDDL_RAS_SERVERS251'RU' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_PREW2KCOMPACCESS, # SDDL_ALIAS_PREW2KCOMPACC252'SA' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_SCHEMA_ADMINS}", # SDDL_SCHEMA_ADMINISTRATORS253'SI' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_MANDATORY_SYSTEM_SID, # SDDL_ML_SYSTEM254'SO' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_SYSTEM_OPS, # SDDL_SERVER_OPERATORS255'SS' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_AUTHENTICATION_SERVICE_ASSERTED_SID, # SDDL_SERVICE_ASSERTED256'SU' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_SERVICE_SID, # SDDL_SERVICE257'SY' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_LOCAL_SYSTEM_SID, # SDDL_LOCAL_SYSTEM258'UD' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_USERMODEDRIVERHOST_ID_BASE_SID, # SDDL_USER_MODE_DRIVERS259'WD' => "#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_WORLD_SID_AUTHORITY}-#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_WORLD_RID}", # SDDL_EVERYONE260'WR' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_WRITE_RESTRICTED_CODE_SID # SDDL_WRITE_RESTRICTED_CODE261}.freeze262263private_constant :SDDL_SIDS264265def to_sddl_text(domain_sid: nil)266sid = to_s267268lookup = domain_sid.blank? ? sid : sid.sub(domain_sid, '${DOMAIN_SID}')269if (sddl_text = self.class.const_get(:SDDL_SIDS).key(lookup)).nil?270sddl_text = sid271end272# these short names aren't supported by all versions of Windows, avoid compatibility issues by not outputting them273sddl_text = sid if %w[ AP CN EK KA ].include?(sddl_text)274275sddl_text276end277278def self.from_sddl_text(sddl_text, domain_sid:)279# see: https://learn.microsoft.com/en-us/windows/win32/secauthz/sid-strings280sddl_text = sddl_text.dup.upcase281282if SDDL_SIDS.key?(sddl_text)283sid_text = SDDL_SIDS[sddl_text].sub('${DOMAIN_SID}', domain_sid)284elsif sddl_text =~ /^S(-\d+)+/285sid_text = sddl_text286else287raise SDDLParseError.new('invalid SID string: ' + sddl_text)288end289290self.new(sid_text)291end292end293294# [Universal Unique Identifier](http://pubs.opengroup.org/onlinepubs/9629399/apdxa.htm)295# The online documentation at [2.3.4.2](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/001eec5a-7f8b-4293-9e21-ca349392db40)296# weirdly doesn't mention this needs to be 4 byte aligned for us to read it correctly,297# which the RubySMB::Dcerpc::Uuid definition takes care of.298class MsDtypGuid < RubySMB::Dcerpc::Uuid299def self.random_generate300# Taken from the "D" format as specified in301# https://learn.microsoft.com/en-us/dotnet/api/system.guid.tostring?view=net-7.0302"{#{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)}}".downcase303end304end305306# 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)307class MsDtypAceType308ACCESS_ALLOWED_ACE_TYPE = 0x0309ACCESS_DENIED_ACE_TYPE = 0x1310SYSTEM_AUDIT_ACE_TYPE = 0x2311SYSTEM_ALARM_ACE_TYPE = 0x3 # Reserved for future use according to documentation.312ACCESS_ALLOWED_COMPOUND_ACE_TYPE = 0x4 # Reserved for future use according to documentation.313ACCESS_ALLOWED_OBJECT_ACE_TYPE = 0x5314ACCESS_DENIED_OBJECT_ACE_TYPE = 0x6315SYSTEM_AUDIT_OBJECT_ACE_TYPE = 0x7316SYSTEM_ALARM_OBJECT_ACE_TYPE = 0x8 # Reserved for future use according to documentation.317ACCESS_ALLOWED_CALLBACK_ACE_TYPE = 0x9318ACCESS_DENIED_CALLBACK_ACE_TYPE = 0xA319ACCESS_ALLOWED_CALLBACK_OBJECT_ACE_TYPE = 0xB320ACCESS_DENIED_CALLBACK_OBJECT_ACE_TYPE = 0xC321SYSTEM_AUDIT_CALLBACK_ACE_TYPE = 0xD322SYSTEM_ALARM_CALLBACK_ACE_TYPE = 0xE # Reserved for future use according to documentation.323SYSTEM_AUDIT_CALLBACK_OBJECT_ACE_TYPE = 0xF324SYSTEM_ALARM_CALLBACK_OBJECT_ACE_TYPE = 0x10 # Reserved for future use according to documentation.325SYSTEM_MANDATORY_LABEL_ACE_TYPE = 0x11326SYSTEM_RESOURCE_ATTRIBUTE_ACE_TYPE = 0x12327SYSTEM_SCOPED_POLICY_ID_ACE_TYPE = 0x13328329def self.name(value)330constants.select { |c| c.upcase == c }.find { |c| const_get(c) == value }331end332333def self.alarm?(type)334[335SYSTEM_ALARM_ACE_TYPE,336SYSTEM_ALARM_OBJECT_ACE_TYPE,337SYSTEM_ALARM_CALLBACK_ACE_TYPE,338SYSTEM_ALARM_CALLBACK_OBJECT_ACE_TYPE,339].include? type340end341342def self.allow?(type)343[344ACCESS_ALLOWED_ACE_TYPE,345ACCESS_ALLOWED_COMPOUND_ACE_TYPE,346ACCESS_ALLOWED_OBJECT_ACE_TYPE,347ACCESS_ALLOWED_CALLBACK_ACE_TYPE,348ACCESS_ALLOWED_CALLBACK_OBJECT_ACE_TYPE,349].include? type350end351352def self.audit?(type)353[354SYSTEM_AUDIT_ACE_TYPE,355SYSTEM_AUDIT_OBJECT_ACE_TYPE,356SYSTEM_AUDIT_CALLBACK_ACE_TYPE,357SYSTEM_AUDIT_CALLBACK_OBJECT_ACE_TYPE,358].include? type359end360361def self.deny?(type)362[363ACCESS_DENIED_ACE_TYPE,364ACCESS_DENIED_OBJECT_ACE_TYPE,365ACCESS_DENIED_CALLBACK_ACE_TYPE,366ACCESS_DENIED_CALLBACK_OBJECT_ACE_TYPE,367].include? type368end369370def self.has_object?(type)371[372ACCESS_ALLOWED_OBJECT_ACE_TYPE,373ACCESS_DENIED_OBJECT_ACE_TYPE,374SYSTEM_AUDIT_OBJECT_ACE_TYPE,375SYSTEM_ALARM_OBJECT_ACE_TYPE,376ACCESS_ALLOWED_CALLBACK_OBJECT_ACE_TYPE,377ACCESS_DENIED_CALLBACK_OBJECT_ACE_TYPE,378SYSTEM_AUDIT_CALLBACK_OBJECT_ACE_TYPE,379SYSTEM_ALARM_CALLBACK_OBJECT_ACE_TYPE380].include? type381end382end383384# [2.4.4.1 ACE_HEADER](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/628ebb1d-c509-4ea0-a10f-77ef97ca4586)385class MsDtypAceHeader < BinData::Record386endian :little387388uint8 :ace_type389struct :ace_flags do390bit1 :failed_access_ace_flag391bit1 :successful_access_ace_flag392bit1 :critical_ace_flag # used only with access allowed ACE types, see: https://www.codemachine.com/downloads/win10.1903/ntifs.h393bit1 :inherited_ace394bit1 :inherit_only_ace395bit1 :no_propagate_inherit_ace396bit1 :container_inherit_ace397bit1 :object_inherit_ace398end399uint16 :ace_size, initial_value: -> { parent&.num_bytes || 0 }400end401402class MsDtypAceNonObjectBody < BinData::Record403endian :little404405ms_dtyp_access_mask :access_mask406ms_dtyp_sid :sid, byte_align: 4407end408409class MsDtypAceObjectBody < BinData::Record410endian :little411412ms_dtyp_access_mask :access_mask413struct :flags do414bit1 :reserved5415bit1 :reserved4416bit1 :reserved3417bit1 :reserved2418bit1 :reserved1419bit1 :reserved420bit1 :ace_inherited_object_type_present421bit1 :ace_object_type_present422end423ms_dtyp_guid :object_type, onlyif: -> { flags.ace_object_type_present != 0x0 }424ms_dtyp_guid :inherited_object_type, onlyif: -> { flags.ace_inherited_object_type_present != 0x0 }425ms_dtyp_sid :sid, byte_align: 4426end427428# [2.4.4.2 ACCESS_ALLOWED_ACE](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/72e7c7ea-bc02-4c74-a619-818a16bf6adb)429class MsDtypAccessAllowedAceBody < MsDtypAceNonObjectBody430end431432# [2.4.4.4 ACCESS_DENIED_ACE](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/b1e1321d-5816-4513-be67-b65d8ae52fe8)433class MsDtypAccessDeniedAceBody < MsDtypAceNonObjectBody434end435436# [2.4.4.10 SYSTEM_AUDIT_ACE](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/9431fd0f-5b9a-47f0-b3f0-3015e2d0d4f9)437class MsDtypSystemAuditAceBody < MsDtypAceNonObjectBody438end439440# [2.4.4.3 ACCESS_ALLOWED_OBJECT_ACE](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/c79a383c-2b3f-4655-abe7-dcbb7ce0cfbe)441class MsDtypAccessAllowedObjectAceBody < MsDtypAceObjectBody442end443444# [2.4.4.5 ACCESS_DENIED_OBJECT_ACE](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/8720fcf3-865c-4557-97b1-0b3489a6c270)445class MsDtypAccessDeniedObjectAceBody < MsDtypAceObjectBody446end447448# [2.4.4.11 SYSTEM_AUDIT_OBJECT_ACE](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/c8da72ae-6b54-4a05-85f4-e2594936d3d5)449class MsDtypSystemAuditObjectAceBody < MsDtypAceObjectBody450endian :little451452string :application_data, read_length: -> { calc_app_data_length }453454def calc_app_data_length455ace_header = parent&.parent&.header456ace_body = parent&.parent&.body457return 0 if ace_header.nil? || ace_body.nil?458459ace_size = ace_header.ace_size460return 0 if ace_size.nil? or (ace_size == 0)461462ace_header_length = ace_header.to_binary_s.length463if ace_body.nil?464return 0 # Read no data as there is no body, so either we have done some data misalignment or we shouldn't be reading data.465else466ace_body_length = ace_body.to_binary_s.length467return ace_size - (ace_header_length + ace_body_length)468end469end470end471472class MsDtypAce < BinData::Record473endian :little474475ms_dtyp_ace_header :header476choice :body, selection: -> { header.ace_type } do477ms_dtyp_access_allowed_ace_body Rex::Proto::MsDtyp::MsDtypAceType::ACCESS_ALLOWED_ACE_TYPE478ms_dtyp_access_denied_ace_body Rex::Proto::MsDtyp::MsDtypAceType::ACCESS_DENIED_ACE_TYPE479ms_dtyp_system_audit_ace_body Rex::Proto::MsDtyp::MsDtypAceType::SYSTEM_AUDIT_ACE_TYPE480# Type 3 is reserved for future use481# Type 4 is reserved for future use482ms_dtyp_access_allowed_object_ace_body Rex::Proto::MsDtyp::MsDtypAceType::ACCESS_ALLOWED_OBJECT_ACE_TYPE483ms_dtyp_access_denied_object_ace_body Rex::Proto::MsDtyp::MsDtypAceType::ACCESS_DENIED_OBJECT_ACE_TYPE484ms_dtyp_system_audit_object_ace_body Rex::Proto::MsDtyp::MsDtypAceType::SYSTEM_AUDIT_OBJECT_ACE_TYPE485# Type 8 is reserved for future use486# Type 14 aka 0xE is reserved for future use487# Type 16 aka 0x10 is reserved for future use488string :default, read_length: -> { header.ace_size - body.rel_offset }489end490491def to_sddl_text(domain_sid: nil)492parts = []493494case header.ace_type495when MsDtypAceType::ACCESS_ALLOWED_ACE_TYPE496parts << 'A'497when MsDtypAceType::ACCESS_DENIED_ACE_TYPE498parts << 'D'499when MsDtypAceType::ACCESS_ALLOWED_OBJECT_ACE_TYPE500parts << 'OA'501when MsDtypAceType::ACCESS_DENIED_OBJECT_ACE_TYPE502parts << 'OD'503when MsDtypAceType::SYSTEM_AUDIT_ACE_TYPE504parts << 'AU'505when MsDtypAceType::SYSTEM_AUDIT_OBJECT_ACE_TYPE506parts << 'OU'507else508raise SDDLParseError.new('unknown ACE type: ' + header.ace_type.to_i)509end510511ace_flags = ''512ace_flags << 'OI' if header.ace_flags.object_inherit_ace == 1513ace_flags << 'CI' if header.ace_flags.container_inherit_ace == 1514ace_flags << 'IO' if header.ace_flags.inherit_only_ace == 1515516ace_flags << 'NP' if header.ace_flags.no_propagate_inherit_ace == 1517ace_flags << 'ID' if header.ace_flags.inherited_ace == 1518ace_flags << 'SA' if header.ace_flags.successful_access_ace_flag == 1519ace_flags << 'FA' if header.ace_flags.failed_access_ace_flag == 1520ace_flags << 'CR' if header.ace_flags.critical_ace_flag == 1521parts << ace_flags522523parts << body.access_mask.to_sddl_text524525if body[:flags]526parts << (body.flags[:ace_object_type_present] == 1 ? body.object_type.to_s : '')527parts << (body.flags[:ace_inherited_object_type_present] == 1 ? body.inherited_object_type.to_s : '')528else529parts << ''530parts << ''531end532533if body.sid?534parts << body.sid.to_sddl_text(domain_sid: domain_sid)535else536parts << ''537end538539parts.join(';')540end541542def self.from_sddl_text(sddl_text, domain_sid:)543parts = sddl_text.upcase.split(';', -1)544raise SDDLParseError.new('too few ACE fields') if parts.length < 6545raise SDDLParseError.new('too many ACE fields') if parts.length > 7546547ace_type, ace_flags, rights, object_guid, inherit_object_guid, account_sid = parts[0...6]548resource_attribute = parts[6]549550ace = self.new551case ace_type552when 'A'553ace.header.ace_type = MsDtypAceType::ACCESS_ALLOWED_ACE_TYPE554when 'D'555ace.header.ace_type = MsDtypAceType::ACCESS_DENIED_ACE_TYPE556when 'OA'557ace.header.ace_type = MsDtypAceType::ACCESS_ALLOWED_OBJECT_ACE_TYPE558when 'OD'559ace.header.ace_type = MsDtypAceType::ACCESS_DENIED_OBJECT_ACE_TYPE560when 'AU'561ace.header.ace_type = MsDtypAceType::SYSTEM_AUDIT_ACE_TYPE562when 'OU'563ace.header.ace_type = MsDtypAceType::SYSTEM_AUDIT_OBJECT_ACE_TYPE564when 'AL', 'OL', 'ML', 'XA', 'SD', 'RA', 'SP', 'XU', 'ZA', 'TL', 'FL'565raise SDDLParseError.new('unsupported ACE type: ' + ace_type)566else567raise SDDLParseError.new('unknown ACE type: ' + ace_type)568end569570ace_flags.split(/(CI|OI|NP|IO|ID|SA|FA|TP|CR)/).each do |flag|571case flag572when 'CI'573ace.header.ace_flags.container_inherit_ace = true574when 'OI'575ace.header.ace_flags.object_inherit_ace = true576when 'NP'577ace.header.ace_flags.no_propagate_inherit_ace = true578when 'IO'579ace.header.ace_flags.inherit_only_ace = true580when 'ID'581ace.header.ace_flags.inherited_ace = true582when 'SA'583ace.header.ace_flags.successful_access_ace_flag = true584when 'FA'585ace.header.ace_flags.failed_access_ace_flag = true586when 'TP'587raise SDDLParseError.new('unsupported ACE flag: TP')588when 'CR'589ace.header.ace_flags.critical_ace_flag = true590when ''591else592raise SDDLParseError.new('unknown ACE flag: ' + flag)593end594end595596ace.body.access_mask = MsDtypAccessMask.from_sddl_text(rights)597598unless object_guid.blank?599begin600guid = MsDtypGuid.new(object_guid)601rescue StandardError602raise SDDLParseError.new('invalid object GUID: ' + object_guid)603end604605unless ace.body.respond_to?('object_type=')606raise SDDLParseError.new('setting object type for incompatible ACE type')607end608ace.body.flags.ace_object_type_present = true609ace.body.object_type = guid610end611612unless inherit_object_guid.blank?613begin614guid = MsDtypGuid.new(inherit_object_guid)615rescue StandardError616raise SDDLParseError.new('invalid inherited object GUID: ' + inherit_object_guid)617end618619unless ace.body.respond_to?('inherited_object_type=')620raise SDDLParseError.new('setting inherited object type for incompatible ACE type')621end622ace.body.flags.ace_inherited_object_type_present = true623ace.body.inherited_object_type = guid624end625626unless account_sid.blank?627ace.body.sid = MsDtypSid.from_sddl_text(account_sid, domain_sid: domain_sid)628end629630unless resource_attribute.blank?631raise SDDLParseError.new('unsupported resource attribute: ' + resource_attribute)632end633634ace635end636end637638# [2.4.5 ACL](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/20233ed8-a6c6-4097-aafa-dd545ed24428)639class MsDtypAcl < BinData::Record640ACL_REVISION = 2641ACL_REVISION_DS = 4642643endian :little644645uint8 :acl_revision, initial_value: ACL_REVISION646uint8 :sbz1647uint16 :acl_size, initial_value: -> { num_bytes }648uint16 :acl_count, initial_value: -> { aces.length }649uint16 :sbz2650array :aces, type: :ms_dtyp_ace, initial_length: :acl_count651end652653# [2.4.6 SECURITY_DESCRIPTOR](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/7d4dac05-9cef-4563-a058-f108abecce1d)654class MsDtypSecurityDescriptor < BinData::Record655endian :little656657uint8 :revision, initial_value: 1658uint8 :sbz1659struct :control do660bit1 :ss661bit1 :dt662bit1 :sd663bit1 :sp, initial_value: -> { sacl? ? 1 : 0 }664bit1 :dd665bit1 :dp, initial_value: -> { dacl? ? 1 : 0 }666bit1 :gd667bit1 :od668669bit1 :sr, initial_value: 1670bit1 :rm671bit1 :ps672bit1 :pd673bit1 :si674bit1 :di675bit1 :sc676bit1 :dc677end678uint32 :offset_owner, value: -> { offset_for(:owner_sid) }679uint32 :offset_group, value: -> { offset_for(:group_sid) }680uint32 :offset_sacl, value: -> { offset_for(:sacl) }681uint32 :offset_dacl, value: -> { offset_for(:dacl) }682rest :buffer, value: -> { build_buffer }683hide :buffer684685def to_sddl_text(domain_sid: nil)686sddl_text = ''687sddl_text << "O:#{owner_sid.to_sddl_text(domain_sid: domain_sid)}" if owner_sid?688sddl_text << "G:#{group_sid.to_sddl_text(domain_sid: domain_sid)}" if group_sid?689sddl_text << "D:#{dacl_to_sddl_text(domain_sid: domain_sid)}" if dacl?690sddl_text << "S:#{sacl_to_sddl_text(domain_sid: domain_sid)}" if sacl?691692sddl_text693end694695def dacl_to_sddl_text(domain_sid: nil)696sddl_text = ''697698if !dacl?699sddl_text << 'NO_ACCESS_CONTROL'700else701sddl_text << 'P' if control.pd == 1702sddl_text << 'AR' if control.dc == 1703sddl_text << 'AI' if control.di == 1704sddl_text << dacl.aces.map { |ace| "(#{ace.to_sddl_text(domain_sid: domain_sid)})" }.join705end706707sddl_text708end709710def sacl_to_sddl_text(domain_sid: nil)711sddl_text = ''712713if !sacl?714sddl_text << 'NO_ACCESS_CONTROL'715else716sddl_text << 'P' if control.ps == 1717sddl_text << 'AR' if control.sc == 1718sddl_text << 'AI' if control.si == 1719sddl_text << sacl.aces.map { |ace| "(#{ace.to_sddl_text(domain_sid: domain_sid)})" }.join720end721722sddl_text723end724725def self.from_sddl_text(sddl_text, domain_sid:)726sacl_set = dacl_set = false727sd = self.new728sddl_text = sddl_text.dup.gsub(/\s/, '') # start by removing all whitespace729sddl_text.scan(/([OGDS]:(?:.(?!:))*)/).each do |part,|730component, _, value = part.partition(':')731case component732when 'O'733if sd.owner_sid.present?734raise SDDLParseError.new('extra owner SID')735end736737sd.owner_sid = MsDtypSid.from_sddl_text(value, domain_sid: domain_sid)738when 'G'739if sd.group_sid.present?740raise SDDLParseError.new('extra group SID')741end742743sd.group_sid = MsDtypSid.from_sddl_text(value, domain_sid: domain_sid)744when 'D'745raise SDDLParseError.new('extra DACL') if dacl_set746747value.upcase!748dacl_set = true749access_control = true750flags = value.split('(', 2).first || ''751flags.split(/(P|AR|AI|NO_ACCESS_CONTROL)/).each do |flag|752case flag753when 'AI'754sd.control.di = true755when 'AR'756sd.control.dc = true757when 'P'758sd.control.pd = true759when 'NO_ACCESS_CONTROL'760access_control = false761when ''762else763raise SDDLParseError.new('unknown DACL flag: ' + flag)764end765end766767next unless access_control768769sd.dacl = MsDtypAcl.new770sd.dacl.aces = self.aces_from_sddl_text(value.delete_prefix(flags), domain_sid: domain_sid)771when 'S'772raise SDDLParseError.new('extra SACL') if sacl_set773774value.upcase!775sacl_set = true776access_control = true777flags = value.split('(', 2).first || ''778flags.split(/(P|AR|AI|NO_ACCESS_CONTROL)/).each do |flag|779case flag780when 'AI'781sd.control.si = true782when 'AR'783sd.control.sc = true784when 'P'785sd.control.ps = true786when 'NO_ACCESS_CONTROL'787access_control = false788when ''789else790raise SDDLParseError.new('unknown SACL flag: ' + flag)791end792end793794next unless access_control795796sd.sacl = MsDtypAcl.new797sd.sacl.aces = self.aces_from_sddl_text(value.delete_prefix(flags), domain_sid: domain_sid)798else799raise SDDLParseError.new('unknown directive: ' + part[0])800end801end802803sd804end805806class << self807private808809def aces_from_sddl_text(aces, domain_sid:)810ace_regex = /\([^\)]*\)/811812invalid_aces = aces.split(ace_regex).reject(&:empty?)813unless invalid_aces.empty?814raise SDDLParseError.new('malformed ACE: ' + invalid_aces.first)815end816817aces.scan(ace_regex).map do |ace_text|818MsDtypAce.from_sddl_text(ace_text[1...-1], domain_sid: domain_sid)819end820end821end822823def initialize_shared_instance824# define accessor methods for the custom fields to expose the same API as BinData825define_field_accessors_for2(:owner_sid)826define_field_accessors_for2(:group_sid)827define_field_accessors_for2(:sacl)828define_field_accessors_for2(:dacl)829super830end831832def initialize_instance833value = super834self.owner_sid = get_parameter(:owner_sid)835self.group_sid = get_parameter(:group_sid)836self.sacl = get_parameter(:sacl)837self.dacl = get_parameter(:dacl)838value839end840841def do_read(val)842value = super843if offset_owner != 0844@owner_sid = MsDtypSid.read(buffer[offset_owner - buffer.rel_offset..])845end846if offset_group != 0847@group_sid = MsDtypSid.read(buffer[offset_group - buffer.rel_offset..])848end849if offset_sacl != 0850@sacl = MsDtypAcl.read(buffer[offset_sacl - buffer.rel_offset..])851end852if offset_dacl != 0853@dacl = MsDtypAcl.read(buffer[offset_dacl - buffer.rel_offset..])854end855value856end857858def snapshot859snap = super860snap[:owner_sid] ||= owner_sid&.snapshot861snap[:group_sid] ||= group_sid&.snapshot862snap[:sacl] ||= sacl&.snapshot863snap[:dacl] ||= dacl&.snapshot864snap865end866867def owner_sid=(sid)868sid = MsDtypSid.new(sid) unless sid.nil? || sid.is_a?(MsDtypSid)869@owner_sid = sid870end871872def group_sid=(sid)873sid = MsDtypSid.new(sid) unless sid.nil? || sid.is_a?(MsDtypSid)874@group_sid = sid875end876877attr_accessor :sacl, :dacl878attr_reader :owner_sid, :group_sid879880private881882BUFFER_FIELD_ORDER = %i[ sacl dacl owner_sid group_sid ]883884def build_buffer885buf = ''886BUFFER_FIELD_ORDER.each do |field_name|887field_value = send(field_name)888buf << field_value.to_binary_s if field_value889end890buf891end892893def define_field_accessors_for2(name)894define_singleton_method("#{name}?") do895!send(name).nil?896end897end898899def offset_for(field)900return 0 unless instance_variable_get("@#{field}")901902offset = buffer.rel_offset903BUFFER_FIELD_ORDER.each do |cursor|904break if cursor == field905906cursor = instance_variable_get("@#{cursor}")907offset += cursor.num_bytes if cursor908end909910offset911end912end913914# [2.3.7 LUID](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/48cbee2a-0790-45f2-8269-931d7083b2c3)915class MsDtypLuid < BinData::Record916endian :little917918uint32 :low_part919int32 :high_part920921def to_s922"0x#{high_part.to_i.to_s(16)}#{low_part.to_i.to_s(16).rjust(8, '0')}"923end924end925926# [2.3.5 LARGE_INTEGER](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/e904b1ba-f774-4203-ba1b-66485165ab1a)927class MsDtypLargeInteger < BinData::Record928endian :big_and_little929930uint32 :low_part931int32 :high_part932933def to_datetime934RubySMB::Field::FileTime.new(to_i).to_datetime935end936937def to_i938(high_part.to_i << 32) | low_part.to_i939end940end941end942943944