From 9ffdf08f20434263f7f58347d58683ee5df817bd Mon Sep 17 00:00:00 2001 From: eve Date: Fri, 28 Mar 2025 11:45:34 +0000 Subject: [PATCH 01/16] 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. --- volatility3/cli/volshell/generic.py | 170 ++++++++++++++++++++++++---- 1 file changed, 147 insertions(+), 23 deletions(-) diff --git a/volatility3/cli/volshell/generic.py b/volatility3/cli/volshell/generic.py index 3a5d514fe..116e9c449 100644 --- a/volatility3/cli/volshell/generic.py +++ b/volatility3/cli/volshell/generic.py @@ -32,6 +32,8 @@ try: except ImportError: has_ipython = False +MAX_DEREFERENCE_COUNT = 4 # the max number of times display_type should follow pointers + class Volshell(interfaces.plugins.PluginInterface): """Shell environment to directly interact with a memory image.""" @@ -386,6 +388,30 @@ class Volshell(interfaces.plugins.PluginInterface): for i in disasm_types[architecture].disasm(remaining_data, offset): print(f"0x{i.address:x}:\t{i.mnemonic}\t{i.op_str}") + def _get_type_name_with_pointer( + self, + member_type: Union[ + str, interfaces.objects.ObjectInterface, interfaces.objects.Template + ], + depth: int = 0, + ) -> str: + """Takes a member_type from and returns the subtype name with a * if the member_type is + a pointer otherwise it returns just the normal type name.""" + pointer_marker = "*" * depth + try: + if member_type.vol.object_class == objects.Pointer: + sub_member_type = member_type.vol.subtype + # follow at most MAX_DEREFERENCE_COUNT pointers. A guard against, hopefully unlikely, infinite loops + if depth < MAX_DEREFERENCE_COUNT: + return self._get_type_name_with_pointer(sub_member_type, depth + 1) + else: + return member_type_name + except AttributeError: + pass # not all objects get a `object_class`, and those that don't are not pointers. + finally: + member_type_name = pointer_marker + member_type.vol.type_name + return member_type_name + def display_type( self, object: Union[ @@ -418,26 +444,51 @@ class Volshell(interfaces.plugins.PluginInterface): volobject.vol.type_name, layer_name=self.current_layer, offset=offset ) - if hasattr(volobject.vol, "size"): - print(f"{volobject.vol.type_name} ({volobject.vol.size} bytes)") - elif hasattr(volobject.vol, "data_format"): - data_format = volobject.vol.data_format - print( - "{} ({} bytes, {} endian, {})".format( - volobject.vol.type_name, - data_format.length, - data_format.byteorder, - "signed" if data_format.signed else "unsigned", - ) - ) + # add special case for pointer so that information about the struct the + # pointer is pointing to is shown rather than simply the fact this is a + # pointer object. The "dereference_count < MAX_DEREFERENCE_COUNT" is to + # guard against loops + dereference_count = 0 + while ( + isinstance(volobject, objects.Pointer) + and dereference_count < MAX_DEREFERENCE_COUNT + ): + # before defreerencing the pointer, show it's information + print(f'{" " * dereference_count}{self._display_simple_type(volobject)}') + + # check that we can follow the pointer before dereferencing and do not + # attempt to follow null pointers. + if volobject.is_readable() and volobject != 0: + # now deference the pointer and store this as the new volobject + volobject = volobject.dereference() + dereference_count = dereference_count + 1 + else: + # if we aren't able to follow the pointers anymore then there will + # be no more information to display as we've already printed the + # details of this pointer including the fact that we're not able to + # follow it anywhere + return if hasattr(volobject.vol, "members"): + # display the header for this object, if the orginal object was just a type string, display the type information + struct_header = f'{" " * dereference_count}{volobject.vol.type_name} ({volobject.vol.size} bytes)' + if isinstance(object, str) and offset is None: + suffix = ":" + else: + # this is an actual object or an offset was given so the offset should be displayed + suffix = f" @ {hex(volobject.vol.offset)}:" + print(struct_header + suffix) + + # it is a more complex type, so all members also need information displayed longest_member = longest_offset = longest_typename = 0 for member in volobject.vol.members: relative_offset, member_type = volobject.vol.members[member] longest_member = max(len(member), longest_member) longest_offset = max(len(hex(relative_offset)), longest_offset) - longest_typename = max(len(member_type.vol.type_name), longest_typename) + member_type_name = self._get_type_name_with_pointer( + member_type + ) # special case for pointers to show what they point to + longest_typename = max(len(member_type_name), longest_typename) for member in sorted( volobject.vol.members, key=lambda x: (volobject.vol.members[x][0], x) @@ -445,40 +496,113 @@ class Volshell(interfaces.plugins.PluginInterface): relative_offset, member_type = volobject.vol.members[member] len_offset = len(hex(relative_offset)) len_member = len(member) - len_typename = len(member_type.vol.type_name) + member_type_name = self._get_type_name_with_pointer( + member_type + ) # special case for pointers to show what they point to + len_typename = len(member_type_name) if isinstance(volobject, interfaces.objects.ObjectInterface): # We're an instance, so also display the data print( + " " * dereference_count, " " * (longest_offset - len_offset), hex(relative_offset), ": ", member, " " * (longest_member - len_member), " ", - member_type.vol.type_name, + member_type_name, " " * (longest_typename - len_typename), " ", self._display_value(getattr(volobject, member)), ) else: + # not provided with an actual object, nor an offset so just display the types print( + " " * dereference_count, " " * (longest_offset - len_offset), hex(relative_offset), ": ", member, " " * (longest_member - len_member), " ", - member_type.vol.type_name, + member_type_name, ) - @classmethod - def _display_value(cls, value: Any) -> str: - if isinstance(value, objects.PrimitiveObject): - return repr(value) - elif isinstance(value, objects.Array): - return repr([cls._display_value(val) for val in value]) + else: # simple type with no members, only one line to print + # if the orginal object was just a type string, display the type information + if isinstance(object, str) and offset is None: + print(self._display_simple_type(volobject, include_value=False)) + + # if the original object was an actual volobject or was a type string + # with an offset. Then append the actual data to the display. + else: + print(" " * dereference_count, self._display_simple_type(volobject)) + + def _display_simple_type( + self, + volobject: Union[ + interfaces.objects.ObjectInterface, interfaces.objects.Template + ], + include_value: bool = True, + ) -> str: + # build the display_type_string based on the aviable information + + if hasattr(volobject.vol, "size"): + # the most common type to display, this shows their full size, e.g.: + # (layer_name) >>> dt('task_struct') + # symbol_table_name1!task_struct (1784 bytes) + display_type_string = ( + f"{volobject.vol.type_name} ({volobject.vol.size} bytes)" + ) + elif hasattr(volobject.vol, "data_format"): + # this is useful for very simple types like ints, e.g.: + # (layer_name) >>> dt('int') + # symbol_table_name1!int (4 bytes, little endian, signed) + data_format = volobject.vol.data_format + display_type_string = "{} ({} bytes, {} endian, {})".format( + volobject.vol.type_name, + data_format.length, + data_format.byteorder, + "signed" if data_format.signed else "unsigned", + ) + elif hasattr(volobject.vol, "type_name"): + # types like void have almost no values to display other than their name, e.g.: + # (layer_name) >>> dt('void') + # symbol_table_name1!void + display_type_string = volobject.vol.type_name else: - return hex(value.vol.offset) + # it should not be possible to have a volobject without at least a type_name + raise AttributeError("Unable to find any details for object") + + if include_value: # if include_value is true also add the value to the display + if isinstance(volobject, objects.Pointer): + # for pointers include the location of the pointer and where it points to + return f"{display_type_string} @ {hex(volobject.vol.offset)} -> {self._display_value(volobject)}" + else: + return f"{display_type_string}: {self._display_value(volobject)}" + else: + return display_type_string + + def _display_value(self, value: Any) -> str: + try: + if isinstance(value, objects.Pointer): + # show pointers in hex to match output for struct addrs + # highlight null or unreadable pointers + if value == 0: + suffix = " (null pointer)" + elif not value.is_readable(): + suffix = " (unreadable pointer)" + else: + suffix = "" + return f"{hex(value)}{suffix}" + elif isinstance(value, objects.PrimitiveObject): + return repr(value) + elif isinstance(value, objects.Array): + return repr([self._display_value(val) for val in value]) + else: + return hex(value.vol.offset) + except exceptions.InvalidAddressException: + return "-" def generate_treegrid( self, plugin: Type[interfaces.plugins.PluginInterface], **kwargs From 82e1813310a5fe168c73ae1905084d3f5d2909a1 Mon Sep 17 00:00:00 2001 From: eve Date: Fri, 28 Mar 2025 11:52:44 +0000 Subject: [PATCH 02/16] Handle InvalidAddressException in volshell value display (thanks @atcuno!) Details: Implements exception handling for InvalidAddressException in volshell. Ensures invalid pointers don't cause large stack traces, displaying "N/A" instead. --- volatility3/cli/volshell/generic.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/volatility3/cli/volshell/generic.py b/volatility3/cli/volshell/generic.py index 116e9c449..ca1c7b73c 100644 --- a/volatility3/cli/volshell/generic.py +++ b/volatility3/cli/volshell/generic.py @@ -502,6 +502,10 @@ class Volshell(interfaces.plugins.PluginInterface): len_typename = len(member_type_name) if isinstance(volobject, interfaces.objects.ObjectInterface): # We're an instance, so also display the data + try: + value = self._display_value(getattr(volobject, member)) + except exceptions.InvalidAddressException: + value = self._display_value(renderers.NotAvailableValue()) print( " " * dereference_count, " " * (longest_offset - len_offset), @@ -513,7 +517,7 @@ class Volshell(interfaces.plugins.PluginInterface): member_type_name, " " * (longest_typename - len_typename), " ", - self._display_value(getattr(volobject, member)), + value, ) else: # not provided with an actual object, nor an offset so just display the types @@ -585,7 +589,9 @@ class Volshell(interfaces.plugins.PluginInterface): def _display_value(self, value: Any) -> str: try: - if isinstance(value, objects.Pointer): + if isinstance(value, interfaces.renderers.BaseAbsentValue): + return "N/A" + elif isinstance(value, objects.Pointer): # show pointers in hex to match output for struct addrs # highlight null or unreadable pointers if value == 0: From 2415e985104c698ba0e9994d292c26f74b120cd5 Mon Sep 17 00:00:00 2001 From: eve Date: Fri, 28 Mar 2025 12:35:32 +0000 Subject: [PATCH 03/16] Fix `display_type()` issue for `.write` attribute in volshell, or other fuctions Previously, `getattr(volobject, member)` in `display_type()` would incorrectly retrieve method references (e.g., `.write`) instead of the intended object addresses, causing an `AttributeError` when `_display_value()` attempted to access `.vol.offset`. This commit replaces `getattr(volobject, member)` with `volobject.member(member)`, ensuring that the correct object address is retrieved instead of method references. Fixes: #1705 --- volatility3/cli/volshell/generic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/cli/volshell/generic.py b/volatility3/cli/volshell/generic.py index ca1c7b73c..4ebda12a2 100644 --- a/volatility3/cli/volshell/generic.py +++ b/volatility3/cli/volshell/generic.py @@ -503,7 +503,7 @@ class Volshell(interfaces.plugins.PluginInterface): if isinstance(volobject, interfaces.objects.ObjectInterface): # We're an instance, so also display the data try: - value = self._display_value(getattr(volobject, member)) + value = self._display_value(volobject.member(member)) except exceptions.InvalidAddressException: value = self._display_value(renderers.NotAvailableValue()) print( From f060e3b8aea71ee8b32f607e5c8cad41d12dc331 Mon Sep 17 00:00:00 2001 From: eve Date: Fri, 28 Mar 2025 12:42:24 +0000 Subject: [PATCH 04/16] Fix type check for pointer detection in volshell Replaced `member_type.vol.object_class == objects.Pointer` with `isinstance(member_type, objects.Pointer)` to identify pointer types consistently. Thanks to @ikelos for the suggestion! --- volatility3/cli/volshell/generic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/cli/volshell/generic.py b/volatility3/cli/volshell/generic.py index 4ebda12a2..cc8027ddd 100644 --- a/volatility3/cli/volshell/generic.py +++ b/volatility3/cli/volshell/generic.py @@ -399,7 +399,7 @@ class Volshell(interfaces.plugins.PluginInterface): a pointer otherwise it returns just the normal type name.""" pointer_marker = "*" * depth try: - if member_type.vol.object_class == objects.Pointer: + if isinstance(member_type, objects.Pointer): sub_member_type = member_type.vol.subtype # follow at most MAX_DEREFERENCE_COUNT pointers. A guard against, hopefully unlikely, infinite loops if depth < MAX_DEREFERENCE_COUNT: From d648bd908dcb220a1c7d22aaaa380c060c077a06 Mon Sep 17 00:00:00 2001 From: eve Date: Fri, 28 Mar 2025 13:07:26 +0000 Subject: [PATCH 05/16] Revert change in _get_type_name_with_pointer as this stopped the pointer marker being calculated correctly --- volatility3/cli/volshell/generic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/cli/volshell/generic.py b/volatility3/cli/volshell/generic.py index cc8027ddd..4ebda12a2 100644 --- a/volatility3/cli/volshell/generic.py +++ b/volatility3/cli/volshell/generic.py @@ -399,7 +399,7 @@ class Volshell(interfaces.plugins.PluginInterface): a pointer otherwise it returns just the normal type name.""" pointer_marker = "*" * depth try: - if isinstance(member_type, objects.Pointer): + if member_type.vol.object_class == objects.Pointer: sub_member_type = member_type.vol.subtype # follow at most MAX_DEREFERENCE_COUNT pointers. A guard against, hopefully unlikely, infinite loops if depth < MAX_DEREFERENCE_COUNT: From 9d1a68256506d797cb3bd4e38986e484611f788f Mon Sep 17 00:00:00 2001 From: eve Date: Fri, 28 Mar 2025 13:36:32 +0000 Subject: [PATCH 06/16] remove unneeded else, this case is caught with finally --- volatility3/cli/volshell/generic.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/volatility3/cli/volshell/generic.py b/volatility3/cli/volshell/generic.py index 4ebda12a2..d6d2fdcc8 100644 --- a/volatility3/cli/volshell/generic.py +++ b/volatility3/cli/volshell/generic.py @@ -404,8 +404,6 @@ class Volshell(interfaces.plugins.PluginInterface): # follow at most MAX_DEREFERENCE_COUNT pointers. A guard against, hopefully unlikely, infinite loops if depth < MAX_DEREFERENCE_COUNT: return self._get_type_name_with_pointer(sub_member_type, depth + 1) - else: - return member_type_name except AttributeError: pass # not all objects get a `object_class`, and those that don't are not pointers. finally: From ec9e738334aa4703bfadd940f94b056f09d55558 Mon Sep 17 00:00:00 2001 From: eve Date: Fri, 28 Mar 2025 14:37:46 +0000 Subject: [PATCH 07/16] fix typo --- volatility3/cli/volshell/generic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/cli/volshell/generic.py b/volatility3/cli/volshell/generic.py index d6d2fdcc8..adb307c62 100644 --- a/volatility3/cli/volshell/generic.py +++ b/volatility3/cli/volshell/generic.py @@ -547,7 +547,7 @@ class Volshell(interfaces.plugins.PluginInterface): ], include_value: bool = True, ) -> str: - # build the display_type_string based on the aviable information + # build the display_type_string based on the available information if hasattr(volobject.vol, "size"): # the most common type to display, this shows their full size, e.g.: From fe6d57554bfda140d51a490da63f1764b04cd1db Mon Sep 17 00:00:00 2001 From: eve Date: Fri, 28 Mar 2025 14:51:31 +0000 Subject: [PATCH 08/16] update _display_value to handle None case --- volatility3/cli/volshell/generic.py | 49 +++++++++++++++++++---------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/volatility3/cli/volshell/generic.py b/volatility3/cli/volshell/generic.py index adb307c62..688a513e5 100644 --- a/volatility3/cli/volshell/generic.py +++ b/volatility3/cli/volshell/generic.py @@ -587,26 +587,43 @@ class Volshell(interfaces.plugins.PluginInterface): def _display_value(self, value: Any) -> str: try: + # if value is a BaseAbsentValue they display N/A if isinstance(value, interfaces.renderers.BaseAbsentValue): return "N/A" - elif isinstance(value, objects.Pointer): - # show pointers in hex to match output for struct addrs - # highlight null or unreadable pointers - if value == 0: - suffix = " (null pointer)" - elif not value.is_readable(): - suffix = " (unreadable pointer)" - else: - suffix = "" - return f"{hex(value)}{suffix}" - elif isinstance(value, objects.PrimitiveObject): - return repr(value) - elif isinstance(value, objects.Array): - return repr([self._display_value(val) for val in value]) else: - return hex(value.vol.offset) + # volobject branch + if isinstance( + value, + Union[ + interfaces.objects.ObjectInterface, interfaces.objects.Template + ], + ): + if isinstance(value, objects.Pointer): + # show pointers in hex to match output for struct addrs + # highlight null or unreadable pointers + if value == 0: + suffix = " (null pointer)" + elif not value.is_readable(): + suffix = " (unreadable pointer)" + else: + suffix = "" + return f"{hex(value)}{suffix}" + elif isinstance(value, objects.PrimitiveObject): + return repr(value) + elif isinstance(value, objects.Array): + return repr([self._display_value(val) for val in value]) + else: + return hex(value.vol.offset) + else: + # non volobject + if value is None: + return "N/A" + else: + return value + except exceptions.InvalidAddressException: - return "-" + # if value causes an InvalidAddressException like BaseAbsentValue then display N/A + return "N/A" def generate_treegrid( self, plugin: Type[interfaces.plugins.PluginInterface], **kwargs From 06815f9c91f33304e08ba8345b5b24d680792844 Mon Sep 17 00:00:00 2001 From: eve Date: Fri, 28 Mar 2025 15:03:53 +0000 Subject: [PATCH 09/16] volshell: add MAX_TYPENAME_DISPLAY_LENGTH to stop extremely large names breaking the output --- volatility3/cli/volshell/generic.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/volatility3/cli/volshell/generic.py b/volatility3/cli/volshell/generic.py index 688a513e5..9a03bd32a 100644 --- a/volatility3/cli/volshell/generic.py +++ b/volatility3/cli/volshell/generic.py @@ -418,6 +418,9 @@ class Volshell(interfaces.plugins.PluginInterface): offset: Optional[int] = None, ): """Display Type describes the members of a particular object in alphabetical order""" + + MAX_TYPENAME_DISPLAY_LENGTH = 256 + if not isinstance( object, (str, interfaces.objects.ObjectInterface, interfaces.objects.Template), @@ -487,6 +490,8 @@ class Volshell(interfaces.plugins.PluginInterface): member_type ) # special case for pointers to show what they point to longest_typename = max(len(member_type_name), longest_typename) + if longest_typename > MAX_TYPENAME_DISPLAY_LENGTH: + longest_typename = MAX_TYPENAME_DISPLAY_LENGTH for member in sorted( volobject.vol.members, key=lambda x: (volobject.vol.members[x][0], x) @@ -497,6 +502,10 @@ class Volshell(interfaces.plugins.PluginInterface): member_type_name = self._get_type_name_with_pointer( member_type ) # special case for pointers to show what they point to + if len(member_type_name) > MAX_TYPENAME_DISPLAY_LENGTH: + member_type_name = ( + f"{member_type_name[:MAX_TYPENAME_DISPLAY_LENGTH - 3]}..." + ) len_typename = len(member_type_name) if isinstance(volobject, interfaces.objects.ObjectInterface): # We're an instance, so also display the data From c43a1f40d333dcf104a9c1cdb3b95803f9d3d7d2 Mon Sep 17 00:00:00 2001 From: Eve <120014766+eve-mem@users.noreply.github.com> Date: Fri, 28 Mar 2025 15:50:36 +0000 Subject: [PATCH 10/16] Update volatility3/cli/volshell/generic.py Tidy up case where type_name is very long by @ikelos Co-authored-by: ikelos --- volatility3/cli/volshell/generic.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/volatility3/cli/volshell/generic.py b/volatility3/cli/volshell/generic.py index 9a03bd32a..22d82ff61 100644 --- a/volatility3/cli/volshell/generic.py +++ b/volatility3/cli/volshell/generic.py @@ -502,11 +502,10 @@ class Volshell(interfaces.plugins.PluginInterface): member_type_name = self._get_type_name_with_pointer( member_type ) # special case for pointers to show what they point to - if len(member_type_name) > MAX_TYPENAME_DISPLAY_LENGTH: - member_type_name = ( - f"{member_type_name[:MAX_TYPENAME_DISPLAY_LENGTH - 3]}..." - ) len_typename = len(member_type_name) + if len(member_type_name) > MAX_TYPENAME_DISPLAY_LENGTH: + len_typename = MAX_TYPENAME_DISPLAY_LENGTH + member_type_name = f"{member_type_name[:len_typename - 3]}..." if isinstance(volobject, interfaces.objects.ObjectInterface): # We're an instance, so also display the data try: From 6653b93b05476781003c5f2258c54ce4619b48eb Mon Sep 17 00:00:00 2001 From: eve Date: Fri, 28 Mar 2025 15:54:24 +0000 Subject: [PATCH 11/16] volshell: update longest_typename calculations to use min() that than if statement --- volatility3/cli/volshell/generic.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/volatility3/cli/volshell/generic.py b/volatility3/cli/volshell/generic.py index 22d82ff61..806c5ab07 100644 --- a/volatility3/cli/volshell/generic.py +++ b/volatility3/cli/volshell/generic.py @@ -489,9 +489,12 @@ class Volshell(interfaces.plugins.PluginInterface): member_type_name = self._get_type_name_with_pointer( member_type ) # special case for pointers to show what they point to + + # find the longest typename longest_typename = max(len(member_type_name), longest_typename) - if longest_typename > MAX_TYPENAME_DISPLAY_LENGTH: - longest_typename = MAX_TYPENAME_DISPLAY_LENGTH + + # if the typename is very long then limit it to MAX_TYPENAME_DISPLAY_LENGTH + longest_typename = min(longest_typename, MAX_TYPENAME_DISPLAY_LENGTH) for member in sorted( volobject.vol.members, key=lambda x: (volobject.vol.members[x][0], x) From fa45b47ed72e7cd37d6becb8d588a4dae58873bb Mon Sep 17 00:00:00 2001 From: eve Date: Fri, 28 Mar 2025 15:58:11 +0000 Subject: [PATCH 12/16] volshell: use repr for none vol objects --- volatility3/cli/volshell/generic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/cli/volshell/generic.py b/volatility3/cli/volshell/generic.py index 806c5ab07..11814f20a 100644 --- a/volatility3/cli/volshell/generic.py +++ b/volatility3/cli/volshell/generic.py @@ -630,7 +630,7 @@ class Volshell(interfaces.plugins.PluginInterface): if value is None: return "N/A" else: - return value + return repr(value) except exceptions.InvalidAddressException: # if value causes an InvalidAddressException like BaseAbsentValue then display N/A From bb2f28a39e096009dd249633cb508d94b8f39336 Mon Sep 17 00:00:00 2001 From: ikelos Date: Tue, 1 Apr 2025 22:29:48 +0100 Subject: [PATCH 13/16] Update volatility3/cli/volshell/generic.py --- volatility3/cli/volshell/generic.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/volatility3/cli/volshell/generic.py b/volatility3/cli/volshell/generic.py index 11814f20a..1c70ceb0a 100644 --- a/volatility3/cli/volshell/generic.py +++ b/volatility3/cli/volshell/generic.py @@ -605,9 +605,7 @@ class Volshell(interfaces.plugins.PluginInterface): # volobject branch if isinstance( value, - Union[ - interfaces.objects.ObjectInterface, interfaces.objects.Template - ], + (interfaces.objects.ObjectInterface, interfaces.objects.Template), ): if isinstance(value, objects.Pointer): # show pointers in hex to match output for struct addrs From b1c16456575d4d771f5b7db25a3085b450dd6cd6 Mon Sep 17 00:00:00 2001 From: eve Date: Thu, 3 Apr 2025 16:38:55 +0100 Subject: [PATCH 14/16] volshell: inform user that value displayed is only an offset for types like embedded structs --- volatility3/cli/volshell/generic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/cli/volshell/generic.py b/volatility3/cli/volshell/generic.py index 1c70ceb0a..e80d65957 100644 --- a/volatility3/cli/volshell/generic.py +++ b/volatility3/cli/volshell/generic.py @@ -622,7 +622,7 @@ class Volshell(interfaces.plugins.PluginInterface): elif isinstance(value, objects.Array): return repr([self._display_value(val) for val in value]) else: - return hex(value.vol.offset) + return f"offset: {hex(value.vol.offset)}" else: # non volobject if value is None: From 86945492a71c07d718bf5db8c95c87f31bf88f89 Mon Sep 17 00:00:00 2001 From: eve Date: Thu, 3 Apr 2025 16:49:27 +0100 Subject: [PATCH 15/16] Volshell: display if embedded struct offest is unreadable in dt output --- volatility3/cli/volshell/generic.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/volatility3/cli/volshell/generic.py b/volatility3/cli/volshell/generic.py index e80d65957..a524efe38 100644 --- a/volatility3/cli/volshell/generic.py +++ b/volatility3/cli/volshell/generic.py @@ -622,7 +622,12 @@ class Volshell(interfaces.plugins.PluginInterface): elif isinstance(value, objects.Array): return repr([self._display_value(val) for val in value]) else: - return f"offset: {hex(value.vol.offset)}" + if self.context.layers[self.current_layer].is_valid( + value.vol.offset + ): + return f"offset: {hex(value.vol.offset)}" + else: + return f"offset: {hex(value.vol.offset)} (unreadable)" else: # non volobject if value is None: From 69e3c7d9aefb0b1964f82b7afd76c8ed2bb2782b Mon Sep 17 00:00:00 2001 From: eve Date: Thu, 3 Apr 2025 17:01:21 +0100 Subject: [PATCH 16/16] Volshell: use built in formatting rather than hex() function --- volatility3/cli/volshell/generic.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/volatility3/cli/volshell/generic.py b/volatility3/cli/volshell/generic.py index 2021911ae..32a5bd933 100644 --- a/volatility3/cli/volshell/generic.py +++ b/volatility3/cli/volshell/generic.py @@ -636,9 +636,9 @@ class Volshell(interfaces.plugins.PluginInterface): if self.context.layers[self.current_layer].is_valid( value.vol.offset ): - return f"offset: {hex(value.vol.offset)}" + return f"offset: 0x{value.vol.offset:x}" else: - return f"offset: {hex(value.vol.offset)} (unreadable)" + return f"offset: 0x{value.vol.offset:x} (unreadable)" else: # non volobject if value is None: