3
3
# Current source: https://github.com/rapid7/metasploit-framework
4
4
##
5
5
6
- require 'msf/core'
7
6
require 'rex'
8
- class Metasploit3 < Msf :: Post
7
+ require 'msf/core'
9
8
9
+ class Metasploit3 < Msf ::Post
10
10
include Msf ::Post ::File
11
11
include Msf ::Post ::Windows ::Priv
12
12
include Msf ::Post ::Windows ::Registry
@@ -15,12 +15,13 @@ def initialize(info={})
15
15
super ( update_info ( info ,
16
16
'Name' => 'Windows Gather Prefetch File Information' ,
17
17
'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.
21
22
} ,
22
23
'License' => MSF_LICENSE ,
23
- 'Author' => [ 'TJ Glad <fraktaali [at]gmail.com >' ] ,
24
+ 'Author' => [ 'TJ Glad <tjglad [at]cmail.nu >' ] ,
24
25
'Platform' => [ 'win' ] ,
25
26
'SessionType' => [ 'meterpreter' ]
26
27
) )
@@ -43,7 +44,7 @@ def print_prefetch_key_value()
43
44
end
44
45
45
46
def print_timezone_key_values ( key_value )
46
- # Looks for timezone from registry
47
+ # Looks for timezone information from registry.
47
48
timezone = registry_getvaldata ( "HKLM\\ SYSTEM\\ CurrentControlSet\\ Control\\ TimeZoneInformation" , key_value )
48
49
tz_bias = registry_getvaldata ( "HKLM\\ SYSTEM\\ CurrentControlSet\\ Control\\ TimeZoneInformation" , "Bias" )
49
50
if timezone . nil? or tz_bias . nil?
@@ -60,36 +61,69 @@ def print_timezone_key_values(key_value)
60
61
end
61
62
end
62
63
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
+
65
68
prefetch_file = read_file ( filename )
66
- if prefetch_file . empty? or prefetch_file . nil ?
69
+ if prefetch_file . blank ?
67
70
print_error ( "Couldn't read file: #{ filename } " )
68
71
return nil
69
72
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 ]
72
75
idx = pf_filename . index ( "\x00 \x00 " )
73
76
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 ( "\\ \x00 D\x00 E\x00 V\x00 I\x00 C\x00 E" )
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
86
115
end
87
- return [ last_modified , created , run_count , path_hash , name ]
88
116
end
117
+ if filepath . blank?
118
+ filepath << "*** Filepath not found ***"
119
+ end
120
+
121
+ return [ last_exec , path_hash , run_count , name , filepath [ 0 ] ]
89
122
end
90
123
91
124
def run
92
125
print_status ( "Prefetch Gathering started." )
126
+
93
127
# Check to see what Windows Version is running.
94
128
# Needed for offsets.
95
129
# Tested on WinXP, Win2k3 and Win7 systems.
@@ -100,18 +134,18 @@ def run
100
134
error_msg = "You don't have enough privileges. Try getsystem."
101
135
102
136
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?
107
139
print_error ( error_msg )
108
140
return nil
109
141
end
142
+
110
143
# Offsets for WinXP & Win2k3
111
144
print_good ( "Detected #{ sysnfo } (max 128 entries)" )
112
145
name_offset = 0x10
113
146
hash_offset = 0x4C
114
147
runcount_offset = 0x90
148
+ filetime_offset = 0x78
115
149
# Registry key for timezone
116
150
key_value = "StandardName"
117
151
@@ -120,14 +154,15 @@ def run
120
154
print_error ( error_msg )
121
155
return nil
122
156
end
157
+
123
158
# Offsets for Win7
124
159
print_good ( "Detected #{ sysnfo } (max 128 entries)" )
125
160
name_offset = 0x10
126
161
hash_offset = 0x4C
127
162
runcount_offset = 0x98
163
+ filetime_offset = 0x78
128
164
# Registry key for timezone
129
165
key_value = "TimeZoneKeyName"
130
-
131
166
else
132
167
print_error ( "No offsets for the target Windows version. Currently works only on WinXP, Win2k3 and Win7." )
133
168
return nil
@@ -138,12 +173,13 @@ def run
138
173
'Indent' => 1 ,
139
174
'Columns' =>
140
175
[
141
- "Modified (mace)" ,
142
- "Created (mace)" ,
176
+ "Last execution (filetime)" ,
143
177
"Run Count" ,
144
178
"Hash" ,
145
- "Filename"
179
+ "Filename" ,
180
+ "Filepath"
146
181
] )
182
+
147
183
print_prefetch_key_value
148
184
print_timezone_key_values ( key_value )
149
185
print_good ( "Current UTC Time: %s" % Time . now . utc )
@@ -165,7 +201,7 @@ def run
165
201
next
166
202
else
167
203
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 )
169
205
if not pf_entry . nil?
170
206
table << pf_entry
171
207
end
0 commit comments