diff options
author | Malfurious <m@lfurio.us> | 2024-01-24 22:07:35 -0500 |
---|---|---|
committer | Malfurious <m@lfurio.us> | 2024-05-19 17:52:10 -0400 |
commit | 78d45802803338c417477f4e8115024fdb68b4fe (patch) | |
tree | 873118e6eb3973ee995c667d4e11ca13f5806f3c | |
parent | debc0a42457619309039579cd1615588d5d221e0 (diff) | |
download | sploit-78d45802803338c417477f4e8115024fdb68b4fe.tar.gz sploit-78d45802803338c417477f4e8115024fdb68b4fe.zip |
symtbl: Refactor abstract IndexTbl interface
There are some useful concepts expressed in the Symtbl class that can
provide good value if applied elsewhere as well. In this particular
case, I want to address the somewhat awkward relationship between Symtbl
and the Payload class by providing an abstract base for both of them. I
will go into more details in an upcoming commit for Payload.
This patch shouldn't change any behavior for Symtbl barring perhaps its
new preference of the new IndexEntry type described below. Some
characteristics of Symtbl are refactored into two new interface types:
IndexEntry provides "base" and implements logic supporting the use of
instance objects as integers. The intent is to extend from this class
when creating special types to be used in IndexTbls, Symtbls, etc.
IndexTbl (extends IndexEntry) provides a unified system for attribute /
element access, and acts as an abstract container where storage and
lookup semantics are up to the specific implementation.
Symtbl (extends IndexTbl) is now better described as an Index table,
where indices represent numeric addresses. The nominal data type is
int, however IndexEntries (which are int-like) may be nested to record
the addresses of ROP gadgets, sub-symtbls, and perhaps more in the
future.
Signed-off-by: Malfurious <m@lfurio.us>
-rw-r--r-- | sploit/symtbl.py | 112 | ||||
-rw-r--r-- | sploit/types/__init__.py | 2 | ||||
-rw-r--r-- | sploit/types/index_entry.py | 44 | ||||
-rw-r--r-- | sploit/types/indextbl.py | 103 |
4 files changed, 193 insertions, 68 deletions
diff --git a/sploit/symtbl.py b/sploit/symtbl.py index a471958..86800f5 100644 --- a/sploit/symtbl.py +++ b/sploit/symtbl.py @@ -73,112 +73,88 @@ with the Symtbl base address. print(s.a, s.b, s.c, s.d) # "998 999 1000 1001" """ +from sploit.types.indextbl import IndexTbl +from sploit.types.index_entry import IndexEntry + def Symtbl(*, base=0, **symbols): """ Create a new Symtbl object. Return an empty Symtbl or, optionally, one initialized with the given - symbol values. Arguments _must_ be keyword arguments. + symbol values. Arguments must be keyword arguments. Users should call this function instead of attempting to construct the Symtbl class. Construction is implemented via a normal function to prevent any argument name from conflicting with __init__'s bound instance parameter. """ - self = SymtblImpl({}, 0, base) + self = SymtblImpl(base, 0, dict()) for k, v in symbols.items(): self[k] = v return self -class SymtblImpl: +class SymtblImpl(IndexTbl): """Symtbl implementation class""" - def __init__(self, entries, adjust, base): + def __init__(self, base, adjust, entries): """Construct Symtbl from instance data.""" + super().__init__(base) + object.__setattr__(self, "__adjust__", adjust) object.__setattr__(self, "__entries__", entries) - object.__setattr__(self, "__adjust__", adjust) - object.__setattr__(self, "base", base) - def __index__(self): - """Convert object to integer using base value.""" - return self.base + def __repr__(self): + """Return human-readable Symtbl.""" + FMT = "\n{:<20} {:<20}" + s = f"{len(self)} symbols @ {hex(self)}" - def __matmul__(self, base): - """Create remapped version of object at absolute base.""" - return SymtblImpl(self.__entries__, self.__adjust__, int(base)) + if len(self) > 0: + s += FMT.format("ADDRESS", "SYMBOL") - def __add__(self, offset): - """Create remapped version of object at relative base.""" - return self @ (self.base + offset) + for key, value in self: + key = f"[{key}]" if isinstance(value, IndexEntry) else key + s += FMT.format(hex(value), key) - def __sub__(self, offset): - """Create remapped version of object at relative base.""" - return self @ (self.base - offset) + return s def __rshift__(self, offset): - """Create symbol adjusted version of object.""" - return SymtblImpl(self.__entries__, self.__adjust__ + int(offset), self.base) + """Return symbol-adjusted version of object.""" + adjust = self.__adjust__ + int(offset) + return SymtblImpl(self.base, adjust, self.__entries__) def __lshift__(self, offset): - """Create symbol adjusted version of object.""" + """Return symbol-adjusted version of object.""" return self >> (-offset) def __mod__(self, offset): - """Create symbol rebased version of object.""" + """Return symbol-rebased version of object.""" return self >> (self.base - offset) - def __getattr__(self, symbol): - """Return symbol offset or subtable via pseudo-attribute.""" - return self[symbol] + # IndexTbl abstract methods - def __setattr__(self, symbol, value): - """Set symbol offset or subtable via pseudo-attribute.""" - self[symbol] = value + def __copy__(self): + """Return copy of object with shared symbol entries.""" + return SymtblImpl(self.base, self.__adjust__, self.__entries__) - def __delattr__(self, symbol): - """Unset symbol via pseudo-attribute.""" - del self[symbol] + def __iter__(self): + """Iterate over table items, sorted by offsets.""" + it = { k: self[k] for k in self.__entries__}.items() + return iter(sorted(it, key=lambda x: int(x[1]))) def __len__(self): """Return number of defined symbols.""" return len(self.__entries__) - def __getitem__(self, symbol): - """Return symbol offset, subtable, or translated offset via subscript.""" - if symbol == "base": - return self.base - offset = self.__entries__[symbol] if type(symbol) is str else symbol + def __getindex__(self, index): + """Return symbol value or translated offset.""" + if isinstance(index, (int, IndexEntry)): offset = index + else: offset = self.__entries__[index] return offset + (self.base + self.__adjust__) - def __setitem__(self, symbol, value): - """Set symbol offset or subtable via subscript.""" - if symbol == "base": - object.__setattr__(self, "base", int(value)) - elif symbol in dir(self): - raise KeyError(f"Symtbl: key is reserved: {symbol}") - elif type(symbol) is not str: - raise TypeError(f"Symtbl: key must be a string: {symbol}") - else: - self.__entries__[symbol] = value - (self.base + self.__adjust__) - - def __delitem__(self, symbol): - """Unset symbol via subscript.""" - del self.__entries__[symbol] + def __setindex__(self, index, value): + """Set symbol value.""" + if isinstance(index, (int, IndexEntry)): + raise TypeError(f"Symtbl: Unsupported key type: {type(index)}") + self.__entries__[index] = value - (self.base + self.__adjust__) - def __iter__(self): - """Iterate over table entries as key:value tuples, like dict.items().""" - return iter(sorted({ k: self[k] for k in self.__entries__ }.items(), key=lambda v: int(v[1]))) - - def __contains__(self, symbol): - """Test symbol name membership in table.""" - return symbol in self.__entries__ - - def __repr__(self): - """Return human-readable Symtbl.""" - FMT = "\n{:<20} {:<20}" - s = f"{len(self)} symbols @ {hex(self)}" - if len(self) > 0: - s += FMT.format("ADDRESS", "SYMBOL") - for symbol, offset in self: - disp = f"[{symbol}]" if type(offset) is not int else symbol - s += FMT.format(hex(offset), disp) - return s + def __delindex__(self, index): + """Delete symbol.""" + del self.__entries__[index] diff --git a/sploit/types/__init__.py b/sploit/types/__init__.py new file mode 100644 index 0000000..1316dad --- /dev/null +++ b/sploit/types/__init__.py @@ -0,0 +1,2 @@ +from .indextbl import * +from .index_entry import * diff --git a/sploit/types/index_entry.py b/sploit/types/index_entry.py new file mode 100644 index 0000000..a03ab92 --- /dev/null +++ b/sploit/types/index_entry.py @@ -0,0 +1,44 @@ +import copy + +class IndexEntry: + """ + Generic IndexTbl entry object + + IndexEntry is intended to be subclassed to create interesting types that are + compatible with IndexTbl directories. IndexEntry gives objects a baseline + int-like personality. + + IndexEntry objects are convertable to int via int(), bin(), hex(), etc. This + integer value is manipulated via the object's "base" property, and a few + operators are implemented to provide nicer syntax for this as well. The use + of operators generally yield distinct copies of the original object. + + The property name "base" is used since it has semantic meaning for the + IndexTbl class, which is itself an extension of this class. + + base (int): Index integer value + """ + + base = 0 + + def __init__(self, base=0): + """Construct index with the given base value.""" + self.base = base + + def __index__(self): + """Convert index to base integer value.""" + return int(self.base) + + def __matmul__(self, base): + """Create new object with the given base value.""" + new = copy.copy(self) + new.base = base + return new + + def __add__(self, add): + """Create new object with the given relative base value.""" + return self @ (self.base + add) + + def __sub__(self, sub): + """Create new object with the given relative base value.""" + return self @ (self.base - sub) diff --git a/sploit/types/indextbl.py b/sploit/types/indextbl.py new file mode 100644 index 0000000..4f57a59 --- /dev/null +++ b/sploit/types/indextbl.py @@ -0,0 +1,103 @@ +from abc import abstractmethod +from collections.abc import Collection + +from sploit.types.index_entry import IndexEntry + +class IndexTbl(IndexEntry, Collection): + """ + Abstract Index Table + + IndexTbl is a common interface to an abstracted key-value store. The + storage mechanism as well as lookup semantics are defined by concrete + implementations. "Index" in this case is more akin to a directory index or + book index than a strictly numeric array index. + + In general, concrete tables may store values of any or multiple different + types. In particular, tables should give special accommodation for values + of type IndexEntry. These objects usually represent "rich" versions of the + nominal data types the table expects to contain. Implementation repr() + methods usually also annotate which members are IndexEntries. + + IndexTbl extends from IndexEntry, and so has a base value which represents + the "base index" of the table. The meaning of this depends on the + implementation. This inheritance also means that tables are generally + expected to be nestable. + + IndexTbls allow indices to be accessed via attribute or subscript notation. + This is probably the key characteristic feature of the class. The class + namespace is kept as clean as possible to make for the fewest collisions + between index names and other (real) class attributes. Note that there are + abstract methods, required to be overridden, which implement the index + access. These methods are only called for normal indices, not the table + base. + + Because this class overrides attribute access, normal automatic object + copying is broken. Because of this, implementations must also provide a + definition for the __copy__() method as well. + """ + + @abstractmethod + def __getindex__(self, index): + """Lookup and retrieve index value.""" + raise NotImplementedError + + @abstractmethod + def __setindex__(self, index, value): + """Lookup and set index value.""" + raise NotImplementedError + + @abstractmethod + def __delindex__(self, index): + """Lookup and delete index value.""" + raise NotImplementedError + + @abstractmethod + def __copy__(self): + """Create a copy of this IndexTbl object.""" + raise NotImplementedError + + def __contains__(self, index): + """Test the existence of the given index.""" + try: + self.__getindex__(index) + except KeyError: + return False + else: + return True + + # Attribute access methods + + def __getattr__(self, index): + """Get value via attribute.""" + return self[index] + + def __setattr__(self, index, value): + """Set value via attribute.""" + self[index] = value + + def __delattr__(self, index): + """Delete value via attribute.""" + del self[index] + + # Subscript/item access methods + + def __getitem__(self, index): + """Get value via subscript.""" + if index == "base": + return self.base + return self.__getindex__(index) + + def __setitem__(self, index, value): + """Set value via subscript.""" + if index == "base": + object.__setattr__(self, "base", value) + elif index in dir(self): + raise KeyError(f"IndexTbl: Index is reserved: {index}") + else: + self.__setindex__(index, value) + + def __delitem__(self, index): + """Delete value via subscript.""" + if index == "base": + raise KeyError("IndexTbl: May not delete index: base") + self.__delindex__(index) |