summaryrefslogtreecommitdiffstats
path: root/sploit/rev
diff options
context:
space:
mode:
Diffstat (limited to 'sploit/rev')
-rw-r--r--sploit/rev/__init__.py4
-rw-r--r--sploit/rev/elf.py204
-rw-r--r--sploit/rev/gadget.py25
-rw-r--r--sploit/rev/ldd.py13
-rw-r--r--sploit/rev/r2.py139
5 files changed, 0 insertions, 385 deletions
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])