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

Source Code for Module rekall.plugins.windows.heap_analysis

  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  """The module implements user mode heap analysis. 
 20   
 21  Recent versions of windows use the Low Fragmentation Heap (LFH). 
 22   
 23  http://illmatics.com/Windows%208%20Heap%20Internals.pdf 
 24  http://illmatics.com/Understanding_the_LFH.pdf 
 25  http://www.leviathansecurity.com/blog/understanding-the-windows-allocator-a-redux/ 
 26   
 27  """ 
 28  from rekall import scan 
 29   
 30  from rekall.plugins import core 
 31  from rekall.plugins.windows import common 
 32  from rekall_lib import utils 
 33   
 34   
35 -class InspectHeap(common.WinProcessFilter):
36 """Inspect the process heap. 37 38 This prints a lot of interesting facts about the process heap. It is also 39 the foundation to many other plugins which find things in the process heaps. 40 41 NOTE: Currently we only support Windows 7 64 bit. 42 """ 43 44 name = "inspect_heap" 45 46 __args = [ 47 dict(name="free", type="Boolean", 48 help="Also show freed chunks."), 49 50 dict(name="heaps", type="ArrayIntParser", 51 help="Only show these heaps (default show all)") 52 ] 53 54 mode = "mode_amd64" 55
56 - def __init__(self, *args, **kwargs):
57 super(InspectHeap, self).__init__(*args, **kwargs) 58 self.segments = utils.SortedCollection()
59
60 - def enumerate_lfh_heap_allocations(self, heap, skip_freed=False):
61 """Dump the low fragmentation heap.""" 62 seen_blocks = set() 63 64 for lfh_block in heap.FrontEndHeap.SubSegmentZones.list_of_type( 65 "_LFH_BLOCK_ZONE", "ListEntry"): 66 block_length = lfh_block.FreePointer.v() - lfh_block.obj_end 67 segments = heap.obj_profile.Array( 68 target="_HEAP_SUBSEGMENT", 69 offset=lfh_block.obj_end, 70 size=block_length) 71 72 for segment in segments: 73 allocation_length = segment.BlockSize * 16 74 75 if segment.UserBlocks.v() in seen_blocks: 76 break 77 78 seen_blocks.add(segment.UserBlocks.v()) 79 80 for entry in segment.UserBlocks.Entries: 81 # http://www.leviathansecurity.com/blog/understanding-the-windows-allocator-a-redux/ 82 # Skip freed blocks if requested. 83 if skip_freed and entry.UnusedBytes & 0x38: 84 continue 85 86 UnusedBytes = entry.UnusedBytes & 0x3f - 0x8 87 88 # The actual length of user allocation is the difference 89 # between the HEAP allocation bin size and the unused bytes 90 # at the end of the allocation. 91 data_len = allocation_length - UnusedBytes 92 93 # The data length can not be larger than the allocation 94 # minus the critical parts of _HEAP_ENTRY. Sometimes, 95 # allocations overrun into the next element's _HEAP_ENTRY so 96 # they can store data in the next entry's 97 # entry.PreviousBlockPrivateData. In this case the 98 # allocation length seems to be larger by 8 bytes. 99 if data_len > allocation_length - 0x8: 100 data_len -= 0x8 101 102 yield (heap.obj_profile.String(entry.obj_end, term=None, 103 length=data_len), 104 allocation_length)
105
107 """Enumerate all allocations for _EPROCESS instance.""" 108 109 for seg in heap.Segments: 110 seg_end = seg.LastValidEntry.v() 111 112 # Ensure sanity. 113 if seg.Heap.deref() != heap: 114 continue 115 116 # The segment is empty - often seg_end is zero here. 117 if seg_end < seg.FirstEntry.v(): 118 break 119 120 for entry in seg.FirstEntry.walk_list("NextEntry", True): 121 # If this is the last entry it goes until the end of the 122 # segment. 123 start = entry.obj_offset + 0x10 124 if start > seg_end: 125 break 126 127 allocation = entry.Allocation 128 yield allocation
129
130 - def GenerateHeaps(self):
131 task = self.session.GetParameter("process_context") 132 resolver = self.session.address_resolver 133 134 # Try to load the ntdll profile. 135 ntdll_mod = resolver.GetModuleByName("ntdll") 136 if not ntdll_mod: 137 return 138 139 ntdll_prof = ntdll_mod.profile 140 141 # Set the ntdll profile on the _PEB member. 142 peb = task.m("Peb").cast( 143 "Pointer", target="_PEB", profile=ntdll_prof, 144 vm=task.get_process_address_space()) 145 146 for heap in peb.ProcessHeaps: 147 yield heap
148
149 - def render(self, renderer):
150 cc = self.session.plugins.cc() 151 with cc: 152 for task in self.filter_processes(): 153 cc.SwitchProcessContext(task) 154 155 renderer.section() 156 renderer.format("{0:r}\n", task) 157 for heap in self.GenerateHeaps(): 158 self.render_process_heap_info(heap, renderer)
159
160 - def render_low_frag_info(self, heap, renderer):
161 """Displays information about the low fragmentation front end.""" 162 renderer.format("Low Fragmentation Front End Information:\n") 163 renderer.table_header([ 164 dict(name="Entry", style="address"), 165 ("Alloc", "allocation_length", "4"), 166 ("Length", "length", ">4"), 167 dict(name="Data"), 168 ]) 169 170 # Render the LFH allocations in increasing allocation sizes. Collect 171 # them first, then display by sorted allocation size, and offset. 172 entries_by_size = {} 173 for entry, allocation_length in self.enumerate_lfh_heap_allocations( 174 heap): 175 entries_by_size.setdefault(allocation_length, []).append(entry) 176 177 for allocation_length, entries in sorted(entries_by_size.iteritems()): 178 for entry in sorted(entries, key=lambda x: x.obj_offset): 179 data = entry.v()[:64] 180 181 renderer.table_row( 182 entry, 183 allocation_length, 184 entry.length, 185 utils.HexDumpedString(data), 186 )
187
188 - def render_process_heap_info(self, heap, renderer):
189 if (self.plugin_args.heaps and 190 heap.ProcessHeapsListIndex not in self.plugin_args.heaps): 191 return 192 193 if 1 <= heap.ProcessHeapsListIndex <= 64: 194 renderer.format("Heap {0}: {1:#x} ({2})\nBackend Info:\n\n", 195 heap.ProcessHeapsListIndex, 196 heap.BaseAddress, 197 heap.FrontEndHeapType) 198 199 renderer.table_header([ 200 dict(name="Segment", type="TreeNode", width=18, 201 child=dict(style="address")), 202 ("End", "segment_end", "[addr]"), 203 ("Length", "length", "8"), 204 dict(name="Data"), 205 ]) 206 207 for seg in heap.Segments: 208 seg_start = seg.FirstEntry.obj_offset 209 seg_end = seg.LastValidEntry.v() 210 211 renderer.table_row( 212 seg_start, seg_end, seg_end - seg_start, depth=1) 213 214 for entry in seg.FirstEntry.walk_list("NextEntry", True): 215 # If this is the last entry it goes until the end of the 216 # segment. 217 start = entry.obj_offset + 0x10 218 if start > seg_end: 219 break 220 221 if entry.Flags.LAST_ENTRY: 222 end = seg.LastValidEntry.v() 223 else: 224 end = entry.obj_offset + entry.Size * 16 225 226 data = heap.obj_vm.read(start, min(16, end-start)) 227 228 renderer.table_row( 229 entry, 230 end, end - start, 231 utils.HexDumpedString(data), 232 depth=2) 233 234 235 if heap.FrontEndHeapType.LOW_FRAG: 236 self.render_low_frag_info(heap, renderer)
237 238
239 -class ShowAllocation(common.WindowsCommandPlugin):
240 """Show the allocation containing the address.""" 241 242 name = "show_allocation" 243 244 __args = [ 245 dict(name="address", type="ArrayIntParser", positional=True, 246 help="The address to display"), 247 248 dict(name="preamble", type="IntParser", default=32, 249 help="How many bytes prior to the address to display."), 250 251 dict(name="length", type="IntParser", default=50 * 16, 252 help="How many bytes after the address to display.") 253 ] 254
255 - def BuildAllocationMap(self):
256 """Build a map of all allocations for fast looksup.""" 257 allocations = utils.RangedCollection() 258 inspect_heap = self.session.plugins.inspect_heap() 259 for heap in inspect_heap.GenerateHeaps(): 260 # First do the backend allocations. 261 for allocation in inspect_heap.enumerate_backend_heap_allocations( 262 heap): 263 264 # Include the header in the allocation. 265 allocations.insert( 266 allocation.obj_offset - 16, 267 allocation.obj_offset + allocation.length + 16, 268 (allocation.obj_offset, allocation.length, "B")) 269 270 self.session.report_progress( 271 "Enumerating backend allocation: %#x", 272 lambda allocation=allocation: allocation.obj_offset) 273 274 # Now do the LFH allocations (These will mask the subsegments in the 275 # RangedCollection). 276 for _ in inspect_heap.enumerate_lfh_heap_allocations( 277 heap, skip_freed=False): 278 allocation, allocation_length = _ 279 self.session.report_progress( 280 "Enumerating frontend allocation: %#x", 281 lambda: allocation.obj_offset) 282 283 # Front end allocations do not have their own headers. 284 allocations.insert( 285 allocation.obj_offset, 286 allocation.obj_offset + allocation_length, 287 (allocation.obj_offset, allocation_length, "F")) 288 289 return allocations
290
291 - def __init__(self, *args, **kwargs):
292 super(ShowAllocation, self).__init__(*args, **kwargs) 293 self.offset = None 294 295 # Get cached allocations for current process context. 296 task = self.session.GetParameter("process_context") 297 cache_key = "heap_allocations_%x" % task.obj_offset 298 self.allocations = self.session.GetParameter(cache_key) 299 if self.allocations == None: 300 self.allocations = self.BuildAllocationMap() 301 302 # Cache the allocations for next time. 303 self.session.SetCache(cache_key, self.allocations)
304
305 - def GetAllocationForAddress(self, address):
306 return self.allocations.get_containing_range(address)
307
308 - def CreateAllocationMap(self, start, length, alloc_start, alloc_type):
309 address_map = core.AddressMap() 310 # For backend allocs we highlight the heap entry before them. 311 if alloc_type == "B": 312 address_map.AddRange(alloc_start-16, alloc_start, "_HEAP_ENTRY") 313 314 # Try to interpret pointers to other allocations and highlight them. 315 count = length / 8 316 for pointer in self.profile.Array( 317 offset=start, count=count, target="Pointer"): 318 name = None 319 alloc_start, alloc_length, alloc_type = ( 320 self.allocations.get_containing_range(pointer.v())) 321 322 if alloc_type is not None: 323 # First check if the pointer points inside this allocation. 324 if alloc_start == start + 16: 325 name = "+%#x(%#x)" % (pointer.v() - start, pointer.v()) 326 else: 327 name = "%#x(%s@%#x)" % ( 328 pointer.v(), alloc_length, alloc_start) 329 330 else: 331 # Maybe it is a resolvable address. 332 name = ",".join(self.session.address_resolver.format_address( 333 pointer.v(), max_distance=1024*1024)) 334 335 336 if name: 337 address_map.AddRange( 338 pointer.obj_offset, pointer.obj_offset + 8, 339 # Color it using a unique color related to the address. This 340 # helps to visually relate the same address across different 341 # dumps. 342 "%s" % name, color_index=pointer.obj_offset) 343 344 return address_map
345
346 - def render(self, renderer):
347 for address in self.plugin_args.address: 348 # If the user requested to view more than one address we do not 349 # support plugin continuation (with v() plugin). 350 if len(self.plugin_args.address) > 1: 351 self.offset = None 352 353 alloc_start, alloc_length, alloc_type = ( 354 self.allocations.get_containing_range(address)) 355 356 if not alloc_type: 357 renderer.format("Allocation not found for address " 358 "{0:style=address} in any heap.\n", address) 359 alloc_start = address 360 alloc_length = 50 * 16 361 alloc_type = None 362 363 else: 364 renderer.format( 365 "Address {0:style=address} is {1} bytes into " 366 "{2} allocation of size {3} " 367 "({4:style=address} - {5:style=address})\n", 368 address, address - alloc_start, alloc_type, 369 alloc_length, alloc_start, alloc_start + alloc_length) 370 371 # Start dumping preamble before the address if self.offset is not 372 # specified. It will be specified when we run the plugin again using 373 # v(). 374 if self.offset is None: 375 # Start dumping a little before the requested address, but do 376 # not go before the start of the allocation. 377 start = max(alloc_start, address - self.plugin_args.preamble) 378 else: 379 # Continue dumping from the last run. 380 start = self.offset 381 382 # Also show the _HEAP_ENTRY before backend allocations (Front end 383 # allocations do not have a _HEAP_ENTRY). 384 if alloc_type == "B": 385 start -= 16 386 387 length = min(alloc_start + alloc_length - start, 388 self.plugin_args.length) 389 390 dump = self.session.plugins.dump( 391 offset=start, length=length, 392 address_map=self.CreateAllocationMap( 393 start, length, alloc_start, alloc_type)) 394 395 dump.render(renderer) 396 397 self.offset = dump.offset
398 399
400 -class FindReferenceAlloc(common.WindowsCommandPlugin):
401 """Show allocations that refer to an address.""" 402 403 name = "show_referrer_alloc" 404 405 __args = [ 406 dict(name="address", type="IntParser", positional=True, required=True, 407 help="The address to display") 408 ] 409
410 - def get_referrers(self, address, maxlen=None):
411 addr = self.profile.address() 412 addr.write(address) 413 414 pointer_scanner = scan.BaseScanner( 415 address_space=self.session.GetParameter("default_address_space"), 416 session=self.session, 417 checks=[ 418 ('StringCheck', dict(needle=addr.obj_vm.getvalue())) 419 ]) 420 421 # Just scan the entire userspace address space. This means we might find 422 # hits outside the heap but this is usually useful as it would locate 423 # static pointers in dlls. 424 if maxlen is None: 425 maxlen = self.session.GetParameter("highest_usermode_address") 426 427 for hit in pointer_scanner.scan(maxlen=maxlen): 428 yield hit
429
430 - def render(self, renderer):
431 show_allocation = None 432 433 for hit in self.get_referrers(self.address): 434 show_allocation = self.session.plugins.show_allocation(hit) 435 show_allocation.render(renderer) 436 437 return show_allocation
438