Skip to content

Commit

Permalink
Add class factory support for pcustom (#819)
Browse files Browse the repository at this point in the history
* [pcustom] add class factory support
instead of creating a static class, class factories allow to generate a `ctypes.Structure` class with information from the runtime, which can drastically simplify (and unify) classes declaration (from on libc version, architecture, ptrsize, etc.)

* [pcustom] added doc for class factory

* linting

* [pcustom] completed the documentation for class factory

* Better filter of external attribute in the `Structure.__init__` to catch both classes and class factory methods

* Apply suggestions from code review

Co-authored-by: Grazfather <grazfather@gmail.com>
  • Loading branch information
hugsy and Grazfather committed Feb 13, 2022
1 parent 82b2570 commit 37bb542
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 7 deletions.
59 changes: 57 additions & 2 deletions docs/commands/pcustom.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@ gef➤ pcustom edit elf32_t
[+] Editing '/home/hugsy/code/gef-extras/structs/elf32_t.py'
```


#### Static `ctypes.Structure`-like classes

The code can be defined just as any Python (using `ctypes`) code.

```
```python
from ctypes import *

'''
Expand Down Expand Up @@ -120,6 +120,61 @@ Which will become:
![ida-structure-imported](https://i.imgur.com/KVhyopO.png)


#### Dynamic `ctypes.Structure`-like classes

`pcustom` also supports the use of class factories to create a `ctypes.Structure` class whose structure will be adjusted based on the runtime information we provide (information about the currently debugged binary, the architecture, the size of a pointer and more).

The syntax is relatively close to the way we use to create static classes (see above), but instead we define a function that will generate the class. The requirements for this class factory are:
- take a single [`Gef`](https://github.com/hugsy/gef/blob/dev/docs/api/gef.md#class-gef) positional argument
- End the function name with `_t`

To continue the `person_t` function we defined in the example above, we could modify the static class as a dynamic one very easily:

```python
import ctypes
from typing import Optional

def person_t(gef: Optional["Gef"]=None):
fields = [
("age", ctypes.c_int),
("name", ctypes.c_char * 256),
("id", ctypes.c_int),
]

class person_cls(ctypes.Structure):
_fields_ = fields

return person_cls
```

Thanks to the `gef` parameter, the structure can be transparently adjusted so that GEF will parse it differently with its runtime information. For example, we can add constraints to the example above:

```python
import ctypes
from typing import Optional

def person_t(gef: Optional["Gef"]==None):
fields = [
("age", ctypes.c_uint8),
("name", ctypes.c_char * 256),
("id", ctypes.c_uint8),
]

# constraint on the libc version
if gef.libc.version > (2, 27):
# or on the pointer size
pointer_type = ctypes.c_uint64 if gef.arch.ptrsize == 8 else ctypes.c_uint32
fields += [
("new_field", pointer_size)
]

class person_cls(ctypes.Structure):
_fields_ = fields

return person_cls
```


### Public repository of structures

A community contributed repository of structures can be found in [`gef-extras`](https://github.com/hugsy/gef-extras). To deploy it:
Expand Down
37 changes: 32 additions & 5 deletions gef.py
Original file line number Diff line number Diff line change
Expand Up @@ -5316,6 +5316,9 @@ def __init__(self, manager: "ExternalStructureManager", mod_path: pathlib.Path,
self.module_path = mod_path
self.name = struct_name
self.class_type = self.__get_structure_class()
# if the symbol points to a class factory method and not a class
if not hasattr(self.class_type, "_fields_") and callable(self.class_type):
self.class_type = self.class_type(gef)
return

def __str__(self) -> str:
Expand Down Expand Up @@ -5438,11 +5441,19 @@ def __str__(self) -> str:

def __iter__(self) -> Generator[str, None, None]:
_invalid = {"BigEndianStructure", "LittleEndianStructure", "Structure"}
_structs = {x for x in dir(self.raw) \
if inspect.isclass(getattr(self.raw, x)) \
and issubclass(getattr(self.raw, x), ctypes.Structure)}
for entry in (_structs - _invalid):
yield entry
for x in dir(self.raw):
if x in _invalid: continue
_attr = getattr(self.raw, x)

# if it's a ctypes.Structure class, add it
if inspect.isclass(_attr) and issubclass(_attr, ctypes.Structure):
yield x
continue

# also accept class factory functions
if callable(_attr) and _attr.__module__ == self.name and x.endswith("_t"):
yield x
continue
return

class Modules(dict):
Expand Down Expand Up @@ -11301,6 +11312,21 @@ def __init__(self) -> None:
return


class GefLibcManager(GefManager):
"""Class managing everything libc-related (except heap)."""
def __init__(self) -> None:
self._version : Optional[Tuple[int, int]] = None
return

@property
def version(self) -> Optional[Tuple[int, int]]:
if not is_alive():
return None
if not self._version:
self._version = get_libc_version()
return self._version


class Gef:
"""The GEF root class, which serves as a entrypoint for all the debugging session attributes (architecture,
memory, settings, etc.)."""
Expand All @@ -11309,6 +11335,7 @@ def __init__(self) -> None:
self.arch: Architecture = GenericArchitecture() # see PR #516, will be reset by `new_objfile_handler`
self.config = GefSettingsManager()
self.ui = GefUiManager()
self.libc = GefLibcManager()
return

def reinitialize_managers(self) -> None:
Expand Down

0 comments on commit 37bb542

Please sign in to comment.