Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/rex/proto/ms_dtyp.rb
19669 views
1
# -*- coding: binary -*-
2
3
require 'bindata'
4
require 'ruby_smb'
5
require 'rex/proto/secauthz/well_known_sids'
6
7
module Rex::Proto::MsDtyp
8
class SDDLParseError < Rex::RuntimeError
9
end
10
11
# [2.4.3 ACCESS_MASK](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/7a53f60e-e730-4dfe-bbe9-b21b62eb790b)
12
class MsDtypAccessMask < BinData::Record
13
endian :little
14
hide :reserved0, :reserved1
15
16
# the protocol field id reserved for protocol-specific access rights
17
uint16 :protocol
18
19
bit3 :reserved0
20
bit1 :sy
21
bit1 :wo
22
bit1 :wd
23
bit1 :rc
24
bit1 :de
25
26
bit1 :gr
27
bit1 :gw
28
bit1 :gx
29
bit1 :ga
30
bit2 :reserved1
31
bit1 :ma
32
bit1 :as
33
34
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 })
35
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 })
36
37
# Obtain an array of the abbreviated names of permissions that the access mask specifies.
38
#
39
# @return Returns nil if the permissions can't be represented as an array of abbreviations.
40
# @rtype [Array<Symbol>, nil]
41
def permissions
42
if (protocol & 0b1111111000000000) != 0 || ma == 1 || as == 1
43
return nil
44
end
45
46
permissions = []
47
permissions << :GA if ga == 1
48
permissions << :GR if gr == 1
49
permissions << :GW if gw == 1
50
permissions << :GX if gx == 1
51
52
file_access_mask = protocol & 0b000111111111
53
permissions << :FA if file_access_mask == 0b000111111111 && de == 1 && rc == 1 && wd == 1 && wo == 1 && sy == 1
54
permissions << :FR if file_access_mask == 0b000010001001
55
permissions << :FW if file_access_mask == 0b000100010110
56
permissions << :FX if file_access_mask == 0b000010100000
57
58
# windows does not reduce registry access flags (i.e. KA, KR, KW) so ignore them here to match it
59
60
permissions << :CC if (protocol & 0b000000000001) != 0 && !permissions.include?(:FA) && !permissions.include?(:FR)
61
permissions << :DC if (protocol & 0b000000000010) != 0 && !permissions.include?(:FA) && !permissions.include?(:FW)
62
permissions << :LC if (protocol & 0b000000000100) != 0 && !permissions.include?(:FA) && !permissions.include?(:FW)
63
permissions << :SW if (protocol & 0b000000001000) != 0 && !permissions.include?(:FA) && !permissions.include?(:FR)
64
permissions << :RP if (protocol & 0b000000010000) != 0 && !permissions.include?(:FA) && !permissions.include?(:FW)
65
permissions << :WP if (protocol & 0b000000100000) != 0 && !permissions.include?(:FA) && !permissions.include?(:FX)
66
permissions << :DT if (protocol & 0b000001000000) != 0 && !permissions.include?(:FA)
67
permissions << :LO if (protocol & 0b000010000000) != 0 && !permissions.include?(:FA)
68
permissions << :CR if (protocol & 0b000100000000) != 0 && !permissions.include?(:FA)
69
70
permissions << :SD if de == 1 && !permissions.include?(:FA)
71
permissions << :RC if rc == 1 && !permissions.include?(:FA)
72
permissions << :WD if wd == 1 && !permissions.include?(:FA)
73
permissions << :WO if wo == 1 && !permissions.include?(:FA)
74
75
permissions
76
end
77
78
def to_sddl_text
79
perms = permissions
80
81
if perms.nil?
82
# if one of these conditions are true, we can't reduce this to a set of flags so dump it as hex
83
return "0x#{to_binary_s.unpack1('L<').to_s(16).rjust(8, '0')}"
84
end
85
86
87
permissions.map(&:to_s).join
88
end
89
90
def self.from_sddl_text(sddl_text)
91
if sddl_text =~ /\A0x[0-9a-fA-F]{1,8}\Z/
92
return self.read([sddl_text.delete_prefix('0x').to_i(16)].pack('L<'))
93
end
94
95
access_mask = self.new
96
sddl_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|
97
case right
98
# generic access rights
99
when 'GA', 'GR', 'GW', 'GX'
100
access_mask.send("#{right.downcase}=", true)
101
# standard access rights
102
when 'RC'
103
access_mask.rc = true
104
when 'SD'
105
access_mask.de = true
106
when 'WD', 'WO'
107
access_mask.send("#{right.downcase}=", true)
108
# directory service object access rights
109
when 'RP'
110
access_mask.protocol |= 16
111
when 'WP'
112
access_mask.protocol |= 32
113
when 'CC'
114
access_mask.protocol |= 1
115
when 'DC'
116
access_mask.protocol |= 2
117
when 'LC'
118
access_mask.protocol |= 4
119
when 'SW'
120
access_mask.protocol |= 8
121
when 'LO'
122
access_mask.protocol |= 128
123
when 'DT'
124
access_mask.protocol |= 64
125
when 'CR'
126
access_mask.protocol |= 256
127
# file access rights
128
when 'FA'
129
access_mask.protocol |= 0x1ff
130
access_mask.de = true
131
access_mask.rc = true
132
access_mask.wd = true
133
access_mask.wo = true
134
access_mask.sy = true
135
when 'FR'
136
access_mask.protocol |= 0x89
137
when 'FW'
138
access_mask.protocol |= 0x116
139
when 'FX'
140
access_mask.protocol |= 0xa0
141
# registry key access rights
142
when 'KA'
143
access_mask.protocol |= 0x3f
144
access_mask.de = true
145
access_mask.rc = true
146
access_mask.wd = true
147
access_mask.wo = true
148
when 'KR'
149
access_mask.protocol |= 0x19
150
when 'KW'
151
access_mask.protocol |= 0x06
152
when 'KX'
153
access_mask.protocol |= 0x19
154
when 'NR', 'NW', 'NX'
155
raise SDDLParseError.new('unsupported ACE access right: ' + right)
156
when ''
157
else
158
raise SDDLParseError.new('unknown ACE access right: ' + right)
159
end
160
end
161
162
access_mask
163
end
164
end
165
166
# [2.4.2.2 SID--Packet Representation](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/f992ad60-0fe4-4b87-9fed-beb478836861)
167
class MsDtypSid < BinData::Primitive
168
endian :little
169
170
uint8 :revision, initial_value: 1
171
uint8 :sub_authority_count, initial_value: -> { self.sub_authority.length }
172
array :identifier_authority, type: :uint8, initial_length: 6
173
array :sub_authority, type: :uint32, initial_length: :sub_authority_count
174
175
def set(val)
176
# allow assignment from the human-readable string representation
177
raise ArgumentError.new("Invalid SID: #{val}") unless val.is_a?(String) && val =~ /^S-1-(\d+)(-\d+)*$/
178
179
_, _, ia, sa = val.split('-', 4)
180
self.identifier_authority = [ia.to_i].pack('Q>')[2..].bytes
181
self.sub_authority = sa.nil? ? [] : sa.split('-').map(&:to_i)
182
end
183
184
def get
185
str = 'S-1'
186
str << "-#{("\x00\x00" + identifier_authority.to_binary_s).unpack1('Q>')}"
187
str << '-' + sub_authority.map(&:to_s).join('-') unless sub_authority.empty?
188
str
189
end
190
191
def rid
192
sub_authority.last
193
end
194
195
# these can be validated using powershell where ?? is the code
196
# (ConvertFrom-SddlString -Sddl "O:??").RawDescriptor.Owner
197
SDDL_SIDS = {
198
'AA' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_ACCESS_CONTROL_ASSISTANCE_OPS, # SDDL_ACCESS_CONTROL_ASSISTANCE_OPS
199
'AC' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_ALL_APP_PACKAGES, # SDDL_ALL_APP_PACKAGES
200
'AN' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_ANONYMOUS_LOGON_SID, # SDDL_ANONYMOUS
201
'AO' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_ACCOUNT_OPS, # SDDL_ACCOUNT_OPERATORS
202
'AP' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_PROTECTED_USERS}", # SDDL_PROTECTED_USERS
203
'AU' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_AUTHENTICATED_USER_SID, # SDDL_AUTHENTICATED_USERS
204
'BA' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_ADMINS, # SDDL_BUILTIN_ADMINISTRATORS
205
'BG' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_GUESTS, # SDDL_BUILTIN_GUESTS
206
'BO' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_BACKUP_OPS, # SDDL_BACKUP_OPERATORS
207
'BU' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_USERS, # SDDL_BUILTIN_USERS
208
'CA' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_CERT_ADMINS}", # SDDL_CERT_SERV_ADMINISTRATORS
209
'CD' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_CERTSVC_DCOM_ACCESS_GROUP, # SDDL_CERTSVC_DCOM_ACCESS
210
'CG' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_CREATOR_GROUP_SID, # SDDL_CREATOR_GROUP
211
'CN' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_CLONEABLE_CONTROLLERS}", # SDDL_CLONEABLE_CONTROLLERS
212
'CO' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_CREATOR_OWNER_SID, # SDDL_CREATOR_OWNER
213
'CY' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_CRYPTO_OPERATORS, # SDDL_CRYPTO_OPERATORS
214
'DA' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_ADMINS}", # SDDL_DOMAIN_ADMINISTRATORS
215
'DC' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_COMPUTERS}", # SDDL_DOMAIN_COMPUTERS
216
'DD' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_CONTROLLERS}", # SDDL_DOMAIN_DOMAIN_CONTROLLERS
217
'DG' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_GUESTS}", # SDDL_DOMAIN_GUESTS
218
'DU' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_USERS}", # SDDL_DOMAIN_USERS
219
'EA' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_ENTERPRISE_ADMINS}", # SDDL_ENTERPRISE_ADMINS
220
'ED' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_ENTERPRISE_CONTROLLERS_SID, # SDDL_ENTERPRISE_DOMAIN_CONTROLLERS
221
'EK' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_ENTERPRISE_KEY_ADMINS}", # SDDL_ENTERPRISE_KEY_ADMINS
222
'ER' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_EVENT_LOG_READERS_GROUP, # SDDL_EVENT_LOG_READERS
223
'ES' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_RDS_ENDPOINT_SERVERS, # SDDL_RDS_ENDPOINT_SERVERS
224
'HA' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_HYPER_V_ADMINS, # SDDL_HYPER_V_ADMINS
225
'HI' => "S-1-16-#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_MANDATORY_HIGH_RID}", # SDDL_ML_HIGH
226
'IS' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_IUSERS, # SDDL_IIS_USERS
227
'IU' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_INTERACTIVE_SID, # SDDL_INTERACTIVE
228
'KA' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_KEY_ADMINS}", # SDDL_KEY_ADMINS
229
'LA' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_USER_RID_ADMIN}", # SDDL_LOCAL_ADMIN
230
'LG' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_USER_RID_GUEST}", # SDDL_LOCAL_GUEST
231
'LS' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_LOCAL_SERVICE_SID, # SDDL_LOCAL_SERVICE
232
'LU' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_LOGGING_USERS, # SDDL_PERFLOG_USERS
233
'LW' => "S-1-16-#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_MANDATORY_LOW_RID}", # SDDL_ML_LOW
234
'ME' => "S-1-16-#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_MANDATORY_MEDIUM_RID}", # SDDL_ML_MEDIUM
235
'MP' => "S-1-16-#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_MANDATORY_MEDIUM_PLUS_RID}", # SDDL_ML_MEDIUM_PLUS
236
'MU' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_MONITORING_USERS, # SDDL_PERFMON_USERS
237
'NO' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_NETWORK_CONFIGURATION_OPS, # SDDL_NETWORK_CONFIGURATION_OPS
238
'NS' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_NETWORK_SERVICE_SID, # SDDL_NETWORK_SERVICE
239
'NU' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_NETWORK_SID, # SDDL_NETWORK
240
'OW' => "#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_CREATOR_SID_AUTHORITY}-4", # SDDL_OWNER_RIGHTS
241
'PA' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_POLICY_ADMINS}", # SDDL_GROUP_POLICY_ADMINS
242
'PO' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_PRINT_OPS, # SDDL_PRINTER_OPERATORS
243
'PS' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_PRINCIPAL_SELF_SID, # SDDL_PERSONAL_SELF
244
'PU' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_POWER_USERS, # SDDL_POWER_USERS
245
'RA' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_RDS_REMOTE_ACCESS_SERVERS, # SDDL_RDS_REMOTE_ACCESS_SERVERS
246
'RC' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_RESTRICTED_CODE_SID, # SDDL_RESTRICTED_CODE
247
'RD' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_REMOTE_DESKTOP_USERS, # SDDL_REMOTE_DESKTOP
248
'RE' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_REPLICATOR, # SDDL_REPLICATOR
249
'RM' => "#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_BUILTIN_DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_REMOTE_MANAGEMENT_USERS}", # SDDL_RMS__SERVICE_OPERATORS
250
'RO' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_ENTERPRISE_READONLY_DOMAIN_CONTROLLERS}", # SDDL_ENTERPRISE_RO_DCs
251
'RS' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_RAS_SERVERS}", # SDDL_RAS_SERVERS
252
'RU' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_PREW2KCOMPACCESS, # SDDL_ALIAS_PREW2KCOMPACC
253
'SA' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_SCHEMA_ADMINS}", # SDDL_SCHEMA_ADMINISTRATORS
254
'SI' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_MANDATORY_SYSTEM_SID, # SDDL_ML_SYSTEM
255
'SO' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_SYSTEM_OPS, # SDDL_SERVER_OPERATORS
256
'SS' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_AUTHENTICATION_SERVICE_ASSERTED_SID, # SDDL_SERVICE_ASSERTED
257
'SU' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_SERVICE_SID, # SDDL_SERVICE
258
'SY' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_LOCAL_SYSTEM_SID, # SDDL_LOCAL_SYSTEM
259
'UD' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_USERMODEDRIVERHOST_ID_BASE_SID, # SDDL_USER_MODE_DRIVERS
260
'WD' => "#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_WORLD_SID_AUTHORITY}-#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_WORLD_RID}", # SDDL_EVERYONE
261
'WR' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_WRITE_RESTRICTED_CODE_SID # SDDL_WRITE_RESTRICTED_CODE
262
}.freeze
263
264
private_constant :SDDL_SIDS
265
266
def to_sddl_text(domain_sid: nil)
267
sid = to_s
268
269
lookup = domain_sid.blank? ? sid : sid.sub(domain_sid, '${DOMAIN_SID}')
270
if (sddl_text = self.class.const_get(:SDDL_SIDS).key(lookup)).nil?
271
sddl_text = sid
272
end
273
# these short names aren't supported by all versions of Windows, avoid compatibility issues by not outputting them
274
sddl_text = sid if %w[ AP CN EK KA ].include?(sddl_text)
275
276
sddl_text
277
end
278
279
def self.from_sddl_text(sddl_text, domain_sid:)
280
# see: https://learn.microsoft.com/en-us/windows/win32/secauthz/sid-strings
281
sddl_text = sddl_text.dup.upcase
282
283
if SDDL_SIDS.key?(sddl_text)
284
sid_text = SDDL_SIDS[sddl_text].sub('${DOMAIN_SID}', domain_sid)
285
elsif sddl_text =~ /^S(-\d+)+/
286
sid_text = sddl_text
287
else
288
raise SDDLParseError.new('invalid SID string: ' + sddl_text)
289
end
290
291
self.new(sid_text)
292
end
293
end
294
295
# [Universal Unique Identifier](http://pubs.opengroup.org/onlinepubs/9629399/apdxa.htm)
296
# The online documentation at [2.3.4.2](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/001eec5a-7f8b-4293-9e21-ca349392db40)
297
# weirdly doesn't mention this needs to be 4 byte aligned for us to read it correctly,
298
# which the RubySMB::Dcerpc::Uuid definition takes care of.
299
class MsDtypGuid < RubySMB::Dcerpc::Uuid
300
def self.random_generate
301
# Taken from the "D" format as specified in
302
# https://learn.microsoft.com/en-us/dotnet/api/system.guid.tostring?view=net-7.0
303
"{#{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
304
end
305
end
306
307
# 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)
308
class MsDtypAceType
309
ACCESS_ALLOWED_ACE_TYPE = 0x0
310
ACCESS_DENIED_ACE_TYPE = 0x1
311
SYSTEM_AUDIT_ACE_TYPE = 0x2
312
SYSTEM_ALARM_ACE_TYPE = 0x3 # Reserved for future use according to documentation.
313
ACCESS_ALLOWED_COMPOUND_ACE_TYPE = 0x4 # Reserved for future use according to documentation.
314
ACCESS_ALLOWED_OBJECT_ACE_TYPE = 0x5
315
ACCESS_DENIED_OBJECT_ACE_TYPE = 0x6
316
SYSTEM_AUDIT_OBJECT_ACE_TYPE = 0x7
317
SYSTEM_ALARM_OBJECT_ACE_TYPE = 0x8 # Reserved for future use according to documentation.
318
ACCESS_ALLOWED_CALLBACK_ACE_TYPE = 0x9
319
ACCESS_DENIED_CALLBACK_ACE_TYPE = 0xA
320
ACCESS_ALLOWED_CALLBACK_OBJECT_ACE_TYPE = 0xB
321
ACCESS_DENIED_CALLBACK_OBJECT_ACE_TYPE = 0xC
322
SYSTEM_AUDIT_CALLBACK_ACE_TYPE = 0xD
323
SYSTEM_ALARM_CALLBACK_ACE_TYPE = 0xE # Reserved for future use according to documentation.
324
SYSTEM_AUDIT_CALLBACK_OBJECT_ACE_TYPE = 0xF
325
SYSTEM_ALARM_CALLBACK_OBJECT_ACE_TYPE = 0x10 # Reserved for future use according to documentation.
326
SYSTEM_MANDATORY_LABEL_ACE_TYPE = 0x11
327
SYSTEM_RESOURCE_ATTRIBUTE_ACE_TYPE = 0x12
328
SYSTEM_SCOPED_POLICY_ID_ACE_TYPE = 0x13
329
330
def self.name(value)
331
constants.select { |c| c.upcase == c }.find { |c| const_get(c) == value }
332
end
333
334
def self.alarm?(type)
335
[
336
SYSTEM_ALARM_ACE_TYPE,
337
SYSTEM_ALARM_OBJECT_ACE_TYPE,
338
SYSTEM_ALARM_CALLBACK_ACE_TYPE,
339
SYSTEM_ALARM_CALLBACK_OBJECT_ACE_TYPE,
340
].include? type
341
end
342
343
def self.allow?(type)
344
[
345
ACCESS_ALLOWED_ACE_TYPE,
346
ACCESS_ALLOWED_COMPOUND_ACE_TYPE,
347
ACCESS_ALLOWED_OBJECT_ACE_TYPE,
348
ACCESS_ALLOWED_CALLBACK_ACE_TYPE,
349
ACCESS_ALLOWED_CALLBACK_OBJECT_ACE_TYPE,
350
].include? type
351
end
352
353
def self.audit?(type)
354
[
355
SYSTEM_AUDIT_ACE_TYPE,
356
SYSTEM_AUDIT_OBJECT_ACE_TYPE,
357
SYSTEM_AUDIT_CALLBACK_ACE_TYPE,
358
SYSTEM_AUDIT_CALLBACK_OBJECT_ACE_TYPE,
359
].include? type
360
end
361
362
def self.deny?(type)
363
[
364
ACCESS_DENIED_ACE_TYPE,
365
ACCESS_DENIED_OBJECT_ACE_TYPE,
366
ACCESS_DENIED_CALLBACK_ACE_TYPE,
367
ACCESS_DENIED_CALLBACK_OBJECT_ACE_TYPE,
368
].include? type
369
end
370
371
def self.has_object?(type)
372
[
373
ACCESS_ALLOWED_OBJECT_ACE_TYPE,
374
ACCESS_DENIED_OBJECT_ACE_TYPE,
375
SYSTEM_AUDIT_OBJECT_ACE_TYPE,
376
SYSTEM_ALARM_OBJECT_ACE_TYPE,
377
ACCESS_ALLOWED_CALLBACK_OBJECT_ACE_TYPE,
378
ACCESS_DENIED_CALLBACK_OBJECT_ACE_TYPE,
379
SYSTEM_AUDIT_CALLBACK_OBJECT_ACE_TYPE,
380
SYSTEM_ALARM_CALLBACK_OBJECT_ACE_TYPE
381
].include? type
382
end
383
end
384
385
# [2.4.4.1 ACE_HEADER](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/628ebb1d-c509-4ea0-a10f-77ef97ca4586)
386
class MsDtypAceHeader < BinData::Record
387
endian :little
388
389
uint8 :ace_type
390
struct :ace_flags do
391
bit1 :failed_access_ace_flag
392
bit1 :successful_access_ace_flag
393
bit1 :critical_ace_flag # used only with access allowed ACE types, see: https://www.codemachine.com/downloads/win10.1903/ntifs.h
394
bit1 :inherited_ace
395
bit1 :inherit_only_ace
396
bit1 :no_propagate_inherit_ace
397
bit1 :container_inherit_ace
398
bit1 :object_inherit_ace
399
end
400
uint16 :ace_size, initial_value: -> { parent&.num_bytes || 0 }
401
end
402
403
class MsDtypAceNonObjectBody < BinData::Record
404
endian :little
405
406
ms_dtyp_access_mask :access_mask
407
ms_dtyp_sid :sid, byte_align: 4
408
end
409
410
class MsDtypAceObjectBody < BinData::Record
411
endian :little
412
413
ms_dtyp_access_mask :access_mask
414
struct :flags do
415
bit1 :reserved5
416
bit1 :reserved4
417
bit1 :reserved3
418
bit1 :reserved2
419
bit1 :reserved1
420
bit1 :reserved
421
bit1 :ace_inherited_object_type_present
422
bit1 :ace_object_type_present
423
end
424
ms_dtyp_guid :object_type, onlyif: -> { flags.ace_object_type_present != 0x0 }
425
ms_dtyp_guid :inherited_object_type, onlyif: -> { flags.ace_inherited_object_type_present != 0x0 }
426
ms_dtyp_sid :sid, byte_align: 4
427
end
428
429
# [2.4.4.2 ACCESS_ALLOWED_ACE](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/72e7c7ea-bc02-4c74-a619-818a16bf6adb)
430
class MsDtypAccessAllowedAceBody < MsDtypAceNonObjectBody
431
end
432
433
# [2.4.4.4 ACCESS_DENIED_ACE](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/b1e1321d-5816-4513-be67-b65d8ae52fe8)
434
class MsDtypAccessDeniedAceBody < MsDtypAceNonObjectBody
435
end
436
437
# [2.4.4.10 SYSTEM_AUDIT_ACE](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/9431fd0f-5b9a-47f0-b3f0-3015e2d0d4f9)
438
class MsDtypSystemAuditAceBody < MsDtypAceNonObjectBody
439
end
440
441
# [2.4.4.3 ACCESS_ALLOWED_OBJECT_ACE](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/c79a383c-2b3f-4655-abe7-dcbb7ce0cfbe)
442
class MsDtypAccessAllowedObjectAceBody < MsDtypAceObjectBody
443
end
444
445
# [2.4.4.5 ACCESS_DENIED_OBJECT_ACE](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/8720fcf3-865c-4557-97b1-0b3489a6c270)
446
class MsDtypAccessDeniedObjectAceBody < MsDtypAceObjectBody
447
end
448
449
# [2.4.4.11 SYSTEM_AUDIT_OBJECT_ACE](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/c8da72ae-6b54-4a05-85f4-e2594936d3d5)
450
class MsDtypSystemAuditObjectAceBody < MsDtypAceObjectBody
451
endian :little
452
453
string :application_data, read_length: -> { calc_app_data_length }
454
455
def calc_app_data_length
456
ace_header = parent&.parent&.header
457
ace_body = parent&.parent&.body
458
return 0 if ace_header.nil? || ace_body.nil?
459
460
ace_size = ace_header.ace_size
461
return 0 if ace_size.nil? or (ace_size == 0)
462
463
ace_header_length = ace_header.to_binary_s.length
464
if ace_body.nil?
465
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.
466
else
467
ace_body_length = ace_body.to_binary_s.length
468
return ace_size - (ace_header_length + ace_body_length)
469
end
470
end
471
end
472
473
class MsDtypAce < BinData::Record
474
endian :little
475
476
ms_dtyp_ace_header :header
477
choice :body, selection: -> { header.ace_type } do
478
ms_dtyp_access_allowed_ace_body Rex::Proto::MsDtyp::MsDtypAceType::ACCESS_ALLOWED_ACE_TYPE
479
ms_dtyp_access_denied_ace_body Rex::Proto::MsDtyp::MsDtypAceType::ACCESS_DENIED_ACE_TYPE
480
ms_dtyp_system_audit_ace_body Rex::Proto::MsDtyp::MsDtypAceType::SYSTEM_AUDIT_ACE_TYPE
481
# Type 3 is reserved for future use
482
# Type 4 is reserved for future use
483
ms_dtyp_access_allowed_object_ace_body Rex::Proto::MsDtyp::MsDtypAceType::ACCESS_ALLOWED_OBJECT_ACE_TYPE
484
ms_dtyp_access_denied_object_ace_body Rex::Proto::MsDtyp::MsDtypAceType::ACCESS_DENIED_OBJECT_ACE_TYPE
485
ms_dtyp_system_audit_object_ace_body Rex::Proto::MsDtyp::MsDtypAceType::SYSTEM_AUDIT_OBJECT_ACE_TYPE
486
# Type 8 is reserved for future use
487
# Type 14 aka 0xE is reserved for future use
488
# Type 16 aka 0x10 is reserved for future use
489
string :default, read_length: -> { header.ace_size - body.rel_offset }
490
end
491
492
def to_sddl_text(domain_sid: nil)
493
parts = []
494
495
case header.ace_type
496
when MsDtypAceType::ACCESS_ALLOWED_ACE_TYPE
497
parts << 'A'
498
when MsDtypAceType::ACCESS_DENIED_ACE_TYPE
499
parts << 'D'
500
when MsDtypAceType::ACCESS_ALLOWED_OBJECT_ACE_TYPE
501
parts << 'OA'
502
when MsDtypAceType::ACCESS_DENIED_OBJECT_ACE_TYPE
503
parts << 'OD'
504
when MsDtypAceType::SYSTEM_AUDIT_ACE_TYPE
505
parts << 'AU'
506
when MsDtypAceType::SYSTEM_AUDIT_OBJECT_ACE_TYPE
507
parts << 'OU'
508
else
509
raise SDDLParseError.new('unknown ACE type: ' + header.ace_type.to_i)
510
end
511
512
ace_flags = ''
513
ace_flags << 'OI' if header.ace_flags.object_inherit_ace == 1
514
ace_flags << 'CI' if header.ace_flags.container_inherit_ace == 1
515
ace_flags << 'IO' if header.ace_flags.inherit_only_ace == 1
516
517
ace_flags << 'NP' if header.ace_flags.no_propagate_inherit_ace == 1
518
ace_flags << 'ID' if header.ace_flags.inherited_ace == 1
519
ace_flags << 'SA' if header.ace_flags.successful_access_ace_flag == 1
520
ace_flags << 'FA' if header.ace_flags.failed_access_ace_flag == 1
521
ace_flags << 'CR' if header.ace_flags.critical_ace_flag == 1
522
parts << ace_flags
523
524
parts << body.access_mask.to_sddl_text
525
526
if body[:flags]
527
parts << (body.flags[:ace_object_type_present] == 1 ? body.object_type.to_s : '')
528
parts << (body.flags[:ace_inherited_object_type_present] == 1 ? body.inherited_object_type.to_s : '')
529
else
530
parts << ''
531
parts << ''
532
end
533
534
if body.sid?
535
parts << body.sid.to_sddl_text(domain_sid: domain_sid)
536
else
537
parts << ''
538
end
539
540
parts.join(';')
541
end
542
543
def self.from_sddl_text(sddl_text, domain_sid:)
544
parts = sddl_text.upcase.split(';', -1)
545
raise SDDLParseError.new('too few ACE fields') if parts.length < 6
546
raise SDDLParseError.new('too many ACE fields') if parts.length > 7
547
548
ace_type, ace_flags, rights, object_guid, inherit_object_guid, account_sid = parts[0...6]
549
resource_attribute = parts[6]
550
551
ace = self.new
552
case ace_type
553
when 'A'
554
ace.header.ace_type = MsDtypAceType::ACCESS_ALLOWED_ACE_TYPE
555
when 'D'
556
ace.header.ace_type = MsDtypAceType::ACCESS_DENIED_ACE_TYPE
557
when 'OA'
558
ace.header.ace_type = MsDtypAceType::ACCESS_ALLOWED_OBJECT_ACE_TYPE
559
when 'OD'
560
ace.header.ace_type = MsDtypAceType::ACCESS_DENIED_OBJECT_ACE_TYPE
561
when 'AU'
562
ace.header.ace_type = MsDtypAceType::SYSTEM_AUDIT_ACE_TYPE
563
when 'OU'
564
ace.header.ace_type = MsDtypAceType::SYSTEM_AUDIT_OBJECT_ACE_TYPE
565
when 'AL', 'OL', 'ML', 'XA', 'SD', 'RA', 'SP', 'XU', 'ZA', 'TL', 'FL'
566
raise SDDLParseError.new('unsupported ACE type: ' + ace_type)
567
else
568
raise SDDLParseError.new('unknown ACE type: ' + ace_type)
569
end
570
571
ace_flags.split(/(CI|OI|NP|IO|ID|SA|FA|TP|CR)/).each do |flag|
572
case flag
573
when 'CI'
574
ace.header.ace_flags.container_inherit_ace = true
575
when 'OI'
576
ace.header.ace_flags.object_inherit_ace = true
577
when 'NP'
578
ace.header.ace_flags.no_propagate_inherit_ace = true
579
when 'IO'
580
ace.header.ace_flags.inherit_only_ace = true
581
when 'ID'
582
ace.header.ace_flags.inherited_ace = true
583
when 'SA'
584
ace.header.ace_flags.successful_access_ace_flag = true
585
when 'FA'
586
ace.header.ace_flags.failed_access_ace_flag = true
587
when 'TP'
588
raise SDDLParseError.new('unsupported ACE flag: TP')
589
when 'CR'
590
ace.header.ace_flags.critical_ace_flag = true
591
when ''
592
else
593
raise SDDLParseError.new('unknown ACE flag: ' + flag)
594
end
595
end
596
597
ace.body.access_mask = MsDtypAccessMask.from_sddl_text(rights)
598
599
unless object_guid.blank?
600
begin
601
guid = MsDtypGuid.new(object_guid)
602
rescue StandardError
603
raise SDDLParseError.new('invalid object GUID: ' + object_guid)
604
end
605
606
unless ace.body.respond_to?('object_type=')
607
raise SDDLParseError.new('setting object type for incompatible ACE type')
608
end
609
ace.body.flags.ace_object_type_present = true
610
ace.body.object_type = guid
611
end
612
613
unless inherit_object_guid.blank?
614
begin
615
guid = MsDtypGuid.new(inherit_object_guid)
616
rescue StandardError
617
raise SDDLParseError.new('invalid inherited object GUID: ' + inherit_object_guid)
618
end
619
620
unless ace.body.respond_to?('inherited_object_type=')
621
raise SDDLParseError.new('setting inherited object type for incompatible ACE type')
622
end
623
ace.body.flags.ace_inherited_object_type_present = true
624
ace.body.inherited_object_type = guid
625
end
626
627
unless account_sid.blank?
628
ace.body.sid = MsDtypSid.from_sddl_text(account_sid, domain_sid: domain_sid)
629
end
630
631
unless resource_attribute.blank?
632
raise SDDLParseError.new('unsupported resource attribute: ' + resource_attribute)
633
end
634
635
ace
636
end
637
end
638
639
# [2.4.5 ACL](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/20233ed8-a6c6-4097-aafa-dd545ed24428)
640
class MsDtypAcl < BinData::Record
641
ACL_REVISION = 2
642
ACL_REVISION_DS = 4
643
644
endian :little
645
646
uint8 :acl_revision, initial_value: ACL_REVISION
647
uint8 :sbz1
648
uint16 :acl_size, initial_value: -> { num_bytes }
649
uint16 :acl_count, initial_value: -> { aces.length }
650
uint16 :sbz2
651
array :aces, type: :ms_dtyp_ace, initial_length: :acl_count
652
end
653
654
# [2.4.6 SECURITY_DESCRIPTOR](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/7d4dac05-9cef-4563-a058-f108abecce1d)
655
class MsDtypSecurityDescriptor < BinData::Record
656
endian :little
657
658
uint8 :revision, initial_value: 1
659
uint8 :sbz1
660
struct :control do
661
bit1 :ss
662
bit1 :dt
663
bit1 :sd
664
bit1 :sp, initial_value: -> { sacl? ? 1 : 0 }
665
bit1 :dd
666
bit1 :dp, initial_value: -> { dacl? ? 1 : 0 }
667
bit1 :gd
668
bit1 :od
669
670
bit1 :sr, initial_value: 1
671
bit1 :rm
672
bit1 :ps
673
bit1 :pd
674
bit1 :si
675
bit1 :di
676
bit1 :sc
677
bit1 :dc
678
end
679
uint32 :offset_owner, value: -> { offset_for(:owner_sid) }
680
uint32 :offset_group, value: -> { offset_for(:group_sid) }
681
uint32 :offset_sacl, value: -> { offset_for(:sacl) }
682
uint32 :offset_dacl, value: -> { offset_for(:dacl) }
683
rest :buffer, value: -> { build_buffer }
684
hide :buffer
685
686
def to_sddl_text(domain_sid: nil)
687
sddl_text = ''
688
sddl_text << "O:#{owner_sid.to_sddl_text(domain_sid: domain_sid)}" if owner_sid?
689
sddl_text << "G:#{group_sid.to_sddl_text(domain_sid: domain_sid)}" if group_sid?
690
sddl_text << "D:#{dacl_to_sddl_text(domain_sid: domain_sid)}" if dacl?
691
sddl_text << "S:#{sacl_to_sddl_text(domain_sid: domain_sid)}" if sacl?
692
693
sddl_text
694
end
695
696
def dacl_to_sddl_text(domain_sid: nil)
697
sddl_text = ''
698
699
if !dacl?
700
sddl_text << 'NO_ACCESS_CONTROL'
701
else
702
sddl_text << 'P' if control.pd == 1
703
sddl_text << 'AR' if control.dc == 1
704
sddl_text << 'AI' if control.di == 1
705
sddl_text << dacl.aces.map { |ace| "(#{ace.to_sddl_text(domain_sid: domain_sid)})" }.join
706
end
707
708
sddl_text
709
end
710
711
def sacl_to_sddl_text(domain_sid: nil)
712
sddl_text = ''
713
714
if !sacl?
715
sddl_text << 'NO_ACCESS_CONTROL'
716
else
717
sddl_text << 'P' if control.ps == 1
718
sddl_text << 'AR' if control.sc == 1
719
sddl_text << 'AI' if control.si == 1
720
sddl_text << sacl.aces.map { |ace| "(#{ace.to_sddl_text(domain_sid: domain_sid)})" }.join
721
end
722
723
sddl_text
724
end
725
726
def self.from_sddl_text(sddl_text, domain_sid:)
727
sacl_set = dacl_set = false
728
sd = self.new
729
sddl_text = sddl_text.dup.gsub(/\s/, '') # start by removing all whitespace
730
sddl_text.scan(/([OGDS]:(?:.(?!:))*)/).each do |part,|
731
component, _, value = part.partition(':')
732
case component
733
when 'O'
734
if sd.owner_sid.present?
735
raise SDDLParseError.new('extra owner SID')
736
end
737
738
sd.owner_sid = MsDtypSid.from_sddl_text(value, domain_sid: domain_sid)
739
when 'G'
740
if sd.group_sid.present?
741
raise SDDLParseError.new('extra group SID')
742
end
743
744
sd.group_sid = MsDtypSid.from_sddl_text(value, domain_sid: domain_sid)
745
when 'D'
746
raise SDDLParseError.new('extra DACL') if dacl_set
747
748
value.upcase!
749
dacl_set = true
750
access_control = true
751
flags = value.split('(', 2).first || ''
752
flags.split(/(P|AR|AI|NO_ACCESS_CONTROL)/).each do |flag|
753
case flag
754
when 'AI'
755
sd.control.di = true
756
when 'AR'
757
sd.control.dc = true
758
when 'P'
759
sd.control.pd = true
760
when 'NO_ACCESS_CONTROL'
761
access_control = false
762
when ''
763
else
764
raise SDDLParseError.new('unknown DACL flag: ' + flag)
765
end
766
end
767
768
next unless access_control
769
770
sd.dacl = MsDtypAcl.new
771
sd.dacl.aces = self.aces_from_sddl_text(value.delete_prefix(flags), domain_sid: domain_sid)
772
when 'S'
773
raise SDDLParseError.new('extra SACL') if sacl_set
774
775
value.upcase!
776
sacl_set = true
777
access_control = true
778
flags = value.split('(', 2).first || ''
779
flags.split(/(P|AR|AI|NO_ACCESS_CONTROL)/).each do |flag|
780
case flag
781
when 'AI'
782
sd.control.si = true
783
when 'AR'
784
sd.control.sc = true
785
when 'P'
786
sd.control.ps = true
787
when 'NO_ACCESS_CONTROL'
788
access_control = false
789
when ''
790
else
791
raise SDDLParseError.new('unknown SACL flag: ' + flag)
792
end
793
end
794
795
next unless access_control
796
797
sd.sacl = MsDtypAcl.new
798
sd.sacl.aces = self.aces_from_sddl_text(value.delete_prefix(flags), domain_sid: domain_sid)
799
else
800
raise SDDLParseError.new('unknown directive: ' + part[0])
801
end
802
end
803
804
sd
805
end
806
807
class << self
808
private
809
810
def aces_from_sddl_text(aces, domain_sid:)
811
ace_regex = /\([^\)]*\)/
812
813
invalid_aces = aces.split(ace_regex).reject(&:empty?)
814
unless invalid_aces.empty?
815
raise SDDLParseError.new('malformed ACE: ' + invalid_aces.first)
816
end
817
818
aces.scan(ace_regex).map do |ace_text|
819
MsDtypAce.from_sddl_text(ace_text[1...-1], domain_sid: domain_sid)
820
end
821
end
822
end
823
824
def initialize_shared_instance
825
# define accessor methods for the custom fields to expose the same API as BinData
826
define_field_accessors_for2(:owner_sid)
827
define_field_accessors_for2(:group_sid)
828
define_field_accessors_for2(:sacl)
829
define_field_accessors_for2(:dacl)
830
super
831
end
832
833
def initialize_instance
834
value = super
835
self.owner_sid = get_parameter(:owner_sid)
836
self.group_sid = get_parameter(:group_sid)
837
self.sacl = get_parameter(:sacl)
838
self.dacl = get_parameter(:dacl)
839
value
840
end
841
842
def do_read(val)
843
value = super
844
if offset_owner != 0
845
@owner_sid = MsDtypSid.read(buffer[offset_owner - buffer.rel_offset..])
846
end
847
if offset_group != 0
848
@group_sid = MsDtypSid.read(buffer[offset_group - buffer.rel_offset..])
849
end
850
if offset_sacl != 0
851
@sacl = MsDtypAcl.read(buffer[offset_sacl - buffer.rel_offset..])
852
end
853
if offset_dacl != 0
854
@dacl = MsDtypAcl.read(buffer[offset_dacl - buffer.rel_offset..])
855
end
856
value
857
end
858
859
def snapshot
860
snap = super
861
snap[:owner_sid] ||= owner_sid&.snapshot
862
snap[:group_sid] ||= group_sid&.snapshot
863
snap[:sacl] ||= sacl&.snapshot
864
snap[:dacl] ||= dacl&.snapshot
865
snap
866
end
867
868
def owner_sid=(sid)
869
sid = MsDtypSid.new(sid) unless sid.nil? || sid.is_a?(MsDtypSid)
870
@owner_sid = sid
871
end
872
873
def group_sid=(sid)
874
sid = MsDtypSid.new(sid) unless sid.nil? || sid.is_a?(MsDtypSid)
875
@group_sid = sid
876
end
877
878
attr_accessor :sacl, :dacl
879
attr_reader :owner_sid, :group_sid
880
881
private
882
883
BUFFER_FIELD_ORDER = %i[ sacl dacl owner_sid group_sid ]
884
885
def build_buffer
886
buf = ''
887
BUFFER_FIELD_ORDER.each do |field_name|
888
field_value = send(field_name)
889
buf << field_value.to_binary_s if field_value
890
end
891
buf
892
end
893
894
def define_field_accessors_for2(name)
895
define_singleton_method("#{name}?") do
896
!send(name).nil?
897
end
898
end
899
900
def offset_for(field)
901
return 0 unless instance_variable_get("@#{field}")
902
903
offset = buffer.rel_offset
904
BUFFER_FIELD_ORDER.each do |cursor|
905
break if cursor == field
906
907
cursor = instance_variable_get("@#{cursor}")
908
offset += cursor.num_bytes if cursor
909
end
910
911
offset
912
end
913
end
914
915
# [2.3.7 LUID](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/48cbee2a-0790-45f2-8269-931d7083b2c3)
916
class MsDtypLuid < BinData::Record
917
endian :little
918
919
uint32 :low_part
920
int32 :high_part
921
922
def to_s
923
"0x#{high_part.to_i.to_s(16)}#{low_part.to_i.to_s(16).rjust(8, '0')}"
924
end
925
end
926
927
# [2.3.5 LARGE_INTEGER](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/e904b1ba-f774-4203-ba1b-66485165ab1a)
928
class MsDtypLargeInteger < BinData::Record
929
endian :big_and_little
930
931
uint32 :low_part
932
int32 :high_part
933
934
def to_datetime
935
RubySMB::Field::FileTime.new(to_i).to_datetime
936
end
937
938
def to_i
939
(high_part.to_i << 32) | low_part.to_i
940
end
941
end
942
end
943
944