Skip to content

Commit 9ffdf08

Browse files
committed
Improve display_type in Volshell with better pointer handling
- Introduced `_get_type_name_with_pointer` to properly display pointer types. - Enhanced `display_type` to follow and display pointer chains up to `MAX_DEREFERENCE_COUNT` levels. - Added `_display_simple_type` to standardize type information display. - Improved `_display_value` to highlight null and unreadable pointers.
1 parent d77d69c commit 9ffdf08

File tree

1 file changed

+147
-23
lines changed

1 file changed

+147
-23
lines changed

volatility3/cli/volshell/generic.py

Lines changed: 147 additions & 23 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."""
@@ -386,6 +388,30 @@ def disassemble(
386388
for i in disasm_types[architecture].disasm(remaining_data, offset):
387389
print(f"0x{i.address:x}:\t{i.mnemonic}\t{i.op_str}")
388390

391+
def _get_type_name_with_pointer(
392+
self,
393+
member_type: Union[
394+
str, interfaces.objects.ObjectInterface, interfaces.objects.Template
395+
],
396+
depth: int = 0,
397+
) -> str:
398+
"""Takes a member_type from and returns the subtype name with a * if the member_type is
399+
a pointer otherwise it returns just the normal type name."""
400+
pointer_marker = "*" * depth
401+
try:
402+
if member_type.vol.object_class == objects.Pointer:
403+
sub_member_type = member_type.vol.subtype
404+
# follow at most MAX_DEREFERENCE_COUNT pointers. A guard against, hopefully unlikely, infinite loops
405+
if depth < MAX_DEREFERENCE_COUNT:
406+
return self._get_type_name_with_pointer(sub_member_type, depth + 1)
407+
else:
408+
return member_type_name
409+
except AttributeError:
410+
pass # not all objects get a `object_class`, and those that don't are not pointers.
411+
finally:
412+
member_type_name = pointer_marker + member_type.vol.type_name
413+
return member_type_name
414+
389415
def display_type(
390416
self,
391417
object: Union[
@@ -418,67 +444,165 @@ def display_type(
418444
volobject.vol.type_name, layer_name=self.current_layer, offset=offset
419445
)
420446

421-
if hasattr(volobject.vol, "size"):
422-
print(f"{volobject.vol.type_name} ({volobject.vol.size} bytes)")
423-
elif hasattr(volobject.vol, "data_format"):
424-
data_format = volobject.vol.data_format
425-
print(
426-
"{} ({} bytes, {} endian, {})".format(
427-
volobject.vol.type_name,
428-
data_format.length,
429-
data_format.byteorder,
430-
"signed" if data_format.signed else "unsigned",
431-
)
432-
)
447+
# add special case for pointer so that information about the struct the
448+
# pointer is pointing to is shown rather than simply the fact this is a
449+
# pointer object. The "dereference_count < MAX_DEREFERENCE_COUNT" is to
450+
# guard against loops
451+
dereference_count = 0
452+
while (
453+
isinstance(volobject, objects.Pointer)
454+
and dereference_count < MAX_DEREFERENCE_COUNT
455+
):
456+
# before defreerencing the pointer, show it's information
457+
print(f'{" " * dereference_count}{self._display_simple_type(volobject)}')
458+
459+
# check that we can follow the pointer before dereferencing and do not
460+
# attempt to follow null pointers.
461+
if volobject.is_readable() and volobject != 0:
462+
# now deference the pointer and store this as the new volobject
463+
volobject = volobject.dereference()
464+
dereference_count = dereference_count + 1
465+
else:
466+
# if we aren't able to follow the pointers anymore then there will
467+
# be no more information to display as we've already printed the
468+
# details of this pointer including the fact that we're not able to
469+
# follow it anywhere
470+
return
433471

434472
if hasattr(volobject.vol, "members"):
473+
# display the header for this object, if the orginal object was just a type string, display the type information
474+
struct_header = f'{" " * dereference_count}{volobject.vol.type_name} ({volobject.vol.size} bytes)'
475+
if isinstance(object, str) and offset is None:
476+
suffix = ":"
477+
else:
478+
# this is an actual object or an offset was given so the offset should be displayed
479+
suffix = f" @ {hex(volobject.vol.offset)}:"
480+
print(struct_header + suffix)
481+
482+
# it is a more complex type, so all members also need information displayed
435483
longest_member = longest_offset = longest_typename = 0
436484
for member in volobject.vol.members:
437485
relative_offset, member_type = volobject.vol.members[member]
438486
longest_member = max(len(member), longest_member)
439487
longest_offset = max(len(hex(relative_offset)), longest_offset)
440-
longest_typename = max(len(member_type.vol.type_name), longest_typename)
488+
member_type_name = self._get_type_name_with_pointer(
489+
member_type
490+
) # special case for pointers to show what they point to
491+
longest_typename = max(len(member_type_name), longest_typename)
441492

442493
for member in sorted(
443494
volobject.vol.members, key=lambda x: (volobject.vol.members[x][0], x)
444495
):
445496
relative_offset, member_type = volobject.vol.members[member]
446497
len_offset = len(hex(relative_offset))
447498
len_member = len(member)
448-
len_typename = len(member_type.vol.type_name)
499+
member_type_name = self._get_type_name_with_pointer(
500+
member_type
501+
) # special case for pointers to show what they point to
502+
len_typename = len(member_type_name)
449503
if isinstance(volobject, interfaces.objects.ObjectInterface):
450504
# We're an instance, so also display the data
451505
print(
506+
" " * dereference_count,
452507
" " * (longest_offset - len_offset),
453508
hex(relative_offset),
454509
": ",
455510
member,
456511
" " * (longest_member - len_member),
457512
" ",
458-
member_type.vol.type_name,
513+
member_type_name,
459514
" " * (longest_typename - len_typename),
460515
" ",
461516
self._display_value(getattr(volobject, member)),
462517
)
463518
else:
519+
# not provided with an actual object, nor an offset so just display the types
464520
print(
521+
" " * dereference_count,
465522
" " * (longest_offset - len_offset),
466523
hex(relative_offset),
467524
": ",
468525
member,
469526
" " * (longest_member - len_member),
470527
" ",
471-
member_type.vol.type_name,
528+
member_type_name,
472529
)
473530

474-
@classmethod
475-
def _display_value(cls, value: Any) -> str:
476-
if isinstance(value, objects.PrimitiveObject):
477-
return repr(value)
478-
elif isinstance(value, objects.Array):
479-
return repr([cls._display_value(val) for val in value])
531+
else: # simple type with no members, only one line to print
532+
# if the orginal object was just a type string, display the type information
533+
if isinstance(object, str) and offset is None:
534+
print(self._display_simple_type(volobject, include_value=False))
535+
536+
# if the original object was an actual volobject or was a type string
537+
# with an offset. Then append the actual data to the display.
538+
else:
539+
print(" " * dereference_count, self._display_simple_type(volobject))
540+
541+
def _display_simple_type(
542+
self,
543+
volobject: Union[
544+
interfaces.objects.ObjectInterface, interfaces.objects.Template
545+
],
546+
include_value: bool = True,
547+
) -> str:
548+
# build the display_type_string based on the aviable information
549+
550+
if hasattr(volobject.vol, "size"):
551+
# the most common type to display, this shows their full size, e.g.:
552+
# (layer_name) >>> dt('task_struct')
553+
# symbol_table_name1!task_struct (1784 bytes)
554+
display_type_string = (
555+
f"{volobject.vol.type_name} ({volobject.vol.size} bytes)"
556+
)
557+
elif hasattr(volobject.vol, "data_format"):
558+
# this is useful for very simple types like ints, e.g.:
559+
# (layer_name) >>> dt('int')
560+
# symbol_table_name1!int (4 bytes, little endian, signed)
561+
data_format = volobject.vol.data_format
562+
display_type_string = "{} ({} bytes, {} endian, {})".format(
563+
volobject.vol.type_name,
564+
data_format.length,
565+
data_format.byteorder,
566+
"signed" if data_format.signed else "unsigned",
567+
)
568+
elif hasattr(volobject.vol, "type_name"):
569+
# types like void have almost no values to display other than their name, e.g.:
570+
# (layer_name) >>> dt('void')
571+
# symbol_table_name1!void
572+
display_type_string = volobject.vol.type_name
573+
else:
574+
# it should not be possible to have a volobject without at least a type_name
575+
raise AttributeError("Unable to find any details for object")
576+
577+
if include_value: # if include_value is true also add the value to the display
578+
if isinstance(volobject, objects.Pointer):
579+
# for pointers include the location of the pointer and where it points to
580+
return f"{display_type_string} @ {hex(volobject.vol.offset)} -> {self._display_value(volobject)}"
581+
else:
582+
return f"{display_type_string}: {self._display_value(volobject)}"
480583
else:
481-
return hex(value.vol.offset)
584+
return display_type_string
585+
586+
def _display_value(self, value: Any) -> str:
587+
try:
588+
if isinstance(value, objects.Pointer):
589+
# show pointers in hex to match output for struct addrs
590+
# highlight null or unreadable pointers
591+
if value == 0:
592+
suffix = " (null pointer)"
593+
elif not value.is_readable():
594+
suffix = " (unreadable pointer)"
595+
else:
596+
suffix = ""
597+
return f"{hex(value)}{suffix}"
598+
elif isinstance(value, objects.PrimitiveObject):
599+
return repr(value)
600+
elif isinstance(value, objects.Array):
601+
return repr([self._display_value(val) for val in value])
602+
else:
603+
return hex(value.vol.offset)
604+
except exceptions.InvalidAddressException:
605+
return "-"
482606

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

0 commit comments

Comments
 (0)