1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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
41
42
43
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 }
95 @utils.safe_property
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
119
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
151
168
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
186 segments = utils.RangedCollection()
187 for seg in heap.Segments:
188 segments.insert(seg.FirstEntry, seg.LastValidEntry, seg)
189
190
191
192 if allocation.length > 1600 * 3 or allocation.length < 1600:
193 return False
194
195
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
208 entry = entry.v()
209 if entry == 0:
210 continue
211
212
213
214
215 dest_segment = segments.get_range(entry)
216 if dest_segment is None:
217 return False
218
219 count += 1
220
221
222
223 if count == 0:
224 return False
225
226 return cache_hash_table
227
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
249 heaps = set([x.v() for x in task.Peb.ProcessHeaps])
250 dnsrslvr_index = self.session.LoadProfile("dnsrslvr/index")
251
252
253 base_address = self.session.address_resolver.get_address_by_name(
254 "dnsrslvr")
255
256
257 for profile, symbols in dnsrslvr_index.index.iteritems():
258
259 lookup = dict((y[0], x + base_address) for x, y in symbols)
260
261
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
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
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
330 self.cc.SwitchProcessContext(task)
331
332
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
361
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
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