From 5d2ff5982e7e0f422cc775a3a66cc30179393096 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Wed, 10 Dec 2014 18:33:46 -0600 Subject: [PATCH] Add support for PreAuthEncTimeStamp decoding/decrypting --- lib/rex/proto/kerberos/model.rb | 3 + .../model/field/pre_auth_enc_time_stamp.rb | 91 +++++++++++- .../kerberos/model/type/encrypted_data.rb | 53 +++++++ .../field/pre_auth_enc_time_stamp_spec.rb | 138 ++++++++++++++---- 4 files changed, 257 insertions(+), 28 deletions(-) diff --git a/lib/rex/proto/kerberos/model.rb b/lib/rex/proto/kerberos/model.rb index cab3cb6159..8f7f7cbb8e 100644 --- a/lib/rex/proto/kerberos/model.rb +++ b/lib/rex/proto/kerberos/model.rb @@ -49,6 +49,9 @@ module Rex PA_ENC_TIMESTAMP = 2 PA_PW_SALT = 3 PA_PAC_REQUEST = 128 + + # From RFC-4757: The RC4-HMAC Kerberos Encryption Types Used by Microsoft Windows + KERB_ETYPE_RC4_HMAC = 23 end end end diff --git a/lib/rex/proto/kerberos/model/field/pre_auth_enc_time_stamp.rb b/lib/rex/proto/kerberos/model/field/pre_auth_enc_time_stamp.rb index 935a527508..72ce0403aa 100644 --- a/lib/rex/proto/kerberos/model/field/pre_auth_enc_time_stamp.rb +++ b/lib/rex/proto/kerberos/model/field/pre_auth_enc_time_stamp.rb @@ -6,8 +6,97 @@ module Rex module Model module Field # This class is a representation of a PA-ENC-TIMESTAMP, an encrypted timestamp - class PreAuthEncTimeStamp < Rex::Proto::Kerberos::Model::Type::EncryptedData + class PreAuthEncTimeStamp < Element + # @!attribute pa_time_stamp + # @return [Time] client's time + attr_accessor :pa_time_stamp + # @!attribute pausec + # @return [Fixnum] optional microseconds client's time + attr_accessor :pausec + + # Decodes a Rex::Proto::Kerberos::Model::Field::PreAuthEncTimeStamp + # + # @param input [String, OpenSSL::ASN1::Sequence] the input to decode from + # @return [self] if decoding succeeds + # @raise [RuntimeError] if decoding doesn't succeed + def decode(input) + case input + when String + decode_string(input) + when OpenSSL::ASN1::Sequence + decode_asn1(input) + else + raise ::RuntimeError, 'Failed to decode EncryptedData Name, invalid input' + end + + self + end + + def encode + raise ::RuntimeError, 'EncryptedData encoding is not supported' + end + + # Decrypts a Rex::Proto::Kerberos::Model::Field::PreAuthEncTimeStamp + # + # @param input [String, OpenSSL::ASN1::Sequence] the input to decrypt from + # @return [Rex::Proto::Kerberos::Model::Field::PreAuthEncTimeStamp] if decryption succeeds + # @raise [RuntimeError] if decryption doesn't succeed + def self.decrypt(input, key) + elem = PreAuthEncTimeStamp.new + elem.decrypt(input, key) + + elem + end + + # Decrypts a Rex::Proto::Kerberos::Model::Field::PreAuthEncTimeStamp + # + # @param input [String, OpenSSL::ASN1::Sequence] the input to decrypt from + # @return [self] if decryption succeeds + # @raise [RuntimeError] if decryption doesn't succeed + def decrypt(input, key) + ed = Rex::Proto::Kerberos::Model::Type::EncryptedData.decode(input) + decrypted = ed.decrypt(key, 1) + decode(decrypted[8, decrypted.length - 1]) + + self + end + + private + + # Decodes a Rex::Proto::Kerberos::Model::Field::PreAuthEncTimeStamp + # + # @param input [String] the input to decode from + def decode_string(input) + asn1 = OpenSSL::ASN1.decode(input) + + decode_asn1(asn1) + end + + # Decodes a Rex::Proto::Kerberos::Model::Type::PreAuthEncTimeStamp from an + # OpenSSL::ASN1::Sequence + # + # @param input [OpenSSL::ASN1::Sequence] the input to decode from + def decode_asn1(input) + self.pa_time_stamp = decode_pa_time_stamp(input.value[0]) + self.pausec = decode_pausec(input.value[1]) + end + + # Decodes the decode_pa_time_stamp from an OpenSSL::ASN1::ASN1Data + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Boolean] + def decode_pa_time_stamp(input) + input.value[0].value + end + + # Decodes the pausec from an OpenSSL::ASN1::ASN1Data + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Fixnum] + def decode_pausec(input) + input.value[0].value.to_i + end end end end diff --git a/lib/rex/proto/kerberos/model/type/encrypted_data.rb b/lib/rex/proto/kerberos/model/type/encrypted_data.rb index 6f8e798cae..3e5c29e46d 100644 --- a/lib/rex/proto/kerberos/model/type/encrypted_data.rb +++ b/lib/rex/proto/kerberos/model/type/encrypted_data.rb @@ -40,8 +40,61 @@ module Rex raise ::RuntimeError, 'EncryptedData encoding is not supported' end + # Decrypts the cipher with etype encryption schema + # + # @param key [String] the key to decrypt + # @param key [Fixnum] the message type + # @return [String] if decryption succeeds + # @raise [RuntimeError] if decryption doesn't succeed + def decrypt(key, msg_type) + if cipher.nil? or cipher.empty? + return '' + end + + res = '' + case etype + when KERB_ETYPE_RC4_HMAC + res = decrypt_rc4_hmac(key, msg_type) + else + raise ::RuntimeError, 'EncryptedData encoding is not supported' + end + + res + end + private + # Decrypts the cipher using RC4-HMAC schema + # + # @param key [String] the key to decrypt + # @param key [Fixnum] the message type + # @return [String] if decryption succeeds + # @raise [RuntimeError] if decryption doesn't succeed + def decrypt_rc4_hmac(key, msg_type) + unless cipher && cipher.length > 16 + raise ::RuntimeError, 'RC4-HMAC decryption failed' + end + + my_key = OpenSSL::Digest.digest('MD4', Rex::Text.to_unicode(key)) + + checksum = cipher[0, 16] + data = cipher[16, cipher.length - 1] + + k1 = OpenSSL::HMAC.digest('MD5', my_key, [msg_type].pack('V')) + k3 = OpenSSL::HMAC.digest('MD5', k1, checksum) + + cipher = OpenSSL::Cipher::Cipher.new("rc4") + cipher.decrypt + cipher.key = k3 + decrypted = cipher.update(data) + cipher.final + + if OpenSSL::HMAC.digest('MD5', k1, decrypted) != checksum + raise ::RuntimeError, 'RC4-HMAC decryption failed, incorrect checksum verification' + end + + decrypted + end + # Decodes a Rex::Proto::Kerberos::Model::Type::EncryptedData from an String # # @param input [String] the input to decode from diff --git a/spec/lib/rex/proto/kerberos/model/field/pre_auth_enc_time_stamp_spec.rb b/spec/lib/rex/proto/kerberos/model/field/pre_auth_enc_time_stamp_spec.rb index 583e13138f..2be55f1ab3 100644 --- a/spec/lib/rex/proto/kerberos/model/field/pre_auth_enc_time_stamp_spec.rb +++ b/spec/lib/rex/proto/kerberos/model/field/pre_auth_enc_time_stamp_spec.rb @@ -10,37 +10,78 @@ describe Rex::Proto::Kerberos::Model::Field::PreAuthEncTimeStamp do end =begin -#>]>, - #]>, + #]>]> + [#>]>]> =end - let(:sample) do + let(:time_stamp_raw) do + "\x30\x1a\xa0\x11\x18\x0f\x32\x30" + + "\x31\x34\x31\x32\x30\x39\x30\x31" + + "\x30\x39\x30\x39\x5a\xa1\x05\x02" + + "\x03\x08\xfc\xc8" + end + +=begin +#> + ]>, + # + ]> + ]> +=end + let(:sample_encrypted_data) do "\x30\x3d\xa0\x03\x02\x01\x17\xa2\x36\x04\x34\x60\xae\x53\xa5\x0b" + "\x56\x2e\x46\x61\xd9\xd6\x89\x98\xfc\x79\x9d\x45\x73\x7d\x0d\x8a" + "\x78\x84\x4d\xd7\x7c\xc6\x50\x08\x8d\xab\x22\x79\xc3\x8d\xd3\xaf" + @@ -55,12 +96,55 @@ describe Rex::Proto::Kerberos::Model::Field::PreAuthEncTimeStamp do describe "#decode" do it "returns the decoded Rex::Proto::Kerberos::Model::Field::PreAuthEncTimeStamp" do - expect(pre_auth_enc_time_stamp.decode(sample)).to eq(pre_auth_enc_time_stamp) + expect(pre_auth_enc_time_stamp.decode(time_stamp_raw)).to eq(pre_auth_enc_time_stamp) end - it "decodes etype" do - pre_auth_enc_time_stamp.decode(sample) - expect(pre_auth_enc_time_stamp.etype).to eq(23) + it "decodes pa_time_stamp correctly" do + pre_auth_enc_time_stamp.decode(time_stamp_raw) + expect(pre_auth_enc_time_stamp.pa_time_stamp.to_s).to eq('2014-12-09 01:09:09 UTC') + end + + it "decodes pausec correctly" do + pre_auth_enc_time_stamp.decode(time_stamp_raw) + expect(pre_auth_enc_time_stamp.pausec).to eq(589000) + end + end + + describe ".decrypt" do + context "correct key" do + it "returns the decoded Rex::Proto::Kerberos::Model::Field::PreAuthEncTimeStamp" do + expect(described_class.decrypt(sample_encrypted_data, 'juan')).to be_a(Rex::Proto::Kerberos::Model::Field::PreAuthEncTimeStamp) + end + end + + context "incorrect key" do + it "raises RuntimeError when decrypting with the incorrect key" do + expect { described_class.decrypt(sample_encrypted_data, 'error') }.to raise_error(RuntimeError) + end + end + end + + describe "#decrypt" do + context "correct key" do + it "returns the decoded Rex::Proto::Kerberos::Model::Field::PreAuthEncTimeStamp" do + expect(pre_auth_enc_time_stamp.decrypt(sample_encrypted_data, 'juan')).to eq(pre_auth_enc_time_stamp) + end + + it "decodes pa_time_stamp correctly" do + pre_auth_enc_time_stamp.decrypt(sample_encrypted_data, 'juan') + expect(pre_auth_enc_time_stamp.pa_time_stamp.to_s).to eq('2014-12-09 01:09:09 UTC') + end + + it "decodes pausec correctly" do + pre_auth_enc_time_stamp.decrypt(sample_encrypted_data, 'juan') + expect(pre_auth_enc_time_stamp.pausec).to eq(589000) + end + end + + context "when incorrect key" do + it "raises RuntimeError when decrypting with the incorrect key" do + expect { pre_auth_enc_time_stamp.decrypt(sample_encrypted_data, 'error') }.to raise_error(RuntimeError) + end end end