-
Notifications
You must be signed in to change notification settings - Fork 463
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
volshell: change dt() output to show where pointers lead #1028
base: develop
Are you sure you want to change the base?
Changes from all commits
9c1b04f
33dd0e2
d5e0dec
43c02b0
f360a66
2155b7d
f1a1d5e
87522fc
8d24d95
c947323
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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.""" | ||
|
@@ -312,6 +314,30 @@ 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: 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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. They should all get a |
||
finally: | ||
member_type_name = pointer_marker + member_type.vol.type_name | ||
return member_type_name | ||
|
||
def display_type( | ||
self, | ||
object: Union[ | ||
|
@@ -344,67 +370,166 @@ def display_type( | |
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 | ||
ikelos marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# 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"): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure this is a good way of telling a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great question - I'll dig into it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've been exploring this issue and found that when dealing with actual objects or types with offsets, checking if it's an instance of However, I ran into difficulties when working with templates, such as when executing (layer_name) >>> type(self.context.symbol_space.get_type('symbol_table_name1!task_struct'))
<class 'volatility3.framework.objects.templates.ObjectTemplate'>
(layer_name) >>> type(self.context.symbol_space.get_type('symbol_table_name1!int'))
<class 'volatility3.framework.objects.templates.ObjectTemplate'> I found (layer_name) >>> self.context.symbol_space.get_type('symbol_table_name1!task_struct').vol.object_class
<class 'volatility3.framework.symbols.linux.extensions.task_struct'>
(layer_name) >>> self.context.symbol_space.get_type('symbol_table_name1!int').vol.object_class
<class 'volatility3.framework.objects.Integer'> However, (layer_name) >>> type(self.context.symbol_space.get_type('symbol_table_name1!task_struct').vol.object_class)
<class 'abc.ABCMeta'>
(layer_name) >>> type(self.context.symbol_space.get_type('symbol_table_name1!int').vol.object_class)
<class 'abc.ABCMeta'> I've looked for solutions and found some discussions like this one on StackOverflow, but I'm still uncertain about the best approach. It might be possible to adjust the templates to facilitate (layer_name) >>> hasattr(self.context.symbol_space.get_type('symbol_table_name1!task_struct'), "members")
True
(layer_name) >>> hasattr(self.context.symbol_space.get_type('symbol_table_name1!int'), "members")
False I'm open to suggestions or guidance on how to approach this more effectively. 🙃 Let me know if you have any good ideas! |
||
# 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) | ||
): | ||
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( | ||
ikelos marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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: | ||
# 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 hex(value.vol.offset) | ||
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 | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
might be better to use isinstance than equality? It's interesting this is a different test than the
isinstance
below used fordisplay_type
They should probably match to avoid weird discrepancies! 5:P