Skip to content

Commit 6ddb0fe

Browse files
authored
Merge pull request #1748 from eve-mem/volshell_display_types_pointer_upgrade_2025
Volshell: Display types pointer upgrade
2 parents ed1b1f5 + 69e3c7d commit 6ddb0fe

File tree

1 file changed

+180
-26
lines changed

1 file changed

+180
-26
lines changed

volatility3/cli/volshell/generic.py

Lines changed: 180 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
except ImportError:
3333
has_ipython = False
3434

35+
MAX_DEREFERENCE_COUNT = 4 # the max number of times display_type should follow pointers
36+
3537

3638
class Volshell(interfaces.plugins.PluginInterface):
3739
"""Shell environment to directly interact with a memory image."""
@@ -394,6 +396,28 @@ def disassemble(
394396
for i in disasm_types[architecture].disasm(remaining_data, offset):
395397
print(f"0x{i.address:x}:\t{i.mnemonic}\t{i.op_str}")
396398

399+
def _get_type_name_with_pointer(
400+
self,
401+
member_type: Union[
402+
str, interfaces.objects.ObjectInterface, interfaces.objects.Template
403+
],
404+
depth: int = 0,
405+
) -> str:
406+
"""Takes a member_type from and returns the subtype name with a * if the member_type is
407+
a pointer otherwise it returns just the normal type name."""
408+
pointer_marker = "*" * depth
409+
try:
410+
if member_type.vol.object_class == objects.Pointer:
411+
sub_member_type = member_type.vol.subtype
412+
# follow at most MAX_DEREFERENCE_COUNT pointers. A guard against, hopefully unlikely, infinite loops
413+
if depth < MAX_DEREFERENCE_COUNT:
414+
return self._get_type_name_with_pointer(sub_member_type, depth + 1)
415+
except AttributeError:
416+
pass # not all objects get a `object_class`, and those that don't are not pointers.
417+
finally:
418+
member_type_name = pointer_marker + member_type.vol.type_name
419+
return member_type_name
420+
397421
def display_type(
398422
self,
399423
object: Union[
@@ -402,6 +426,9 @@ def display_type(
402426
offset: Optional[int] = None,
403427
):
404428
"""Display Type describes the members of a particular object in alphabetical order"""
429+
430+
MAX_TYPENAME_DISPLAY_LENGTH = 256
431+
405432
if not isinstance(
406433
object,
407434
(str, interfaces.objects.ObjectInterface, interfaces.objects.Template),
@@ -426,75 +453,202 @@ def display_type(
426453
volobject.vol.type_name, layer_name=self.current_layer, offset=offset
427454
)
428455

429-
if hasattr(volobject.vol, "size"):
430-
print(f"{volobject.vol.type_name} ({volobject.vol.size} bytes)")
431-
elif hasattr(volobject.vol, "data_format"):
432-
data_format = volobject.vol.data_format
433-
print(
434-
"{} ({} bytes, {} endian, {})".format(
435-
volobject.vol.type_name,
436-
data_format.length,
437-
data_format.byteorder,
438-
"signed" if data_format.signed else "unsigned",
439-
)
440-
)
456+
# add special case for pointer so that information about the struct the
457+
# pointer is pointing to is shown rather than simply the fact this is a
458+
# pointer object. The "dereference_count < MAX_DEREFERENCE_COUNT" is to
459+
# guard against loops
460+
dereference_count = 0
461+
while (
462+
isinstance(volobject, objects.Pointer)
463+
and dereference_count < MAX_DEREFERENCE_COUNT
464+
):
465+
# before defreerencing the pointer, show it's information
466+
print(f'{" " * dereference_count}{self._display_simple_type(volobject)}')
467+
468+
# check that we can follow the pointer before dereferencing and do not
469+
# attempt to follow null pointers.
470+
if volobject.is_readable() and volobject != 0:
471+
# now deference the pointer and store this as the new volobject
472+
volobject = volobject.dereference()
473+
dereference_count = dereference_count + 1
474+
else:
475+
# if we aren't able to follow the pointers anymore then there will
476+
# be no more information to display as we've already printed the
477+
# details of this pointer including the fact that we're not able to
478+
# follow it anywhere
479+
return
441480

442481
if hasattr(volobject.vol, "members"):
482+
# display the header for this object, if the orginal object was just a type string, display the type information
483+
struct_header = f'{" " * dereference_count}{volobject.vol.type_name} ({volobject.vol.size} bytes)'
484+
if isinstance(object, str) and offset is None:
485+
suffix = ":"
486+
else:
487+
# this is an actual object or an offset was given so the offset should be displayed
488+
suffix = f" @ {hex(volobject.vol.offset)}:"
489+
print(struct_header + suffix)
490+
491+
# it is a more complex type, so all members also need information displayed
443492
longest_member = longest_offset = longest_typename = 0
444493
for member in volobject.vol.members:
445494
relative_offset, member_type = volobject.vol.members[member]
446495
longest_member = max(len(member), longest_member)
447496
longest_offset = max(len(hex(relative_offset)), longest_offset)
448-
longest_typename = max(len(member_type.vol.type_name), longest_typename)
497+
member_type_name = self._get_type_name_with_pointer(
498+
member_type
499+
) # special case for pointers to show what they point to
500+
501+
# find the longest typename
502+
longest_typename = max(len(member_type_name), longest_typename)
503+
504+
# if the typename is very long then limit it to MAX_TYPENAME_DISPLAY_LENGTH
505+
longest_typename = min(longest_typename, MAX_TYPENAME_DISPLAY_LENGTH)
449506

450507
for member in sorted(
451508
volobject.vol.members, key=lambda x: (volobject.vol.members[x][0], x)
452509
):
453510
relative_offset, member_type = volobject.vol.members[member]
454511
len_offset = len(hex(relative_offset))
455512
len_member = len(member)
456-
len_typename = len(member_type.vol.type_name)
513+
514+
member_type_name = self._get_type_name_with_pointer(
515+
member_type
516+
) # special case for pointers to show what they point to
517+
len_typename = len(member_type_name)
518+
if len(member_type_name) > MAX_TYPENAME_DISPLAY_LENGTH:
519+
len_typename = MAX_TYPENAME_DISPLAY_LENGTH
520+
member_type_name = f"{member_type_name[:len_typename - 3]}..."
457521

458522
if isinstance(volobject, interfaces.objects.ObjectInterface):
459523
# We're an instance, so also display the data
460524
try:
461525
value = self._display_value(volobject.member(member))
462526
except exceptions.InvalidAddressException:
463527
value = self._display_value(renderers.NotAvailableValue())
464-
465528
print(
529+
" " * dereference_count,
466530
" " * (longest_offset - len_offset),
467531
hex(relative_offset),
468532
": ",
469533
member,
470534
" " * (longest_member - len_member),
471535
" ",
472-
member_type.vol.type_name,
536+
member_type_name,
473537
" " * (longest_typename - len_typename),
474538
" ",
475539
value,
476540
)
477541
else:
542+
# not provided with an actual object, nor an offset so just display the types
478543
print(
544+
" " * dereference_count,
479545
" " * (longest_offset - len_offset),
480546
hex(relative_offset),
481547
": ",
482548
member,
483549
" " * (longest_member - len_member),
484550
" ",
485-
member_type.vol.type_name,
551+
member_type_name,
486552
)
487553

488-
@classmethod
489-
def _display_value(cls, value: Any) -> str:
490-
if isinstance(value, interfaces.renderers.BaseAbsentValue):
491-
return "N/A"
492-
elif isinstance(value, objects.PrimitiveObject):
493-
return repr(value)
494-
elif isinstance(value, objects.Array):
495-
return repr([cls._display_value(val) for val in value])
554+
else: # simple type with no members, only one line to print
555+
# if the orginal object was just a type string, display the type information
556+
if isinstance(object, str) and offset is None:
557+
print(self._display_simple_type(volobject, include_value=False))
558+
559+
# if the original object was an actual volobject or was a type string
560+
# with an offset. Then append the actual data to the display.
561+
else:
562+
print(" " * dereference_count, self._display_simple_type(volobject))
563+
564+
def _display_simple_type(
565+
self,
566+
volobject: Union[
567+
interfaces.objects.ObjectInterface, interfaces.objects.Template
568+
],
569+
include_value: bool = True,
570+
) -> str:
571+
# build the display_type_string based on the available information
572+
573+
if hasattr(volobject.vol, "size"):
574+
# the most common type to display, this shows their full size, e.g.:
575+
# (layer_name) >>> dt('task_struct')
576+
# symbol_table_name1!task_struct (1784 bytes)
577+
display_type_string = (
578+
f"{volobject.vol.type_name} ({volobject.vol.size} bytes)"
579+
)
580+
elif hasattr(volobject.vol, "data_format"):
581+
# this is useful for very simple types like ints, e.g.:
582+
# (layer_name) >>> dt('int')
583+
# symbol_table_name1!int (4 bytes, little endian, signed)
584+
data_format = volobject.vol.data_format
585+
display_type_string = "{} ({} bytes, {} endian, {})".format(
586+
volobject.vol.type_name,
587+
data_format.length,
588+
data_format.byteorder,
589+
"signed" if data_format.signed else "unsigned",
590+
)
591+
elif hasattr(volobject.vol, "type_name"):
592+
# types like void have almost no values to display other than their name, e.g.:
593+
# (layer_name) >>> dt('void')
594+
# symbol_table_name1!void
595+
display_type_string = volobject.vol.type_name
596+
else:
597+
# it should not be possible to have a volobject without at least a type_name
598+
raise AttributeError("Unable to find any details for object")
599+
600+
if include_value: # if include_value is true also add the value to the display
601+
if isinstance(volobject, objects.Pointer):
602+
# for pointers include the location of the pointer and where it points to
603+
return f"{display_type_string} @ {hex(volobject.vol.offset)} -> {self._display_value(volobject)}"
604+
else:
605+
return f"{display_type_string}: {self._display_value(volobject)}"
606+
496607
else:
497-
return hex(value.vol.offset)
608+
return display_type_string
609+
610+
def _display_value(self, value: Any) -> str:
611+
try:
612+
# if value is a BaseAbsentValue they display N/A
613+
if isinstance(value, interfaces.renderers.BaseAbsentValue):
614+
return "N/A"
615+
else:
616+
# volobject branch
617+
if isinstance(
618+
value,
619+
(interfaces.objects.ObjectInterface, interfaces.objects.Template),
620+
):
621+
if isinstance(value, objects.Pointer):
622+
# show pointers in hex to match output for struct addrs
623+
# highlight null or unreadable pointers
624+
if value == 0:
625+
suffix = " (null pointer)"
626+
elif not value.is_readable():
627+
suffix = " (unreadable pointer)"
628+
else:
629+
suffix = ""
630+
return f"{hex(value)}{suffix}"
631+
elif isinstance(value, objects.PrimitiveObject):
632+
return repr(value)
633+
elif isinstance(value, objects.Array):
634+
return repr([self._display_value(val) for val in value])
635+
else:
636+
if self.context.layers[self.current_layer].is_valid(
637+
value.vol.offset
638+
):
639+
return f"offset: 0x{value.vol.offset:x}"
640+
else:
641+
return f"offset: 0x{value.vol.offset:x} (unreadable)"
642+
else:
643+
# non volobject
644+
if value is None:
645+
return "N/A"
646+
else:
647+
return repr(value)
648+
649+
except exceptions.InvalidAddressException:
650+
# if value causes an InvalidAddressException like BaseAbsentValue then display N/A
651+
return "N/A"
498652

499653
def generate_treegrid(
500654
self, plugin: Type[interfaces.plugins.PluginInterface], **kwargs

0 commit comments

Comments
 (0)