1# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
2# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
3#
4
5import collections.abc
6import datetime
7import functools
8import logging
9import struct
10from typing import Iterable, Iterator, Optional, Union, Dict, Tuple, List
11
12from volatility.framework import constants, exceptions, interfaces, objects, renderers, symbols
13from volatility.framework.layers import intel
14from volatility.framework.renderers import conversion
15from volatility.framework.symbols import generic
16from volatility.framework.symbols.windows.extensions import pool
17
18vollog = logging.getLogger(__name__)
19
20# Keep these in a basic module, to prevent import cycles when symbol providers require them
21
22
23class KSYSTEM_TIME(objects.StructType):
24    """A system time structure that stores a high and low part."""
25
26    def get_time(self):
27        wintime = (self.High1Time << 32) | self.LowPart
28        return conversion.wintime_to_datetime(wintime)
29
30
31class MMVAD_SHORT(objects.StructType):
32    """A class that represents process virtual memory ranges.
33
34    Each instance is a node in a binary tree structure and is pointed to
35    by VadRoot.
36    """
37
38    @functools.lru_cache(maxsize = None)
39    def get_tag(self):
40        vad_address = self.vol.offset
41
42        # the offset is different on 32 and 64 bits
43        symbol_table_name = self.vol.type_name.split(constants.BANG)[0]
44        if not symbols.symbol_table_is_64bit(self._context, symbol_table_name):
45            vad_address -= 4
46        else:
47            vad_address -= 12
48
49        try:
50            # TODO: instantiate a _POOL_HEADER and return PoolTag
51            bytesobj = self._context.object(symbol_table_name + constants.BANG + "bytes",
52                                            layer_name = self.vol.layer_name,
53                                            offset = vad_address,
54                                            native_layer_name = self.vol.native_layer_name,
55                                            length = 4)
56
57            return bytesobj.decode()
58        except exceptions.InvalidAddressException:
59            return None
60        except UnicodeDecodeError:
61            return None
62
63    def traverse(self, visited = None, depth = 0):
64        """Traverse the VAD tree, determining each underlying VAD node type by
65        looking up the pool tag for the structure and then casting into a new
66        object."""
67
68        # TODO: this is an arbitrary limit chosen based on past observations
69        if depth > 100:
70            vollog.log(constants.LOGLEVEL_VVV, "Vad tree is too deep, something went wrong!")
71            raise RuntimeError("Vad tree is too deep")
72
73        if visited is None:
74            visited = set()
75
76        vad_address = self.vol.offset
77
78        if vad_address in visited:
79            vollog.log(constants.LOGLEVEL_VVV, "VAD node already seen!")
80            return
81
82        visited.add(vad_address)
83        tag = self.get_tag()
84
85        if tag in ["VadS", "VadF"]:
86            target = "_MMVAD_SHORT"
87        elif tag != None and tag.startswith("Vad"):
88            target = "_MMVAD"
89        elif depth == 0:
90            # the root node at depth 0 is allowed to not have a tag
91            # but we still want to continue and access its right & left child
92            target = None
93        else:
94            # any node other than the root that doesn't have a recognized tag
95            # is just garbage and we skip the node entirely
96            vollog.log(constants.LOGLEVEL_VVV,
97                       "Skipping VAD at {} depth {} with tag {}".format(self.vol.offset, depth, tag))
98            return
99
100        if target:
101            vad_object = self.cast(target)
102            yield vad_object
103
104        try:
105            for vad_node in self.get_left_child().dereference().traverse(visited, depth + 1):
106                yield vad_node
107        except exceptions.InvalidAddressException as excp:
108            vollog.log(constants.LOGLEVEL_VVV, "Invalid address on LeftChild: {0:#x}".format(excp.invalid_address))
109
110        try:
111            for vad_node in self.get_right_child().dereference().traverse(visited, depth + 1):
112                yield vad_node
113        except exceptions.InvalidAddressException as excp:
114            vollog.log(constants.LOGLEVEL_VVV, "Invalid address on RightChild: {0:#x}".format(excp.invalid_address))
115
116    def get_right_child(self):
117        """Get the right child member."""
118
119        if self.has_member("RightChild"):
120            return self.RightChild
121
122        elif self.has_member("Right"):
123            return self.Right
124
125        raise AttributeError("Unable to find the right child member")
126
127    def get_left_child(self):
128        """Get the left child member."""
129
130        if self.has_member("LeftChild"):
131            return self.LeftChild
132
133        elif self.has_member("Left"):
134            return self.Left
135
136        raise AttributeError("Unable to find the left child member")
137
138    def get_parent(self):
139        """Get the VAD's parent member."""
140
141        # this is for xp and 2003
142        if self.has_member("Parent"):
143            return self.Parent
144
145        # this is for vista through windows 7
146        elif self.has_member("u1") and self.u1.has_member("Parent"):
147            return self.u1.Parent & ~0x3
148
149        # this is for windows 8 and 10
150        elif self.has_member("VadNode"):
151
152            if self.VadNode.has_member("u1"):
153                return self.VadNode.u1.Parent & ~0x3
154
155            elif self.VadNode.has_member("ParentValue"):
156                return self.VadNode.ParentValue & ~0x3
157
158        # also for windows 8 and 10
159        elif self.has_member("Core"):
160
161            if self.Core.VadNode.has_member("u1"):
162                return self.Core.VadNode.u1.Parent & ~0x3
163
164            elif self.Core.VadNode.has_member("ParentValue"):
165                return self.Core.VadNode.ParentValue & ~0x3
166
167        raise AttributeError("Unable to find the parent member")
168
169    def get_start(self):
170        """Get the VAD's starting virtual address."""
171
172        if self.has_member("StartingVpn"):
173
174            if self.has_member("StartingVpnHigh"):
175                return (self.StartingVpn << 12) | (self.StartingVpnHigh << 44)
176            else:
177                return self.StartingVpn << 12
178
179        elif self.has_member("Core"):
180
181            if self.Core.has_member("StartingVpnHigh"):
182                return (self.Core.StartingVpn << 12) | (self.Core.StartingVpnHigh << 44)
183            else:
184                return self.Core.StartingVpn << 12
185
186        raise AttributeError("Unable to find the starting VPN member")
187
188    def get_end(self):
189        """Get the VAD's ending virtual address."""
190
191        if self.has_member("EndingVpn"):
192
193            if self.has_member("EndingVpnHigh"):
194                return (((self.EndingVpn + 1) << 12) | (self.EndingVpnHigh << 44)) - 1
195            else:
196                return ((self.EndingVpn + 1) << 12) - 1
197
198        elif self.has_member("Core"):
199            if self.Core.has_member("EndingVpnHigh"):
200                return (((self.Core.EndingVpn + 1) << 12) | (self.Core.EndingVpnHigh << 44)) - 1
201            else:
202                return ((self.Core.EndingVpn + 1) << 12) - 1
203
204        raise AttributeError("Unable to find the ending VPN member")
205
206    def get_commit_charge(self):
207        """Get the VAD's commit charge (number of committed pages)"""
208
209        if self.has_member("u1") and self.u1.has_member("VadFlags1"):
210            return self.u1.VadFlags1.CommitCharge
211
212        elif self.has_member("u") and self.u.has_member("VadFlags"):
213            return self.u.VadFlags.CommitCharge
214
215        elif self.has_member("Core"):
216            return self.Core.u1.VadFlags1.CommitCharge
217
218        raise AttributeError("Unable to find the commit charge member")
219
220    def get_private_memory(self):
221        """Get the VAD's private memory setting."""
222
223        if self.has_member("u1") and self.u1.has_member("VadFlags1") and self.u1.VadFlags1.has_member("PrivateMemory"):
224            return self.u1.VadFlags1.PrivateMemory
225
226        elif self.has_member("u") and self.u.has_member("VadFlags") and self.u.VadFlags.has_member("PrivateMemory"):
227            return self.u.VadFlags.PrivateMemory
228
229        elif self.has_member("Core"):
230            if (self.Core.has_member("u1") and self.Core.u1.has_member("VadFlags1")
231                    and self.Core.u1.VadFlags1.has_member("PrivateMemory")):
232                return self.Core.u1.VadFlags1.PrivateMemory
233
234            elif (self.Core.has_member("u") and self.Core.u.has_member("VadFlags")
235                  and self.Core.u.VadFlags.has_member("PrivateMemory")):
236                return self.Core.u.VadFlags.PrivateMemory
237
238        raise AttributeError("Unable to find the private memory member")
239
240    def get_protection(self, protect_values, winnt_protections):
241        """Get the VAD's protection constants as a string."""
242
243        protect = None
244
245        if self.has_member("u"):
246            protect = self.u.VadFlags.Protection
247
248        elif self.has_member("Core"):
249            protect = self.Core.u.VadFlags.Protection
250
251        try:
252            value = protect_values[protect]
253        except IndexError:
254            value = 0
255
256        names = []
257
258        for name, mask in winnt_protections.items():
259            if value & mask != 0:
260                names.append(name)
261
262        return "|".join(names)
263
264    def get_file_name(self):
265        """Only long(er) vads have mapped files."""
266        return renderers.NotApplicableValue()
267
268
269class MMVAD(MMVAD_SHORT):
270    """A version of the process virtual memory range structure that contains
271    additional fields necessary to map files from disk."""
272
273    def get_file_name(self):
274        """Get the name of the file mapped into the memory range (if any)"""
275
276        file_name = renderers.NotApplicableValue()
277
278        try:
279            # this is for xp and 2003
280            if self.has_member("ControlArea"):
281                file_name = self.ControlArea.FilePointer.FileName.get_string()
282
283            # this is for vista through windows 7
284            else:
285                file_name = self.Subsection.ControlArea.FilePointer.dereference().cast(
286                    "_FILE_OBJECT").FileName.get_string()
287
288        except exceptions.InvalidAddressException:
289            pass
290
291        return file_name
292
293
294class EX_FAST_REF(objects.StructType):
295    """This is a standard Windows structure that stores a pointer to an object
296    but also leverages the least significant bits to encode additional details.
297
298    When dereferencing the pointer, we need to strip off the extra bits.
299    """
300
301    def dereference(self) -> interfaces.objects.ObjectInterface:
302
303        if constants.BANG not in self.vol.type_name:
304            raise ValueError("Invalid symbol table name syntax (no {} found)".format(constants.BANG))
305
306        # the mask value is different on 32 and 64 bits
307        symbol_table_name = self.vol.type_name.split(constants.BANG)[0]
308        if not symbols.symbol_table_is_64bit(self._context, symbol_table_name):
309            max_fast_ref = 7
310        else:
311            max_fast_ref = 15
312
313        return self._context.object(symbol_table_name + constants.BANG + "pointer",
314                                    layer_name = self.vol.layer_name,
315                                    offset = self.Object & ~max_fast_ref,
316                                    native_layer_name = self.vol.native_layer_name)
317
318
319class DEVICE_OBJECT(objects.StructType, pool.ExecutiveObject):
320    """A class for kernel device objects."""
321
322    def get_device_name(self) -> str:
323        header = self.get_object_header()
324        return header.NameInfo.Name.String  # type: ignore
325
326
327class DRIVER_OBJECT(objects.StructType, pool.ExecutiveObject):
328    """A class for kernel driver objects."""
329
330    def get_driver_name(self) -> str:
331        header = self.get_object_header()
332        return header.NameInfo.Name.String  # type: ignore
333
334    def is_valid(self) -> bool:
335        """Determine if the object is valid."""
336        return True
337
338
339class OBJECT_SYMBOLIC_LINK(objects.StructType, pool.ExecutiveObject):
340    """A class for kernel link objects."""
341
342    def get_link_name(self) -> str:
343        header = self.get_object_header()
344        return header.NameInfo.Name.String  # type: ignore
345
346    def is_valid(self) -> bool:
347        """Determine if the object is valid."""
348        return True
349
350    def get_create_time(self):
351        return conversion.wintime_to_datetime(self.CreationTime.QuadPart)
352
353
354class FILE_OBJECT(objects.StructType, pool.ExecutiveObject):
355    """A class for windows file objects."""
356
357    def is_valid(self) -> bool:
358        """Determine if the object is valid."""
359        return self.FileName.Length > 0 and self._context.layers[self.vol.layer_name].is_valid(self.FileName.Buffer)
360
361    def file_name_with_device(self) -> Union[str, interfaces.renderers.BaseAbsentValue]:
362        name = renderers.UnreadableValue()  # type: Union[str, interfaces.renderers.BaseAbsentValue]
363
364        if self._context.layers[self.vol.layer_name].is_valid(self.DeviceObject):
365            name = "\\Device\\{}".format(self.DeviceObject.get_device_name())
366
367        try:
368            name += self.FileName.String
369        except (TypeError, exceptions.InvalidAddressException):
370            pass
371
372        return name
373
374
375class KMUTANT(objects.StructType, pool.ExecutiveObject):
376    """A class for windows mutant objects."""
377
378    def is_valid(self) -> bool:
379        """Determine if the object is valid."""
380        return True
381
382    def get_name(self) -> str:
383        """Get the object's name from the object header."""
384        header = self.get_object_header()
385        return header.NameInfo.Name.String  # type: ignore
386
387
388class ETHREAD(objects.StructType):
389    """A class for executive thread objects."""
390
391    def owning_process(self, kernel_layer: str = None) -> interfaces.objects.ObjectInterface:
392        """Return the EPROCESS that owns this thread."""
393        return self.ThreadsProcess.dereference(kernel_layer)
394
395
396class UNICODE_STRING(objects.StructType):
397    """A class for Windows unicode string structures."""
398
399    def get_string(self) -> interfaces.objects.ObjectInterface:
400        # We explicitly do *not* catch errors here, we allow an exception to be thrown
401        # (otherwise there's no way to determine anything went wrong)
402        # It's up to the user of this method to catch exceptions
403        return self.Buffer.dereference().cast("string",
404                                              max_length = self.Length,
405                                              errors = "replace",
406                                              encoding = "utf16")
407
408    String = property(get_string)
409
410
411class EPROCESS(generic.GenericIntelProcess, pool.ExecutiveObject):
412    """A class for executive kernel processes objects."""
413
414    def is_valid(self) -> bool:
415        """Determine if the object is valid."""
416
417        try:
418            name = objects.utility.array_to_string(self.ImageFileName)
419            if not name or len(name) == 0 or name[0] == "\x00":
420                return False
421
422            # The System/PID 4 process has no create time
423            if not (str(name) == "System" and self.UniqueProcessId == 4):
424                if self.CreateTime.QuadPart == 0:
425                    return False
426
427                ctime = self.get_create_time()
428                if not isinstance(ctime, datetime.datetime):
429                    return False
430
431                if not (1998 < ctime.year < 2030):
432                    return False
433
434            # NT pids are divisible by 4
435            if self.UniqueProcessId % 4 != 0:
436                return False
437
438            # check for all 0s besides the PCID entries
439            if isinstance(self.Pcb.DirectoryTableBase, objects.Array):
440                dtb = self.Pcb.DirectoryTableBase.cast("pointer")
441            else:
442                dtb = self.Pcb.DirectoryTableBase
443
444            if dtb == 0:
445                return False
446
447            # check for all 0s besides the PCID entries
448            if dtb & ~0xfff == 0:
449                return False
450
451            ## TODO: we can also add the thread Flink and Blink tests if necessary
452
453        except exceptions.InvalidAddressException:
454            return False
455
456        return True
457
458    def add_process_layer(self, config_prefix: str = None, preferred_name: str = None):
459        """Constructs a new layer based on the process's DirectoryTableBase."""
460
461        parent_layer = self._context.layers[self.vol.layer_name]
462
463        if not isinstance(parent_layer, intel.Intel):
464            # We can't get bits_per_register unless we're an intel space (since that's not defined at the higher layer)
465            raise TypeError("Parent layer is not a translation layer, unable to construct process layer")
466
467        # Presumably for 64-bit systems, the DTB is defined as an array, rather than an unsigned long long
468        dtb = 0  # type: int
469        if isinstance(self.Pcb.DirectoryTableBase, objects.Array):
470            dtb = self.Pcb.DirectoryTableBase.cast("unsigned long long")
471        else:
472            dtb = self.Pcb.DirectoryTableBase
473        dtb = dtb & ((1 << parent_layer.bits_per_register) - 1)
474
475        if preferred_name is None:
476            preferred_name = self.vol.layer_name + "_Process{}".format(self.UniqueProcessId)
477
478        # Add the constructed layer and return the name
479        return self._add_process_layer(self._context, dtb, config_prefix, preferred_name)
480
481    def get_peb(self) -> interfaces.objects.ObjectInterface:
482        """Constructs a PEB object"""
483        if constants.BANG not in self.vol.type_name:
484            raise ValueError("Invalid symbol table name syntax (no {} found)".format(constants.BANG))
485
486        # add_process_layer can raise InvalidAddressException.
487        # if that happens, we let the exception propagate upwards
488        proc_layer_name = self.add_process_layer()
489
490        proc_layer = self._context.layers[proc_layer_name]
491        if not proc_layer.is_valid(self.Peb):
492            raise exceptions.InvalidAddressException(proc_layer_name, self.Peb,
493                                                     "Invalid address at {:0x}".format(self.Peb))
494
495        sym_table = self.vol.type_name.split(constants.BANG)[0]
496        peb = self._context.object("{}{}_PEB".format(sym_table, constants.BANG),
497                                   layer_name = proc_layer_name,
498                                   offset = self.Peb)
499        return peb
500
501    def load_order_modules(self) -> Iterable[interfaces.objects.ObjectInterface]:
502        """Generator for DLLs in the order that they were loaded."""
503
504        try:
505            peb = self.get_peb()
506            for entry in peb.Ldr.InLoadOrderModuleList.to_list(
507                    "{}{}_LDR_DATA_TABLE_ENTRY".format(self.get_symbol_table_name(), constants.BANG),
508                    "InLoadOrderLinks"):
509                yield entry
510        except exceptions.InvalidAddressException:
511            return
512
513    def init_order_modules(self) -> Iterable[interfaces.objects.ObjectInterface]:
514        """Generator for DLLs in the order that they were initialized"""
515
516        try:
517            peb = self.get_peb()
518            for entry in peb.Ldr.InInitializationOrderModuleList.to_list(
519                    "{}{}_LDR_DATA_TABLE_ENTRY".format(self.get_symbol_table_name(), constants.BANG),
520                    "InInitializationOrderLinks"):
521                yield entry
522        except exceptions.InvalidAddressException:
523            return
524
525    def mem_order_modules(self) -> Iterable[interfaces.objects.ObjectInterface]:
526        """Generator for DLLs in the order that they appear in memory"""
527
528        try:
529            peb = self.get_peb()
530            for entry in peb.Ldr.InMemoryOrderModuleList.to_list(
531                    "{}{}_LDR_DATA_TABLE_ENTRY".format(self.get_symbol_table_name(), constants.BANG),
532                    "InMemoryOrderLinks"):
533                yield entry
534        except exceptions.InvalidAddressException:
535            return
536
537    def get_handle_count(self):
538        try:
539            if self.has_member("ObjectTable"):
540                if self.ObjectTable.has_member("HandleCount"):
541                    return self.ObjectTable.HandleCount
542
543        except exceptions.InvalidAddressException:
544            vollog.log(constants.LOGLEVEL_VVV,
545                       "Cannot access _EPROCESS.ObjectTable.HandleCount at {0:#x}".format(self.vol.offset))
546
547        return renderers.UnreadableValue()
548
549    def get_session_id(self):
550        try:
551            if self.has_member("Session"):
552                if self.Session == 0:
553                    return renderers.NotApplicableValue()
554
555                symbol_table_name = self.get_symbol_table_name()
556                kvo = self._context.layers[self.vol.native_layer_name].config['kernel_virtual_offset']
557                ntkrnlmp = self._context.module(symbol_table_name,
558                                                layer_name = self.vol.native_layer_name,
559                                                offset = kvo,
560                                                native_layer_name = self.vol.native_layer_name)
561                session = ntkrnlmp.object(object_type = "_MM_SESSION_SPACE", offset = self.Session, absolute = True)
562
563                if session.has_member("SessionId"):
564                    return session.SessionId
565
566        except exceptions.InvalidAddressException:
567            vollog.log(constants.LOGLEVEL_VVV,
568                       "Cannot access _EPROCESS.Session.SessionId at {0:#x}".format(self.vol.offset))
569
570        return renderers.UnreadableValue()
571
572    def get_create_time(self):
573        return conversion.wintime_to_datetime(self.CreateTime.QuadPart)
574
575    def get_exit_time(self):
576        return conversion.wintime_to_datetime(self.ExitTime.QuadPart)
577
578    def get_wow_64_process(self):
579        if self.has_member("Wow64Process"):
580            return self.Wow64Process
581
582        elif self.has_member("WoW64Process"):
583            return self.WoW64Process
584
585        raise AttributeError("Unable to find Wow64Process")
586
587    def get_is_wow64(self):
588        try:
589            value = self.get_wow_64_process()
590        except AttributeError:
591            return False
592
593        return value != 0 and value != None
594
595    def get_vad_root(self):
596
597        # windows 8 and 2012 (_MM_AVL_TABLE)
598        if self.VadRoot.has_member("BalancedRoot"):
599            return self.VadRoot.BalancedRoot
600
601        # windows 8.1 and windows 10 (_RTL_AVL_TREE)
602        elif self.VadRoot.has_member("Root"):
603            return self.VadRoot.Root.dereference()  # .cast("_MMVAD")
604
605        else:
606            # windows xp and 2003
607            return self.VadRoot.dereference().cast("_MMVAD")
608
609
610class LIST_ENTRY(objects.StructType, collections.abc.Iterable):
611    """A class for double-linked lists on Windows."""
612
613    def to_list(self,
614                symbol_type: str,
615                member: str,
616                forward: bool = True,
617                sentinel: bool = True,
618                layer: Optional[str] = None) -> Iterator[interfaces.objects.ObjectInterface]:
619        """Returns an iterator of the entries in the list."""
620
621        layer = layer or self.vol.layer_name
622
623        relative_offset = self._context.symbol_space.get_type(symbol_type).relative_child_offset(member)
624
625        direction = 'Blink'
626        if forward:
627            direction = 'Flink'
628
629        trans_layer = self._context.layers[layer]
630
631        try:
632            trans_layer.is_valid(self.vol.offset)
633            link = getattr(self, direction).dereference()
634        except exceptions.InvalidAddressException:
635            return
636
637        if not sentinel:
638            yield self._context.object(symbol_type,
639                                       layer,
640                                       offset = self.vol.offset - relative_offset,
641                                       native_layer_name = layer or self.vol.native_layer_name)
642
643        seen = {self.vol.offset}
644        while link.vol.offset not in seen:
645            obj_offset = link.vol.offset - relative_offset
646
647            try:
648                trans_layer.is_valid(obj_offset)
649            except exceptions.InvalidAddressException:
650                return
651
652            obj = self._context.object(symbol_type,
653                                       layer,
654                                       offset = obj_offset,
655                                       native_layer_name = layer or self.vol.native_layer_name)
656            yield obj
657
658            seen.add(link.vol.offset)
659
660            try:
661                link = getattr(link, direction).dereference()
662            except exceptions.InvalidAddressException:
663                return
664
665    def __iter__(self) -> Iterator[interfaces.objects.ObjectInterface]:
666        return self.to_list(self.vol.parent.vol.type_name, self.vol.member_name)
667