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

Source Code for Module rekall.plugins.windows.dns

  1  # Rekall Memory Forensics 
  2  # 
  3  # Copyright 2014 Google Inc. All Rights Reserved. 
  4  # 
  5  # Authors: 
  6  # Michael Cohen <scudette@google.com> 
  7  # 
  8  # Acknowledgments: 
  9  # We would like to thank Chakib Gzenayi for his patient testing and suggestions 
 10  # in the development of this plugin. 
 11  # 
 12  # This program is free software; you can redistribute it and/or modify 
 13  # it under the terms of the GNU General Public License as published by 
 14  # the Free Software Foundation; either version 2 of the License, or (at 
 15  # your option) any later version. 
 16  # 
 17  # This program is distributed in the hope that it will be useful, but 
 18  # WITHOUT ANY WARRANTY; without even the implied warranty of 
 19  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
 20  # General Public License for more details. 
 21  # 
 22  # You should have received a copy of the GNU General Public License 
 23  # along with this program; if not, write to the Free Software 
 24  # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
 25   
 26  """This module implements plugins to inspect Window's DNS resolver cache. 
 27   
 28  In windows DNS requests are cached by the DNS resolver service. This is a 
 29  service running in svchost.exe and implemented as the mostly undocumented DLL 
 30  dnsrslvr.dll. 
 31   
 32  """ 
 33  import socket 
 34   
 35  from rekall import scan 
 36  from rekall import obj 
 37  from rekall.plugins.windows import common 
 38  from rekall_lib import utils 
 39   
 40  # pylint: disable=protected-access 
 41   
 42   
 43  # Most common DNS types. 
 44  DNS_TYPES = { 
 45      1: "A", 
 46      5: "CNAME", 
 47      28: "AAAA", 
 48  } 
 49   
 50  types = { 
 51      "DNS_HASHTABLE_ENTRY": [None, { 
 52          "List": [0x0, ["_LIST_ENTRY"]], 
 53          "Name": [0x8, ["Pointer", dict( 
 54              target="UnicodeString" 
 55              )]], 
 56   
 57          "Record": [0x18, ["Pointer", dict( 
 58              target="DNS_RECORD" 
 59          )]], 
 60      }], 
 61   
 62      "DNS_RECORD": [None, { 
 63          "Next": [0, ["Pointer", dict( 
 64              target="DNS_RECORD" 
 65              )]], 
 66          "Name": [8, ["Pointer", dict( 
 67              target="UnicodeString" 
 68              )]], 
 69          "Type": [16, ["Enumeration", dict( 
 70              choices=DNS_TYPES, 
 71              target="unsigned short" 
 72          )]], 
 73          "DataLength": [18, ['unsigned short']], 
 74          "Data": [0x20, ['char']], 
 75      }], 
 76  } 
 77   
 78  win10_overlays = { 
 79      "DNS_HASHTABLE_ENTRY": [None, { 
 80          "List": [0x8, ["_LIST_ENTRY"]], 
 81          "Name": [0x38, ["Pointer", dict( 
 82              target="UnicodeString" 
 83              )]], 
 84   
 85          "Record": [0x58, ["Pointer", dict( 
 86              target="DNS_RECORD" 
 87          )]], 
 88      }], 
 89   
 90  } 
91 92 93 94 -class DNS_RECORD(obj.Struct):
95 @utils.safe_property
96 - def Data(self):
97 if self.Type == "CNAME": 98 return self.m("Data").cast( 99 "Pointer", target="UnicodeString").deref() 100 elif self.Type == "A": 101 return utils.inet_ntop( 102 socket.AF_INET, self.obj_vm.read(self.m("Data").obj_offset, 4))
103
104 105 -def InitializedDNSTypes(profile):
106 profile.add_types(types) 107 profile.add_types(dict( 108 _LIST_ENTRY=profile.session.profile.vtypes["_LIST_ENTRY"])) 109 110 # Windows 10 changes things around a bit. 111 if profile.session.profile.metadata("major") == 10: 112 profile.add_overlay(win10_overlays) 113 114 profile.add_classes( 115 DNS_RECORD=DNS_RECORD 116 ) 117 118 return profile
119
120 121 -class WinDNSCache(common.WindowsCommandPlugin):
122 """Dump the windows DNS resolver cache.""" 123 124 name = "dns_cache" 125 126 mode = ["mode_amd64", "mode_vista_plus"] 127 128 __args = [ 129 dict(name="hashtable", 130 help="Optionally provide the hashtable"), 131 132 dict(name="no_index", type="Boolean", 133 help="Should we not use the index"), 134 ] 135 136 table_header = [ 137 dict(name="Name", type="TreeNode", width=45), 138 dict(name="record", style="address"), 139 dict(name="type", width=16), 140 dict(name="data"), 141 ] 142
143 - def column_types(self):
144 profile = InitializedDNSTypes(self.profile) 145 return dict( 146 Name=self.profile.UnicodeString(), 147 record=profile.DNS_HASHTABLE_ENTRY(), 148 type="", 149 data="127.0.0.1" 150 )
151
152 - def _find_svchost_vad(self):
153 """Returns the vad and _EPROCESS of the dnsrslvr.dll.""" 154 pslist = self.session.plugins.pslist(proc_regex="svchost.exe") 155 for task in pslist.filter_processes(): 156 self.session.report_progress("Checking pid %s for dnsrslvr.dll", 157 task.pid) 158 159 for vad in task.RealVadRoot.traverse(): 160 try: 161 filename = vad.ControlArea.FilePointer.FileName.v() 162 if filename.endswith("\\dnsrslvr.dll"): 163 return vad, task 164 except AttributeError: 165 pass 166 167 return None, None
168
169 - def _verify_hash_table(self, allocation, heap):
170 """Verify the allocation between start and end for a hash table. 171 172 We have observed that often the hash table may contain corrupt data due 173 to paging smear during acquisition, hence more rigorous checks might 174 actually fail to find the correct hash table due to corrupted data 175 confusing the sanity checks here. It is always better to detect the 176 correct version using the profile repository. 177 """ 178 self.session.logging.debug("Verifying hash table at %#x", 179 allocation.obj_offset) 180 181 if (self.plugin_args.hashtable and 182 allocation.obj_offset != self.plugin_args.hashtable): 183 return False 184 185 # Find all segments in this heap. 186 segments = utils.RangedCollection() 187 for seg in heap.Segments: 188 segments.insert(seg.FirstEntry, seg.LastValidEntry, seg) 189 190 # We usually observe the hash table to be about 1600 bytes, but it might 191 # grow. 192 if allocation.length > 1600 * 3 or allocation.length < 1600: 193 return False 194 195 # Cast the allocation into a hash table. 196 cache_hash_table = allocation.cast( 197 "Array", 198 target="Pointer", 199 target_args=dict( 200 target="DNS_HASHTABLE_ENTRY", 201 ), 202 profile=self.profile, 203 size=allocation.length) 204 205 count = 0 206 for entry in cache_hash_table: 207 # If the hashtable entry is null, keep searching. 208 entry = entry.v() 209 if entry == 0: 210 continue 211 212 # ALL entry pointers must point back into one of the other segments 213 # in this heap (Since DNS_HASHTABLE_ENTRY are allocated from this 214 # heap).. 215 dest_segment = segments.get_range(entry) 216 if dest_segment is None: 217 return False 218 219 count += 1 220 221 # It may be that the hashtable is all empty but otherwise we will match 222 # a zero allocated block. 223 if count == 0: 224 return False 225 226 return cache_hash_table
227
228 - def _locate_heap_using_index(self, task):
229 """Locate the heap by referencing the index. 230 231 We consult the profile repository for all known versions of dnsrslvr.dll 232 and use the known symbol offsets to identify the currently running 233 version. Unfortunately often the RSDS section of the PE file is not 234 mapped and so we can not directly identify the running version. We 235 therefore use the following algorithm: 236 237 1. Enumerate the g_CacheHeap constant for each known version and ensure 238 it is referring to a valid heap. 239 240 2. Using the matching profile, dereference the g_HashTable constant and 241 ensure it refers to a valid allocation within the above heap. 242 243 3. If both these conditions exist, we return the hash table without 244 further checks. 245 246 This method is generally more robust than scanning for the pointers. 247 """ 248 # The base addresses of all valid heaps. 249 heaps = set([x.v() for x in task.Peb.ProcessHeaps]) 250 dnsrslvr_index = self.session.LoadProfile("dnsrslvr/index") 251 252 # The base address of dnsrslvr.dll. 253 base_address = self.session.address_resolver.get_address_by_name( 254 "dnsrslvr") 255 256 # Now check all profiles for these symbols. 257 for profile, symbols in dnsrslvr_index.index.iteritems(): 258 # Correct symbols offset for dll base address. 259 lookup = dict((y[0], x + base_address) for x, y in symbols) 260 261 # According to this profile, where is the cache heap and hash table? 262 heap_pointer = self.profile.Pointer(lookup.get("g_CacheHeap")).v() 263 hash_tbl_ptr = self.profile.Pointer(lookup.get("g_HashTable")).v() 264 265 if heap_pointer in heaps: 266 heap = self.heap_profile._HEAP( 267 offset=heap_pointer 268 ) 269 270 for entry in heap.Entries: 271 if entry.Allocation.obj_offset == hash_tbl_ptr: 272 self.session.logging.info( 273 "dnsrslvr.dll match profile %s. Hash table is at " 274 "%#x", profile, hash_tbl_ptr) 275 276 return self.profile.Array( 277 hash_tbl_ptr, 278 target="Pointer", 279 target_args=dict( 280 target="DNS_HASHTABLE_ENTRY", 281 ), 282 count=entry.Allocation.length / 8) 283 284 self.session.logging.info( 285 "Failed to detect the exact version of dnsrslvr.dll, please " 286 "consider sending a copy of this DLL's GUID to the Rekall team so " 287 "we can add it to the index.")
288
289 - def _locate_heap(self, task, vad):
290 """Locate the correct heap by scanning for its reference. 291 292 Find the references into the heap from the dnsrslvr.dll vad. This will 293 normally be stored in dnsrslvr.dll's global variable called g_CacheHeap. 294 """ 295 scanner = scan.PointerScanner( 296 pointers=task.Peb.ProcessHeaps, 297 session=self.session, 298 address_space=self.session.GetParameter("default_address_space")) 299 300 seen = set() 301 for hit in scanner.scan(vad.Start, maxlen=vad.Length): 302 heap = self.heap_profile.Pointer( 303 hit, target="_HEAP" 304 ).deref() 305 306 307 if heap in seen: 308 continue 309 310 seen.add(heap) 311 312 for entry in heap.Entries: 313 hash_table = self._verify_hash_table(entry.Allocation, heap) 314 if hash_table: 315 return hash_table
316
317 - def locate_cache_hashtable(self):
318 """Finds the DNS cache hashtable. 319 320 The dns cache runs inside one of the svchost.exe processes and is 321 implemented via the dnsrslvr.dll service. We therefore first search for 322 the correct VAD region for this DLL. We then find the private heap that 323 belongs to the resolver. 324 """ 325 vad, task = self._find_svchost_vad() 326 if task is None: 327 raise RuntimeError("Unable to find svchost.exe for dnsrslvr.dll.") 328 329 # Switch to the svchost process context now. 330 self.cc.SwitchProcessContext(task) 331 332 # Load the profile for dnsrslvr and add the new types. 333 dnsrslvr_mod = self.session.address_resolver.GetModuleByName("dnsrslvr") 334 if not dnsrslvr_mod: 335 raise RuntimeError("Unable to find dnsrslvr.dll.") 336 337 self.profile = InitializedDNSTypes(dnsrslvr_mod.profile) 338 339 hash_table = self.session.address_resolver.get_constant_object( 340 "dnsrslvr!g_HashTable", 341 "Pointer", 342 target_args=dict( 343 target="Array", 344 target_args=dict( 345 count=self.session.address_resolver.get_constant_object( 346 "dnsrslvr!g_HashTableSize", "unsigned int").v(), 347 target="Pointer", 348 target_args=dict( 349 target="DNS_HASHTABLE_ENTRY" 350 ) 351 ) 352 ) 353 ) 354 if hash_table: 355 return hash_table.deref() 356 357 ntdll_mod = self.session.address_resolver.GetModuleByName("ntdll") 358 self.heap_profile = ntdll_mod.profile 359 360 # First try to locate the hash table using the index, then fallback to 361 # using scanning techniques: 362 if not self.plugin_args.no_index: 363 hash_table = self._locate_heap_using_index(task) 364 if hash_table: 365 return hash_table 366 367 return self._locate_heap(task, vad)
368
369 - def collect(self):
370 self.cc = self.session.plugins.cc() 371 with self.cc: 372 cache_hash_table = self.locate_cache_hashtable() 373 buckets = set() 374 entries = set() 375 if cache_hash_table: 376 for bucket in cache_hash_table: 377 if bucket.obj_offset in buckets: 378 continue 379 380 buckets.add(bucket.obj_offset) 381 382 for entry in bucket.List.list_of_type_fast( 383 "DNS_HASHTABLE_ENTRY", "List", 384 include_current=True): 385 386 if entries.obj_offset in buckets: 387 continue 388 389 entries.add(bucket.obj_offset) 390 391 name = entry.Name.deref() 392 393 yield dict(Name=name, 394 record=entry, 395 type="HTABLE", 396 depth=0) 397 398 for record in entry.Record.walk_list("Next", True): 399 name = record.Name.deref() or name 400 yield dict(Name=name, 401 record=record, 402 type=record.Type, 403 data=record.Data, 404 depth=1)
405