Skip to content

Commit a043d38

Browse files
committed
Land rapid7#2738, @jiuweigui update to enum_prefetch
2 parents 2510580 + 446db78 commit a043d38

File tree

1 file changed

+70
-34
lines changed

1 file changed

+70
-34
lines changed

modules/post/windows/gather/enum_prefetch.rb

Lines changed: 70 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
# Current source: https://github.com/rapid7/metasploit-framework
44
##
55

6-
require 'msf/core'
76
require 'rex'
8-
class Metasploit3 < Msf::Post
7+
require 'msf/core'
98

9+
class Metasploit3 < Msf::Post
1010
include Msf::Post::File
1111
include Msf::Post::Windows::Priv
1212
include Msf::Post::Windows::Registry
@@ -15,12 +15,13 @@ def initialize(info={})
1515
super(update_info(info,
1616
'Name' => 'Windows Gather Prefetch File Information',
1717
'Description' => %q{
18-
This module gathers prefetch file information from WinXP, Win2k3 and Win7 systems.
19-
Run count, hash and filename information is collected from each prefetch file while
20-
Last Modified and Create times are file MACE values.
18+
This module gathers prefetch file information from WinXP, Win2k3 and Win7 systems
19+
and current values of related registry keys. From each prefetch file we'll collect
20+
filetime (converted to utc) of the last execution, file path hash, run count, filename
21+
and the execution path.
2122
},
2223
'License' => MSF_LICENSE,
23-
'Author' => ['TJ Glad <fraktaali[at]gmail.com>'],
24+
'Author' => ['TJ Glad <tjglad[at]cmail.nu>'],
2425
'Platform' => ['win'],
2526
'SessionType' => ['meterpreter']
2627
))
@@ -43,7 +44,7 @@ def print_prefetch_key_value()
4344
end
4445

4546
def print_timezone_key_values(key_value)
46-
# Looks for timezone from registry
47+
# Looks for timezone information from registry.
4748
timezone = registry_getvaldata("HKLM\\SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", key_value)
4849
tz_bias = registry_getvaldata("HKLM\\SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", "Bias")
4950
if timezone.nil? or tz_bias.nil?
@@ -60,36 +61,69 @@ def print_timezone_key_values(key_value)
6061
end
6162
end
6263

63-
def gather_pf_info(name_offset, hash_offset, runcount_offset, filename)
64-
# We'll load the file and parse information from the offsets
64+
def gather_pf_info(name_offset, hash_offset, runcount_offset, filetime_offset, filename)
65+
# Collects the desired information from each prefetch file found
66+
# from the system.
67+
6568
prefetch_file = read_file(filename)
66-
if prefetch_file.empty? or prefetch_file.nil?
69+
if prefetch_file.blank?
6770
print_error("Couldn't read file: #{filename}")
6871
return nil
6972
else
70-
# First we'll get the filename
71-
pf_filename = prefetch_file[name_offset..name_offset+60]
73+
# First we extract the saved filename
74+
pf_filename = prefetch_file[name_offset, 60]
7275
idx = pf_filename.index("\x00\x00")
7376
name = Rex::Text.to_ascii(pf_filename.slice(0..idx))
74-
# Next we'll get the run count
75-
run_count = prefetch_file[runcount_offset..runcount_offset+4].unpack('L*')[0].to_s
76-
# Then file path hash
77-
path_hash = prefetch_file[hash_offset..hash_offset+4].unpack('h8')[0].reverse.upcase.to_s
78-
# Last is mace value for timestamps
79-
mtimes = client.priv.fs.get_file_mace(filename)
80-
if mtimes.nil? or mtimes.empty?
81-
last_modified = "Error reading value"
82-
created = "Error reading value"
83-
else
84-
last_modified = mtimes['Modified'].utc.to_s
85-
created = mtimes['Created'].utc.to_s
77+
78+
# Then we get the runcount
79+
run_count = prefetch_file[runcount_offset, 4].unpack('v')[0]
80+
81+
# Then the filepath hash
82+
path_hash = prefetch_file[hash_offset, 4].unpack('h*')[0].upcase.reverse
83+
84+
# Last we get the latest execution time
85+
filetime_a = prefetch_file[filetime_offset, 16].unpack('q*')
86+
filetime = filetime_a[0] + filetime_a[1]
87+
last_exec = Time.at((filetime - 116444736000000000) / 10000000).utc.to_s
88+
89+
# This is for reading file paths of the executable from
90+
# the prefetch file. We'll use this to find out from where the
91+
# file was executed.
92+
93+
# First we'll use specific offsets for finding out the location
94+
# and length of the filepath so that we can find it.
95+
filepath = []
96+
fpath_offset = prefetch_file[0x64, 2].unpack('v').first
97+
fpath_length = prefetch_file[0x68, 2].unpack('v').first
98+
filepath_data = prefetch_file[fpath_offset, fpath_length]
99+
100+
# This part will extract the filepath so that we can find and
101+
# compare its contents to the filename we found previously. This
102+
# allows us to find the filepath (if it can be found inside the
103+
# prefetch file) used to execute the program
104+
# referenced in the prefetch-file.
105+
unless filepath_data.blank?
106+
fpath_data_array = filepath_data.split("\\\x00D\x00E\x00V\x00I\x00C\x00E")
107+
fpath_data_array.each do |path|
108+
unless path.blank?
109+
fpath_name = path.split("\\").last.gsub(/\0/, '')
110+
if fpath_name == name
111+
filepath << path
112+
end
113+
end
114+
end
86115
end
87-
return [last_modified, created, run_count, path_hash, name]
88116
end
117+
if filepath.blank?
118+
filepath << "*** Filepath not found ***"
119+
end
120+
121+
return [last_exec, path_hash, run_count, name, filepath[0]]
89122
end
90123

91124
def run
92125
print_status("Prefetch Gathering started.")
126+
93127
# Check to see what Windows Version is running.
94128
# Needed for offsets.
95129
# Tested on WinXP, Win2k3 and Win7 systems.
@@ -100,18 +134,18 @@ def run
100134
error_msg = "You don't have enough privileges. Try getsystem."
101135

102136
if sysnfo =~/(Windows XP|2003|.NET)/
103-
# For some reason we need system privileges to read file
104-
# mace time on XP/2003 while we can do the same only
105-
# as admin on Win7.
106-
if not is_system?
137+
138+
if not is_admin?
107139
print_error(error_msg)
108140
return nil
109141
end
142+
110143
# Offsets for WinXP & Win2k3
111144
print_good("Detected #{sysnfo} (max 128 entries)")
112145
name_offset = 0x10
113146
hash_offset = 0x4C
114147
runcount_offset = 0x90
148+
filetime_offset = 0x78
115149
# Registry key for timezone
116150
key_value = "StandardName"
117151

@@ -120,14 +154,15 @@ def run
120154
print_error(error_msg)
121155
return nil
122156
end
157+
123158
# Offsets for Win7
124159
print_good("Detected #{sysnfo} (max 128 entries)")
125160
name_offset = 0x10
126161
hash_offset = 0x4C
127162
runcount_offset = 0x98
163+
filetime_offset = 0x78
128164
# Registry key for timezone
129165
key_value = "TimeZoneKeyName"
130-
131166
else
132167
print_error("No offsets for the target Windows version. Currently works only on WinXP, Win2k3 and Win7.")
133168
return nil
@@ -138,12 +173,13 @@ def run
138173
'Indent' => 1,
139174
'Columns' =>
140175
[
141-
"Modified (mace)",
142-
"Created (mace)",
176+
"Last execution (filetime)",
143177
"Run Count",
144178
"Hash",
145-
"Filename"
179+
"Filename",
180+
"Filepath"
146181
])
182+
147183
print_prefetch_key_value
148184
print_timezone_key_values(key_value)
149185
print_good("Current UTC Time: %s" % Time.now.utc)
@@ -165,7 +201,7 @@ def run
165201
next
166202
else
167203
filename = ::File.join(file['path'], file['name'])
168-
pf_entry = gather_pf_info(name_offset, hash_offset, runcount_offset, filename)
204+
pf_entry = gather_pf_info(name_offset, hash_offset, runcount_offset, filetime_offset, filename)
169205
if not pf_entry.nil?
170206
table << pf_entry
171207
end

0 commit comments

Comments
 (0)