Class: Rex::Proto::Kerberos::Crypto::Rc4Hmac

Inherits:
Object
  • Object
show all
Includes:
Gss::Asn1, Utils
Defined in:
lib/rex/proto/kerberos/crypto/rc4_hmac.rb

Constant Summary collapse

MAC_SIZE =
16
CONFOUNDER_SIZE =
8
PADDING_SIZE =
1

Instance Method Summary collapse

Methods included from Gss::Asn1

#unwrap_pseudo_asn1, #wrap_pseudo_asn1

Methods included from Utils

#xor_bytes, #xor_strings

Instance Method Details

#calculate_encrypted_length(plaintext_len) ⇒ Object



270
271
272
273
# File 'lib/rex/proto/kerberos/crypto/rc4_hmac.rb', line 270

def calculate_encrypted_length(plaintext_len)
  # We add 1-8 bytes of padding, per RFC1964 section 1.2.2.3
  plaintext_len + (8 - (plaintext_len % 8))
end

#checksum(key, msg_type, data) ⇒ String

Use this class's encryption routines to create a checksum of the data based on the key and message type

Parameters:

  • key (String)

    the key to use to generate the checksum

  • msg_type (Integer)

    type of kerberos message

  • data (String)

    the data to checksum

Returns:

  • (String)

    the generated checksum



35
36
37
38
39
40
# File 'lib/rex/proto/kerberos/crypto/rc4_hmac.rb', line 35

def checksum(key, msg_type, data)
  ksign = OpenSSL::HMAC.digest('MD5', key, "signaturekey\x00")
  md5_hash = Rex::Text.md5_raw(usage_str(msg_type) + data)

  ksign = OpenSSL::HMAC.digest('MD5', ksign, md5_hash)
end

#decrypt(ciphertext, key, msg_type) ⇒ String Also known as: decrypt_asn1

Decrypts the cipher using RC4-HMAC schema datatracker.ietf.org/doc/rfc4757/

Parameters:

  • ciphertext (String)

    the data to decrypt

  • key (String)

    the key to decrypt

  • msg_type (Integer)

    type of kerberos message

Returns:

  • (String)

    the decrypted cipher

Raises:



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/rex/proto/kerberos/crypto/rc4_hmac.rb', line 50

def decrypt(ciphertext, key, msg_type)
  unless ciphertext && ciphertext.length > MAC_SIZE
    raise Rex::Proto::Kerberos::Model::Error::KerberosError, 'RC4-HMAC decryption failed'
  end

  checksum = ciphertext[0, MAC_SIZE]
  data = ciphertext[MAC_SIZE, ciphertext.length - 1]

  k1 = OpenSSL::HMAC.digest('MD5', key, usage_str(msg_type))
  k3 = OpenSSL::HMAC.digest('MD5', k1, checksum)

  cipher = OpenSSL::Cipher.new('rc4')
  cipher.decrypt
  cipher.key = k3
  decrypted = cipher.update(data) + cipher.final

  if OpenSSL::HMAC.digest('MD5', k1, decrypted) != checksum
    raise ::Rex::Proto::Kerberos::Model::Error::KerberosError, 'RC4-HMAC decryption failed, incorrect checksum verification'
  end

  # Expect the first CONFOUNDER_SIZE bytes to be the confounder
  raise ::Rex::Proto::Kerberos::Model::Error::KerberosDecodingError, 'EncryptedData failed to decrypt' if decrypted.length < CONFOUNDER_SIZE

  # Skip the confounder when returning
  decrypted[CONFOUNDER_SIZE,decrypted.length]
end

#encrypt(plaintext, key, msg_type, confounder: nil) ⇒ String

Encrypts the cipher using RC4-HMAC schema datatracker.ietf.org/doc/rfc4757/

Parameters:

  • plaintext (String)

    the data to encrypt

  • key (String)

    the key to encrypt

  • msg_type (Integer)

    type of kerberos message

  • confounder (String) (defaults to: nil)

    Optionally force the confounder to a specific value

Returns:

  • (String)

    the encrypted data



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/rex/proto/kerberos/crypto/rc4_hmac.rb', line 87

def encrypt(plaintext, key, msg_type, confounder: nil)
  k1 = OpenSSL::HMAC.digest('MD5', key, usage_str(msg_type))

  confounder = Random.urandom(CONFOUNDER_SIZE) if confounder == nil
  data_encrypt = confounder + plaintext

  checksum = OpenSSL::HMAC.digest('MD5', k1, data_encrypt)

  k3 = OpenSSL::HMAC.digest('MD5', k1, checksum)

  cipher = OpenSSL::Cipher.new('rc4')
  cipher.encrypt
  cipher.key = k3
  encrypted = cipher.update(data_encrypt) + cipher.final

  res = checksum + encrypted
  res
end

#gss_unwrap(ciphertext, key, expected_sequence_number, is_initiator, opts = {}) ⇒ Object



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/rex/proto/kerberos/crypto/rc4_hmac.rb', line 106

def gss_unwrap(ciphertext, key, expected_sequence_number, is_initiator, opts={})
  # Always 32-bit sequence number
  expected_sequence_number &= 0xFFFFFFFF unless expected_sequence_number.nil?

  mech_id, ciphertext = unwrap_pseudo_asn1(ciphertext)

  raise Rex::Proto::Kerberos::Model::Error::KerberosError unless ciphertext.length > 0x20
  header = ciphertext[0,8]
  tok_id, alg, seal_alg, filler = header.unpack('nnnn')
  raise Rex::Proto::Kerberos::Model::Error::KerberosError, "Invalid token id: #{tok_id}" unless tok_id == 0x0201
  raise Rex::Proto::Kerberos::Model::Error::KerberosError, "Invalid alg: #{alg}" unless alg == 0x1100
  raise Rex::Proto::Kerberos::Model::Error::KerberosError, "Invalid seal_alg: #{seal_alg}" unless seal_alg == 0x1000
  raise Rex::Proto::Kerberos::Model::Error::KerberosError, "Invalid filler: #{filler}" unless filler == 0xFFFF

  encrypted_sequence_num = ciphertext[8,8]
  eight_checksum_bytes = ciphertext[16,8]
  encrypted_confounder = ciphertext[24,8]
  emessage = ciphertext[32, ciphertext.length - 32]

  kseq = OpenSSL::HMAC.digest('MD5', key.value, [0].pack('V'))

  kseq = OpenSSL::HMAC.digest('MD5', kseq, eight_checksum_bytes)

  cipher_seq = OpenSSL::Cipher.new('rc4')
  cipher_seq.decrypt
  cipher_seq.key = kseq

  decrypted_sequence_num = cipher_seq.update(encrypted_sequence_num)
  decrypted_sequence_num = decrypted_sequence_num.unpack('N')[0]

  #raise Rex::Proto::Kerberos::Model::Error::KerberosError, 'Invalid sequence number' unless (decrypted_sequence_num == expected_sequence_number || expected_sequence_number.nil?)

  klocal = xor_strings(key.value, "\xF0"*16)
  kcrypt = OpenSSL::HMAC.digest('MD5', klocal, [0].pack('V'))

  # Salt it with the sequence number
  kcrypt = OpenSSL::HMAC.digest('MD5', kcrypt, [decrypted_sequence_num].pack('N'))

  cipher = OpenSSL::Cipher.new('rc4')
  cipher.encrypt
  cipher.key = kcrypt
  decrypted_confounder = cipher.update(encrypted_confounder)

  plaintext = cipher.update(emessage)

  chksum_input = usage_str(Rex::Proto::Kerberos::Crypto::KeyUsage::KRB_PRIV_ENCPART) + header + decrypted_confounder
  ksign = OpenSSL::HMAC.digest('MD5', key.value, "signaturekey\x00")
  sgn_cksum = Rex::Text.md5_raw(chksum_input+plaintext)
  sgn_cksum = OpenSSL::HMAC.digest('MD5', ksign, sgn_cksum)

  verification_eight_checksum_bytes = sgn_cksum[0,8]
  raise Rex::Proto::Kerberos::Model::Error::KerberosError, 'Checksum error' unless verification_eight_checksum_bytes == eight_checksum_bytes

  # Remove padding, if present (seems MS may not send it back?)
  pad_char = plaintext[-1].ord
  if 1 <= pad_char && pad_char <= 8
    plaintext = plaintext[0, plaintext.length-pad_char]
  end

  plaintext
end

#gss_wrap(plaintext, key, sequence_number, is_initiator, opts = {}) ⇒ Object

Parameters:

  • options (Hash)

    a customizable set of options



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/rex/proto/kerberos/crypto/rc4_hmac.rb', line 170

def gss_wrap(plaintext, key, sequence_number, is_initiator, opts={})
  dce_style = opts.fetch(:dce_style) { false }
  pad_style = opts.fetch(:rc4_pad_style) { :single_byte }
  # Always 32-bit sequence number
  sequence_number &= 0xFFFFFFFF

  # Header
  tok_id = 0x0201
  alg = 0x1100
  seal_alg = 0x1000
  filler = 0xFFFF
  header = [tok_id, alg, seal_alg, filler].pack('nnnn')

  # Add padding (see RFC1964 section 1.2.2.3)
  # Some protocols (LDAP) only support a single byte and seem to fail otherwise
  # Others (DRSR) only support 8-byte and seem to fail otherwise
  # Some (WinRM) are lenient and are fine with either
  #
  # It's not entirely clear why
  if pad_style == :single_byte
    pad_num = 1
  elsif pad_style == :eight_byte_aligned
    pad_num = (8 - (plaintext.length % 8))
  else
    raise ArgumentError.new('Unknown pad_style setting')
  end

  plaintext += (pad_num.chr * pad_num)

  send_seq = [sequence_number].pack('N')
  # See errata on RFC4757
  initiator_bytes = "\xFF" * 4
  initiator_bytes = "\x00" * 4 if is_initiator
  send_seq += initiator_bytes

  confounder = Random.urandom(CONFOUNDER_SIZE)
  chksum_input = usage_str(Rex::Proto::Kerberos::Crypto::KeyUsage::KRB_PRIV_ENCPART) + header + confounder
  ksign = OpenSSL::HMAC.digest('MD5', key.value, "signaturekey\x00")
  sgn_cksum = Rex::Text.md5_raw(chksum_input+plaintext)

  klocal = xor_strings(key.value, "\xF0"*16)
  kcrypt = OpenSSL::HMAC.digest('MD5', klocal, [0].pack('V'))

  # Salt it with the sequence number
  kcrypt = OpenSSL::HMAC.digest('MD5', kcrypt, [sequence_number].pack('N'))

  cipher = OpenSSL::Cipher.new('rc4')
  cipher.encrypt
  cipher.key = kcrypt
  encrypted_confounder = cipher.update(confounder)

  encrypted = cipher.update(plaintext)

  sgn_cksum = OpenSSL::HMAC.digest('MD5', ksign, sgn_cksum)
  eight_checksum_bytes = sgn_cksum[0,8]

  kseq = OpenSSL::HMAC.digest('MD5', key.value, [0].pack('V'))

  kseq = OpenSSL::HMAC.digest('MD5', kseq, eight_checksum_bytes)

  cipher_seq = OpenSSL::Cipher.new('rc4')
  cipher_seq.encrypt
  cipher_seq.key = kseq

  encrypted_sequence_num = cipher_seq.update(send_seq)

  token = header + encrypted_sequence_num + eight_checksum_bytes + encrypted_confounder
  size_prior = (token+encrypted).length

  if dce_style
    wrapped_token = wrap_pseudo_asn1(
        ::Rex::Proto::Gss::OID_KERBEROS_5,
        token
    ) + encrypted
  else
    wrapped_token = wrap_pseudo_asn1(
        ::Rex::Proto::Gss::OID_KERBEROS_5,
        token + encrypted
    )
  end
  asn1_length = wrapped_token.length - size_prior
  token_length = asn1_length + token.length

  [wrapped_token, token_length, pad_num]
end

#header_byte_countObject

The number of bytes in the encrypted plaintext that precede the actual plaintext



259
260
261
# File 'lib/rex/proto/kerberos/crypto/rc4_hmac.rb', line 259

def header_byte_count
  MAC_SIZE + CONFOUNDER_SIZE
end

#string_to_key(password, salt = nil, params: nil) ⇒ String

Derive an encryption key based on a password and salt for the given cipher type

Parameters:

  • password (String)

    The password to use as the basis for key generation

  • salt (String) (defaults to: nil)

    Ignored for this encryption algorithm

  • params (String) (defaults to: nil)

    Unused for this encryption type

Returns:

  • (String)

    The derived key

Raises:



22
23
24
25
26
27
# File 'lib/rex/proto/kerberos/crypto/rc4_hmac.rb', line 22

def string_to_key(password, salt=nil, params: nil)
  raise Rex::Proto::Kerberos::Model::Error::KerberosError, 'Params not supported for RC4_HMAC' unless params == nil

  unicode_password = password.encode('utf-16le')
  password_digest = OpenSSL::Digest.digest('MD4', unicode_password)
end

#trailing_byte_countObject

The number of bytes in the encrypted plaintext that follow the actual plaintext



266
267
268
# File 'lib/rex/proto/kerberos/crypto/rc4_hmac.rb', line 266

def trailing_byte_count
  0
end