|
32 | 32 | except ImportError: |
33 | 33 | has_ipython = False |
34 | 34 |
|
| 35 | +MAX_DEREFERENCE_COUNT = 4 # the max number of times display_type should follow pointers |
| 36 | + |
35 | 37 |
|
36 | 38 | class Volshell(interfaces.plugins.PluginInterface): |
37 | 39 | """Shell environment to directly interact with a memory image.""" |
@@ -386,6 +388,30 @@ def disassemble( |
386 | 388 | for i in disasm_types[architecture].disasm(remaining_data, offset): |
387 | 389 | print(f"0x{i.address:x}:\t{i.mnemonic}\t{i.op_str}") |
388 | 390 |
|
| 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 | + |
389 | 415 | def display_type( |
390 | 416 | self, |
391 | 417 | object: Union[ |
@@ -418,67 +444,165 @@ def display_type( |
418 | 444 | volobject.vol.type_name, layer_name=self.current_layer, offset=offset |
419 | 445 | ) |
420 | 446 |
|
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 |
433 | 471 |
|
434 | 472 | 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 |
435 | 483 | longest_member = longest_offset = longest_typename = 0 |
436 | 484 | for member in volobject.vol.members: |
437 | 485 | relative_offset, member_type = volobject.vol.members[member] |
438 | 486 | longest_member = max(len(member), longest_member) |
439 | 487 | 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) |
441 | 492 |
|
442 | 493 | for member in sorted( |
443 | 494 | volobject.vol.members, key=lambda x: (volobject.vol.members[x][0], x) |
444 | 495 | ): |
445 | 496 | relative_offset, member_type = volobject.vol.members[member] |
446 | 497 | len_offset = len(hex(relative_offset)) |
447 | 498 | 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) |
449 | 503 | if isinstance(volobject, interfaces.objects.ObjectInterface): |
450 | 504 | # We're an instance, so also display the data |
451 | 505 | print( |
| 506 | + " " * dereference_count, |
452 | 507 | " " * (longest_offset - len_offset), |
453 | 508 | hex(relative_offset), |
454 | 509 | ": ", |
455 | 510 | member, |
456 | 511 | " " * (longest_member - len_member), |
457 | 512 | " ", |
458 | | - member_type.vol.type_name, |
| 513 | + member_type_name, |
459 | 514 | " " * (longest_typename - len_typename), |
460 | 515 | " ", |
461 | 516 | self._display_value(getattr(volobject, member)), |
462 | 517 | ) |
463 | 518 | else: |
| 519 | + # not provided with an actual object, nor an offset so just display the types |
464 | 520 | print( |
| 521 | + " " * dereference_count, |
465 | 522 | " " * (longest_offset - len_offset), |
466 | 523 | hex(relative_offset), |
467 | 524 | ": ", |
468 | 525 | member, |
469 | 526 | " " * (longest_member - len_member), |
470 | 527 | " ", |
471 | | - member_type.vol.type_name, |
| 528 | + member_type_name, |
472 | 529 | ) |
473 | 530 |
|
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)}" |
480 | 583 | 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 "-" |
482 | 606 |
|
483 | 607 | def generate_treegrid( |
484 | 608 | self, plugin: Type[interfaces.plugins.PluginInterface], **kwargs |
|
0 commit comments