From 93d467cd4a7986638abda791c042b6b61bf74d0f Mon Sep 17 00:00:00 2001 From: sjanusz-r7 Date: Thu, 12 Oct 2023 14:33:32 +0100 Subject: [PATCH] Move encryption/decryption into a separate Crypto module --- gem/Rakefile | 16 +------- gem/lib/metasploit-payloads.rb | 25 ++---------- gem/lib/metasploit-payloads/crypto.rb | 44 +++++++++++++++++++++ gem/spec/metasploit_payloads/crypto_spec.rb | 22 +++++++++++ 4 files changed, 71 insertions(+), 36 deletions(-) create mode 100644 gem/lib/metasploit-payloads/crypto.rb create mode 100644 gem/spec/metasploit_payloads/crypto_spec.rb diff --git a/gem/Rakefile b/gem/Rakefile index 5bdad4c1..14f23501 100644 --- a/gem/Rakefile +++ b/gem/Rakefile @@ -1,5 +1,6 @@ require "bundler/gem_tasks" require 'openssl' +require 'metasploit-payloads/crypto' c_source = "../c/meterpreter/" java_source = "../java" @@ -46,19 +47,6 @@ platform_config = { } } -def chacha20_encrypt(contents: '') - cipher = ::OpenSSL::Cipher.new('chacha20') - cipher.encrypt - cipher.iv = 'EncryptedPayload' - cipher.key = 'Rapid7MetasploitEncryptedPayload' - - encrypted_contents = 'encrypted_payload_chacha20_v1' - encrypted_contents << cipher.update(contents) - encrypted_contents << cipher.final - - encrypted_contents -end - def copy_files(cnf, meterpreter_dest) cnf[:sources].each do |f| cnf[:extensions].each do |ext| @@ -66,7 +54,7 @@ def copy_files(cnf, meterpreter_dest) target = File.join(meterpreter_dest, File.basename(bin)) print("Copying: #{bin} -> #{target}\n") contents = ::File.binread(::File.expand_path(bin)) - encrypted_contents = chacha20_encrypt(contents: contents) + encrypted_contents = ::MetasploitPayloads::Crypto.encrypt(plaintext: contents) ::File.binwrite(::File.expand_path(target), encrypted_contents) end end diff --git a/gem/lib/metasploit-payloads.rb b/gem/lib/metasploit-payloads.rb index f73cf75c..221c2ea7 100644 --- a/gem/lib/metasploit-payloads.rb +++ b/gem/lib/metasploit-payloads.rb @@ -3,6 +3,7 @@ require 'openssl' unless defined? OpenSSL::Digest require 'metasploit-payloads/version' unless defined? MetasploitPayloads::VERSION require 'metasploit-payloads/error' unless defined? MetasploitPayloads::Error +require 'metasploit-payloads/crypto' unless defined? MetasploitPayloads::Crypto # # This module dispenses Metasploit payload binary files @@ -12,10 +13,6 @@ module MetasploitPayloads METERPRETER_SUBFOLDER = 'meterpreter' USER_DATA_SUBFOLDER = 'payloads' - ENCRYPTED_PAYLOAD_HEADER = 'encrypted_payload_chacha20_v1' - CHACHA20_IV = 'EncryptedPayload' # 16 bytes - CHACHA20_KEY = 'Rapid7MetasploitEncryptedPayload' # 32 bytes - # # @return [Array>] An array of filenames with warnings. Provides a file name and error. # Empty if all needed Meterpreter files exist and have the correct hash. @@ -157,26 +154,10 @@ module MetasploitPayloads raise e end - encrypted_file = file_contents.start_with?(ENCRYPTED_PAYLOAD_HEADER) + encrypted_file = file_contents.start_with?(Crypto::ENCRYPTED_PAYLOAD_HEADER) return file_contents unless encrypted_file - self.decrypt_payload(payload: file_contents) - end - - def self.decrypt_payload(payload: '') - return payload unless payload.start_with?(ENCRYPTED_PAYLOAD_HEADER) - - # Remove the header from the file. - encrypted_contents = payload.sub(ENCRYPTED_PAYLOAD_HEADER, '') - cipher = ::OpenSSL::Cipher.new('chacha20') - cipher.decrypt # Call before using .key - cipher.iv = CHACHA20_IV - cipher.key = CHACHA20_KEY - - decrypted_contents = cipher.update(encrypted_contents) - decrypted_contents << cipher.final - - decrypted_contents + Crypto.decrypt(ciphertext: file_contents) end # diff --git a/gem/lib/metasploit-payloads/crypto.rb b/gem/lib/metasploit-payloads/crypto.rb new file mode 100644 index 00000000..7476036d --- /dev/null +++ b/gem/lib/metasploit-payloads/crypto.rb @@ -0,0 +1,44 @@ +require 'openssl' + +module MetasploitPayloads + module Crypto + CIPHER_NAME = 'chacha20'.freeze + IV = 'EncryptedPayload'.freeze # 16 bytes + KEY = 'Rapid7MetasploitEncryptedPayload'.freeze # 32 bytes + ENCRYPTED_PAYLOAD_HEADER = 'encrypted_payload_chacha20_v1'.freeze + + def self.encrypt(plaintext: '') + raise ::ArgumentError, 'Unable to encrypt plaintext: ' << plaintext, caller unless plaintext.to_s + + cipher = ::OpenSSL::Cipher.new(CIPHER_NAME) + + cipher.encrypt + cipher.iv = IV + cipher.key = KEY + + output = ENCRYPTED_PAYLOAD_HEADER.dup + output << cipher.update(plaintext) + output << cipher.final + + output + end + + def self.decrypt(ciphertext: '') + raise ::ArgumentError, 'Unable to decrypt ciphertext: ' << ciphertext, caller unless ciphertext.to_s + + cipher = ::OpenSSL::Cipher.new(CIPHER_NAME) + + cipher.decrypt + cipher.iv = IV + cipher.key = KEY + + # Remove encrypted header if present + ciphertext = ciphertext.sub(ENCRYPTED_PAYLOAD_HEADER, '') + + output = cipher.update(ciphertext) + output << cipher.final + + output + end + end +end \ No newline at end of file diff --git a/gem/spec/metasploit_payloads/crypto_spec.rb b/gem/spec/metasploit_payloads/crypto_spec.rb new file mode 100644 index 00000000..1c5c87f5 --- /dev/null +++ b/gem/spec/metasploit_payloads/crypto_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' +require 'metasploit-payloads' + +RSpec.describe ::MetasploitPayloads::Crypto do + describe '#encrypt' do + let(:encrypted_header) { "encrypted_payload_chacha20_v1".b } + let(:plaintext) { "Hello World!".b } + let(:ciphertext) { encrypted_header + "\\c\xB6N\x95\xE58\x8D\xDF\xBF4c".b } + + it 'can encrypt plaintext' do + expect(described_class.encrypt(plaintext: plaintext)).to eq ciphertext + end + + it 'can decrypt ciphertext' do + expect(described_class.decrypt(ciphertext: ciphertext)).to eq plaintext + end + + it 'is idempotent' do + expect(described_class.decrypt(ciphertext: described_class.encrypt(plaintext: plaintext))).to eq plaintext + end + end +end