From f587eadbcc87e77a6e8b0f65038b3fa27df782ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20Bastian?= Date: Thu, 26 Apr 2018 19:16:01 +0200 Subject: [PATCH] Add a tentative stack walker using eh_elfs This is only a beginning: this does so far load the appropriate eh_elf.so files and read a context. Unwinding is still absent. --- stack_walker/Makefile | 18 ++++ stack_walker/stack_walker.cpp | 151 ++++++++++++++++++++++++++++++++++ stack_walker/stack_walker.hpp | 37 +++++++++ 3 files changed, 206 insertions(+) create mode 100644 stack_walker/Makefile create mode 100644 stack_walker/stack_walker.cpp create mode 100644 stack_walker/stack_walker.hpp diff --git a/stack_walker/Makefile b/stack_walker/Makefile new file mode 100644 index 0000000..3cb03eb --- /dev/null +++ b/stack_walker/Makefile @@ -0,0 +1,18 @@ +CXX=g++ +CXXLIBS=-ldl +CXXFLAGS=-O0 -g -fPIC -Wall -Wextra + +TARGET=libstack_walker.so +OBJS=stack_walker.o + +all: $(TARGET) + +$(TARGET): $(OBJS) + $(CXX) $(CXXFLAGS) -shared $< $(CXXLIBS) -o $@ + +%.o: %.cpp + $(CXX) $(CXXFLAGS) -c $< -o $@ + + +clean: + rm -f $(OBJS) $(TARGET) diff --git a/stack_walker/stack_walker.cpp b/stack_walker/stack_walker.cpp new file mode 100644 index 0000000..c417072 --- /dev/null +++ b/stack_walker/stack_walker.cpp @@ -0,0 +1,151 @@ +#include "stack_walker.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + + +typedef void* dl_handle_t; + +struct MemoryMapEntry { + MemoryMapEntry(): + beg(0), end(0), offset(0), obj_path(), eh_dl_handle(nullptr) + {} + + uintptr_t beg, end; + int offset; + std::string obj_path; + + dl_handle_t eh_dl_handle; +}; + +typedef std::map MemoryMap; + +static MemoryMap memory_map; + + +std::string readlink_rec(const char* path) { + char buf[2][1024]; + int parity = 1; + strcpy(buf[1], path); + + do { + int rc = readlink(buf[parity], buf[1-parity], 1024); + parity = 1 - parity; + if(rc < 0) { + if(errno == EINVAL) + break; + } + } while(true); + + return std::string(buf[1 - parity]); +} + +int fill_memory_map_callback( + struct dl_phdr_info* info, + size_t /*size*/, + void* /*data*/) +{ + for(int sec = 0; sec < info->dlpi_phnum; ++sec) { + const ElfW(Phdr)& cur_hdr = info->dlpi_phdr[sec]; + if(cur_hdr.p_type != PT_LOAD || (cur_hdr.p_flags & PF_X) == 0) + continue; + if(std::string(info->dlpi_name).find("linux-vdso") + != std::string::npos) + { + continue; + } + + MemoryMapEntry entry; + entry.beg = info->dlpi_addr + cur_hdr.p_vaddr; + entry.obj_path = std::string(info->dlpi_name); + entry.offset = cur_hdr.p_offset; + + entry.end = entry.beg + cur_hdr.p_memsz; + + if(entry.obj_path.empty()) { // The source binary itself + entry.obj_path = readlink_rec("/proc/self/exe"); + } + + memory_map.insert(std::make_pair(entry.beg, entry)); + } + return 0; +} + +void stack_walker_close() { + for(auto& mmap_entry_pair: memory_map) { + auto& mmap_entry = mmap_entry_pair.second; + + if(mmap_entry.eh_dl_handle != nullptr) + dlclose(mmap_entry.eh_dl_handle); + } +} + +bool stack_walker_init() { + if(dl_iterate_phdr(&fill_memory_map_callback, nullptr) != 0) { + stack_walker_close(); + return false; + } + + for(const auto& mmap_entry: memory_map) { + printf("%012lx-%012lx %08x %s\n", + mmap_entry.second.beg, + mmap_entry.second.end, + mmap_entry.second.offset, + mmap_entry.second.obj_path.c_str()); + } + + for(auto& mmap_entry_pair: memory_map) { + auto& mmap_entry = mmap_entry_pair.second; + + // Find SO's basename + size_t last_slash = mmap_entry.obj_path.rfind("/"); + if(last_slash == std::string::npos) + last_slash = 0; + else + last_slash++; + std::string basename(mmap_entry.obj_path, last_slash); + + // Load the SO + std::string eh_elf_name = basename + ".eh_elf.so"; + mmap_entry.eh_dl_handle = dlopen(eh_elf_name.c_str(), RTLD_LAZY); + + if(mmap_entry.eh_dl_handle == nullptr) { + fprintf(stderr, + "Error: cannot load shared object %s.\ndlerror: %s\n", + eh_elf_name.c_str(), + dlerror()); + stack_walker_close(); + return false; + } + } + + return true; +} + +unwind_context_t get_context() { + unwind_context_t out; + ucontext_t uctx; + + if(getcontext(&uctx) < 0) { + assert(0); // Cleaner code will come later — TODO + } + + out.rip = uctx.uc_mcontext.gregs[REG_RIP]; + out.rsp = uctx.uc_mcontext.gregs[REG_RSP]; + out.rbp = uctx.uc_mcontext.gregs[REG_RBP]; + + unwind_context(out); + return out; +} + +bool unwind_context(unwind_context_t& ctx) { +} + +void walk_stack(const std::function& mapped) { +} diff --git a/stack_walker/stack_walker.hpp b/stack_walker/stack_walker.hpp new file mode 100644 index 0000000..970ebe1 --- /dev/null +++ b/stack_walker/stack_walker.hpp @@ -0,0 +1,37 @@ +/** Provides stack walking facilities exploiting the eh_elf objects, that can + * be loaded at runtime. */ + + +#pragma once + +#include +#include + +#include "../shared/context_struct.h" + + +/** Initialize the stack walker. This must be called only once. + * + * \return true iff everything was correctly initialized. + */ +bool stack_walker_init(); + +/** Deallocate everything that was allocated by the stack walker */ +void stack_walker_close(); + +/** Get the unwind context of the point from which this function was called. + * + * This context must then be exploited straight away: it is unsafe to alter the + * call stack before using it, in particular by returning from the calling + * function. */ +unwind_context_t get_context(); + +/** Unwind the passed context once, in place. + * + * Returns `true` if the context was actually unwinded, or `false` if the end + * of the call stack was reached. */ +bool unwind_context(unwind_context_t& ctx); + +/** Call the passed function once per frame in the call stack, most recent + * frame first, with the current context as its sole argument. */ +void walk_stack(const std::function& mapped);