summaryrefslogtreecommitdiffstats
path: root/sploit/payload/payload_entry.py
diff options
context:
space:
mode:
authorMalfurious <m@lfurio.us>2024-01-27 00:47:49 -0500
committerMalfurious <m@lfurio.us>2025-01-01 07:08:49 -0500
commit3768139ffba6a3faaffe5229b1e7a8e348004399 (patch)
tree4c0ec775b94494eaf39a872396ecd856e7f216ef /sploit/payload/payload_entry.py
parent8338e1baec2f2e5714863aacda44177e616b6dfc (diff)
downloadnsploit-3768139ffba6a3faaffe5229b1e7a8e348004399.tar.gz
nsploit-3768139ffba6a3faaffe5229b1e7a8e348004399.zip
payload: Refactor as a concrete IndexTbl
Payload is now an index table, wherein each index is a byte string (or compatible type). The retrieval of indices will return a corresponding offset or address of the indexed data (which is sensitive to the payload base). There is no longer a Symtbl member. Due to this new design, the class no longer keeps a running payload buffer that is appended to every time the payload is updated. When the user wants to get the full data, this buffer is constructed from the Lict elements backing the payload. This allows individual elements to be modified or removed easily after they are inserted. The use of a Lict allows data elements to be referred to by either their positional array index, or the key specified when first creating that element (done using the IndexTbl interface). Payload objects may now be directly nested inside eachother, as opposed to simply taking a payload's bytes and inserting those. This allows payloads to be used in a way resembling C structures. The type-specific insertion functions have been removed and we instead now lean on the __setindex__ interface inherited from IndexTbl to directly assign values and append them to the payload. In this case, values are taken as-is from the assignment if they are bytes-like, and automatically converted in some cases. Payload's __call__ overload is now used to perform the quick, chainable, and inline value insertion that was lost by the removal of the type-specific functions. "Calling" a payload with zero arguments will still provide the old behavior of returning the payload bytes, however. The semi-advanced features such as padding, alignment, and inserting placeholder bytes have been removed from the main payload interface and are now provided as compatible types that can be directly inserted into Payload via the means described above. In most cases, these are now implemented to dynamically react to changes in the Payload content. For example, a "padlen" element, which is constructed with a fixed target length parameter, will grow or shrink in length if the data preceding it changes. Automatic "badbytes" detection is removed, simply due to API conflict. In my experience, this feature was little-used and can easily be done manually by scripts if desired. I don't plan to reintroduce this feature. pad_front functionality is also removed by this patch, since at the moment it doesn't fit into the new design very well. We may attempt to reimplement it as a PayloadEntry down the road. However, this feature has also only seen rare use in my experience. Signed-off-by: Malfurious <m@lfurio.us>
Diffstat (limited to 'sploit/payload/payload_entry.py')
-rw-r--r--sploit/payload/payload_entry.py99
1 files changed, 99 insertions, 0 deletions
diff --git a/sploit/payload/payload_entry.py b/sploit/payload/payload_entry.py
new file mode 100644
index 0000000..7088f83
--- /dev/null
+++ b/sploit/payload/payload_entry.py
@@ -0,0 +1,99 @@
+from sploit.arch import arch, itob
+from sploit.types.index_entry import IndexEntry
+
+_PLACEHOLDER_MAGIC = b"\xef"
+
+class PayloadEntry(IndexEntry):
+ """Base class for dynamic Payload entries"""
+
+ def __repr__(self):
+ """Return human-readable entry description."""
+ return f"{self.__class__.__name__}{self.__dict__}"
+
+ def payload_insert(self, payload):
+ """
+ Called on insert into a payload object.
+
+ Override this method to perform any initialization which requires a
+ reference to the payload object. self.base is set to the insertion
+ location.
+ """
+ pass
+
+ def payload_bytes(self, payload):
+ """
+ Called to generate bytes for this entry.
+
+ Override this method to generate and return the binary output for this
+ dynamic payload entry. self.base is set to the current entry address
+ or offset.
+ """
+ return b""
+
+# Concrete payload entry definitions
+
+class pointer(PayloadEntry):
+ """Generate an integer which is always a fixed offset from self.base."""
+
+ def __init__(self, target=None):
+ self.target = target
+
+ def payload_insert(self, payload):
+ if self.target is None:
+ self.target = self.base
+ self.target -= self.base
+
+ def payload_bytes(self, payload):
+ return itob(self.target + self.base)
+
+class padlen(PayloadEntry):
+ """Generate padding to reach a target payload length."""
+
+ def __init__(self, size, data=None):
+ self.size = size
+ self.data = data
+
+ def _gen_padding(self, size):
+ data = self.data or arch.nopcode
+ if size < 0:
+ raise ValueError("padding: Available space is negative")
+ if (size := size / len(data)) != int(size):
+ raise ValueError("padding: Element does not divide the space evenly")
+ return data * int(size)
+
+ def payload_bytes(self, payload):
+ return self._gen_padding(self.size - (self.base - payload.base))
+
+class padabs(padlen):
+ """Generate padding to reach a target absolute address."""
+
+ def payload_bytes(self, payload):
+ return self._gen_padding(self.size - self.base)
+
+class padrel(padlen):
+ """Generate a fixed length of padding (aka: length relative to self)."""
+
+ def payload_bytes(self, payload):
+ return self._gen_padding(self.size)
+
+class padalign(padlen):
+ """Generate padding to reach next aligned address."""
+
+ def __init__(self, size=None, data=None):
+ self.size = size
+ self.data = data
+
+ def payload_bytes(self, payload):
+ size = self.size or arch.alignment
+ return self._gen_padding(-self.base % size)
+
+class placeholder(padlen):
+ """Generate fixed length of magic bytes, one word length by default."""
+
+ def __init__(self, size=None):
+ self.size = size
+ self.data = _PLACEHOLDER_MAGIC
+
+ def payload_bytes(self, payload):
+ size = self.size or arch.wordsize
+ return self._gen_padding(size)