Package rekall :: Package plugins :: Package windows :: Module cache
[frames] | no frames]

Source Code for Module rekall.plugins.windows.cache

  1  # Rekall Memory Forensics 
  2  # Copyright 2014 Google Inc. All Rights Reserved. 
  3  # 
  4  # This program is free software; you can redistribute it and/or modify 
  5  # it under the terms of the GNU General Public License as published by 
  6  # the Free Software Foundation; either version 2 of the License, or (at 
  7  # your option) any later version. 
  8  # 
  9  # This program is distributed in the hope that it will be useful, but 
 10  # WITHOUT ANY WARRANTY; without even the implied warranty of 
 11  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
 12  # General Public License for more details. 
 13  # 
 14  # You should have received a copy of the GNU General Public License 
 15  # along with this program; if not, write to the Free Software 
 16  # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
 17  # 
 18   
 19  # pylint: disable=protected-access 
 20   
 21  """This module adds plugins to inspect the windows cache manager. 
 22   
 23  The windows cache manager is responsible for maintaining file cache for files 
 24  read from disk. The manager maintains a large arena of 256kb cached 
 25  blocks. These blocks are controlled using the VACB (Virtual Address Control 
 26  Block) arrays. 
 27   
 28  References: 
 29  http://www.codemachine.com/article_kernelstruct.html 
 30   
 31  """ 
 32   
 33  __author__ = "Michael Cohen <scudette@google.com>" 
 34  from rekall import obj 
 35  from rekall import testlib 
 36   
 37  from rekall.plugins import core 
 38  from rekall.plugins.windows import common 
 39  from rekall_lib import utils 
 40   
 41   
42 -class EnumerateVacbs(common.WindowsCommandPlugin):
43 """Enumerate all blocks cached in the cache manager.""" 44 name = "vacbs" 45
46 - def GetVACBs_Win7(self):
47 """Yield all system VACBs. 48 49 Walks the VACB tables and produce all valid VACBs. This essentially 50 produces the entire contents of the cache manager. 51 """ 52 # The Kernel variable CcVacbArrays is a pointer to an array of pointers 53 # to the _VACB_ARRAY_HEADER tables. The total number of tables is stored 54 # in CcVacbArraysAllocated. 55 total_vacb_arrays = self.profile.get_constant_object( 56 'CcVacbArraysAllocated', 'unsigned int') 57 58 vacb_arrays = self.profile.get_constant_object( 59 'CcVacbArrays', 60 target="Pointer", 61 target_args=dict( 62 target='Array', 63 target_args=dict( 64 target="Pointer", 65 target_args=dict( 66 target='_VACB_ARRAY_HEADER' 67 ), 68 count=int(total_vacb_arrays), 69 ) 70 ) 71 ) 72 73 for table in vacb_arrays: 74 self.session.report_progress( 75 "Scanning VACB table %s", table.VacbArrayIndex) 76 77 for vacb in table.VACBs: 78 if vacb.ArrayHead != table: 79 continue 80 81 yield vacb
82
83 - def GetVACBs_WinXP(self):
84 """Yield all system VACBs for older Windows XP based kernels. 85 86 Walks the VACB tables and produce all valid VACBs. This essentially 87 produces the entire contents of the cache manager. 88 """ 89 # The Kernel variable CcVacbArrays is a pointer to an array of pointers 90 # to the _VACB_ARRAY_HEADER tables. The total number of tables is stored 91 # in CcVacbArraysAllocated. 92 total_vacb_arrays = self.profile.get_constant_object( 93 'CcNumberVacbs', 'unsigned int') 94 95 vacb_array = self.profile.get_constant_object( 96 'CcVacbs', 97 target="Pointer", 98 target_args=dict( 99 target='Array', 100 target_args=dict( 101 target="_VACB", 102 count=int(total_vacb_arrays), 103 ) 104 ) 105 ) 106 107 for vacb in vacb_array: 108 yield vacb
109
110 - def GetVACBs(self):
111 # Support the old XP way. 112 if self.session.profile.get_constant("CcVacbs"): 113 return self.GetVACBs_WinXP() 114 115 return self.GetVACBs_Win7()
116 117 table_header = [ 118 dict(name="_VACB", style="address"), 119 dict(name='valid', width=7), 120 dict(name="base", style="address"), 121 dict(name="offset", style="address"), 122 dict(name="filename"), 123 ] 124
125 - def column_types(self):
126 return dict(_VACB=self.session.profile._VACB(), 127 valid=True, 128 base=0, 129 offset=0, 130 filename="")
131
132 - def collect(self):
133 for vacb in self.GetVACBs(): 134 filename = vacb.SharedCacheMap.FileObject.file_name_with_drive() 135 if filename: 136 yield (vacb, 137 bool(self.kernel_address_space.vtop( 138 vacb.BaseAddress.v() 139 )), 140 vacb.BaseAddress.v(), 141 142 vacb.Overlay.FileOffset.QuadPart, 143 filename, 144 )
145 146
147 -class DumpFiles(core.DirectoryDumperMixin, common.WinProcessFilter):
148 """Dump files from memory. 149 150 The interface is loosely based on the Volatility plugin of the same name, 151 although the implementation is quite different. 152 """ 153 name = "dumpfiles" 154 155 __args = [ 156 dict(name="file_objects", type="ArrayIntParser", 157 help="Kernel addresses of _FILE_OBJECT structs.") 158 ] 159
160 - def CollectFileObject(self):
161 """Collect all known file objects.""" 162 163 # Collect known file objects for selected processes. 164 for task in self.filter_processes(): 165 # First scan the vads. 166 self.session.report_progress("Inspecting VAD for %s", task.name) 167 for vad in task.RealVadRoot.traverse(): 168 file_object = vad.m("Subsection").ControlArea.FilePointer 169 if file_object: 170 self.file_objects.add(file_object) 171 172 # Now check handles. 173 self.session.report_progress("Inspecting Handles for %s", task.name) 174 for handle in task.ObjectTable.handles(): 175 if handle.get_object_type() == "File": 176 self.file_objects.add(handle.Object) 177 178 # Now scan all the objects in the cache manager. 179 for vacb in self.session.plugins.vacbs().GetVACBs(): 180 shared_cache_map = vacb.SharedCacheMap.v() 181 if shared_cache_map: 182 # Keep a tally of all VACBs for each file_object. 183 self.vacb_by_cache_map.setdefault( 184 shared_cache_map, []).append(vacb)
185
186 - def _dump_ca(self, ca, out_fd, type, filename, renderer):
187 sectors_per_page = 0x1000 / 512 188 189 for subsection in ca.FirstSubsection.walk_list("NextSubsection"): 190 for i, pte in enumerate(subsection.SubsectionBase): 191 pte_value = pte.u.Long.v() 192 try: 193 phys_address = self.kernel_address_space.ResolveProtoPTE( 194 pte_value, 0) 195 except AttributeError: 196 # For address spaces which do not support prototype 197 # (currently non PAE 32 bits) just support the absolute 198 # basic - valid PTE only. 199 if pte & 1: 200 phys_address = pte_value & 0xffffffffff000 201 else: 202 continue 203 204 if phys_address == None: 205 continue 206 207 # The file offset of this page. 208 file_sector_offset = ( 209 subsection.StartingSector + i * sectors_per_page) 210 211 # Sometimes not the entire page is mapped in. 212 file_sectors_mapped_in_page = min( 213 sectors_per_page, 214 subsection.NumberOfFullSectors - i * sectors_per_page) 215 216 if file_sectors_mapped_in_page < 0: 217 continue 218 219 # This should not happen but it does if the data is corrupt. 220 if phys_address > self.physical_address_space.end(): 221 continue 222 223 renderer.table_row( 224 type, phys_address, file_sector_offset * 512, 225 file_sectors_mapped_in_page * 512, filename) 226 227 # This writes a sparse file. 228 out_fd.seek(file_sector_offset * 512) 229 out_fd.write(self.physical_address_space.read( 230 phys_address, file_sectors_mapped_in_page * 512))
231 232 table_header = [ 233 dict(name="type", width=20), 234 dict(name="p_offset", style="address"), 235 dict(name="f_offset", style="address"), 236 dict(name="f_length", style="address"), 237 dict(name="filename") 238 ] 239
240 - def column_types(self):
241 return dict(type="VACB", p_offset=0, f_offset=0, 242 f_length=0x1000, filename="")
243
244 - def collect(self):
245 246 self.file_objects = set() 247 self.vacb_by_cache_map = {} 248 249 renderer = self.session.GetRenderer() 250 if not self.plugin_args.file_objects: 251 self.CollectFileObject() 252 else: 253 self.file_objects = set( 254 [self.session.profile._FILE_OBJECT(int(x)) 255 for x in self.plugin_args.file_objects]) 256 257 seen_filenames = set() 258 for file_object in self.file_objects: 259 filename = unicode( 260 file_object.file_name_with_device()).replace("\\", "_") 261 262 if filename in seen_filenames: 263 continue 264 265 seen_filenames.add(filename) 266 267 self.session.report_progress(" Dumping %s", filename) 268 with renderer.open(directory=self.dump_dir, 269 filename=filename, mode="w") as out_fd: 270 filename = out_fd.name 271 272 # Sometimes we get both subsections. 273 ca = file_object.SectionObjectPointer.ImageSectionObject 274 if ca: 275 self._dump_ca(ca, out_fd, "ImageSectionObject", 276 filename, renderer) 277 278 ca = file_object.SectionObjectPointer.DataSectionObject 279 if ca: 280 self._dump_ca(ca, out_fd, "DataSectionObject", 281 filename, renderer) 282 283 scm = file_object.SectionObjectPointer.SharedCacheMap.v() 284 285 # Augment the data with the cache manager. 286 for vacb in self.vacb_by_cache_map.get(scm, []): 287 base_address = vacb.BaseAddress.v() 288 file_offset = vacb.Overlay.FileOffset.QuadPart.v() 289 290 # Each VACB controls a 256k buffer. 291 for offset in utils.xrange(0, 0x40000, 0x1000): 292 phys_address = self.kernel_address_space.vtop( 293 base_address + offset) 294 295 if phys_address: 296 yield dict(type="VACB", 297 p_offset=phys_address, 298 f_offset=file_offset+offset, 299 f_length=0x1000, 300 filename=filename) 301 302 # This writes a sparse file. 303 out_fd.seek(file_offset + offset) 304 out_fd.write(self.physical_address_space.read( 305 phys_address, 0x1000))
306 307
308 -class TestDumpFiles(testlib.HashChecker):
309 PARAMETERS = dict( 310 commandline="dumpfiles --dump_dir %(tempdir)s" 311 )
312 313
314 -class SparseArray(dict):
315 - def __getitem__(self, key):
316 return self.get(key, obj.NoneObject())
317 318
319 -class MftDump(common.WindowsCommandPlugin):
320 """Enumerate MFT entries from the cache manager.""" 321 name = "mftdump" 322
323 - def __init__(self, *args, **kwargs):
324 super(MftDump, self).__init__(*args, **kwargs) 325 self.ntfs_profile = self.session.LoadProfile("ntfs") 326 self.mft_size = 0x400 327 self.vacb_size = 0x40000 328 # A sparse MFT table - basically a map between mft id and MFT entry. 329 self.mfts = SparseArray() 330 331 # A directory tree. For each MFT id a dict of its direct children. 332 self.dir_tree = {2: {}}
333
334 - def extract_mft_entries_from_vacb(self, vacb):
335 base = vacb.BaseAddress.v() 336 for offset in utils.xrange(base, base + self.vacb_size, self.mft_size): 337 # Fixups are not applied in memory. 338 mft = self.ntfs_profile.MFT_ENTRY( 339 offset, context=dict(mft=self.mfts, ApplyFixup=False)) 340 if mft.magic != "FILE": 341 continue 342 343 mft_id = mft.mft_entry 344 self.mfts[mft_id] = mft 345 self.session.report_progress( 346 "Added: %s", lambda mft=mft: mft.filename.name) 347 348 parent_id = mft.filename.mftReference.v() 349 if parent_id not in self.dir_tree: 350 self.dir_tree[parent_id] = set() 351 352 self.dir_tree[parent_id].add(mft_id)
353
354 - def collect_tree(self, root, seen, depth=0):
355 if root not in self.mfts or root in seen: 356 return 357 358 mft = self.mfts[root] 359 standard_info = mft.get_attribute( 360 "$STANDARD_INFORMATION").DecodeAttribute() 361 362 yield dict(MFT=root, 363 mft_entry=mft, 364 file_modified=standard_info.file_altered_time, 365 mft_modified=standard_info.mft_altered_time, 366 access=standard_info.file_accessed_time, 367 create_time=standard_info.create_time, 368 Name=self.mfts[root].filename.name, 369 depth=depth) 370 seen.add(root) 371 372 for child in sorted(self.dir_tree.get(root, [])): 373 if child not in seen: 374 for x in self.collect_tree(child, seen, depth=depth+1): 375 yield x
376 377 table_header = [ 378 dict(name="MFT", width=5, align="r"), 379 dict(name="mft_entry", hidden=True), 380 dict(name="file_modified", width=25), 381 dict(name="mft_modified", width=25), 382 dict(name="access", width=25), 383 dict(name="create_time", width=25), 384 dict(name="Name", type="TreeNode", max_depth=15, width=100), 385 ] 386
387 - def column_types(self):
388 wft = self.session.profile.WinFileTime() 389 return dict(MFT=int, 390 mft_entry=self.ntfs_profile.MFT_ENTRY(), 391 file_modified=wft, 392 mft_modified=wft, 393 access=wft, 394 create_time=wft, 395 Name=self.session.profile.UnicodeString())
396
397 - def collect(self):
398 for vacb in self.session.plugins.vacbs().GetVACBs(): 399 filename = vacb.SharedCacheMap.FileObject.FileName 400 if filename == r"\$Mft": 401 self.extract_mft_entries_from_vacb(vacb) 402 403 # Avoid loops. 404 seen = set() 405 for mft_id in self.dir_tree: 406 for x in self.collect_tree(mft_id, seen, depth=0): 407 yield x
408 409
410 -class TestMftDump(testlib.SortedComparison):
411 """The order is someone non-deterministic."""
412