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

Source Code for Module rekall.plugins.windows.filescan

  1  #!/usr/bin/env python 
  2  # 
  3  #       fileobjscan.py 
  4  #       Copyright 2009 Andreas Schuster <a.schuster@yendor.net> 
  5  #       Copyright (C) 2009-2011 Volatile Systems 
  6  # 
  7  #       This program is free software; you can redistribute it and/or modify 
  8  #       it under the terms of the GNU General Public License as published by 
  9  #       the Free Software Foundation; either version 2 of the License, or 
 10  #       (at your option) any later version. 
 11  # 
 12  #       This program is distributed in the hope that it will be useful, 
 13  #       but WITHOUT ANY WARRANTY; without even the implied warranty of 
 14  #       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 15  #       GNU General Public License for more details. 
 16  # 
 17  #       You should have received a copy of the GNU General Public License 
 18  #       along with this program; if not, write to the Free Software 
 19  #       Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 
 20  #       MA 02110-1301, USA. 
 21   
 22  # pylint: disable=protected-access 
 23   
 24  """ 
 25  @author:       Andreas Schuster 
 26  @license:      GNU General Public License 2.0 or later 
 27  @contact:      a.schuster@forensikblog.de 
 28  @organization: http://computer.forensikblog.de/en/ 
 29  """ 
 30  from rekall.plugins.windows import common 
 31   
 32   
33 -class PoolScanFile(common.PoolScanner):
34 """PoolScanner for File objects"""
35 - def __init__(self, **kwargs):
36 super(PoolScanFile, self).__init__(**kwargs) 37 self.checks = [ 38 ('PoolTagCheck', dict( 39 tag=self.profile.get_constant("FILE_POOLTAG"))), 40 41 ('CheckPoolSize', dict( 42 min_size=self.profile.get_obj_size("_FILE_OBJECT"))), 43 44 ('CheckPoolType', dict( 45 paged=True, non_paged=True, free=True)), 46 47 ('CheckPoolIndex', dict(value=0)), 48 ]
49 50
51 -class FileScan(common.PoolScannerPlugin):
52 """ Scan Physical memory for _FILE_OBJECT pool allocations 53 """ 54 __name = "filescan" 55 56 table_header = [ 57 dict(name='a', width=1), 58 dict(name="offset", style="address"), 59 dict(name="ptr_no", width=6, align="r"), 60 dict(name="hnd_no", width=3, align="r"), 61 dict(name="access", width=6), 62 dict(name='Owner', type="_EPROCESS"), 63 dict(name="path") 64 ] 65 66 scanner_defaults = dict( 67 scan_kernel_nonpaged_pool=True 68 ) 69
70 - def collect(self):
71 """Generate possible hits.""" 72 for run in self.generate_memory_ranges(): 73 scanner = PoolScanFile(profile=self.profile, session=self.session, 74 address_space=run.address_space) 75 76 for pool_obj in scanner.scan(run.start, run.length): 77 for object_obj in pool_obj.IterObject("File", freed=True): 78 ## If the string is not reachable we skip it 79 file_obj = self.session.profile._FILE_OBJECT( 80 offset=object_obj.obj_end, vm=run.address_space) 81 82 if not file_obj.FileName.v(vm=self.kernel_address_space): 83 continue 84 85 # Real file objects have valid DeviceObject types. 86 device_obj = file_obj.DeviceObject.deref( 87 vm=self.session.kernel_address_space) 88 89 if not device_obj.DeviceType.is_valid(): 90 continue 91 92 # The Process member in the HandleInfo sometimes points at 93 # the _EPROCESS owning the handle. 94 owner_process = ( 95 object_obj.HandleInfo.SingleEntry.Process.deref( 96 vm=self.kernel_address_space)) 97 98 filename = file_obj.file_name_with_drive( 99 vm=self.kernel_address_space) 100 101 yield dict(a='F' if pool_obj.FreePool else "", 102 offset=file_obj.obj_offset, 103 ptr_no=object_obj.PointerCount, 104 hnd_no=object_obj.HandleCount, 105 access=file_obj.AccessString, 106 Owner=owner_process, 107 path=filename)
108 109
110 -class PoolScanDriver(common.PoolScanner):
111 """ Scanner for _DRIVER_OBJECT """ 112
113 - def __init__(self, **kwargs):
114 super(PoolScanDriver, self).__init__(**kwargs) 115 self.checks = [ 116 ('PoolTagCheck', dict( 117 tag=self.profile.get_constant("DRIVER_POOLTAG"))), 118 119 # Must be large enough to hold the driver object. 120 ('CheckPoolSize', dict( 121 condition=lambda x: x > self.profile.get_obj_size( 122 "_DRIVER_OBJECT"))), 123 124 ('CheckPoolType', dict( 125 paged=True, non_paged=True, free=True)), 126 127 ('CheckPoolIndex', dict(value=0)), 128 ]
129 130
131 -class DriverScan(common.PoolScannerPlugin):
132 "Scan for driver objects _DRIVER_OBJECT " 133 134 __name = "driverscan" 135 136 table_header = [ 137 dict(name='a', width=1), 138 dict(name="offset", style="address"), 139 dict(name="ptr_no", width=6, align="r"), 140 dict(name="hnd_no", width=3, align="r"), 141 dict(name="start", style="address"), 142 dict(name="size", style="address"), 143 dict(name="servicekey", width=20), 144 dict(name="name", width=12), 145 dict(name="path") 146 ] 147 148 scanner_defaults = dict( 149 scan_kernel_nonpaged_pool=True 150 ) 151
152 - def collect(self):
153 """Generate possible hits.""" 154 for run in self.generate_memory_ranges(): 155 scanner = PoolScanDriver(session=self.session, 156 profile=self.profile, 157 address_space=run.address_space) 158 159 for pool_obj in scanner.scan(run.start, run.length): 160 for object_obj in pool_obj.IterObject("Driver", freed=True): 161 object_name = object_obj.NameInfo.Name.v( 162 vm=self.kernel_address_space) 163 164 driver_obj = self.profile._DRIVER_OBJECT( 165 object_obj.obj_end, vm=run.address_space) 166 167 extension_obj = self.profile._DRIVER_EXTENSION( 168 driver_obj.obj_end, vm=run.address_space) 169 170 yield dict(a='F' if pool_obj.FreePool else "", 171 offset=driver_obj.obj_offset, 172 ptr_no=object_obj.PointerCount, 173 hnd_no=object_obj.HandleCount, 174 start=driver_obj.DriverStart, 175 size=driver_obj.DriverSize, 176 servicekey=extension_obj.ServiceKeyName.v( 177 vm=self.kernel_address_space), 178 name=object_name, 179 path=driver_obj.DriverName.v( 180 vm=self.kernel_address_space) 181 )
182 183 198 199
200 -class SymLinkScan(common.PoolScannerPlugin):
201 "Scan for symbolic link objects " 202 203 __name = "symlinkscan" 204 205 table_header = [ 206 dict(name='a', width=1), 207 dict(name="offset", style="address"), 208 dict(name="ptr_no", width=6, align="r"), 209 dict(name="hnd_no", width=3, align="r"), 210 dict(name="creation_time", width=24), 211 dict(name="from_link"), 212 dict(name="to_link", width=60), 213 ] 214 215 scanner_defaults = dict( 216 # According to pool_tracker plugin this always comes from paged pool. 217 scan_kernel_paged_pool=True 218 ) 219
220 - def collect(self):
221 """Generate possible hits.""" 222 for run in self.generate_memory_ranges(): 223 scanner = PoolScanSymlink(profile=self.profile, 224 session=self.session, 225 address_space=run.address_space) 226 for pool_obj in scanner.scan(run.start, run.length): 227 for object_obj in pool_obj.IterObject( 228 "SymbolicLink", freed=True): 229 object_name = object_obj.NameInfo.Name.v( 230 vm=self.kernel_address_space) 231 232 link_obj = self.profile._OBJECT_SYMBOLIC_LINK( 233 object_obj.obj_end, vm=run.address_space) 234 235 yield dict(a='F' if pool_obj.FreePool else "", 236 offset=link_obj.obj_offset, 237 ptr_no=object_obj.PointerCount, 238 hnd_no=object_obj.HandleCount, 239 creation_time=link_obj.CreationTime or '', 240 from_link=object_name, 241 to_link=link_obj.LinkTarget.v( 242 vm=self.kernel_address_space))
243 244
245 -class PoolScanMutant(PoolScanDriver):
246 """ Scanner for Mutants _KMUTANT """
247 - def __init__(self, **kwargs):
248 super(PoolScanMutant, self).__init__(**kwargs) 249 self.checks = [ 250 ('PoolTagCheck', dict(tag=self.profile.get_constant( 251 "MUTANT_POOLTAG"))), 252 253 ('CheckPoolSize', dict( 254 min_size=self.profile.get_obj_size("_KMUTANT"))), 255 256 ('CheckPoolType', dict( 257 paged=True, non_paged=True, free=True)), 258 259 ('CheckPoolIndex', dict(value=0)), 260 ]
261 262
263 -class MutantScan(common.PoolScannerPlugin):
264 "Scan for mutant objects _KMUTANT " 265 266 __name = "mutantscan" 267 268 table_header = [ 269 dict(name='a', width=1), 270 dict(name="offset", style="address"), 271 dict(name="ptr_no", width=6, align="r"), 272 dict(name="hnd_no", width=3, align="r"), 273 dict(name="signal", width=6), 274 dict(name="thread", style="address"), 275 dict(name="cid", width=9, align="r"), 276 dict(name="name") 277 ] 278 279 scanner_defaults = dict( 280 scan_kernel_nonpaged_pool=True, 281 ) 282
283 - def collect(self):
284 for run in self.generate_memory_ranges(): 285 scanner = PoolScanMutant(profile=self.profile, session=self.session, 286 address_space=run.address_space) 287 288 for pool_obj in scanner.scan(run.start, run.length): 289 for object_obj in pool_obj.IterObject("Mutant", freed=True): 290 object_name = object_obj.NameInfo.Name.v( 291 vm=self.kernel_address_space) 292 293 # By default we suppress non-named mutants because they are 294 # not very interesting. 295 if self.plugin_args.verbosity < 5 and not object_name: 296 continue 297 298 mutant = self.profile._KMUTANT( 299 object_obj.obj_end, vm=run.address_space) 300 301 if mutant.OwnerThread > 0x80000000: 302 thread = self.profile._ETHREAD( 303 offset=mutant.OwnerThread, 304 vm=self.kernel_address_space) 305 306 CID = "{0}:{1}".format(thread.Cid.UniqueProcess, 307 thread.Cid.UniqueThread) 308 else: 309 CID = "" 310 311 yield dict(a='F' if pool_obj.FreePool else "", 312 offset=mutant.obj_offset, 313 ptr_no=object_obj.PointerCount, 314 hnd_no=object_obj.HandleCount, 315 signal=mutant.Header.SignalState, 316 thread=mutant.OwnerThread, 317 cid=CID, 318 name=object_obj.NameInfo.Name.v( 319 vm=self.kernel_address_space))
320 321
322 -class PoolScanProcess(common.PoolScanner):
323 """PoolScanner for File objects""" 324 325 # Kernel addresses are above this value. 326 kernel = 0x80000000 327
328 - def __init__(self, **kwargs):
329 super(PoolScanProcess, self).__init__(**kwargs) 330 self.kernel = self.profile.get_constant_object( 331 "MmSystemRangeStart", "Pointer").v() or 0x80000000 332 333 self.checks = [ 334 # Must have the right pool tag. 335 ('PoolTagCheck', dict( 336 tag=self.profile.get_constant("EPROCESS_POOLTAG"))), 337 338 # Must be large enough for an _EPROCESS. 339 ('CheckPoolSize', dict(min_size=self.profile.get_obj_size( 340 "_EPROCESS"))), 341 342 # It seems that on old XP versions _EPROCESS was allocated from 343 # paged pool but it's rare to see that. 344 ('CheckPoolType', dict( 345 paged=True, non_paged=True, free=True)), 346 347 ('CheckPoolIndex', dict(value=0)), 348 ] 349 350 # The DTB is page aligned on AMD64 and I386 but aligned to 0x20 351 # on PAE kernels. 352 if self.session.kernel_address_space.metadata("pae"): 353 self.dtb_alignment = 0x20 354 else: 355 self.dtb_alignment = 0x1000
356
357 - def scan(self, **kwargs):
358 for pool_obj in super(PoolScanProcess, self).scan(**kwargs): 359 # Also fetch freed objects. 360 for object_header in pool_obj.IterObject("Process", freed=True): 361 eprocess = object_header.Body.cast("_EPROCESS") 362 363 if eprocess.Pcb.DirectoryTableBase == 0: 364 continue 365 366 if eprocess.Pcb.DirectoryTableBase % self.dtb_alignment != 0: 367 continue 368 369 # Pointers must point to the kernel part of the address space. 370 list_head = eprocess.ActiveProcessLinks 371 if (list_head.Flink < self.kernel or 372 list_head.Blink < self.kernel): 373 continue 374 375 yield pool_obj, eprocess
376 377
378 -class PSScan(common.WinScanner):
379 """Scan Physical memory for _EPROCESS pool allocations. 380 381 Status flags: 382 E: A known _EPROCESS address from pslist. 383 P: A known pid from pslist. 384 """ 385 386 name = "psscan" 387 388 table_header = [ 389 dict(name='a', width=1), 390 dict(name="offset_p", type="_EPROCESS"), 391 dict(name="offset_v", style="address"), 392 dict(name="ppid", width=6, align="r"), 393 dict(name="pdb", style="address"), 394 dict(name='stat', width=4), 395 dict(name="create_time", width=24), 396 dict(name="exit_time", width=24), 397 ] 398 399 # Only bother to scan non paged pool by default. 400 scanner_defaults = dict( 401 scan_kernel_nonpaged_pool=True 402 ) 403
404 - def collect(self):
405 """Render results in a table.""" 406 # Try to do a regular process listing so we can compare if the process 407 # is known. 408 pslist = self.session.plugins.pslist() 409 410 # These are virtual addresses. 411 known_eprocess = set() 412 known_pids = set() 413 for task in pslist.list_eprocess(): 414 known_eprocess.add(task) 415 known_pids.add(task.UniqueProcessId) 416 417 # Scan each requested run in turn. 418 for run in self.generate_memory_ranges(): 419 # Just grab the AS and scan it using our scanner 420 scanner = PoolScanProcess(session=self.session, 421 profile=self.profile, 422 address_space=run.address_space) 423 424 for pool_obj, eprocess in scanner.scan( 425 offset=run.start, maxlen=run.length): 426 if run.data["type"] == "PhysicalAS": 427 # Switch address space from physical to virtual. 428 virtual_eprocess = ( 429 pslist.virtual_process_from_physical_offset(eprocess)) 430 else: 431 virtual_eprocess = eprocess 432 433 known = "" 434 if virtual_eprocess in known_eprocess: 435 known += "E" 436 437 if eprocess.UniqueProcessId in known_pids: 438 known += "P" 439 440 yield dict(a='F' if pool_obj.FreePool else "", 441 offset_p=eprocess, 442 offset_v=virtual_eprocess.obj_offset, 443 ppid=eprocess.InheritedFromUniqueProcessId, 444 pdb=eprocess.Pcb.DirectoryTableBase, 445 stat=known, 446 create_time=eprocess.CreateTime or '', 447 exit_time=eprocess.ExitTime or '')
448