From 9c1b04f1d1857b07f16a84e26d9aa8a3dabda257 Mon Sep 17 00:00:00 2001 From: Eve Date: Thu, 2 Nov 2023 12:28:23 +0000 Subject: [PATCH 1/9] change dt() output in volshell to show where pointers lead when displaying types --- volatility3/cli/volshell/generic.py | 56 +++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/volatility3/cli/volshell/generic.py b/volatility3/cli/volshell/generic.py index ea9e65d9b5..c0e83374b0 100644 --- a/volatility3/cli/volshell/generic.py +++ b/volatility3/cli/volshell/generic.py @@ -312,6 +312,22 @@ def disassemble(self, offset, count=128, layer_name=None, architecture=None): 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) -> 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.""" + if isinstance(member_type, objects.templates.ReferenceTemplate): + member_type_name = member_type.vol.type_name + elif member_type.vol.object_class == objects.Pointer: + # to handle pointers to pointers without recursion + sub_member_type = member_type.vol.subtype + if sub_member_type.vol.object_class == objects.Pointer: + member_type_name = f"**{sub_member_type.vol.subtype.vol.type_name}" + else: + member_type_name = f"*{sub_member_type.vol.type_name}" + else: + member_type_name = member_type.vol.type_name + return member_type_name + def display_type( self, object: Union[ @@ -344,16 +360,38 @@ def display_type( volobject.vol.type_name, layer_name=self.current_layer, offset=offset ) + # 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. + dereference_count = 0 + while isinstance(volobject, objects.Pointer) and dereference_count < 2: + # check that we can follow the pointer before dereferencing + if volobject.is_readable(): + volobject = volobject.dereference() + dereference_count = dereference_count + 1 + else: + break + + if dereference_count == 0: + dereference_comment = "" + elif dereference_count == 1: + dereference_comment = "(deferenced once)" + elif dereference_count == 2: + dereference_comment = "(deferenced twice)" + if hasattr(volobject.vol, "size"): - print(f"{volobject.vol.type_name} ({volobject.vol.size} bytes)") + print( + f"{volobject.vol.type_name} ({volobject.vol.size} bytes) {dereference_comment}" + ) elif hasattr(volobject.vol, "data_format"): data_format = volobject.vol.data_format print( - "{} ({} bytes, {} endian, {})".format( + "{} ({} bytes, {} endian, {} {})".format( volobject.vol.type_name, data_format.length, data_format.byteorder, "signed" if data_format.signed else "unsigned", + dereference_comment, ) ) @@ -363,7 +401,10 @@ def display_type( 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) @@ -371,7 +412,10 @@ def display_type( 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( @@ -381,7 +425,7 @@ def display_type( member, " " * (longest_member - len_member), " ", - member_type.vol.type_name, + member_type_name, " " * (longest_typename - len_typename), " ", self._display_value(getattr(volobject, member)), @@ -394,7 +438,7 @@ def display_type( member, " " * (longest_member - len_member), " ", - member_type.vol.type_name, + member_type_name, ) @classmethod From 33dd0e263495ad36085ea5af19dda63a1b23247f Mon Sep 17 00:00:00 2001 From: Eve Date: Thu, 2 Nov 2023 13:08:16 +0000 Subject: [PATCH 2/9] update dt() to handle triple pointers (or more) --- volatility3/cli/volshell/generic.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/volatility3/cli/volshell/generic.py b/volatility3/cli/volshell/generic.py index c0e83374b0..9987d94a97 100644 --- a/volatility3/cli/volshell/generic.py +++ b/volatility3/cli/volshell/generic.py @@ -312,20 +312,17 @@ def disassemble(self, offset, count=128, layer_name=None, architecture=None): 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) -> str: + def _get_type_name_with_pointer(self, member_type, depth=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 if isinstance(member_type, objects.templates.ReferenceTemplate): - member_type_name = member_type.vol.type_name + member_type_name = pointer_marker + member_type.vol.type_name elif member_type.vol.object_class == objects.Pointer: - # to handle pointers to pointers without recursion sub_member_type = member_type.vol.subtype - if sub_member_type.vol.object_class == objects.Pointer: - member_type_name = f"**{sub_member_type.vol.subtype.vol.type_name}" - else: - member_type_name = f"*{sub_member_type.vol.type_name}" + return self._get_type_name_with_pointer(sub_member_type, depth + 1) else: - member_type_name = member_type.vol.type_name + member_type_name = pointer_marker + member_type.vol.type_name return member_type_name def display_type( @@ -364,7 +361,7 @@ def display_type( # pointer is pointing to is shown rather than simply the fact this is a # pointer object. dereference_count = 0 - while isinstance(volobject, objects.Pointer) and dereference_count < 2: + while isinstance(volobject, objects.Pointer): # check that we can follow the pointer before dereferencing if volobject.is_readable(): volobject = volobject.dereference() @@ -376,8 +373,8 @@ def display_type( dereference_comment = "" elif dereference_count == 1: dereference_comment = "(deferenced once)" - elif dereference_count == 2: - dereference_comment = "(deferenced twice)" + else: + dereference_comment = f"(deferenced {dereference_count} times)" if hasattr(volobject.vol, "size"): print( From d5e0dec8184636e3a3bd91573a1b1402e9f184b8 Mon Sep 17 00:00:00 2001 From: Eve Date: Thu, 2 Nov 2023 13:24:39 +0000 Subject: [PATCH 3/9] update dt() typo in for the dereference comment. --- 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 9987d94a97..bee0af409d 100644 --- a/volatility3/cli/volshell/generic.py +++ b/volatility3/cli/volshell/generic.py @@ -372,9 +372,9 @@ def display_type( if dereference_count == 0: dereference_comment = "" elif dereference_count == 1: - dereference_comment = "(deferenced once)" + dereference_comment = "(dereferenced once)" else: - dereference_comment = f"(deferenced {dereference_count} times)" + dereference_comment = f"(dereferenced {dereference_count} times)" if hasattr(volobject.vol, "size"): print( From 43c02b0fcb5467c41d85d22a49ec4958a6f0fa27 Mon Sep 17 00:00:00 2001 From: Eve Date: Thu, 2 Nov 2023 13:47:40 +0000 Subject: [PATCH 4/9] update dt() to handle errors in _get_type_name_with_pointer differently --- volatility3/cli/volshell/generic.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/volatility3/cli/volshell/generic.py b/volatility3/cli/volshell/generic.py index bee0af409d..02a1d46872 100644 --- a/volatility3/cli/volshell/generic.py +++ b/volatility3/cli/volshell/generic.py @@ -316,12 +316,13 @@ def _get_type_name_with_pointer(self, member_type, depth=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 - if isinstance(member_type, objects.templates.ReferenceTemplate): - member_type_name = pointer_marker + member_type.vol.type_name - elif member_type.vol.object_class == objects.Pointer: - sub_member_type = member_type.vol.subtype - return self._get_type_name_with_pointer(sub_member_type, depth + 1) - else: + try: + if member_type.vol.object_class == objects.Pointer: + sub_member_type = member_type.vol.subtype + return self._get_type_name_with_pointer(sub_member_type, depth + 1) + 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 From f360a665cd284981ac2fdf7d172cabc55ff7fe38 Mon Sep 17 00:00:00 2001 From: Eve Date: Tue, 7 Nov 2023 15:59:19 +0000 Subject: [PATCH 5/9] update dt() to indent when following pointers and also show pointer values --- volatility3/cli/volshell/generic.py | 106 +++++++++++++++++++--------- 1 file changed, 72 insertions(+), 34 deletions(-) diff --git a/volatility3/cli/volshell/generic.py b/volatility3/cli/volshell/generic.py index 02a1d46872..47d4addbd7 100644 --- a/volatility3/cli/volshell/generic.py +++ b/volatility3/cli/volshell/generic.py @@ -319,7 +319,11 @@ def _get_type_name_with_pointer(self, member_type, depth=0) -> str: try: if member_type.vol.object_class == objects.Pointer: sub_member_type = member_type.vol.subtype - return self._get_type_name_with_pointer(sub_member_type, depth + 1) + # follow at most 8 pointers. A guard against, hopefully unlikely, infinite loops + if depth < 8: + 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: @@ -360,40 +364,35 @@ def display_type( # 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. + # pointer object. The "dereference_count < 8" is to guard against loops dereference_count = 0 - while isinstance(volobject, objects.Pointer): - # check that we can follow the pointer before dereferencing - if volobject.is_readable(): + while isinstance(volobject, objects.Pointer) and dereference_count < 8: + # before defreerencing the pointer, show it's information + print(" " * 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: - break + # 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 + return - if dereference_count == 0: - dereference_comment = "" - elif dereference_count == 1: - dereference_comment = "(dereferenced once)" - else: - dereference_comment = f"(dereferenced {dereference_count} times)" + if hasattr(volobject.vol, "members"): + # display the header for this object, if the orginal object was just a type string, display the type information + if isinstance(object, str): + print(self._display_simple_type(volobject, include_value=False)) - if hasattr(volobject.vol, "size"): - print( - f"{volobject.vol.type_name} ({volobject.vol.size} bytes) {dereference_comment}" - ) - 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", - dereference_comment, - ) - ) + # 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)) - if hasattr(volobject.vol, "members"): + # 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] @@ -417,6 +416,7 @@ def display_type( 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), ": ", @@ -430,6 +430,7 @@ def display_type( ) else: print( + " " * dereference_count, " " * (longest_offset - len_offset), hex(relative_offset), ": ", @@ -439,14 +440,51 @@ def display_type( member_type_name, ) + 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): + 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: Any, include_value: bool = True) -> str: + # build the display_type_string based on the aviable information + if hasattr(volobject.vol, "size"): + display_type_string = ( + f"{volobject.vol.type_name} ({volobject.vol.size} bytes)" + ) + elif hasattr(volobject.vol, "data_format"): + 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", + ) + + if include_value == True: + # if include_value is true also add the value to the display + return f"{display_type_string}: {self._display_value(volobject)}" + else: + return display_type_string + @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: - return hex(value.vol.offset) + try: + if isinstance(value, objects.Pointer): + # show pointers in hex to match output for struct addrs + return hex(value) + elif isinstance(value, objects.PrimitiveObject): + return repr(value) + elif isinstance(value, objects.Array): + return repr([cls._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 f1a1d5eeb086ac3a1554155b11011e0b54edd8dc Mon Sep 17 00:00:00 2001 From: Eve Date: Wed, 21 Feb 2024 14:09:56 +0000 Subject: [PATCH 6/9] Volshell: update how pointers are diaplyed in the dt command, showing both where the pointer is and where it is pointing to. Add extra output for null or unreadable pointers. --- volatility3/cli/volshell/generic.py | 34 ++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/volatility3/cli/volshell/generic.py b/volatility3/cli/volshell/generic.py index 3d9185bd79..e702060f5b 100644 --- a/volatility3/cli/volshell/generic.py +++ b/volatility3/cli/volshell/generic.py @@ -23,6 +23,8 @@ except ImportError: has_capstone = 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.""" @@ -319,8 +321,8 @@ def _get_type_name_with_pointer(self, member_type, depth=0) -> str: try: if member_type.vol.object_class == objects.Pointer: sub_member_type = member_type.vol.subtype - # follow at most 8 pointers. A guard against, hopefully unlikely, infinite loops - if depth < 8: + # 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 @@ -364,11 +366,15 @@ def display_type( # 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 < 8" is to guard against loops + # pointer object. The "dereference_count < MAX_DEREFERENCE_COUNT" is to + # guard against loops dereference_count = 0 - while isinstance(volobject, objects.Pointer) and dereference_count < 8: + while ( + isinstance(volobject, objects.Pointer) + and dereference_count < MAX_DEREFERENCE_COUNT + ): # before defreerencing the pointer, show it's information - print(" " * dereference_count, self._display_simple_type(volobject)) + 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. @@ -390,7 +396,9 @@ def display_type( # 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)) + print( + f'{" " * dereference_count}{self._display_simple_type(volobject)}' + ) # it is a more complex type, so all members also need information displayed longest_member = longest_offset = longest_typename = 0 @@ -467,7 +475,10 @@ def _display_simple_type(self, volobject: Any, include_value: bool = True) -> st if include_value == True: # if include_value is true also add the value to the display - return f"{display_type_string}: {self._display_value(volobject)}" + if isinstance(volobject, objects.Pointer): + 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 @@ -476,7 +487,14 @@ def _display_value(cls, value: Any) -> str: try: if isinstance(value, objects.Pointer): # show pointers in hex to match output for struct addrs - return hex(value) + # 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): From 87522fc179b3010295915b34887acb2b82904250 Mon Sep 17 00:00:00 2001 From: Eve Date: Wed, 21 Feb 2024 21:48:13 +0000 Subject: [PATCH 7/9] Volshell: update how structs and simple types are printed when using the dt command --- volatility3/cli/volshell/generic.py | 59 +++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 16 deletions(-) diff --git a/volatility3/cli/volshell/generic.py b/volatility3/cli/volshell/generic.py index e702060f5b..047d04230f 100644 --- a/volatility3/cli/volshell/generic.py +++ b/volatility3/cli/volshell/generic.py @@ -314,7 +314,13 @@ def disassemble(self, offset, count=128, layer_name=None, architecture=None): 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, depth=0) -> 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 @@ -385,20 +391,19 @@ def display_type( 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 + # 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 - if isinstance(object, str): - 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. + struct_header = f'{" " * dereference_count}{volobject.vol.type_name} ({volobject.vol.size} bytes)' + if isinstance(object, str) and offset is None: + suffix = ":" else: - print( - f'{" " * dereference_count}{self._display_simple_type(volobject)}' - ) + # 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 @@ -421,6 +426,7 @@ def display_type( 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( @@ -437,6 +443,7 @@ def display_type( 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), @@ -450,7 +457,7 @@ def display_type( 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): + 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 @@ -458,13 +465,26 @@ def display_type( else: print(" " * dereference_count, self._display_simple_type(volobject)) - def _display_simple_type(self, volobject: Any, include_value: bool = True) -> str: + 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, @@ -472,17 +492,24 @@ def _display_simple_type(self, volobject: Any, include_value: bool = True) -> st 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: + # 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 == True: - # if include_value is true also add the value to the display + 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)}" + return f"{display_type_string}: {self._display_value(volobject)}" else: return display_type_string - @classmethod def _display_value(cls, value: Any) -> str: try: if isinstance(value, objects.Pointer): From 8d24d95124576f7f4c478c667cfaa2925c3a4b2a Mon Sep 17 00:00:00 2001 From: Eve Date: Wed, 21 Feb 2024 21:59:18 +0000 Subject: [PATCH 8/9] Volshell: fix Code scanning issue - First parameter of a method is not named 'self' --- 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 047d04230f..055a44d978 100644 --- a/volatility3/cli/volshell/generic.py +++ b/volatility3/cli/volshell/generic.py @@ -510,7 +510,7 @@ def _display_simple_type( else: return display_type_string - def _display_value(cls, value: Any) -> str: + def _display_value(self, value: Any) -> str: try: if isinstance(value, objects.Pointer): # show pointers in hex to match output for struct addrs From c947323a16c1d77bbbe2d21faf21c20aa72b59b6 Mon Sep 17 00:00:00 2001 From: Eve Date: Wed, 21 Feb 2024 22:26:05 +0000 Subject: [PATCH 9/9] Volshell: fix self variable name in _display_value --- 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 055a44d978..ae3fba0fc7 100644 --- a/volatility3/cli/volshell/generic.py +++ b/volatility3/cli/volshell/generic.py @@ -525,7 +525,7 @@ def _display_value(self, value: Any) -> str: elif isinstance(value, objects.PrimitiveObject): return repr(value) elif isinstance(value, objects.Array): - return repr([cls._display_value(val) for val in value]) + return repr([self._display_value(val) for val in value]) else: return hex(value.vol.offset) except exceptions.InvalidAddressException: