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

Source Code for Module rekall.plugins.windows.misc

  1  # Rekall Memory Forensics 
  2  # 
  3  # Copyright 2014 Google Inc. All Rights Reserved. 
  4  # 
  5  # This program is free software; you can redistribute it and/or modify 
  6  # it under the terms of the GNU General Public License as published by 
  7  # the Free Software Foundation; either version 2 of the License, or (at 
  8  # your option) any later version. 
  9  # 
 10  # This program is distributed in the hope that it will be useful, but 
 11  # WITHOUT ANY WARRANTY; without even the implied warranty of 
 12  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
 13  # General Public License for more details. 
 14  # 
 15  # You should have received a copy of the GNU General Public License 
 16  # along with this program; if not, write to the Free Software 
 17  # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
 18  """Miscelaneous information gathering plugins.""" 
 19   
 20  __author__ = "Michael Cohen <scudette@google.com>" 
 21  import hashlib 
 22   
 23  # pylint: disable=protected-access 
 24  from rekall import obj 
 25  from rekall.plugins import core 
 26  from rekall.plugins.overlays import basic 
 27  from rekall.plugins.windows import common 
 28  from rekall_lib import utils 
29 30 31 -class WinPhysicalMap(common.WindowsCommandPlugin):
32 """Prints the boot physical memory map.""" 33 34 __name = "phys_map" 35 36 table_header = [ 37 dict(name="phys_start", style="address"), 38 dict(name="phys_end", style="address"), 39 dict(name="pages"), 40 ] 41
42 - def collect(self):
43 descriptor = self.profile.get_constant_object( 44 "MmPhysicalMemoryBlock", 45 target="Pointer", 46 target_args=dict( 47 target="_PHYSICAL_MEMORY_DESCRIPTOR", 48 )) 49 50 for memory_range in descriptor.Run: 51 yield (memory_range.BasePage * 0x1000, 52 (memory_range.BasePage + memory_range.PageCount) * 0x1000, 53 memory_range.PageCount)
54
55 56 -class WindowsSetProcessContext(core.SetProcessContextMixin, 57 common.WinProcessFilter):
58 """A cc plugin for windows."""
59
60 61 -class WinVirtualMap(common.WindowsCommandPlugin):
62 """Prints the Windows Kernel Virtual Address Map. 63 64 On 32 bit windows, the kernel virtual address space can be managed 65 dynamically. This plugin shows each region and what it is used for. 66 67 Note that on 64 bit windows the address space is large enough to not worry 68 about it. In that case, the offsets and regions are hard coded. 69 70 http://www.woodmann.com/forum/entry.php?219-Using-nt!_MiSystemVaType-to-navigate-dynamic-kernel-address-space-in-Windows7 71 """ 72 73 __name = "virt_map" 74 75 table_header = [ 76 dict(name="virt_start", style="address"), 77 dict(name="virt_end", style="address"), 78 dict(name="type", width=10), 79 ] 80 81 @classmethod
82 - def is_active(cls, session):
83 return (super(WinVirtualMap, cls).is_active(session) and 84 session.profile.get_constant('MiSystemVaType'))
85
86 - def collect(self):
87 system_va_table = self.profile.get_constant_object( 88 "MiSystemVaType", 89 target="Array", 90 target_args=dict( 91 target="Enumeration", 92 target_args=dict( 93 target="byte", 94 enum_name="_MI_SYSTEM_VA_TYPE" 95 ), 96 ) 97 ) 98 99 system_range_start = self.profile.get_constant_object( 100 "MiSystemRangeStart", "unsigned int") 101 102 # The size varies on PAE profiles. 103 va_table_size = 0x1000 * 0x1000 / self.profile.get_obj_size("_MMPTE") 104 105 # Coalesce the ranges. 106 range_type = range_start = range_length = 0 107 108 for offset in range(system_range_start, 0xffffffff, va_table_size): 109 table_index = (offset - system_range_start) / va_table_size 110 page_type = system_va_table[table_index] 111 if page_type != range_type: 112 if range_type: 113 yield (range_start, range_start + range_length, range_type) 114 115 range_type = page_type 116 range_start = offset 117 range_length = va_table_size 118 else: 119 range_length += va_table_size
120
121 122 -class Objects(common.WindowsCommandPlugin):
123 """Displays all object Types on the system.""" 124 125 name = "object_types" 126 127 table_header = [ 128 dict(name="type", style="address"), 129 dict(name="index", align="r", width=5), 130 dict(name="NumberOfObjects", align="r", width=15), 131 dict(name="PoolType", width=20), 132 dict(name="name") 133 ] 134
135 - def object_types(self):
136 # The size of the type array depends on the operating system and it is 137 # hard coded. We can find out the size by seeing how many Type objects 138 # were allocated. 139 type_table = self.profile.get_constant_object( 140 "ObpObjectTypes", 141 target="Array", target_args=dict( 142 target="Pointer", 143 count=0, 144 target_args=dict( 145 target="_OBJECT_TYPE") 146 ) 147 ) 148 149 type_type = type_table[0] # The "Type" object. 150 type_table.count = type_type.TotalNumberOfObjects 151 152 for t in type_table: 153 if t: 154 yield t
155
156 - def collect(self):
157 for obj_type in self.object_types(): 158 yield dict(type=obj_type, 159 index=obj_type.Index, 160 NumberOfObjects=obj_type.TotalNumberOfObjects, 161 PoolType=obj_type.TypeInfo.PoolType, 162 name=obj_type.Name)
163
164 165 -class ImageInfo(common.WindowsCommandPlugin):
166 """List overview information about this image.""" 167 168 name = "imageinfo" 169 170 table_header = [ 171 dict(name="key", width=20), 172 dict(name="value") 173 ] 174 175 @staticmethod
176 - def KeQueryTimeIncrement(profile):
177 """Return the time of each tick (float). 178 179 dis "nt!KeQueryTimeIncrement" 180 ------ nt!KeQueryTimeIncrement ------ 181 MOV EAX, [RIP+0x24af66] 0x26161 nt!KeMaximumIncrement 182 RET 183 """ 184 return profile.get_constant_object( 185 "KeMaximumIncrement", target="unsigned int") * 100e-9
186
187 - def GetBootTime(self, kuser_shared):
188 """Returns the number of seconds since boot. 189 Ref: 190 KeQueryTickCount * KeQueryTimeIncrement 191 192 reactos/include/ddk/wdm.h: 193 194 #define SharedTickCount (KI_USER_SHARED_DATA + 0x320) 195 196 #define KeQueryTickCount(CurrentCount) \ 197 *(ULONG64*)(CurrentCount) = *(volatile ULONG64*)SharedTickCount 198 """ 199 current_tick_count = ( 200 int(kuser_shared.TickCountQuad) or # Win7 201 int(kuser_shared.TickCountLow)) # WinXP 202 203 return current_tick_count * self.KeQueryTimeIncrement(self.profile)
204
205 - def collect(self):
206 yield ("Kernel DTB", "%#x" % self.kernel_address_space.dtb) 207 208 for desc, name, type in ( 209 ("NT Build", "NtBuildLab", "String"), 210 ("NT Build Ex", "NtBuildLabEx", "String"), 211 ("Signed Drivers", "g_CiEnabled", "bool"), 212 ): 213 214 yield dict( 215 key=desc, 216 value=self.profile.get_constant_object(name, target=type)) 217 218 # Print kuser_shared things. 219 kuser_shared = self.profile.get_constant_object( 220 "KI_USER_SHARED_DATA", "_KUSER_SHARED_DATA") 221 222 yield ("Time (UTC)", kuser_shared.SystemTime) 223 224 # The bias is given in windows file time (i.e. in 100ns ticks). 225 bias = kuser_shared.TimeZoneBias.cast("long long") / 1e7 226 yield ("Time (Local)", kuser_shared.SystemTime.display( 227 utc_shift=-bias)) 228 229 yield ("Sec Since Boot", self.GetBootTime(kuser_shared)) 230 yield ("NtSystemRoot", kuser_shared.NtSystemRoot)
231
232 233 -class WinImageFingerprint(common.AbstractWindowsParameterHook):
234 """Fingerprint the current image. 235 236 This parameter tries to get something unique about the image quickly. The 237 idea is that two different images (even of the same system at different 238 points in time) will have very different fingerprints. The fingerprint is 239 used as a key to cache persistent information about the system. 240 241 Live systems can not have a stable fingerprint and so return a NoneObject() 242 here. 243 244 We return a list of tuples: 245 (physical_offset, expected_data) 246 247 The list uniquely identifies the image. If one were to read all physical 248 offsets and find the expected_data at these locations, then we have a very 249 high level of confidence that the image is unique and matches the 250 fingerprint. 251 """ 252 name = "image_fingerprint" 253
254 - def calculate(self):
255 if not self.session.physical_address_space: 256 return None 257 258 if self.session.physical_address_space.volatile: 259 return obj.NoneObject("No fingerprint for volatile image.") 260 261 result = [] 262 profile = self.session.profile 263 phys_as = self.session.physical_address_space 264 265 address_space = self.session.GetParameter("default_address_space") 266 267 label = profile.get_constant_object("NtBuildLab", "String") 268 result.append((address_space.vtop(label.obj_offset), label.v())) 269 270 label = profile.get_constant_object("NtBuildLabEx", "String") 271 result.append((address_space.vtop(label.obj_offset), label.v())) 272 273 kuser_shared = profile.get_constant_object( 274 "KI_USER_SHARED_DATA", "_KUSER_SHARED_DATA") 275 276 system_time_offset = address_space.vtop( 277 kuser_shared.SystemTime.obj_offset) 278 279 result.append((system_time_offset, phys_as.read(system_time_offset, 8))) 280 281 tick_time_offset = address_space.vtop( 282 kuser_shared.multi_m("TickCountQuad", "TickCountLow").obj_offset) 283 result.append((tick_time_offset, phys_as.read(tick_time_offset, 8))) 284 285 # List of processes should also be pretty unique. 286 for task in self.session.plugins.pslist().filter_processes(): 287 name = task.name.cast("String", length=30) 288 task_name_offset = address_space.vtop(name.obj_offset) 289 290 # Read the raw data for the task name. Usually the task name is 291 # encoded in utf8 but then we might not be able to compare it 292 # exactly - we really want bytes here. 293 result.append((task_name_offset, name.v())) 294 295 return dict( 296 hash=hashlib.sha1(unicode(result).encode("utf8")).hexdigest(), 297 tests=result)
298
299 300 -class ObjectTree(common.WindowsCommandPlugin):
301 """Visualize the kernel object tree. 302 303 Ref: 304 http://msdn.microsoft.com/en-us/library/windows/hardware/ff557762(v=vs.85).aspx 305 """ 306 307 name = "object_tree" 308 309 __args = [ 310 dict(name="type_regex", default=".", type="RegEx", 311 help="Filter the type of objects shown.") 312 ] 313 314 table_header = [ 315 dict(name="_OBJECT_HEADER", style="address"), 316 dict(name="type", width=20), 317 dict(name="name", type="TreeNode"), 318 ] 319
320 - def GetObjectByName(self, path):
321 root = self.session.GetParameter("object_tree") 322 for component in utils.SplitPath(path): 323 root = root["Children"][component] 324 325 return self.profile.Object(type_name=root["type_name"], 326 offset=root["offset"])
327
328 - def FileNameWithDrive(self, path):
329 """Tries to resolve the path back to something with a drive letter.""" 330 # First normalize the path. 331 try: 332 path = self.ResolveSymlinks(path) 333 for prefix, drive_letter in self.session.GetParameter( 334 "drive_letter_device_map").iteritems(): 335 prefix = self.ResolveSymlinks(prefix) 336 if path.startswith(prefix): 337 return drive_letter + path[len(prefix):] 338 339 # This will be triggered if the path does not resolve to anything in the 340 # object tree. 341 except KeyError: 342 return path
343 352
353 - def _parse_path_components(self, components):
354 node = self.session.GetParameter("object_tree") 355 new_components = [] 356 357 for i, component in enumerate(components): 358 if not component: 359 continue 360 361 if component == "??": 362 component = "GLOBAL??" 363 364 next_node = utils.CaseInsensitiveDictLookup( 365 component, node["Children"]) 366 367 # If the first component is not found, search for it in the global 368 # namespace. 369 if next_node is None and i == 0 and component != "GLOBAL??": 370 return self._parse_path_components(["GLOBAL??"] + components) 371 372 if next_node is None: 373 raise KeyError( 374 "component %r not found at %s" % ( 375 component, "\\".join(new_components))) 376 377 elif next_node["type"] == "SymbolicLink": 378 object_header = self.session.profile._OBJECT_HEADER( 379 next_node["offset"]) 380 381 target = object_header.Object.LinkTarget.v() 382 383 # Append the next components to the target and re-parse 384 return self._parse_path_components( 385 target.split("\\") + components[i+1:]) 386 387 elif next_node["type"] != "Directory": 388 return new_components + components[i:] 389 390 new_components.append(component) 391 node = next_node 392 393 return new_components
394
395 - def _collect_directory(self, directory, seen, depth=0):
396 for obj_header in directory.list(): 397 if obj_header in seen: 398 continue 399 seen.add(obj_header) 400 401 name = unicode(obj_header.NameInfo.Name) 402 obj_type = str(obj_header.get_object_type()) 403 404 if obj_type == "SymbolicLink": 405 name += u"-> %s (%s)" % (obj_header.Object.LinkTarget, 406 obj_header.Object.CreationTime) 407 408 if self.plugin_args.type_regex.search(obj_type): 409 yield dict(_OBJECT_HEADER=obj_header, type=obj_type, 410 name=name, depth=depth) 411 412 if obj_type == "Directory": 413 for x in self._collect_directory( 414 obj_header.Object, seen, depth=depth+1): 415 yield x
416
417 - def collect(self):
418 # The root object. 419 root = self.GetObjectByName("/") 420 421 seen = set() 422 for x in self._collect_directory(root, seen): 423 yield x
424
425 426 -class WindowsTimes(common.WindowsCommandPlugin):
427 """Return current time, as known to the kernel.""" 428 429 name = "times" 430 431 table_header = [ 432 dict(name="Times"), 433 ] 434
435 - def collect(self):
436 kuser_shared = self.session.address_resolver.get_constant_object( 437 "nt!KI_USER_SHARED_DATA", "_KUSER_SHARED_DATA") 438 439 seconds_since_boot = self.session.plugins.imageinfo().GetBootTime( 440 kuser_shared) 441 442 kernel_time = kuser_shared.SystemTime 443 boot_timestamp = basic.UnixTimeStamp( 444 value=kernel_time - seconds_since_boot, 445 session=self.session) 446 447 yield [utils.AttributeDict(now=kernel_time, boot=boot_timestamp, 448 uptime=seconds_since_boot)]
449