summaryrefslogtreecommitdiffstats
path: root/sploit
diff options
context:
space:
mode:
Diffstat (limited to 'sploit')
-rw-r--r--sploit/__init__.py6
-rw-r--r--sploit/__main__.py77
-rw-r--r--sploit/arch.py153
-rw-r--r--sploit/comm/__init__.py1
-rw-r--r--sploit/comm/comm.py205
-rw-r--r--sploit/payload/__init__.py6
-rw-r--r--sploit/payload/fmtstring.py178
-rw-r--r--sploit/payload/gadhint.py98
-rw-r--r--sploit/payload/payload.py224
-rw-r--r--sploit/payload/payload_entry.py113
-rw-r--r--sploit/payload/ret2dlresolve.py226
-rw-r--r--sploit/payload/rop.py364
-rw-r--r--sploit/rev/__init__.py4
-rw-r--r--sploit/rev/elf.py204
-rw-r--r--sploit/rev/gadget.py25
-rw-r--r--sploit/rev/ldd.py13
-rw-r--r--sploit/rev/r2.py139
-rw-r--r--sploit/symtbl.py160
-rw-r--r--sploit/types/__init__.py3
-rw-r--r--sploit/types/index_entry.py44
-rw-r--r--sploit/types/indextbl.py103
-rw-r--r--sploit/types/lict.py202
-rw-r--r--sploit/until.py14
-rw-r--r--sploit/util/__init__.py2
-rw-r--r--sploit/util/cmd.py26
-rw-r--r--sploit/util/log.py32
26 files changed, 0 insertions, 2622 deletions
diff --git a/sploit/__init__.py b/sploit/__init__.py
deleted file mode 100644
index dc5943f..0000000
--- a/sploit/__init__.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from sploit.arch import *
-from sploit.symtbl import *
-from sploit.until import *
-
-from sploit.util import git_version as __git_version
-__version__ = __git_version()
diff --git a/sploit/__main__.py b/sploit/__main__.py
deleted file mode 100644
index 5d53ca6..0000000
--- a/sploit/__main__.py
+++ /dev/null
@@ -1,77 +0,0 @@
-from argparse import ArgumentParser, REMAINDER
-import gc
-from os.path import isdir
-import tempfile
-import traceback
-
-from sploit.comm.comm import *
-from sploit.util.log import *
-from sploit import __version__
-
-def print_banner(color, line1=__version__, line2='', line3=''):
- ilog()
- ilog(' ░▒█▀▀▀█░▒█▀▀█░▒█░░░░▒█▀▀▀█░▀█▀░▀▀█▀▀ ', end='', color=ALT)
- ilog(line1, color=ALT)
- ilog(' ░░▀▀▀▄▄░▒█▄▄█░▒█░░░░▒█░░▒█░▒█░░░▒█░░ ', end='', color=color)
- ilog(line2, color=ALT)
- ilog(' ░▒█▄▄▄█░▒█░░░░▒█▄▄█░▒█▄▄▄█░▄█▄░░▒█░░ ', end='', color=ALT)
- ilog(line3, color=ALT)
- ilog()
-
-def main():
- parser = ArgumentParser(description='Execute Sploit script against target')
- parser.add_argument('script', help='Exploit script to run')
- parser.add_argument('target', nargs=REMAINDER, help='Target cmdline or pipes directory')
- args = parser.parse_args()
-
- if len(args.target) == 0:
- with tempfile.TemporaryDirectory() as tmpdir:
- pipe(args.script, tmpdir)
- elif len(args.target) == 1 and isdir(args.target[0]):
- pipe(args.script, args.target[0])
- else:
- target(args.script, args.target)
-
-def pipe(script, tmpdir):
- print_banner(ERROR, line3='Pipe Mode')
- while True:
- try:
- p = Pipes(tmpdir)
- except KeyboardInterrupt:
- break
- runscript(script, Comm(p))
- del p
-
-def target(script, target):
- print_banner(STATUS, line3='Subprocess Mode')
- runscript(script, Comm(Process(target)))
-
-def user_scope(comm):
- import sploit as lib
- scope = { name: getattr(lib, name) for name in dir(lib) }
- scope['__version__'] = __version__
- scope['print'] = elog
- scope['io'] = comm
- return scope
-
-def runscript(script, comm):
- try:
- ilog("Running Script...")
- code = compile(open(script).read(), script, 'exec')
- exec(code, user_scope(comm))
- ilog("Script Finished!")
- return
- except KeyboardInterrupt:
- pass
- except:
- ilog(traceback.format_exc(), end='', color=ERROR)
- finally:
- comm.shutdown()
- comm.readall()
- gc.collect()
-
- ilog("Script Ended Early!", color=WARNING)
-
-
-if __name__ == "__main__":
- main()
diff --git a/sploit/arch.py b/sploit/arch.py
deleted file mode 100644
index a9dea61..0000000
--- a/sploit/arch.py
+++ /dev/null
@@ -1,153 +0,0 @@
-"""
-Architecture-aware utilities and global architecture config
-
-It is common within sploit and for users of sploit to need different behavior
-depending on the architecture of the target. This module encapsulates those
-behaviors and bases them on a global architecture that is also configured here.
-
-Users can set the global arch with arch.set() and all of the methods in this
-module will honor it. An architecture can be defined through the Arch dataclass
-and there are also several predefined architecture constants that can be used.
-These are accessible by name at module scope. (i.e. sploit.arch.x86_64)
-
-arch (Arch): the architecture config that sploit will use whenever it needs to
-know the architecture of the target
-
-DEFAULT_ARCH (Arch): the default architecture that arch is set to
-"""
-
-from dataclasses import dataclass
-
-def __define_architectures():
- # All predefined architectures should be listed here
- # These will also be added to the module's namespace
- __arch_list = {
- 'x86' : Arch('x86', 4, 'little', 16, 'ret', 'int 0x80', b'\x90', ['pop {}','ret'], [r'add esp, (\w+)','ret'], [r'mov dword \[(?P<dst>\w+)\], (?P<src>\w+)','ret'], [], ['eax','ebx','ecx','edx','esi','edi','ebp']),
- 'x86_64' : Arch('x86', 8, 'little', 16, 'ret', 'syscall', b'\x90', ['pop {}','ret'], [r'add rsp, (\w+)','ret'], [r'mov qword \[(?P<dst>\w+)\], (?P<src>\w+)','ret'], ['rdi','rsi','rdx','rcx','r8','r9'], ['rax','rdi','rsi','rdx','r10','r8','r9']),
- 'ARM' : Arch('arm', 4, 'little', 8, 'pop {pc}', 'svc 0', b'\xe1\xa0\x00\x00', ['pop {{{}, pc}}'], [r'add sp, sp, ([^r]\w*)','pop {pc}'], [r'str (?P<src>\w+), \[(?P<dst>\w+)\]','pop {pc}'], ['r0','r1','r2','r3'], ['r7','r0','r1','r2','r3','r4','r5']),
- 'THUMB' : Arch('arm', 4, 'little', 8, 'pop {pc}', 'svc 0', b'\x46\xc0', ['pop {{{}, pc}}'], [r'add sp, sp, ([^r]\w*)','pop {pc}'], [r'str (?P<src>\w+), \[(?P<dst>\w+)\]','pop {pc}'], ['r0','r1','r2','r3'], ['r7','r0','r1','r2','r3','r4','r5']),
- }
- globals().update(__arch_list)
- global __arch_lookup
- __arch_lookup = {(a.arch_string, a.wordsize, a.endianness) : a for a in reversed(__arch_list.values())}
-
-@dataclass(frozen=True)
-class Arch:
- """
- Dataclass of information about a target architecture
-
- arch_string (str): string returned by r2 iI in the arch field
- wordsize (int): the width, in bytes, of the natural unit of data
- endianness (str): byte order. either "little" or "big"
- alignment (int): the multiple, in bytes, that return addresses must exist
- on the stack
- ret (str): mnemonic for a "return" instruction
- syscall (str): mnemonic for a "syscall" or "service call" instruction
- nopcode (bytes): the exact bytes of a "do nothing" instruction
- popgad (list[str]): ROP gadget template used to pop a value into a register
- cleangad (list[str]): ROP gadget template used to remove values from the
- stack
- writegad (list[str]): ROP gadget template used to write data to memory
- funcargs (list[str]): function argument registers used by the architecture
- calling convention
- kernargs (list[str]): kernel syscall argument registers
- """
-
- arch_string: str
- wordsize: int
- endianness: str
- alignment: int
- ret: str
- syscall: str
- nopcode: bytes
- popgad: list
- cleangad: list
- writegad: list
- funcargs: list
- kernargs: list
-
- def set(self,new_arch):
- """Copy the given Arch into this instance."""
- if type(new_arch) is not Arch:
- raise TypeError(f'arch: new_arch must be an Arch: {new_arch}')
- self.__dict__.update(new_arch.__dict__)
-__define_architectures()
-
-DEFAULT_ARCH = x86_64
-arch = Arch(**DEFAULT_ARCH.__dict__)
-
-def lookup_arch(arch_string, wordsize, endianness):
- """
- Return an Arch object with the matching search parameters.
-
- If a predefined Arch matches the specified fields, it will be returned.
- Otherwise, None is returned.
-
- arch_string (str): The "arch" string returned from r2 iI.
- wordsize (int): The natural width of an int in bytes.
- endianness (str): The order of bytes in an int (either "little" or "big")
- """
- return __arch_lookup.get((arch_string, wordsize, endianness))
-
-def sint(i):
- """Convert given int to signed int of arch.wordsize width."""
- return __int(i, True)
-
-def uint(i):
- """Convert given int to unsigned int of arch.wordsize width."""
- return __int(i, False)
-
-def int8(i):
- """Convert given int to signed 8 bit int."""
- return __int(i, True, 1)
-
-def int16(i):
- """Convert given int to signed 16 bit int."""
- return __int(i, True, 2)
-
-def int32(i):
- """Convert given int to signed 32 bit int."""
- return __int(i, True, 4)
-
-def int64(i):
- """Convert given int to signed 64 bit int."""
- return __int(i, True, 8)
-
-def uint8(i):
- """Convert given int to unsigned 8 bit int."""
- return __int(i, False, 1)
-
-def uint16(i):
- """Convert given int to unsigned 16 bit int."""
- return __int(i, False, 2)
-
-def uint32(i):
- """Convert given int to unsigned 32 bit int."""
- return __int(i, False, 4)
-
-def uint64(i):
- """Convert given int to unsigned 64 bit int."""
- return __int(i, False, 8)
-
-def btoi(b, byteorder=None):
- """Convert given byte array to an int."""
- byteorder = byteorder or arch.endianness
- return int.from_bytes(b, byteorder, signed=False)
-
-def itob(i, width=None, byteorder=None):
- """Convert given int to a byte array."""
- width = width or arch.wordsize
- byteorder = byteorder or arch.endianness
- return __int(i,False,width).to_bytes(width, byteorder, signed=False)
-
-def __int(i, signed=False, width=None):
- # type conversion from int to int of given sign and width
- i = int(i)
- width = width or arch.wordsize
- bits = 8 * width
- if signed:
- sign_bit = 1 << (bits - 1)
- return (i & (sign_bit - 1)) - (i & sign_bit)
- else:
- mask = (1 << bits) - 1
- return i & mask
diff --git a/sploit/comm/__init__.py b/sploit/comm/__init__.py
deleted file mode 100644
index ffbc402..0000000
--- a/sploit/comm/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-from .comm import *
diff --git a/sploit/comm/comm.py b/sploit/comm/comm.py
deleted file mode 100644
index 3bc448e..0000000
--- a/sploit/comm/comm.py
+++ /dev/null
@@ -1,205 +0,0 @@
-import subprocess
-import tempfile
-import os
-import sys
-import select
-
-from sploit.until import bind
-from sploit.util.log import *
-
-class Comm:
- logonread = True
- logonwrite = False
- flushonwrite = True
- readonwrite = False
- timeout = 250 # milliseconds
- last = None # result of last discrete read
-
- def __init__(self, backend):
- self.back = backend
-
- def shutdown(self):
- try:
- self.back.stdout.close()
- except BrokenPipeError:
- pass
-
- def read(self, size=-1):
- if size < 0:
- return self.readall_nonblock()
- elif size == 0:
- data = b''
- else:
- data = self.back.stdin.read(size)
- if(data == b''):
- raise BrokenPipeError('Tried to read on broken pipe')
- if self.logonread : ilog(data, file=sys.stdout, color=NORMAL)
- self.last = data
- return data
-
- def readline(self):
- data = self.back.stdin.readline()
- if(data == b''):
- raise BrokenPipeError('Tried to read on broken pipe')
- if data.endswith(b'\n'):
- data = data[:-1]
- if self.logonread : ilog(data, file=sys.stdout, color=NORMAL)
- self.last = data
- return data
-
- def readall(self):
- data = b''
- try:
- for line in self.back.stdin:
- tolog = (line[:-1] if line.endswith(b'\n') else line)
- if self.logonread : ilog(tolog, file=sys.stdout, color=NORMAL)
- data += line
- except KeyboardInterrupt:
- pass
- self.last = data
- return data
-
- def readall_nonblock(self):
- try:
- data = b''
- os.set_blocking(self.back.stdin.fileno(), False)
- poll = select.poll()
- poll.register(self.back.stdin, select.POLLIN)
- while True:
- poll.poll(self.timeout)
- d = self.readall()
- if len(d) == 0:
- self.last = data
- return data
- data += d
- finally:
- os.set_blocking(self.back.stdin.fileno(), True)
-
- def readuntil(self, pred, /, *args, **kwargs):
- data = b''
- pred = bind(pred, *args, **kwargs)
- l = self.logonread
- self.logonread = False
- try:
- while(True):
- data += self.read(1)
- if(pred(data)):
- break
- finally:
- self.logonread = l
- if self.logonread : ilog(data, file=sys.stdout, color=NORMAL)
- self.last = data
- return data
-
- def readlineuntil(self, pred, /, *args, **kwargs):
- dataarr = []
- pred = bind(pred, *args, **kwargs)
- while(True):
- dataarr.append(self.readline())
- if(pred(dataarr)):
- break
- self.last = dataarr
- return dataarr
-
- def write(self, data):
- self.back.stdout.write(data)
- if self.flushonwrite : self.back.stdout.flush()
- if self.logonwrite : ilog(data, file=sys.stdout, color=ALT)
- if self.readonwrite : self.readall_nonblock()
-
- def writeline(self, data=b''):
- self.write(data + b'\n')
-
- def interact(self):
- stdin = sys.stdin.buffer
- event = select.POLLIN
-
- def readall_stdin():
- try:
- os.set_blocking(stdin.fileno(), False)
- for line in stdin:
- self.write(line)
- finally:
- os.set_blocking(stdin.fileno(), True)
-
- readtable = {
- self.back.stdin.fileno(): self.readall_nonblock,
- stdin.fileno(): readall_stdin,
- }
-
- try:
- ilog("<--Interact Mode-->")
- l = self.logonread
- self.logonread = True
-
- poll = select.poll()
- poll.register(self.back.stdin, event)
- poll.register(stdin, event)
-
- readtable[self.back.stdin.fileno()]()
- while True:
- for fd, e in poll.poll(self.timeout):
- if not e & event: return
- readtable[fd]()
- except KeyboardInterrupt:
- pass
- finally:
- self.logonread = l
- ilog("<--Interact Mode Done-->")
-
-def popen(cmdline=''):
- io = Comm((Process(cmdline.split()) if len(cmdline) > 0 else Pipes()))
- io.readall_nonblock()
- io.readonwrite = True
- return io
-
-class Process:
- def __init__(self, args):
- ilog(f"Running: {' '.join(args)}")
- self.proc = subprocess.Popen(args,
- stdin=subprocess.PIPE,
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT,
- preexec_fn=lambda : os.setpgrp())
- ilog(f"PID: {self.proc.pid}")
- self.stdin = self.proc.stdout
- self.stdout = self.proc.stdin
-
- def __del__(self):
- if getattr(self, 'proc', None) == None : return
- if(self.proc.poll() != None):
- return
- try:
- ilog("Waiting on Target Program to End...")
- ilog("Press Ctrl+C to Forcefully Kill It...")
- self.proc.wait()
- except KeyboardInterrupt:
- self.proc.kill()
-
-class Pipes:
- def __init__(self, tmp=None):
- if(tmp == None):
- self.dir = tempfile.TemporaryDirectory()
- dirname = self.dir.name
- else:
- if(not os.path.exists(tmp)):
- os.mkdir(tmp)
- dirname = tmp
- self.pathin = os.path.join(dirname, "in")
- self.pathout = os.path.join(dirname, "out")
- os.mkfifo(self.pathin)
- os.mkfifo(self.pathout)
- ilog("Waiting on Target to Connect...", file=sys.stdout)
- ilog(f"<{self.pathin} >{self.pathout}", file=sys.stdout)
- self.stdout = open(self.pathin, "wb")
- self.stdin = open(self.pathout, "rb")
- ilog("Connected!")
-
- def __del__(self):
- try:
- if getattr(self,'stdout',None) : self.stdout.close()
- if getattr(self,'stdin',None) : self.stdin.close()
- except BrokenPipeError:
- pass
- if getattr(self,'pathin',None) and os.path.exists(self.pathin) : os.unlink(self.pathin)
- if getattr(self,'pathout',None) and os.path.exists(self.pathout) : os.unlink(self.pathout)
diff --git a/sploit/payload/__init__.py b/sploit/payload/__init__.py
deleted file mode 100644
index da47cc1..0000000
--- a/sploit/payload/__init__.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from .fmtstring import *
-from .gadhint import *
-from .payload import *
-from .payload_entry import *
-from .ret2dlresolve import *
-from .rop import *
diff --git a/sploit/payload/fmtstring.py b/sploit/payload/fmtstring.py
deleted file mode 100644
index 54da6f2..0000000
--- a/sploit/payload/fmtstring.py
+++ /dev/null
@@ -1,178 +0,0 @@
-"""
-Exploit C-style format string vulnerabilities
-
-These techniques leverage functions such as printf, fprintf, sprintf, etc. when
-run on unchecked user input to perform arbitrary memory read or write. This is
-made possible by the unintended use of user input as the function's format
-string argument, instead of an ordinary data argument. Attackers may inject
-their own conversion specifiers, which act as operating instructions to the
-function. Interesting formatters include:
-
- %p Read argument value. These are the values of function argument
- registers and values from the stack. The value is printed as
- hexadecimal (with leading "0x") and interprets values as unsigned
- long (aka, same size as arch.wordsize).
-
- %s Read memory as asciiz string. Prints the data pointed to by
- argument value.
-
- %c Read argument as 8-bit character, printing the interpreted character
- value. This formatter is useful in combination with a field width
- specifier in order to print a controlled number of bytes to the
- output, which is meaningful to the next formatter.
-
- %n Write memory as integer. Prints no output, but writes the number of
- characters printed so far to the location pointed to by the argument
- pointer. A length modifier will control the bit-width of the
- integer written.
-
-See `man 3 printf` for more details.
-"""
-
-from sploit.arch import arch, btoi, itob
-from sploit.payload.payload import Payload
-from sploit.payload.payload_entry import padalign, padrel
-
-_FMTSTR_MAGIC = b"\xcd"
-
-def _make_fmtstr_payload():
- # A typical layout will look like this:
- # b'%123c%10$hn%456c%11$hn\x00\x90\xde\xad\xbe\xef\xca\xfe\xba\xbe'
- # ^ ^ ^ ^ ^ ^
- # fmt[0] fmt[1] nul | addrs[0] addrs[1]
- # align
- #
- # Many examples found online will demo placing addresses at the front. Eg:
- # b'\xde\xad\xbe\xef\xca\xfe\xba\xbe%123c%7$hn%456c%8$hn\x00'
- # This has the benefit that %n positional values are simple to calculate
- # (they just start at the payload position and increase by one). However,
- # any NULL bytes in the addresses break the exploit, since printf will stop
- # processing its string once a NULL is encountered.
- #
- # Moving addresses to the end mitigates this. Wordsize alignment is then
- # necessary to give for valid argument positions. We also intentionally
- # NULL terminate the format string portion of the payload to prevent printf
- # from processing beyond formatters.
- fp = Payload()
- fp.fmt = Payload()
- fp.null = b"\x00"
- fp.align = padalign(arch.wordsize)
- fp.addrs = Payload()
- return fp
-
-def _fixup_positionals(fp, position, offset=None):
- if offset is None:
- offset = fp.addrs.base // arch.wordsize
-
- fixup = _make_fmtstr_payload()
- fixup.addrs = fp.addrs
-
- for i, fmt in enumerate(fp.fmt):
- pos = position + offset + i
- fixup.fmt(fmt.decode().format(pos).encode())
-
- # String formatting positional values may grow the format string so much
- # as to cause the addrs offset to shift. Detect this and correct.
- check = fixup.addrs.base // arch.wordsize
- if offset != check:
- return _fixup_positionals(fp, position, check)
-
- return fixup
-
-def fmtstr_dump(start=None, end=None):
- """
- Return a format string payload which dumps annotated argument values.
-
- start (int): Starting argument position (default: 1)
- end (int): Ending argument position (default: start + 20)
- """
- if start is None: start = 1
- if end is None: end = start + 19 # inclusive, so 20 total arguments
-
- fp = Payload()
- fp.magic = padrel(arch.wordsize, _FMTSTR_MAGIC)
- fp.fmt = Payload()
- fp.null = b"\x00"
-
- for pos in range(start, end+1):
- if pos < len(arch.funcargs):
- label = arch.funcargs[pos]
- else:
- offset = (pos - len(arch.funcargs)) * arch.wordsize
- label = f"stack+{hex(offset)}"
-
- fp.fmt(f"({pos}$ {label}) %{pos}$p ".encode())
-
- return fp
-
-def fmtstr_get(*positions, join=" "):
- """
- Return a format string payload which prints specific argument values.
-
- positions (*int): Argument positions
- join (str): Delimiter string
- """
- fp = Payload()
- fp.fmt = Payload()
- fp.null = join
-
- for p in positions:
- fp.fmt(f"{join}%{p}$p".encode())
-
- return fp
-
-def fmtstr_read(position, address):
- """
- Return a format string payload which reads data (as string, via %s).
-
- position (int): printf positional offset of payload on stack.
- address (int): Address of data to read.
- """
- fp = _make_fmtstr_payload()
- fp.fmt(b"%{}$s")
- fp.addrs(address)
- return _fixup_positionals(fp, position)
-
-def fmtstr_write(position, _data, _value=None):
- """
- Return a format string payload which writes data.
-
- One option for calling this function is to give a write destination in _data
- as an integer, and the value to write in _value.
-
- Alternatively, _data may contain a dictionary, with write destinations as
- keys and contents to write as values. _value is ignored in this case.
-
- In either case, the contents to write is generally expected to be bytes.
- However, integers are converted automatically via itob().
-
- position (int): printf positional offset of payload on stack.
- _data (int|dict{int:bytes}): Write data (see above)
- _value (int|bytes): Write value (see above)
- """
- # Convert from 2-argument style to dictionary.
- if type(_data) is int:
- _data = { _data: _value }
-
- pairs = {}
-
- # Collect each 2-byte word to write.
- for addr, value in _data.items():
- value = itob(value) if type(value) is int else bytes(value)
- words = [ value[i:i+2] for i in range(0, len(value), 2) ]
- words = { addr+(i*2): btoi(w) for i, w in enumerate(words) }
- pairs.update(words)
-
- fp = _make_fmtstr_payload()
- prev = 0
-
- # Craft writes.
- for addr, word in sorted(pairs.items(), key=lambda x: x[1]):
- diff = word - prev
- prev = word
-
- size = "" if diff == 0 else f"%{diff}c"
- fp.fmt(f"{size}%{{}}$hn".encode())
- fp.addrs(addr)
-
- return _fixup_positionals(fp, position)
diff --git a/sploit/payload/gadhint.py b/sploit/payload/gadhint.py
deleted file mode 100644
index 1bef9f0..0000000
--- a/sploit/payload/gadhint.py
+++ /dev/null
@@ -1,98 +0,0 @@
-import copy
-from dataclasses import dataclass, field
-
-from sploit.rev.gadget import Gadget
-from sploit.types.index_entry import IndexEntry
-
-@dataclass
-class GadHint(IndexEntry):
- """
- User-annotated gadget description object
-
- base (Gadget|int): The gadget being annotated. May be a Gadget object or
- an offset as an int.
-
- pops (list[str]): The registers popped by this gadget, in order of
- occurrence.
-
- movs (dict{str:str}): The register-to-register moves made by this gadget.
- Keys are destination register names, values are source register names. The
- order given is insignificant.
-
- imms (dict{str:int}): The immediate-to-register loads made by this gadget.
- Keys are destination register names, values are immediate values. The order
- given is insignificant.
-
- writes (dict{str:str}): The register-to-memory stores made by this gadget.
- Keys are the destination register names (which hold memory addresses),
- values are source register names (which hold values to-be-stored). The
- order given is insignificant.
-
- requirements (dict{str:int}): The register state that is required before
- this gadget should be executed. Keys are register names, values are the
- required register values.
-
- stack (list[int]): A list of words to append to the stack following this
- gadget. The first element given is nearest to the top of the stack and the
- rest follow in order.
-
- align (bool): If True, this gadget expects the stack to be aligned prior
- to entry.
-
- syscall (bool): If True, this gadget contains a syscall instruction.
-
- spm (int): "Stack pointer move" - The amount the stack pointer is adjusted
- by this gadget. The effect of executing a terminating "return" instruction
- should not be accounted for. A value of zero is taken as "unspecified".
- """
-
- base: int = 0
- pops: list = field(default_factory=list)
- movs: dict = field(default_factory=dict)
- imms: dict = field(default_factory=dict)
- writes: dict = field(default_factory=dict)
- requirements: dict = field(default_factory=dict)
- stack: list = field(default_factory=list)
- align: bool = False
- syscall: bool = False
- spm: int = 0
-
- @property
- def offset(self):
- """Return gadget offset as an integer."""
- return int(self.base)
-
- def with_requirements(self, reqs):
- """Return new object with additional requirements."""
- for k, v in reqs.items():
- if self.requirements.get(k, v) != v:
- raise ValueError(
- f"GadHint: Conflicting gadget requirements: "
- f"{self.requirements}, {reqs}")
-
- new = copy.deepcopy(self)
- new.requirements |= reqs
- return new
-
- def __repr__(self):
- """Return human-readable GadHint."""
- def fmt(name, prop):
- if len(prop) > 0:
- return f", {name}={prop}"
- return ""
-
- s = hex(self.base)
- s = f"Gadget({s})" if isinstance(self.base, Gadget) else s
- s += fmt("pops", self.pops)
- s += fmt("movs", self.movs)
- s += fmt("imms", self.imms)
- s += fmt("writes", self.writes)
- s += fmt("requirements", self.requirements)
- s += fmt("stack", self.stack)
- if self.align:
- s += ", align"
- if self.syscall:
- s += ", syscall"
- if self.spm > 0:
- s += f", spm={self.spm}"
- return f"GadHint({s})"
diff --git a/sploit/payload/payload.py b/sploit/payload/payload.py
deleted file mode 100644
index 2a9521f..0000000
--- a/sploit/payload/payload.py
+++ /dev/null
@@ -1,224 +0,0 @@
-from sploit.arch import itob
-from sploit.payload.payload_entry import PayloadEntry
-from sploit.types.indextbl import IndexTbl
-from sploit.types.index_entry import IndexEntry
-from sploit.types.lict import Lict
-
-_REPR_DATA_LEN = 64
-
-class Payload(IndexTbl):
- """
- Binary payload builder
-
- This class provides an API for fluently specifying structured payloads from
- an assortment of input data. Payload "indices" are any bytes-like data,
- which includes some supported IndexEntry types as well as nested Payloads.
-
- Payload is an IndexTbl based on a Lict and features two main use-cases or
- syntaxes for interacting with data.
-
- The first method (the formal method) is through the use of normal index
- access via attributes or subscripts. In this case, element keys are usually
- given. When a new index is defined, it is inserted at the end of the
- payload. Modifications to existing indices change the data in-place, and
- this causes the content of the payload to shift around if the replaced data
- is of a different length.
-
- The second method (the quick method) is through the use of Payload's __call__
- method. This is a general purpose "quick action" method that, among other
- things, will insert data to the payload. If the Payload object is called
- with 1 or more arguments, the values of these arguments are appended to the
- payload in the order given. There is no way to specify keys using this
- option, so the data simply occupies unkeyed elements in the underlying Lict.
-
- In either case, the data inserted must be bytes-like. In some common cases,
- the data will be coerced into bytes. See the method __prep_insertion for
- details on how this is handled. See the PayloadEntry module for some
- additional features.
-
- When retrieving indices from the payload, instead of the element's value,
- either the element offset or (for IndexEntries) the value based at that
- offset is returned to you. If the payload has a non-zero base, this is
- interpreted as the element's address in memory. This is useful for any
- exploit that requires pointers to other crafted data.
-
- The binary output of a payload is simply the binary output of each of its
- elements, concatenated together - there are no gaps. If you need to space
- or separate two elements, you need to insert padding bytes between them.
- The element binary content is either the object itself (for bytes elements),
- the output from `payload_bytes()` (for PayloadEntries), or the output from
- `bytes(obj)` for everything else.
-
- The following is a simple example using the Payload module to perform a
- hypothetical stack buffer overrun "ret2win" with both of the build syntaxes:
-
- # 100 bytes from the start of the buffer to the saved frame pointer
- # call (return into) the function "pwned", given by an ELF Symtbl
- # 3 arguments, which are given on the stack
-
- # formal method
- p = Payload()
- p.smash = padlen(100)
- p.fp = placeholder()
- p.ret = elf.sym.pwned
- p.ret2 = placeholder()
- p.arg1 = 0
- p.arg2 = 1
- p.arg3 = 2
- io.write(bytes(p))
-
- # quick method
- p = Payload()(padlen(100), placeholder())
- p(elf.sym.pwned, placeholder(), 0, 1, 2)
- io.write(p())
- """
-
- def __init__(self, base=0, entries=None):
- """Construct new Payload with optional base and content."""
- super().__init__(base)
- if not isinstance(entries, Lict):
- entries = Lict(entries)
- object.__setattr__(self, "__entries__", entries)
-
- def __repr__(self):
- """Return human-readable Payload."""
- FMT = "\n{:<20} {:<20} {:<20}"
- s = f"{len(self.__entries__)} items, {len(self)} bytes @ {hex(self)}"
- memo = {}
-
- if len(self.__entries__) > 0:
- s += FMT.format("ADDRESS", "SYMBOL", "DATA")
-
- for i, value in enumerate(self.__entries__):
- key = self.__entries__.idx2key(i)
- key = "(unkeyed)" if key is None else str(key)
- key = f"[{key}]" if isinstance(value, IndexEntry) else key
-
- addr = self.__addrof(i, memo)
- data = str(self.__bytesof(i, memo))
- if len(data) > _REPR_DATA_LEN:
- data = data[:_REPR_DATA_LEN] + " ..."
-
- s += FMT.format(hex(addr), key, data)
-
- return s
-
- def __bytes__(self):
- """Return calculated payload bytes."""
- memo = {}
- x = [ self.__bytesof(i, memo) for i in range(len(self.__entries__)) ]
- return b"".join(x)
-
- def __call__(self, *args):
- """
- Payload quick-action call operator.
-
- If called with arguments, append these values to the payload in the
- order given. The payload (self) is returned for easily chaining calls.
-
- If called without arguments, return the rendered payload content as if
- `bytes(payload)` was called.
- """
- if len(args) == 0:
- return bytes(self)
-
- for value in args:
- value = self.__prep_insertion(value, self.end())
- self.__entries__.append(value)
-
- return self
-
- def end(self):
- """Return the offset or address of the end of the payload."""
- return self.base + len(self)
-
- # IndexTbl abstract methods
-
- def __copy__(self):
- """Return copy of object with shared data entries."""
- return Payload(self.base, self.__entries__)
-
- def __iter__(self):
- """Iterate over data entries."""
- return iter(self.__entries__)
-
- def __len__(self):
- """Return the size of the payload content in bytes."""
- memo = {}
- x = [ self.__lenof(i, memo) for i in range(len(self.__entries__)) ]
- return sum(x)
-
- def __getindex__(self, index):
- """Return payload index value or address."""
- value, _ = self.__valueof(index, {})
- return value
-
- def __setindex__(self, index, value):
- """Set payload index value."""
- try:
- addr = self.__addrof(index, {})
- except KeyError:
- addr = self.end()
- value = self.__prep_insertion(value, addr)
- self.__entries__[index] = value
-
- def __delindex__(self, index):
- """Delete payload index."""
- del self.__entries__[index]
-
- # Payload helpers
-
- def __valueof(self, index, memo):
- """Return a tuple (addr of value, literal value) for index."""
- value = self.__entries__[index]
- addr = self.__addrof(index, memo)
- if isinstance(value, IndexEntry):
- value @= addr
- return value, value
- return addr, value
-
- def __addrof(self, index, memo):
- """Return address (base + offset) for index."""
- index = self.__entries__.key2idx(index)
- try:
- return memo[index]
- except KeyError:
- sizes = [ self.__lenof(i, memo) for i in range(index) ]
- addr = self.base + sum(sizes)
- memo[index] = addr
- return addr
-
- def __lenof(self, index, memo):
- """Return element length for index."""
- _, value = self.__valueof(index, memo)
- if isinstance(value, PayloadEntry):
- return value.payload_len(self)
- return len(value)
-
- def __bytesof(self, index, memo):
- """Return byte output for index."""
- _, value = self.__valueof(index, memo)
- if isinstance(value, PayloadEntry):
- return value.payload_bytes(self)
- return bytes(value)
-
- def __prep_insertion(self, value, addr):
- """Initialize or type coerce input value for payload insert."""
- if isinstance(value, PayloadEntry):
- value @= addr
- value.payload_insert(self)
- return value
-
- if type(value) is str:
- value = value.encode() + b"\x00"
- elif type(value) is int:
- value = itob(value)
-
- try:
- # Confirm value supports our required operations
- len(value)
- bytes(value)
- except TypeError as ex:
- raise TypeError(f"Payload: Bad type {type(value)} given") from ex
-
- return value
diff --git a/sploit/payload/payload_entry.py b/sploit/payload/payload_entry.py
deleted file mode 100644
index 2f8dbdd..0000000
--- a/sploit/payload/payload_entry.py
+++ /dev/null
@@ -1,113 +0,0 @@
-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_len(self, payload):
- """
- Called to compute size of this entry.
-
- Implement this method to calculate the length of this dynamic payload
- entry. self.base is set to the current entry address or offset.
- """
- raise NotImplementedError
-
- def payload_bytes(self, payload):
- """
- Called to generate bytes for this entry.
-
- Implement this method to generate the binary output for this dynamic
- payload entry. self.base is set to the current entry address or offset.
- """
- raise NotImplementedError
-
-# Concrete payload entry definitions
-
-class pointer(PayloadEntry):
- """Generate an integer which tracks the address of another payload field."""
-
- def __init__(self, target=None, math=None):
- self.target = target
- self.math = math
-
- def payload_len(self, payload):
- return arch.wordsize
-
- def payload_bytes(self, payload):
- if self.target is None:
- addr = self.base
- else:
- addr = payload[self.target]
- if callable(self.math):
- addr = self.math(addr)
- return itob(addr)
-
-class padlen(PayloadEntry):
- """Generate padding to reach a target payload length."""
-
- def __init__(self, size, data=None):
- self.size = size
- self.data = data
-
- def payload_len(self, payload):
- return self.size - (self.base - payload.base)
-
- def payload_bytes(self, payload):
- size = self.payload_len(payload)
- 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)
-
-class padabs(padlen):
- """Generate padding to reach a target absolute address."""
-
- def payload_len(self, payload):
- return self.size - self.base
-
-class padrel(padlen):
- """Generate a fixed length of padding (aka: length relative to self)."""
-
- def payload_len(self, payload):
- return self.size
-
-class padalign(padlen):
- """Generate padding to reach next aligned address."""
-
- def __init__(self, size=None, data=None, reference=0):
- self.size = size
- self.data = data
- self.reference = reference
-
- def payload_len(self, payload):
- size = self.size or arch.alignment
- return (self.reference - 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_len(self, payload):
- return self.size or arch.wordsize
diff --git a/sploit/payload/ret2dlresolve.py b/sploit/payload/ret2dlresolve.py
deleted file mode 100644
index 8862e22..0000000
--- a/sploit/payload/ret2dlresolve.py
+++ /dev/null
@@ -1,226 +0,0 @@
-"""
-Perform "Return to dlresolve" dynamic linker attack
-
-The ret2dlresolve technique is useful to defeat library ASLR against targets
-with partial relro (or less) and where no useable data leaks are available.
-This is specifically a workaround for ASLR of libraries such as libc, and
-addresses within the target executable are expected to be known (non-pic or
-otherwise).
-
-When a dynamic library call is performed normally, applications jump to code
-stubs in the .plt section to perform the actual relocation. This process relies
-on a couple of meta-data structures in the ELF object:
-
-Elf*_Rel: Contains a pointer to the corresponding GOT entry, which is used to
-cache the real subroutine address for later calls, as well as an info field
-describing the relocation. This info field contains a type subfield and an
-index into the ELF's symbol table for the symbol to be relocated.
-
-Elf*_Sym: Contains all the data relevant to the symbol. For the purposes of the
-exploit, only the symbol name field is utilized (the others are set to zeroes).
-The name field is an offset into the ELF's string table, and the actual symbol
-name string can be found at this offset.
-
-All of the data tables mentioned above are located by their corresponding
-section in the ELF. The relocation process however does not perform any bounds
-checks to ensure the runtime data structures actually come from these sections.
-By forging custom structures, and ensuring they can be written into memory at
-precise locations, an attacker can trick the resolver to link any library
-function they desire by setting up the equivalent PLT function call via ROP.
-
-Read on for more background details:
-http://phrack.org/issues/58/4.html
-https://gist.github.com/ricardo2197/8c7f6f5b8950ed6771c1cd3a116f7e62
-
-Structure definitions from your standard elf.h header:
-
-typedef struct {
- Elf32_Word st_name; /* 4b Symbol name (string tbl index) */
- Elf32_Addr st_value; /* 4b Symbol value */
- Elf32_Word st_size; /* 4b Symbol size */
- unsigned char st_info; /* 1b Symbol type and binding */
- unsigned char st_other; /* 1b Symbol visibility */
- Elf32_Section st_shndx; /* 2b Section index */
-} Elf32_Sym;
-
-typedef struct {
- Elf64_Word st_name; /* 4b Symbol name (string tbl index) */
- unsigned char st_info; /* 1b Symbol type and binding */
- unsigned char st_other; /* 1b Symbol visibility */
- Elf64_Section st_shndx; /* 2b Section index */
- Elf64_Addr st_value; /* 8b Symbol value */
- Elf64_Xword st_size; /* 8b Symbol size */
-} Elf64_Sym;
-
-typedef struct {
- Elf32_Addr r_offset; /* 4b Address */
- Elf32_Word r_info; /* 4b Relocation type and symbol index */
-} Elf32_Rel;
-
-typedef struct {
- Elf64_Addr r_offset; /* 8b Address */
- Elf64_Xword r_info; /* 8b Relocation type and symbol index */
-} Elf64_Rel;
-
-Elf32_Rel.r_info = 0xAAAAAABB
- | |
- | type
- symidx
-
-Elf64_Rel.r_info = 0xAAAAAAAABBBBBBBB
- | |
- symidx type
-"""
-
-from sploit.arch import arch, itob
-from sploit.payload.gadhint import GadHint
-from sploit.payload.payload import Payload
-from sploit.payload.payload_entry import padalign, padlen, pointer
-from sploit.payload.rop import ROP
-from sploit.rev.r2 import run_cmd
-
-_JMP_SLOT = 0x07
-
-def _symsize():
- # Size of Elf*_Sym, used for padding and indexing
- if arch.wordsize == 4: return 16
- elif arch.wordsize == 8: return 24
- raise ValueError("Ret2dlresolve: Architecture wordsize unsupported")
-
-def _relsize():
- # Size of Elf*_Rel, used only for indexing on 64bit (32bit uses offset)
- if arch.wordsize == 4: return 1
- elif arch.wordsize == 8: return 24
- raise ValueError("Ret2dlresolve: Architecture wordsize unsupported")
-
-def _infoshift():
- # Partition subfields of Elf*_Rel.r_info
- if arch.wordsize == 4: return 8
- elif arch.wordsize == 8: return 32
- raise ValueError("Ret2dlresolve: Architecture wordsize unsupported")
-
-class Ret2dlresolve(ROP):
- # Use constructor from ROP class
-
- def reloc(self, symbol_name):
- """
- Generate relocation structures for the function with given symbol name.
-
- The returned data structures are packed into a single Payload object.
- This payload must be written into the target's memory before attempting
- to use it with Ret2dlresolve.call(). Furthermore, the chosen write
- location must be assigned to the payload base property, so that internal
- pointers may take on the appropriate values.
-
- See Ret2dlresolve.determine_address() for advice on choosing a write
- location.
-
- symbol_name (str): Name of library function to link
- """
- binary = self.objects[0]
- symtab = binary.sym.sect['.dynsym']
- strtab = binary.sym.sect['.dynstr']
-
- try:
- jmprel = binary.sym.sect['.rel.plt']
- except KeyError:
- jmprel = binary.sym.sect['.rela.plt']
-
- # Elf*_Rel.r_info
- info = lambda x: ((int(x - symtab) // _symsize()) << _infoshift()) | _JMP_SLOT
-
- # The sym structure is the most picky about its location in memory. So
- # it is listed first in the main dlres struct, which can be placed at
- # the desired location.
- sym = Payload()
- sym.name = pointer("symbol_string", lambda x: x - strtab)
- sym.pad = padlen(_symsize(), b"\x00")
- sym.symbol_string = symbol_name
-
- dlres = Payload()
- dlres.symalign = padalign(_symsize(), reference=symtab)
- dlres.sym = sym
- dlres.relalign = padalign(_relsize(), reference=jmprel)
- dlres.offset = pointer()
- dlres.info = pointer("sym", info)
- return dlres
-
- def determine_address(self, start=None, end=None, n=0):
- """
- Determine recommended address for relocation structures.
-
- There are a couple considerations to make when determining the memory
- locations. First of all, the location must be writable. More
- importantly, since most items are referred to by an array index, the
- structures themselves must be properly aligned, reference to the array
- origins.
-
- The payload returned from Ret2dlresolve.reloc() has some of these
- alignments built in, but one crucial one is not. The index implied by
- Elf*_Sym's offset from the symtab base is the same index used to lookup
- symbol version information, reference to versym. The data at this index
- must constitute a valid version half-word (16-bits). This function
- attempts to ensure that the coincident version info for any returned
- value is the data \x00\x00. Getting this wrong can cause the dlresolve
- routine to crash.
-
- start (int): Minimum address to recommend (default: .bss section address)
- end (int): Maximum address to recommend (default: end of memory page)
- n (int): Return the Nth useable address within the defined range
- """
- binary = self.objects[0]
- symtab = binary.sym.sect['.dynsym']
- versym = binary.sym.sect['.gnu.version']
- bss = binary.sym.sect['.bss']
-
- if start is None: start = bss
- if end is None: end = (start & ~0xfff) + 0x1000
-
- zero_words = run_cmd(binary.path, "/x 0000")
- zero_words = [ int(x.split(" ")[0], 0) for x in zero_words ]
-
- # Size of version entry is always 2 bytes.
- veroff = [ x - versym for x in zero_words ]
- idx = [ x//2 for x in veroff if x%2 == 0 ]
- symoff = [ x * _symsize() for x in idx ]
- addr = [ x + symtab for x in symoff ]
- addr = [ x for x in addr if start <= x < end ]
-
- if len(addr) > n:
- return addr[n]
-
- raise AssertionError("Ret2dlresolve: No suitable memory location")
-
- # Overrides ROP.call()
- def call(self, reloc, *params):
- """
- Return a ROP payload to call function via dynamic linker.
-
- reloc's base address must be set appropriately.
-
- reloc (Payload): Relocation payload obtained from Ret2dlresolve.reloc()
- *params (int): Remaining positional args are passed to function.
- """
- binary = self.objects[0]
- plt = binary.sym.sect['.plt']
-
- try:
- jmprel = binary.sym.sect['.rel.plt']
- except KeyError:
- jmprel = binary.sym.sect['.rela.plt']
-
- register_params = dict(zip(arch.funcargs, params))
- stack_params = params[len(register_params):]
- index = int(reloc.offset - jmprel) // _relsize()
-
- reqs = GadHint(requirements=register_params)
- call = GadHint(index, stack=stack_params)
- ret = GadHint(self.search_gadget(arch.ret))
-
- chain = Payload()
- try: chain.requirements = self.gadget(reqs).requirements
- except KeyError: pass
- chain.alignment = padalign(0, itob(ret))
- chain.plt = plt
- chain.call = self.gadget(call)
- return chain
diff --git a/sploit/payload/rop.py b/sploit/payload/rop.py
deleted file mode 100644
index 30467de..0000000
--- a/sploit/payload/rop.py
+++ /dev/null
@@ -1,364 +0,0 @@
-from graphlib import TopologicalSorter
-
-from sploit.arch import arch, btoi, itob
-from sploit.payload.gadhint import GadHint
-from sploit.payload.payload import Payload
-from sploit.payload.payload_entry import padalign, padlen
-
-_POP_MAGIC = 0xdead
-_SPM_MAGIC = b"\x69"
-_ERROR_MAGIC = 0xbaadc0de
-
-class ROP:
- """
- ROP chain generation tool
-
- This class contains methods for automating basic return-oriented programming
- workloads, such as loading register values and calling into arbitrary
- functions or syscalls. The tools are currently designed to work on x86
- (32 or 64 bit) and ARM (32 bit only).
-
- The main appeal of the ROP class is the ability to abstract away the manual
- construction of ROP chain data, and instead make declarative statements
- like "call this function with these arguments". The ROP class will also
- utilize its supplied binary objects to automatically find and use trivial
- gadgets.
-
- The user is able to provide annotations for more complicated gadgets, which
- help instruct the class how to incorporate them into a ROP chain. This is
- done with the GadHint dataclass. GadHint objects are provided to a ROP
- instance by including them in the Symtbl of one of the binary objects it is
- constructed with. If applicable, a user-supplied gadget will take
- precedence over automatic gadget searching. See the GadHint module to learn
- more about the descriptive attributes that are supported.
-
- objects (list[ELF]): The binary objects this ROP instance will consider for
- gadget searching. If one of these is the target executable binary, it
- should appear first in the list.
-
- safe_syscalls (bool): If True, require that automatically found syscall
- instructions are immediately followed by a return instruction.
-
- align_calls (bool): If True, ensure that the stack return address into
- function calls is aligned according to the architecture alignment property.
-
- clean_stack (bool): If True, attempt to locate a cleaning gadget to "pop"
- stack data that is leftover from a function call. Required if attempting
- to make multiple calls that involve stack-based arguments.
- """
-
- def __init__(self, *objects, safe_syscalls=True, align_calls=True,
- clean_stack=True):
- """Construct new ROP builder."""
- self.objects = objects
- self.safe_syscalls = safe_syscalls
- self.align_calls = align_calls
- self.clean_stack = clean_stack
-
- def search_gadgets(self, *regexes, cont=False):
- """Return a list of matching gadgets, considering all objects."""
- results = []
- for obj in self.objects:
- results += obj.gadgets(*regexes, cont=cont)
- return results
-
- def search_gadget(self, *regexes):
- """Return the first matching gadget, considering all objects."""
- for obj in self.objects:
- try:
- return obj.gadget(*regexes)
- except:
- pass
- raise LookupError(
- f"ROP: Need to define gadget symbol for {'; '.join(regexes)}")
-
- def gadget(self, gadget):
- """
- Return a generic ROP payload.
-
- gadget (GadHint): Annotated gadget to prepare a chain from.
- """
- return self.__build_chain(gadget, {})
-
- def assign(self, **sets):
- """
- Return a ROP payload to control given registers.
-
- **sets (str:int): Keyword arguments specify register assignments to
- perform with this ROP chain. Argument names correspond to register
- names.
- """
- return self.gadget(GadHint(requirements=sets))
-
- def call(self, func, *params):
- """
- Return a ROP payload to call function.
-
- func (int): Entry address of function to call.
- *params (int): Remaining positional args are passed to func.
- """
- register_params = dict(zip(arch.funcargs, params))
- stack_params = params[len(register_params):]
- gadget = GadHint(func, requirements=register_params, stack=stack_params,
- align=self.align_calls)
- return self.gadget(gadget)
-
- def syscall(self, *params):
- """
- Return a ROP payload to call kernel.
-
- *params (int): The first argument is the syscall number. Remaining
- positional arguments are passed to the syscall.
- """
- if len(params) > len(arch.kernargs):
- raise TypeError("ROP: Too many arguments passed to syscall. "
- f"Target architecture supports up to {len(arch.kernargs)-1}.")
-
- register_params = dict(zip(arch.kernargs, params))
- sc = self.__get_gadget("syscall", {})
- return self.gadget(sc.with_requirements(register_params))
-
- def memcpy(self, dst, src):
- """
- Return a ROP payload to write data into memory.
-
- dst (int): The destination memory address.
- src (bytes): The content to write.
- """
- data = Payload()
- for idx in range(0, len(src), arch.wordsize):
- word = btoi(src[idx:idx+arch.wordsize])
- data(self.gadget(self.__get_write(dst+idx, word)))
- return data
-
- def __get_hints(self):
- """Return all user-supplied gadget hints."""
- return [h for obj in self.objects for _,h in obj.sym if type(h) is GadHint]
-
- def __discover_requirements(self, seen, graph, current):
- """
- Populate gadget dependency graph.
-
- This function recursively looks up gadgets to ensure all necessary
- required gadgets can be found, and stores this information into the
- given graph object. Established dependencies encode the order that the
- chain builder should attempt to satisfy register requirements.
- Dependency loops are detected by the TopologicalSorter.
-
- seen (set): Set of (register,value) tuples we have already discovered.
- graph (TopologicalSorter): Dependency graph model object.
- current (GadHint): Current gadget we are processing.
- """
- for r, v in current.requirements.items():
- # We key on register name _and_ value because some gadgets may
- # only be capable of storing specific values in a target register.
- # Requiring a register to store different values may require the
- # use of multiple gadgets.
- if (r, v) not in seen:
- gadget = self.__get_gadget(r, current.requirements)
-
- # Add gadget's requirements to the dependency graph.
- # We say that each requirement is a 'successor' to this
- # current gadget 'r', so that the chain builder will satisfy
- # 'r' first. This prevents the fulfillment of 'r' from
- # clobbering targets it requires, as the builder will satisfy
- # them afterward.
- for x in gadget.requirements:
- graph.add(x, r)
-
- # Treat gadget's load immediates as pseudo-requirements for
- # the sake of target ordering, following the same logic
- # as above.
- for x in gadget.imms:
- graph.add(x, r)
-
- # Mark node as visited
- seen.add((r, v))
- self.__discover_requirements(seen, graph, gadget)
-
- def __get_gadget(self, target, sets):
- """
- Get context-specific gadget.
-
- target (str): Either "ret", "syscall", or the name of a register we
- would like to modify.
-
- sets (dict{str:int}): The set of other register requirements we are
- trying to fulfill in parallel. Values may affect the gadget we decide
- to use.
- """
- # First, consider user-provided hints before automatically locating
- # gadgets.
- for hint in self.__get_hints():
- # Setup additional requirements based on hint's register moves.
- # If a mov target is in sets, require to set the src to the 'sets'
- # value.
- addl_reqs = { src:sets[dst] for dst, src in hint.movs.items() if dst in sets }
- hint = hint.with_requirements(addl_reqs)
-
- # Pops will be accounted for by the chain builder.
- # Immediates will be handled by gadget ordering in chain builder.
- # Writes are a non-issue here.
-
- if hint.syscall:
- # Only consider syscalls if the target is syscall.
- if target == "syscall":
- return hint
- elif target in hint.imms:
- if hint.imms[target] == sets[target]:
- return hint
- elif target in hint.pops:
- return hint
- elif target in hint.movs:
- return hint
-
- # Automatically locate simple gadgets
- if target == "ret":
- return GadHint(self.search_gadget(arch.ret))
-
- if target == "syscall":
- insns = [arch.syscall, arch.ret] if self.safe_syscalls else [arch.syscall]
- return GadHint(self.search_gadget(*insns), syscall=True)
-
- # target == register
- insns = [ i.format(target) for i in arch.popgad ]
- return GadHint(self.search_gadget(*insns), pops=[target])
-
- def __get_clean(self, size):
- """
- Get a stack cleaning gadget that moves sp by _at least_ size.
-
- size (int): Minimum stack pointer move.
- """
- # spm values of zero (the default) can't be trusted, as in this case
- # the user likely hasn't annotated the GadHint properly. Returning a
- # larger move than requested is fine, since the chain builder can insert
- # junk to be popped.
- for hint in self.__get_hints():
- if hint.spm >= size and hint.spm > 0:
- return hint
-
- results = self.search_gadgets(*arch.cleangad)
- table = { int(g.asm[0].group(1), 0): g for g in results }
- sizes = sorted([ x for x in table.keys() if x >= size ])
-
- if len(sizes) > 0:
- return GadHint(table[sizes[0]], spm=sizes[0])
-
- raise LookupError(
- f"ROP: Need to define a stack move gadget of at least {size}")
-
- def __get_write(self, dst, src):
- """
- Get a memory write gadget, injected with requirements for user data.
-
- dst (int): The intended memory write location.
- src (int): The intended value to write.
- """
- # If any exist, take the first write provided by user hints, assuming
- # the user's intent to specifically use _this_ write. Follow-on gadgets
- # to setup the dst and src registers must be findable.
- for hint in self.__get_hints():
- if hint.writes:
- d, s = list(hint.writes.items())[0]
- return hint.with_requirements({d:dst, s:src})
-
- # Only take an automatic write gadget if we can prove up front that its
- # requirements can be met, otherwise move on. A later search result may
- # pass the test.
- results = self.search_gadgets(*arch.writegad)
-
- for gad in results:
- d = gad.asm[0].group("dst")
- s = gad.asm[0].group("src")
-
- try:
- # Assert requirements are met.
- gadget = GadHint(gad, writes={d: s}, requirements={d:dst, s:src})
- self.__discover_requirements(set(), TopologicalSorter(), gadget)
- return gadget
- except:
- pass
-
- raise LookupError("ROP: Need to define gadgets for memory write / deps")
-
- def __build_chain(self, gadget, sets):
- """
- Generate ROP chain data for a given gadget.
-
- This function recursively builds a ROP chain for the given gadget and
- its requirements, returning the result as a Payload.
-
- gadget (GadHint): Current gadget to process.
-
- sets (dict{str:int}): The set of other register requirements we are
- trying to fulfill in parallel.
- """
- # Form a to-do-list of registers from our immediate requirements,
- # attempting to order them such that we avoid overwriting/conflicting
- # values. This may not be possible, in which case graph.static_order()
- # will raise an exception.
- reqs = gadget.requirements
- graph = TopologicalSorter({ r:set() for r in reqs })
- self.__discover_requirements(set(), graph, gadget)
- to_do_list = [ x for x in graph.static_order() if x in reqs ]
-
- chain = Payload()
-
- # Start chain by satisfying to-do-list requirements.
- if len(to_do_list) > 0:
- chain.requirements = Payload()
-
- while len(to_do_list) > 0:
- r = to_do_list[0]
- g = self.__get_gadget(r, reqs)
- c = self.__build_chain(g, reqs)
- chain.requirements[f"{r}_{reqs[r]}"] = c
-
- # This gadget may satisfy multiple items in the to-do-list.
- # Specifically, all of its pop and mov targets, and any load
- # immediates that match our requirements. Non-matching
- # immediates will be handled by a later gadget.
- imms = g.imms.keys() & reqs.keys()
- imms = [ x for x in imms if g.imms[x] == reqs[x] ]
- done = g.pops + list(g.movs) + imms
- to_do_list = [ x for x in to_do_list if x not in done ]
-
- # Append chain data to execute this gadget, but respect offset == 0
- # as a way to disable this gadget (perform a NULL gadget).
- if gadget.offset != 0:
- # Stack alignment if required.
- if gadget.align:
- ret = self.__get_gadget("ret", {})
- chain.alignment = padalign(0, itob(ret))
-
- # "Return address" entry into this gadget.
- chain.gadget = gadget.offset
-
- # The gadget's "inner stack data" will be values to be popped and
- # additional junk data to be deallocated by the gadget itself.
- if gadget.pops or gadget.spm > 0:
- chain.inner = Payload()
- chain.inner(*[ sets.get(p, _POP_MAGIC) for p in gadget.pops ])
- if gadget.spm > 0:
- chain.inner.pad = padlen(gadget.spm, _SPM_MAGIC)
-
- # The gadget's "outer stack data" will be the additional values
- # explicitly specified by the gadget. Append a separate gadget
- # to clean up these values.
- if gadget.stack:
- size = len(gadget.stack) * arch.wordsize
-
- if self.clean_stack:
- clean = self.__get_clean(size)
- chain.cleanup = clean.offset
- pad = padlen(clean.spm, _SPM_MAGIC)
- else:
- chain.cleanup = _ERROR_MAGIC
- pad = None
-
- chain.outer = Payload()
- chain.outer(*gadget.stack)
- if pad: chain.outer.pad = pad
-
- return chain
diff --git a/sploit/rev/__init__.py b/sploit/rev/__init__.py
deleted file mode 100644
index 42e2f5b..0000000
--- a/sploit/rev/__init__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-from .elf import *
-from .gadget import *
-from .ldd import *
-from .r2 import *
diff --git a/sploit/rev/elf.py b/sploit/rev/elf.py
deleted file mode 100644
index b1479d6..0000000
--- a/sploit/rev/elf.py
+++ /dev/null
@@ -1,204 +0,0 @@
-"""
-Definition of the ELF class
-"""
-
-from sploit.rev import ldd, r2
-from sploit.arch import lookup_arch
-from itertools import zip_longest
-
-class ELF:
- """
- Representation of an ELF binary file.
-
- This class is effectively a front-end for the r2 module. Through ELF, you
- can get information about an ELF binary in a convenient, object-oriented
- interface and automate some of your static analysis reverse engineering.
-
- Because much of the functionality of r2 is cached, much of the functionality
- of ELF is also implicitly cached. Longer operations like retrieving the
- symbol table or looking up gadgets will be faster on subsequent attempts.
- This is mostly useful when sploit is run from the REPL or in Pipes mode
- where this cache is preserved across script runs.
-
- Some of the behavior of this class is done upfront while other operations
- are performed lazily. Retrieving symbols, binary info, security info, and
- the list of library dependencies are all done upfront when the object is
- constructed.
-
- path (str): Absolute file path to the underlying ELF file.
-
- sym (Symtbl): A collection of named address offsets exposed through the ELF.
-
- libs (dict{str:ELF}): A dictionary of ELFs representing linked library
- dependencies of the current ELF. They are indexed by their base filename
- (i.e. elf.libs["libc.so.6"]). The actual ELF is lazily constructed when it
- is requested. Pretty printing of the libs dict is implemented.
-
- locals (->Symtbl): A psuedo-namespace to access Symtbls for the local
- variables of functions. i.e. If a function existed in the elf called foo(),
- you could get a Symtbl of its local variables with elf.locals.foo
-
- info (->str|int): A psuedo-namespace to access various info about the ELF
- file. Printing elf.info will pretty-print this info in a tabulated form.
-
- info.type (str): The type of file.
-
- info.os (str): The os the binary was compiled for.
-
- info.baddr (int): The virtual base address of the binary.
-
- info.arch_string (str): A string given by r2 iI that helps identify the
- architecture the binary was compiled for.
-
- info.wordsize (int): The natual width of an int on the architecture the
- binary was compiled for.
-
- info.endianness (str): The byte order of an int on the architecture the
- binary was compiled for.
-
- security (->bool|str): A psuedo-namespace to access security info about the
- binary. Printing elf.security will pretty-print this info in a tabulated
- form.
-
- security.stripped (bool): True if the binary was stripped of debugging
- information, symbols, and strings.
-
- security.pic (bool): True if the binary's code is position independent.
-
- security.relro (str): The level of "Relocation Read-Only" that the binary
- was compiled with. Pertains to if the Global Offset Table is read-only.
- This is often "partial" or "full".
-
- security.relocs (bool): True if the binary uses dynamic runtime relocation.
-
- security.canary (bool): True if the binary uses stack canaries.
-
- security.nx (bool): True if the binary does not have stack execution
- privileges.
-
- security.rpath (str): Runtime library lookup path. If there isn't one, this
- will say "NONE".
-
- arch (Arch): On Construction, an ELF will automatically try to figure out if
- it was compiled for one of sploit's predefined Arch's. If so, it will set it
- here. Otherwise, this is None.
- """
-
- def __init__(self, path):
- """
- Construct an ELF.
-
- path (str): The filepath to the ELF binary file.
- """
- self.path = path
- self.sym = r2.get_elf_symbols(self.path)
- try:
- libs = ldd.get_libraries(self.path)
- except:
- libs = {}
- self.libs = self.__LIBS__(libs)
- self.locals = self.__LOCALS__(self)
- bininfo = r2.get_bin_info(self.path)
- self.info = self.__BININFO__(bininfo)
- self.security = self.__SECINFO__(bininfo)
- self.arch = lookup_arch(self.info.arch_string, self.info.wordsize, self.info.endianness)
-
- def __repr__(self):
- """Pretty-print a summary of the ELF."""
- s = 'ELF: '
- s += self.path
- s += f'\n{len(self.sym)} symbols @ {hex(self.sym)}'
- column_fmt = '\n{0:36}{1:36}'
- border = '------------'
- s += column_fmt.format(border,border)
- s += column_fmt.format('Binary Info','Security Info')
- s += column_fmt.format(border,border)
- for line in zip_longest(str(self.info).split('\n'),str(self.security).split('\n'),fillvalue=''):
- s += column_fmt.format(line[0],line[1])
- s += f'\n{border}'
- s += '\nLibraries'
- s += f'\n{border}'
- s += '\n'
- s += str(self.libs)
- return s
-
- class __LIBS__(dict):
- # Fancy magic dict of {filename:ELF} which will lazy load the ELF
- def __init__(self, libs):
- super().__init__({lib.name:lib.path for lib in libs.values() if lib.path})
- def __getitem__(self, lib):
- get = super().__getitem__
- if(type(get(lib))==str):self[lib] = ELF(get(lib))
- return get(lib)
- def __repr__(self):
- s = ''
- for name,lib in self.items():
- s += '\n' + str(name) + ' => ' + (lib if(type(lib)==str) else str(lib.path))
- return s.strip()
-
- class __LOCALS__:
- # Fancy magic class that provides a psuedo-namespace to lookup locals for functions
- def __init__(self, elf):
- self.elf = elf
- def __getattr__(self, sym):
- return r2.get_locals(self.elf.path, getattr(self.elf.sym, sym))
-
- class __BININFO__:
- # Fancy magic class that provides a psuedo-namespace to get properties of the binary
- def __init__(self, bininfo):
- self.info = {
- "type" : bininfo['bintype'],
- "os" : bininfo['os'],
- "baddr" : bininfo['baddr'],
- "arch_string" : bininfo['arch'],
- "wordsize" : bininfo['bits']//8,
- "endianness" : bininfo['endian'],
- }
- def __getattr__(self, k):
- return self.info[k]
- def __repr__(self):
- s = ''
- for name,val in self.info.items():
- if name == 'baddr': val = hex(val)
- s += '\n{0:14}{1}'.format(name,val)
- return s.strip()
-
- class __SECINFO__(__BININFO__):
- # Fancy magic class that provides a psuedo-namespace to get security properties of the binary
- def __init__(self, bininfo):
- self.info = {
- "stripped" : bininfo['stripped'],
- "pic" : bininfo['pic'],
- "relro" : bininfo.get('relro',''),
- "relocs" : bininfo['relocs'],
- "canary" : bininfo['canary'],
- "nx" : bininfo['nx'],
- "rpath" : bininfo['rpath'],
- }
-
- def retaddr(self, caller, callee):
- """
- Returns a list of addresses where a function returns into another
- function at.
-
- caller (int): Address of the calling function to be returned into.
-
- callee (int): Address of the function that was called and will return.
- """
- return [c.ret_addr for c in r2.get_call_returns(self.path, caller, callee)]
-
- def gadgets(self, *regexes, cont=False):
- """
- Returns a list of gadgets that match the given regex list.
-
- *regexes (str): All positional arguments are treated as regex strings
- for the gadget search.
-
- cont (bool): If true, this function will return all of the assembly past
- the found gadget up to the next return point.
- """
- return [ self.sym[g] for g in r2.rop_gadgets(self.path, *regexes, cont=cont) ]
-
- def gadget(self, *regexes):
- """Returns the first gadget found that matches the given regex list."""
- return self.sym[r2.rop_gadget(self.path, *regexes)]
diff --git a/sploit/rev/gadget.py b/sploit/rev/gadget.py
deleted file mode 100644
index cc69723..0000000
--- a/sploit/rev/gadget.py
+++ /dev/null
@@ -1,25 +0,0 @@
-from dataclasses import dataclass, field
-from sploit.types.index_entry import IndexEntry
-
-@dataclass
-class Gadget(IndexEntry):
- """
- Basic gadget description object
-
- base (int): The location this gadget is found at. What `base` is relative
- to depends on context.
-
- asm (list[re.Match]): A list of assembly instructions matched by the gadget
- search query.
- """
-
- base: int = 0
- asm: list = field(default_factory=list)
-
- def __repr__(self):
- """Return human-readable Gadget."""
- s = hex(self.base)
- if len(self.asm) > 0:
- asm = "; ".join([ m.string for m in self.asm ])
- s += f", '{asm}'"
- return f"Gadget({s})"
diff --git a/sploit/rev/ldd.py b/sploit/rev/ldd.py
deleted file mode 100644
index b773abf..0000000
--- a/sploit/rev/ldd.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from sploit.util.cmd import run_cmd_cached
-from sploit.util.log import ilog
-
-import re
-from collections import namedtuple as nt
-
-def get_libraries(elf):
- ilog(f'Retrieving linked libraries of {elf} with ldd...')
- out = run_cmd_cached(['ldd',elf])
- out = [re.split(r'\s+',lib)[1:] for lib in out]
- Lib = nt("Lib", "name path addr")
- out = {l[0]:Lib(l[0],l[0] if l[0][0]=='/' else l[2] if l[1]=='=>' else None,l[-1]) for l in out}
- return out
diff --git a/sploit/rev/r2.py b/sploit/rev/r2.py
deleted file mode 100644
index e81adc9..0000000
--- a/sploit/rev/r2.py
+++ /dev/null
@@ -1,139 +0,0 @@
-from sploit.arch import arch
-from sploit.rev.gadget import Gadget
-from sploit.symtbl import Symtbl
-from sploit.util.cmd import run_cmd_cached
-from sploit.util.log import ilog
-
-from collections import namedtuple as nt
-from functools import cache
-import json
-import re
-
-def run_cmd(binary,cmd):
- return run_cmd_cached(['r2','-q','-c',cmd,'-e','scr.color=false','-e','rop.len=10','-e','search.in=io.maps.x',binary])
-
-def get_elf_symbols(elf):
- ilog(f'Retrieving symbols of {elf} with r2...')
-
- base = get_bin_info(elf)['baddr']
-
- sect = json.loads(run_cmd(elf,'iSj')[0])
- sect = {s['name']:s['vaddr'] for s in sect}
-
- syms = json.loads(run_cmd(elf,'isj')[0])
- syms = [s for s in syms if s['type'] in ['OBJ', 'FUNC', 'NOTYPE']]
-
- plt = [s for s in syms if s['is_imported']]
- plt = {sym['realname']:sym['vaddr'] for sym in plt}
- plt = Symtbl(base=sect.get('.plt',0), **plt)
-
- syms = [s for s in syms if not s['is_imported']]
- syms = {sym['realname']:sym['vaddr'] for sym in syms}
- syms = Symtbl(base=base, **syms)
-
- got = json.loads(run_cmd(elf,'irj')[0])
- got = {sym['name']:sym['vaddr'] for sym in got if 'name' in sym}
- got = Symtbl(base=sect.get('.got',0), **got)
-
- strings = json.loads(run_cmd(elf,'izj')[0])
- strings = {s['string']:s['vaddr'] for s in strings}
- strings = Symtbl(base=sect.get('.rodata',0), **strings)
-
- sect = Symtbl(**sect)
- syms.sect = sect
- syms.imp = plt
- syms.rel = got
- syms.str = strings
- return syms
-
-def get_locals(binary,func):
- ilog(f'Retrieving local stack frame of {hex(func)} in {binary} with r2...')
-
- addr = hex(func)
- cmd_locals = f's {func};af;aafr;aaft;afvf'
- out = run_cmd(binary,cmd_locals)
- out = [re.split(r':?\s+',var) for var in out]
- out = {var[1]:-(int(var[0],0)-arch.wordsize) for var in out}
- return Symtbl(sbp=0, **out)
-
-def rop_json(binary):
- # Gadget JSON schema:
- # [
- # {
- # retaddr: int
- # size: int
- # opcodes: [
- # {
- # offset: int
- # size: int
- # opcode: string
- # type: string
- # }
- # ]
- # }
- # ]
- return json.loads("\n".join(run_cmd(binary, "/Rj")))
-
-@cache
-def rop_gadgets(binary, *regexes, cont=False):
- ilog(f"Searching {binary} for {'; '.join(regexes)} gadgets with r2...")
- gadgets = rop_json(binary)
- results = []
- result_offsets = []
- base = get_bin_info(binary)['baddr']
-
- for gadget in gadgets:
- opcodes = gadget['opcodes']
- end_idx = len(opcodes) - len(regexes)
-
- for start_idx in range(end_idx + 1):
- idx = start_idx
- size = end_idx - idx
- regexes_use = (regexes + (".*",) * size) if cont else regexes
-
- offset = opcodes[idx]['offset'] - base
- if offset in result_offsets:
- continue
-
- matches = []
-
- for regex in regexes_use:
- match = re.fullmatch(regex, opcodes[idx]['opcode'])
- if not match:
- break
- matches.append(match)
- idx += 1
-
- if len(matches) == len(regexes_use):
- results.append(Gadget(offset, matches))
- result_offsets.append(offset)
-
- return results
-
-def rop_gadget(binary, *regexes):
- results = rop_gadgets(binary, *regexes)
- if len(results) == 0:
- raise LookupError(f"Could not find gadget for: {'; '.join(regexes)}")
- return results[0]
-
-@cache
-def get_call_returns(binary,xref_from,xref_to):
- ilog(f'Getting return addresses of calls from {hex(xref_from)} to {hex(xref_to)} in {binary} with r2...')
-
- cmd_xrefs = f's {hex(xref_from)};af;axq'
- xrefs = run_cmd(binary,cmd_xrefs)
- xrefs = [re.split(r'\s+',x) for x in xrefs]
- xrefs = [x for x in xrefs if int(x[2],0)==xref_to]
- rets = []
- CallRet = nt("CallRet", "xref_from xref_to call_addr ret_addr")
- for x in xrefs:
- cmd_ret = f's {x[0]};so;s'
- ret = run_cmd(binary,cmd_ret)
- rets.append(CallRet(xref_from,xref_to,int(x[0],0),int(ret[0],0)))
- return rets
-
-@cache
-def get_bin_info(binary):
- ilog(f'Retrieving binary and security info about {binary} with r2...')
-
- return json.loads(run_cmd(binary,'iIj')[0])
diff --git a/sploit/symtbl.py b/sploit/symtbl.py
deleted file mode 100644
index 86800f5..0000000
--- a/sploit/symtbl.py
+++ /dev/null
@@ -1,160 +0,0 @@
-"""
-Symtbl data structure
-
-A Symtbl (symbol table) is an associative data container intended to model
-arbitrary memory layouts, such as structure definitions or memory-mapped
-objects. Elements may be accessed via subscript or attribute notation.
-
-A Symtbl is essentially a dictionary, in which each key (symbol name string)
-is associated with an offset value. A special key "base" represents the
-base or starting address of the overall table in memory. Whenever offset
-values are accessed, they are adjusted relative to the table's base value.
-This enables the primary function of Symtbl objects: the ability to resolve
-mapped, or absolute, addresses of objects in memory.
-
-Therefore, even though a Symtbl internally tracks symbol offsets, the apparent
-value of any symbol will always be its offset plus the table's base address.
-The table's base address will also be subtracted from values being stored in
-the table, as the provided value is assumed to be mapped in the same manner as
-the table itself.
-
- s = Symtbl()
- s.a = 10
- s.b = 20
- print(s.a, s.b) # "10 20"
- s.base = 100
- print(s.a, s.b) # "110 120"
- s.c = 150
- s.base = 10
- print(s.a, s.b, s.c) # "20 30 60"
-
-A Symtbl's base value may be changed at any time, and this will affect the
-interpretation of offsets as described above. However, one may also create a
-remapped version of a Symtbl (without modifying the original) using the '@'
-operator. This new object will have the base value given on the right hand
-side of the '@' and its collection of symbols is referentially linked to the
-source object, meaning changes to symbol entries will be visible in both
-objects.
-
- s1 = Symtbl()
- s1.a = 10
- s2 = s1 @ 1000
- print(s1.a, s2.a) # "10 1010"
- s2.b = 1234
- print(s1.b, s2.b) # "234 1234"
-
-Symtbl's are also nestable, to support modeling composite memory layouts. If
-a symbol's value is assigned to another Symtbl object, rather than an integer
-offset, the child object's base value serves as its offset in the parent
-Symtbl. Symbols on the child object may then be accessed recursively from the
-parent's scope. If the parent has a non-zero base, it adjusts the offsets
-interpreted in the child.
-
- child = Symtbl()
- child.a = 1
- child.b = 2
- parent = Symtbl()
- parent.nested = child @ 70
- print(parent.nested.a, parent.nested.b) # "71 72"
-
-A Symtbl will allow you to uniformly adjust all offsets contained, while leaving
-the base value the same, using the '<<' and '>>' operators. A custom
-"rebase" operation is also available via the "%" operator. A rebase applies
-a uniform shift, such that the right hand side offset operand ends up coinciding
-with the Symtbl base address.
-
- s = Symtbl()
- s.a = 1
- s.b = 2
- s.c = 3
- s.d = 4
- s.base = 1000
- s %= s.c # rebase at symbol 'c'
- 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.
-
- 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(base, 0, dict())
- for k, v in symbols.items():
- self[k] = v
- return self
-
-class SymtblImpl(IndexTbl):
- """Symtbl implementation class"""
-
- def __init__(self, base, adjust, entries):
- """Construct Symtbl from instance data."""
- super().__init__(base)
- object.__setattr__(self, "__adjust__", adjust)
- object.__setattr__(self, "__entries__", 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 key, value in self:
- key = f"[{key}]" if isinstance(value, IndexEntry) else key
- s += FMT.format(hex(value), key)
-
- return s
-
- def __rshift__(self, offset):
- """Return symbol-adjusted version of object."""
- adjust = self.__adjust__ + int(offset)
- return SymtblImpl(self.base, adjust, self.__entries__)
-
- def __lshift__(self, offset):
- """Return symbol-adjusted version of object."""
- return self >> (-offset)
-
- def __mod__(self, offset):
- """Return symbol-rebased version of object."""
- return self >> (self.base - offset)
-
- # IndexTbl abstract methods
-
- def __copy__(self):
- """Return copy of object with shared symbol entries."""
- return SymtblImpl(self.base, self.__adjust__, self.__entries__)
-
- 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 __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 __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 __delindex__(self, index):
- """Delete symbol."""
- del self.__entries__[index]
diff --git a/sploit/types/__init__.py b/sploit/types/__init__.py
deleted file mode 100644
index a618162..0000000
--- a/sploit/types/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from .indextbl import *
-from .index_entry import *
-from .lict import *
diff --git a/sploit/types/index_entry.py b/sploit/types/index_entry.py
deleted file mode 100644
index a03ab92..0000000
--- a/sploit/types/index_entry.py
+++ /dev/null
@@ -1,44 +0,0 @@
-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
deleted file mode 100644
index 4f57a59..0000000
--- a/sploit/types/indextbl.py
+++ /dev/null
@@ -1,103 +0,0 @@
-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)
diff --git a/sploit/types/lict.py b/sploit/types/lict.py
deleted file mode 100644
index ab6cb1f..0000000
--- a/sploit/types/lict.py
+++ /dev/null
@@ -1,202 +0,0 @@
-from collections.abc import MutableSequence, MutableMapping
-
-class Lict(MutableSequence, MutableMapping):
- """
- List / dictionary hybrid container
-
- Lict attempts to provide an API for a list which supports optional element
- keys in addition to normal positional indices. For Lict, index types are
- int and slice. Keys may be any other type except for None, as None
- indicates an unkeyed value.
-
- Nearly all of the operations you'd except to perform on list or dict are
- implemented here. However, in cases of conflict, the list behavior is
- usually preferred (for example: iter(Lict) iterates over values instead of
- keys). In general, Licts are slightly more list-like, since keys are
- optional, but sequence is required.
-
- Licts can be constructed from any iterable, including other Licts. When
- constructing from mappings, similar heuristics as dict's are used to parse
- key values.
-
- In addition to keys and indices, slices may be used to return, modify, or
- delete a portion of a Lict. The start and end fields of a slice may be
- either keys or indices, however the step field must be an integer as usual.
-
- When assigning to a non-existent key, the new element is sequentially
- inserted at the end of the Lict.
- """
-
- def __init__(self, values=None):
- """Construct new Lict, optionally populated by the given iterable."""
- self.__keys = []
- self.__vals = []
- if values is not None:
- self.extend(values)
-
- def __repr__(self):
- """Return human-readable Lict."""
- s = ""
- for i in range(len(self)):
- if self.__keys[i] is not None:
- s += f"{repr(self.__keys[i])}: "
- s += f"{repr(self.__vals[i])}, "
- return f"{{[{s[:-2]}]}}"
-
- def __copy__(self):
- """Return shallow copy of object."""
- return self.copy()
-
- def copy(self):
- """Return shallow copy of object."""
- return Lict(self)
-
- def key2idx(self, arg):
- """
- Return value of index type for the given input.
-
- For keys, return the corresponding index, or raise KeyError.
- For slices, return a slice with components converted to index type.
-
- If arg is already an index (or None) it is returned as-is with no
- assertions made.
- """
- if isinstance(arg, slice):
- return slice(self.key2idx(arg.start), self.key2idx(arg.stop), arg.step)
- if isinstance(arg, int) or arg is None:
- return arg
- try:
- return self.__keys.index(arg)
- except ValueError as ex:
- raise KeyError(f"Lict: Key does not exist: {arg}") from ex
-
- def idx2key(self, arg):
- """
- Return value of key type for the given input.
-
- For indices, return the corresponding key, None, or raise IndexError.
- For slices, return a slice with components converted to key type.
-
- If arg is already a key type (or None) it is returned as-is with no
- assertions made.
- """
- if isinstance(arg, slice):
- return slice(self.idx2key(arg.start), self.idx2key(arg.stop), arg.step)
- if not isinstance(arg, int) or arg is None:
- return arg
- return self.__keys[arg]
-
- def haskey(self, arg):
- """
- Test existence of key in Lict object.
-
- `x in Lict` only tests for the _value_ x in Lict. This is consistent
- with list behavior. This method is provided to test keys as well.
- Raises TypeError on any index type.
- """
- if arg is None:
- return False
- if isinstance(arg, (int, slice)):
- raise TypeError(f"Lict: Unsupported key type: {type(arg)}")
- return arg in self.__keys
-
- def __assign_slice(self, i, value):
- """Update Lict values according to element slice."""
- value = Lict(value)
- tmp = self.copy()
- tmp.__keys[i] = value.__keys
- tmp.__vals[i] = value.__vals
-
- check_keys = [ x for x in tmp.__keys if x is not None ]
- if len(check_keys) != len(set(check_keys)):
- raise ValueError("Lict: Slice assignment results in duplicate keys")
-
- self.__keys = tmp.__keys
- self.__vals = tmp.__vals
-
- # collections.abc abstract methods
-
- def __len__(self):
- """Return number of elements in Lict."""
- assert len(self.__keys) == len(self.__vals)
- return len(self.__keys)
-
- def __getitem__(self, arg):
- """Return value for given index, key, or slice."""
- i = self.key2idx(arg)
- if isinstance(i, slice):
- return Lict(zip(self.__keys[i], self.__vals[i]))
- return self.__vals[i]
-
- def __setitem__(self, arg, value):
- """Set value for given index, key, or slice."""
- try:
- i = self.key2idx(arg)
- except KeyError:
- self.append(value, arg)
- else:
- if isinstance(i, slice):
- self.__assign_slice(i, value)
- else:
- self.__vals[i] = value
-
- def __delitem__(self, arg):
- """Delete value for given index, key, or slice."""
- i = self.key2idx(arg)
- del self.__keys[i]
- del self.__vals[i]
-
- def insert(self, where, value, key=None):
- """Insert value into Lict. Optionally apply the given key."""
- if self.haskey(key):
- raise KeyError(f"Lict: Key is not unique: {key}")
- i = self.key2idx(where)
- self.__keys.insert(i, key)
- self.__vals.insert(i, value)
-
- # Sequence overrides
-
- def append(self, value, key=None):
- """Append value to Lict. Optionally apply the given key."""
- self.insert(len(self), value, key)
-
- def extend(self, values):
- """Append all values in given iterable to the Lict."""
- try: values = [ [k, v] for k, v in values.items() ]
- except:
- try: values = [ [k, v] for k, v in iter(values) ]
- except: values = [ [None, v] for v in iter(values) ]
- for k, v in values:
- self.append(v, k)
-
- def reverse(self):
- """Reverse the sequence of Lict in-place. Keys follow values."""
- self.__keys.reverse()
- self.__vals.reverse()
-
- # Mapping overrides
-
- def get(self, key, default=None):
- """Return value for given key, or default if unable."""
- try: return self[key]
- except: return default
-
- def popitem(self):
- """Pop a key-value pair from the Lict and return it."""
- return (self.__keys.pop(), self.__vals.pop())
-
- def items(self):
- """Return an iterable of key-value pairs."""
- return list(zip(self.__keys, self.__vals))
-
- def keys(self):
- """Return an iterable of the Lict's keys."""
- return [ x for x in self.__keys if x is not None ]
-
- def values(self):
- """Return an iterable of the Lict's values."""
- return list(self.__vals)
-
- def update(self, values):
- """Method is unsupported."""
- raise NotImplementedError
diff --git a/sploit/until.py b/sploit/until.py
deleted file mode 100644
index b4f390f..0000000
--- a/sploit/until.py
+++ /dev/null
@@ -1,14 +0,0 @@
-from functools import partial as bind
-import re
-
-def lastline(pred, /, *args, **kwargs):
- s = args[-1]
- args = args[:-1]
- p = bind(pred, *args, **kwargs)
- return p(s[-1])
-
-def contains(regex, s):
- return re.search(regex, s)
-
-def equals(regex, s):
- return re.fullmatch(regex, s)
diff --git a/sploit/util/__init__.py b/sploit/util/__init__.py
deleted file mode 100644
index 32a079b..0000000
--- a/sploit/util/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-from .cmd import *
-from .log import *
diff --git a/sploit/util/cmd.py b/sploit/util/cmd.py
deleted file mode 100644
index 3a2b842..0000000
--- a/sploit/util/cmd.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from os import path
-from subprocess import run
-
-def run_cmd(cmd,cwd=None):
- return run(cmd,cwd=cwd,capture_output=True,text=True,check=True).stdout.split('\n')[:-1]
-
-__RUN_CACHE__ = {}
-def run_cmd_cached(cmd,cwd=None):
- key = ''.join(cmd)
- if key in __RUN_CACHE__:
- return __RUN_CACHE__[key]
- else:
- result = run_cmd(cmd,cwd)
- __RUN_CACHE__[key] = result
- return result
-
-#try to get the version through git
-def git_version():
- try:
- cwd = path.dirname(path.realpath(__file__))
- version = run_cmd(["git","describe","--always","--first-parent","--dirty"],cwd=cwd)[0]
- #PEP440 compliance
- version = version.replace('-','+',1).replace('-','.')
- return version
- except:
- return "0+unknown.version"
diff --git a/sploit/util/log.py b/sploit/util/log.py
deleted file mode 100644
index 823b252..0000000
--- a/sploit/util/log.py
+++ /dev/null
@@ -1,32 +0,0 @@
-import codecs
-import sys
-
-# https://docs.python.org/3/library/codecs.html#standard-encodings
-ENCODING = None
-
-ERROR = 31
-WARNING = 33
-STATUS = 32
-NORMAL = 0
-ALT = 90
-
-def enc_value(value, enc):
- if type(value) is bytes:
- if enc is not None:
- value = codecs.encode(value, enc)
- elif ENCODING is not None:
- value = codecs.encode(value, ENCODING)
- value = str(value)[2:-1] # strip b''
- return str(value)
-
-def generic_log(*values, sep, end, file, flush, enc, color):
- string = sep.join([ enc_value(x, enc) for x in values ])
- print(f'\033[{color}m{string}\033[0m', end=end, file=file, flush=flush)
-
-# For library internal use
-def ilog(*values, sep=' ', end='\n', file=sys.stderr, flush=True, enc=None, color=STATUS):
- generic_log(*values, sep=sep, end=end, file=file, flush=flush, enc=enc, color=color)
-
-# For external use in user script (via print = elog)
-def elog(*values, sep=' ', end='\n', file=sys.stdout, flush=True, enc=None, color=ALT):
- generic_log(*values, sep=sep, end=end, file=file, flush=flush, enc=enc, color=color)