diff options
Diffstat (limited to 'sploit/rev')
-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 |
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]) |