CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/rex/proto/ms_adts/key_credential.rb
Views: 11704
1
module Rex::Proto::MsAdts
2
class KeyCredential
3
4
KEY_USAGE_NGC = 0x01
5
KEY_USAGE_FIDO = 0x07
6
KEY_USAGE_FEK = 0x08
7
8
KEY_CREDENTIAL_VERSION_2 = 0x200
9
DEFAULT_KEY_INFORMATION = "\x01\x00" # Version and flags
10
11
def initialize
12
self.key_source = 0
13
self.device_id = Rex::Proto::MsDtyp::MsDtypGuid.new
14
self.device_id.set(Rex::Proto::MsDtyp::MsDtypGuid.random_generate)
15
self.custom_key_information = DEFAULT_KEY_INFORMATION
16
end
17
18
# Set the key material for this credential object
19
# @param public_key [OpenSSL::PKey::RSA] Public key used for authentication
20
# @param key_usage [Enumeration] From the KEY_USAGE constants in this class
21
def set_key(public_key, key_usage)
22
self.key_usage = key_usage
23
24
case self.key_usage
25
when KEY_USAGE_NGC
26
result = Rex::Proto::BcryptPublicKey.new
27
result.key_length = public_key.n.num_bits
28
n = self.class.int_to_bytes(public_key.n)
29
e = self.class.int_to_bytes(public_key.e)
30
result.exponent = e
31
result.modulus = n
32
result.prime1 = ''
33
result.prime2 = ''
34
self.raw_key_material = result.to_binary_s
35
else
36
# Unknown key type
37
return
38
end
39
sha256 = OpenSSL::Digest.new('SHA256')
40
self.key_id = sha256.digest(self.raw_key_material)
41
end
42
43
# Approximate time this key was last used
44
# @return [Time] Approximate time this key was last used
45
def key_approximate_last_logon_time
46
ft = key_approximate_last_logon_time_raw
47
RubySMB::Field::FileTime.new(ft.unpack('Q')[0]).to_time
48
end
49
50
# Set the approximate last logon time for this credential object
51
# @param time [Time] Last time this credential was used to log on
52
def key_approximate_last_logon_time=(time)
53
self.key_approximate_last_logon_time_raw = RubySMB::Field::FileTime.new(time).to_binary_s
54
end
55
56
# Approximate time this key was created
57
# @return [Time] Approximate time this key was created
58
def key_creation_time
59
ft = key_creation_time_raw
60
RubySMB::Field::FileTime.new(ft.unpack('Q')[0]).to_time
61
end
62
63
# Set the creation time for this credential object
64
# @param time [Time] Time that this key was created
65
def key_creation_time=(time)
66
self.key_creation_time_raw = RubySMB::Field::FileTime.new(time).to_binary_s
67
end
68
69
# Creates a MsAdtsKeyCredentialStruct, including calculating the value for key_hash
70
# @return [MsAdtsKeyCredentialStruct] A structured object able to be converted to binary and sent to a DCc
71
def to_struct
72
result = MsAdtsKeyCredentialStruct.new
73
result.version = KEY_CREDENTIAL_VERSION_2
74
add_entry(result, 3, self.raw_key_material)
75
add_entry(result, 4, [self.key_usage].pack('C'))
76
add_entry(result, 5, [self.key_source].pack('C'))
77
add_entry(result, 6, self.device_id.to_binary_s)
78
add_entry(result, 7, self.custom_key_information)
79
add_entry(result, 8, self.key_approximate_last_logon_time_raw)
80
add_entry(result, 9, self.key_creation_time_raw)
81
82
calculate_key_hash(result)
83
84
add_entry(result, 2, self.key_hash, insert_at_end: false)
85
add_entry(result, 1, self.key_id, insert_at_end: false)
86
87
result
88
end
89
90
# Construct a KeyCredential object from a MsAdtsKeyCredentialStruct (likely received from a Domain Controller)
91
# @param cred_struct [MsAdtsKeyCredentialStruct] Credential structure to convert
92
def self.from_struct(cred_struct)
93
obj = KeyCredential.new
94
obj.key_id = get_entry(cred_struct, 1)
95
obj.key_hash = get_entry(cred_struct, 2)
96
obj.raw_key_material = get_entry(cred_struct, 3)
97
obj.key_usage = get_entry(cred_struct, 4).unpack('C')[0]
98
obj.key_source = get_entry(cred_struct, 5).unpack('C')[0]
99
obj.device_id = Rex::Proto::MsDtyp::MsDtypGuid.read(get_entry(cred_struct, 6))
100
obj.custom_key_information = get_entry(cred_struct, 7)
101
ft = get_entry(cred_struct, 8)
102
obj.key_approximate_last_logon_time_raw = ft
103
ft = get_entry(cred_struct, 9)
104
obj.key_creation_time_raw = ft
105
106
obj
107
end
108
109
# Properties
110
attr_accessor :key_id # SHA256 hash of KeyMaterial
111
attr_accessor :key_hash # SHA256 hash of all entries after this entry
112
attr_accessor :raw_key_material # Key material of the credential, in bytes
113
attr_accessor :key_usage # Enumeration
114
attr_accessor :key_source # Always KEY_SOURCE_AD (0)
115
attr_accessor :device_id # [MsDtypGuid] Identifier for this credential
116
attr_accessor :custom_key_information # Two bytes is fine: Version and Flags
117
attr_accessor :key_approximate_last_logon_time_raw # Raw bytes for approximate time this key was last used
118
attr_accessor :key_creation_time_raw # Raw bytes for approximate time this key was created
119
120
# Find the entry with the given identifier
121
# @param struct [MsAdtsKeyCredentialStruct] Structure containing entries to search through
122
# @param struct [Integer] Identifier to search for, from https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/a99409ea-4982-4f72-b7ef-8596013a36c7
123
# @return [String] The data associated with this identifier, or nil if not found
124
def self.get_entry(struct, identifier)
125
struct.credential_entries.each do |entry|
126
if entry.identifier == identifier
127
return entry.data
128
end
129
end
130
end
131
132
# Parse the object's raw key material field into a OpenSSL::PKey::RSA object
133
# @param obj [KeyCredential] The object for which to parse the key
134
def public_key
135
case key_usage
136
when KEY_USAGE_NGC
137
if raw_key_material.start_with?([Rex::Proto::BcryptPublicKey::MAGIC].pack('I'))
138
result = Rex::Proto::BcryptPublicKey.read(raw_key_material)
139
exponent = OpenSSL::ASN1::Integer.new(bytes_to_int(result.exponent))
140
modulus = OpenSSL::ASN1::Integer.new(bytes_to_int(result.modulus))
141
# OpenSSL's API has changed over time - constructing from DER has been consistent
142
data_sequence = OpenSSL::ASN1::Sequence([modulus, exponent])
143
144
OpenSSL::PKey::RSA.new(data_sequence.to_der)
145
end
146
end
147
end
148
149
private
150
151
# Create a MsAdtsKeyCredentialEntryStruct from the provided data, and insert it in to the provided structure
152
# @param struct [MsAdtsKeyCredentialStruct] Structure to insert the resulting entry into
153
# @param identifier [Integer] Identifier associated with this entry, from https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/a99409ea-4982-4f72-b7ef-8596013a36c7
154
# @param data [String] The data to create an entry from
155
# @param insert_at_end [Boolean] Whether to insert the new entry at the end of the credential_entries; otherwise will insert at start
156
def add_entry(struct, identifier, data, insert_at_end: true)
157
entry = MsAdtsKeyCredentialEntryStruct.new
158
entry.identifier = identifier
159
entry.data = data
160
entry.struct_length = data.length
161
if insert_at_end
162
struct.credential_entries.insert(struct.credential_entries.length, entry)
163
else # Insert at start
164
struct.credential_entries.insert(0, entry)
165
end
166
end
167
168
def self.int_to_bytes(num)
169
str = num.to_s(16).rjust(2, '0')
170
171
[str].pack('H*')
172
end
173
174
def bytes_to_int(num)
175
num.unpack('H*')[0].to_i(16)
176
end
177
178
# Sets self.key_hash based on the credential_entries value in the provided parameter
179
# @param struct [MsAdtsKeyCredentialStruct] Its credential_entries value should have only those required to calculate the key_hash value (no key_id or key_hash)
180
def calculate_key_hash(struct)
181
sha256 = OpenSSL::Digest.new('SHA256')
182
self.key_hash = sha256.digest(struct.credential_entries.to_binary_s)
183
end
184
end
185
end
186