From a4d59d09f719c46fb0a107bc60f5d7e15548bb2e Mon Sep 17 00:00:00 2001 From: mollismerx Date: Fri, 16 Sep 2016 17:45:35 +0200 Subject: [PATCH] Initial version --- Arg.h | 51 ++++++++ Call.cpp | 11 ++ Call.h | 46 +++++++ Capture.h | 59 +++++++++ ELFInfo.cpp | 311 ++++++++++++++++++++++++++++++++++++++++++++++++ ELFInfo.h | 73 ++++++++++++ ELFObject.h | 19 +++ Error.cpp | 28 +++++ Error.h | 14 +++ Fail.cpp | 14 +++ Fail.h | 17 +++ Fake.h | 60 ++++++++++ Function.h | 22 ++++ GOTEntry.cpp | 243 +++++++++++++++++++++++++++++++++++++ GOTEntry.h | 94 +++++++++++++++ Hook.h | 115 ++++++++++++++++++ HookBase.h | 95 +++++++++++++++ HookImpl.h | 82 +++++++++++++ Lambda.h | 75 ++++++++++++ MFile.cpp | 40 +++++++ MFile.h | 52 ++++++++ Method.h | 75 ++++++++++++ MethodInfo.h | 26 ++++ MethodPointer.h | 34 ++++++ Profiler.h | 105 ++++++++++++++++ README | 1 + Report.cpp | 29 +++++ Report.h | 24 ++++ Result.h | 47 ++++++++ SPY.cpp | 13 ++ SPY.h | 141 ++++++++++++++++++++++ Thunk.h | 28 +++++ ThunkHandle.h | 94 +++++++++++++++ Variadic.h | 38 ++++++ demo.cpp | 53 +++++++++ demo.h | 26 ++++ example.cpp | 105 ++++++++++++++++ example1.cpp | 32 +++++ example2.cpp | 26 ++++ example3.cpp | 41 +++++++ example5.cpp | 20 ++++ example6.cpp | 43 +++++++ example7.cpp | 31 +++++ example9.cpp | 32 +++++ makefile | 56 +++++++++ 45 files changed, 2641 insertions(+) create mode 100644 Arg.h create mode 100644 Call.cpp create mode 100644 Call.h create mode 100644 Capture.h create mode 100644 ELFInfo.cpp create mode 100644 ELFInfo.h create mode 100644 ELFObject.h create mode 100644 Error.cpp create mode 100644 Error.h create mode 100644 Fail.cpp create mode 100644 Fail.h create mode 100644 Fake.h create mode 100644 Function.h create mode 100644 GOTEntry.cpp create mode 100644 GOTEntry.h create mode 100644 Hook.h create mode 100644 HookBase.h create mode 100644 HookImpl.h create mode 100644 Lambda.h create mode 100644 MFile.cpp create mode 100644 MFile.h create mode 100644 Method.h create mode 100644 MethodInfo.h create mode 100644 MethodPointer.h create mode 100644 Profiler.h create mode 100644 README create mode 100644 Report.cpp create mode 100644 Report.h create mode 100644 Result.h create mode 100644 SPY.cpp create mode 100644 SPY.h create mode 100644 Thunk.h create mode 100644 ThunkHandle.h create mode 100644 Variadic.h create mode 100644 demo.cpp create mode 100644 demo.h create mode 100644 example.cpp create mode 100644 example1.cpp create mode 100644 example2.cpp create mode 100644 example3.cpp create mode 100644 example5.cpp create mode 100644 example6.cpp create mode 100644 example7.cpp create mode 100644 example9.cpp create mode 100644 makefile diff --git a/Arg.h b/Arg.h new file mode 100644 index 0000000..e022e8a --- /dev/null +++ b/Arg.h @@ -0,0 +1,51 @@ +#ifndef ELFSPY_ARG_H +#define ELFSPY_ARG_H + +#include "elfspy/Variadic.h" +#include "elfspy/Thunk.h" +#include "elfspy/ThunkHandle.h" +#include "elfspy/Capture.h" + +namespace spy +{ + +/** + * @namespace spy + * @class Arg + * This captures one argument of an invocation + */ + +template +class Arg : public Thunk, + public Capture::Type> +{ +public: + virtual void invoke(ArgTypes&&... args) override; +}; + +template +void Arg::invoke(ArgTypes&&... args) +{ + using Var = Variadic; + // forward arguments to avoid copying + this->captures_.push_back(Var::get(std::forward(args)...)); +} + +template +inline auto arg(H& hook) +-> ThunkHandle::template Type> +{ + return hook.thunks(); +} + +template +inline auto new_arg(H& hook) +-> ThunkHandle::template Type>* +{ + using TP = ThunkHandle::template Type>; + return new TP(hook.thunks()); +} + +} // namespace elfspy + +#endif diff --git a/Call.cpp b/Call.cpp new file mode 100644 index 0000000..80f4a07 --- /dev/null +++ b/Call.cpp @@ -0,0 +1,11 @@ +#include "Call.h" + +namespace spy +{ + +void Call::invoke() +{ + ++count_; +} + +} // namespace spy diff --git a/Call.h b/Call.h new file mode 100644 index 0000000..aa81446 --- /dev/null +++ b/Call.h @@ -0,0 +1,46 @@ +#ifndef ELFSPY_CALL_H +#define ELFSPY_CALL_H + +#include "elfspy/Thunk.h" +#include "elfspy/ThunkHandle.h" + +namespace spy +{ + +/** + * @namespace spy + * @class Call + * Will count each call made to the Thunk's function + */ + +class Call : public Thunk<> +{ +public: + virtual void invoke() override; + + std::size_t count() const; + +private: + std::size_t count_ = 0; +}; + +inline std::size_t Call::count() const +{ + return count_; +} + +template +inline ThunkHandle call(H&& hook) +{ + return hook.no_arg_thunks(); +} + +template +inline ThunkHandle* new_call(H&& hook) +{ + return new ThunkHandle(hook.no_arg_thunks()); +} + +} // namespace spy + +#endif diff --git a/Capture.h b/Capture.h new file mode 100644 index 0000000..00bdadc --- /dev/null +++ b/Capture.h @@ -0,0 +1,59 @@ +#ifndef ELFSPY_CAPTURE_H +#define ELFSPY_CAPTURE_H + +#include + +namespace spy +{ + +/* + * @namespace spy + * @class Capture + * Base class for holding copies of 1 or nore values encountered during + * invocations. Provides methods for comparison in asserts after invocation. + */ + +template +class Capture +{ +public: + using Container = std::vector; + using const_iterator = typename Container::const_iterator; + const_iterator begin() const; + const_iterator end() const; + std::size_t size() const; + const T& value(std::size_t index = 0) const; + +protected: + Container captures_; +}; + +template +inline typename Capture::const_iterator Capture::begin() const +{ + return captures_.begin(); +} + +template +inline typename Capture::const_iterator Capture::end() const +{ + return captures_.end(); +} + +template +//inline typename Capture::size_type Capture::size() const +inline std::size_t Capture::size() const +{ + return captures_.size(); +} + +template +const T& Capture::value(std::size_t index) const +{ + return captures_[index]; +} + +} // namespace spy + +#endif + diff --git a/ELFInfo.cpp b/ELFInfo.cpp new file mode 100644 index 0000000..76a13ef --- /dev/null +++ b/ELFInfo.cpp @@ -0,0 +1,311 @@ +#include "elfspy/ELFInfo.h" + +#include +#include +#include +#include + +#include "elfspy/Report.h" +#include "elfspy/Fail.h" + +#ifdef __x86_64__ +using Elf_Phdr = Elf64_Phdr; +using Elf_Ehdr = Elf64_Ehdr; +using Elf_Shdr = Elf64_Shdr; +using Elf_Rel = Elf64_Rel; +using Elf_Rela = Elf64_Rela; +using Elf_Sym = Elf64_Sym; +#define ELF_STTYPE(X) ELF64_ST_TYPE(X) +#define ELF_STBIND(X) ELF64_ST_BIND(X) +#define ELF_R_SYM(X) ELF64_R_SYM(X) +#define ELF_R_TYPE(X) ELF64_R_TYPE(X) +#else +using Elf_Phdr = Elf32_Phdr; +using Elf_Ehdr = Elf32_Ehdr; +using Elf_Shdr = Elf32_Shdr; +using Elf_Rel = Elf32_Rel; +using Elf_Rela = Elf32_Rela; +using Elf_Sym = Elf32_Sym; +#define ELF_STTYPE(X) ELF32_ST_TYPE(X) +#define ELF_STBIND(X) ELF32_ST_BIND(X) +#define ELF_R_SYM(X) ELF32_R_SYM(X) +#define ELF_R_TYPE(X) ELF32_R_TYPE(X) +#endif + +namespace +{ + +long page_size = sysconf(_SC_PAGESIZE); +union Address +{ + unsigned char* pointer_; + size_t value_; + void round_down(); + void round_up(); +}; + +inline void Address::round_down() +{ + value_ /= page_size; + value_ *= page_size; +} + +inline void Address::round_up() +{ + value_ += page_size - 1; + value_ /= page_size; + value_ *= page_size; +} + +} // namespace + +namespace spy +{ + +ELFInfo::ELFInfo(unsigned char* data, const char* name) +{ + data_ = data; + name_ = name; + if (!is_elf()) { + Fail() << "Not ELF data - no ELF header found in " << name; + } +} + +bool ELFInfo::is_elf() const +{ + auto elf = reinterpret_cast(data_); + return elf->e_ident[EI_MAG0] == ELFMAG0 + && elf->e_ident[EI_MAG1] == ELFMAG1 + && elf->e_ident[EI_MAG2] == ELFMAG2 + && elf->e_ident[EI_MAG3] == ELFMAG3; +} + +void* ELFInfo::find_section(const char* name) +{ + auto elf = reinterpret_cast(data_); + auto header = data_ + elf->e_shoff; + auto section_names = + reinterpret_cast(header + elf->e_shstrndx * elf->e_shentsize); + auto names = reinterpret_cast(elf) + section_names->sh_offset; + // .shstrtab is the "section header" string table, it is indexed in elf header + for (size_t n = 0; n != elf->e_shnum; ++n, header += elf->e_shentsize) { + auto section = reinterpret_cast(header); + if (strcmp(names + section->sh_name, name) == 0) { + return section; + } + } + if (strncmp(name_, "/lib", 4) != 0 && strncmp(name_, "/usr", 4) != 0) { + Report() << "no " << name << " section found in " << name_; + } + return nullptr; +} + +void ELFInfo::unprotect(unsigned char* base, const char* name) +{ + auto section = reinterpret_cast(find_section(name)); + if (section) { + Address begin; + Address end; + begin.pointer_ = base + section->sh_addr; + end.pointer_ = begin.pointer_ + section->sh_size; + begin.round_down(); + end.round_up(); + size_t size = end.value_ - begin.value_; + if (mprotect(begin.pointer_, size, + PROT_READ | PROT_WRITE | PROT_EXEC) != 0) { + spy::Fail() << "Cannot set " << name << " to writable for " << name_; + } + } +} + +void ELFInfo::unprotect(unsigned char* base) +{ + // by default this memory is read only - ELFspy needs to change it + unprotect(base, ".got"); + unprotect(base, ".got.plt"); +} + +ELFInfo::Symbol ELFInfo::get_symbol_rela(size_t value) +{ + Symbol result; + auto str_tab = reinterpret_cast(find_section(".dynstr")); + if (!str_tab) { + return result; + } + auto strings = reinterpret_cast(data_ + str_tab->sh_offset); + // find dynamic symbol table + auto symbol_table = reinterpret_cast(find_section(".dynsym")); + if (!symbol_table) { + return result; + } + // find symbol by value in dynamic symbol table + size_t index = 0; + auto symbols = data_ + symbol_table->sh_offset; + auto end = symbols + symbol_table->sh_size; + for ( ; symbols < end; symbols += symbol_table->sh_entsize) { + auto symbol = reinterpret_cast(symbols); + if (ELF_STTYPE(symbol->st_info) == STT_FUNC && symbol->st_value == value) { + // the symbol is defined in this file as 0 is undefined. + // an STT_GNU_IFUNC will not match here as the function value will be the + // the resulting function of the resolver function and therefore at a + // different address + result.name_ = strings + symbol->st_name; + break; + } + ++index; + } + if (!result.name_) { + return result; // not found + } + auto rela_plt = reinterpret_cast(find_section(".rela.plt")); + if (!rela_plt) { + return result; + } + size_t rela_plt_entries = rela_plt->sh_size / rela_plt->sh_entsize; + unsigned char* rela = data_ + rela_plt->sh_offset; + // attempt to find symbol in .rela.plt + for (size_t n = 0; n != rela_plt_entries; ++n) { + auto reloc = reinterpret_cast(rela); + if (ELF_R_TYPE(reloc->r_info) == R_X86_64_JUMP_SLOT) { + // find symbol by index + size_t symbol_index = ELF_R_SYM(reloc->r_info); + if (symbol_index == index) { + result.rela_offset_ = reloc->r_offset; + break; + } + } + rela += rela_plt->sh_entsize; + } + // a symbol will only be present in .rela.plt iff it was used in the ELF + // object - otherwise only .dynsym and .symtab will contain it + // this means, no GOT entry for function and in this case result.rela_plt + // returns 0 + return result; +} + +// look for STT_IFUNC symbols by finding the function address in the relocated +// .rela.plt entries. STT_IFUNC are rare, but time(time_t*) is one of them +ELFInfo::Symbol ELFInfo::get_indirect_symbol_rela(unsigned char* base, + void* function) +{ + Symbol result; + auto str_tab = reinterpret_cast(find_section(".dynstr")); + if (!str_tab) { + return result; + } + auto strings = reinterpret_cast(data_ + str_tab->sh_offset); + // find dynamic symbol table + auto symbol_table = reinterpret_cast(find_section(".dynsym")); + if (!symbol_table) { + return result; + } + auto symbols = data_ + symbol_table->sh_offset; + // find function in relocated GOT + auto rela_plt = reinterpret_cast(find_section(".rela.plt")); + if (!rela_plt) { + return result; + } + size_t rela_plt_entries = rela_plt->sh_size / rela_plt->sh_entsize; + unsigned char* rela = data_ + rela_plt->sh_offset; + for (size_t n = 0; n != rela_plt_entries; ++n) { + auto reloc = reinterpret_cast(rela); + if (ELF_R_TYPE(reloc->r_info) == R_X86_64_IRELATIVE + && *reinterpret_cast(base + reloc->r_offset) == function) { + result.rela_offset_ = reloc->r_offset; + // find symbol by r_addend in dynamic symbol table + auto end = symbols + symbol_table->sh_size; + for ( ; symbols < end; symbols += symbol_table->sh_entsize) { + auto symbol = reinterpret_cast(symbols); + if (ELF_STTYPE(symbol->st_info) == STT_GNU_IFUNC + && symbol->st_value == reloc->r_addend) { + result.name_ = strings + symbol->st_name; + break; + } + } + break; + } + rela += rela_plt->sh_entsize; + } + return result; // whether found or not +} + +size_t ELFInfo::get_symbol_rela_dyn(const char* name) +{ + auto str_tab = reinterpret_cast(find_section(".dynstr")); + if (!str_tab) { + return 0; + } + auto strings = reinterpret_cast(data_ + str_tab->sh_offset); + // find symbol table + auto symbol_table = reinterpret_cast(find_section(".dynsym")); + if (symbol_table == nullptr) { + return 0; + } + auto symbols = data_ + symbol_table->sh_offset; + size_t symbol_entries = symbol_table->sh_size / symbol_table->sh_entsize; + // find symbol in .rela.dyn + for (const char* section : { ".rela.dyn", ".rela.plt" }) { + auto rela_dyn = reinterpret_cast(find_section(section)); + if (!rela_dyn) { + return 0; + } + size_t rela_entries = rela_dyn->sh_size / rela_dyn->sh_entsize; + unsigned char* rela = data_ + rela_dyn->sh_offset; + for (size_t n = 0; n != rela_entries; ++n) { + auto reloc = reinterpret_cast(rela); + size_t symbol_index = ELF_R_SYM(reloc->r_info); + if (symbol_index < symbol_entries) { + symbol_index *= symbol_table->sh_entsize; + auto symbol = reinterpret_cast(symbols + symbol_index); + const char* symbol_name = strings + symbol->st_name; + if (strcmp(name, symbol_name) == 0) { + return reloc->r_offset; + } + } + rela += rela_dyn->sh_entsize; + } + } + return 0; +} + +std::unordered_map +ELFInfo::get_vtables(unsigned char* base) +{ + if (!is_elf()) { + return { }; + } + auto elf = reinterpret_cast(data_); + auto header = data_ + elf->e_shoff; + // get string table by finding the right strtab section + // .dynstr is for DYNSYM section (it has a non-zero address) + // .shstrtab is the "section header" string table, it is indexed in elf header + // .strtab is for SYMTAB, which is what we want here + auto str_tab = reinterpret_cast(find_section(".strtab")); + if (!str_tab) { + return { }; + } + auto strings = reinterpret_cast(data_ + str_tab->sh_offset); + // find symbol table + auto symbol_table = reinterpret_cast(find_section(".symtab")); + if (symbol_table == nullptr) { + return { }; + } + std::unordered_map vtables; + // look for virtual tables + auto symbols = data_ + symbol_table->sh_offset; + auto end = symbols + symbol_table->sh_size; + for ( ; symbols < end; symbols += symbol_table->sh_entsize) { + auto symbol = reinterpret_cast(symbols); + // check if is defined (value != 0) + if (symbol->st_value && ELF_STTYPE(symbol->st_info) == STT_OBJECT) { + // check if it is a virtual table (starts with "_ZTV") + auto name = strings + symbol->st_name; + if (strncmp(name, "_ZTV", 4) == 0) { + vtables[name + 4] = base + symbol->st_value; + } + } + } + return vtables; +} + +} // namespace spy diff --git a/ELFInfo.h b/ELFInfo.h new file mode 100644 index 0000000..6a94ff6 --- /dev/null +++ b/ELFInfo.h @@ -0,0 +1,73 @@ +#ifndef ELFSPY_ELFINFO_H +#define ELFSPY_ELFINFO_H + +#include +#include +#include "elfspy/ELFObject.h" + +namespace spy +{ + +/** + * @namespace spy + * @class ELFInfo + * can read data in ELF format and extract information from it + */ + +class ELFInfo : public ELFObject +{ +public: + /** + * @param data ELF data + * @param name name of ELF data for diagnostics + */ + ELFInfo(unsigned char* data, const char* name); + + /// @return true if data is in ELF format + bool is_elf() const; + struct Symbol + { + const char* name_ = nullptr; + size_t rela_offset_ = 0; + }; + /** + * find symbol and offset name in .rela.plt if symbol is defined in file + * @param value symbol offset value expected to be in symtab + * @return name and offset (empty if not defined in file) + */ + Symbol get_symbol_rela(size_t value); + /** + * find symbol and offset name in .rela.plt if symbol is defined as STT_IFUNC + * @param base base address of ELF object in memory + * @param function function pointer + * @return name and offset (0 if not defined in file) + */ + Symbol get_indirect_symbol_rela(unsigned char* base, void* function); + /** + * find symbol offset in .rela.dyn if symbol is found as undefined in file + * @param name symbol name + * @return offset (0 if not defined in file) + */ + size_t get_symbol_rela_dyn(const char* name); + /** + * remove write protection from the areas in memory that need to change + * @param base base address in memory + */ + void unprotect(unsigned char* base); + /** + * find vtable adresses from symbol table + * @param base - offset to return addresses relative to + * @return map of type name to vtable address + */ + std::unordered_map + get_vtables(unsigned char* base = nullptr); + +private: + unsigned char* data_; + void* find_section(const char* name); + void unprotect(unsigned char* base, const char* name); +}; + +} // namespace spy + +#endif diff --git a/ELFObject.h b/ELFObject.h new file mode 100644 index 0000000..314133c --- /dev/null +++ b/ELFObject.h @@ -0,0 +1,19 @@ +#ifndef ELFSPY_ELFOBJECT_HXX +#define ELFSPY_ELFOBJECT_HXX + +namespace spy +{ + +struct ELFObject +{ + const char* name_ = "no name"; + unsigned char* base_ = nullptr; + size_t size_ = 0; + size_t symbols_ = 0; + size_t strings_ = 0; + size_t rela_plt_ = 0; +}; + +} // namespace spy + +#endif diff --git a/Error.cpp b/Error.cpp new file mode 100644 index 0000000..9be9da6 --- /dev/null +++ b/Error.cpp @@ -0,0 +1,28 @@ +#include "elfspy/Error.h" + +#include + +namespace +{ +std::function reporter = + [](const char* text) + { + std::cerr << text << std::endl; + }; + +} // namespace + +namespace spy +{ + +void error(const char* text) +{ + reporter(text); +} + +void set_reporter(const std::function& reporter) +{ + ::reporter = reporter; +} + +} // namespace spy diff --git a/Error.h b/Error.h new file mode 100644 index 0000000..98e508f --- /dev/null +++ b/Error.h @@ -0,0 +1,14 @@ +#ifndef ELFSPY_ERROR_H +#define ELFSPY_ERROR_H + +#include + +namespace spy +{ + +void error(const char* text); +void set_reporter(const std::function& reporter); + +} // namespace spy + +#endif diff --git a/Fail.cpp b/Fail.cpp new file mode 100644 index 0000000..e67f2e3 --- /dev/null +++ b/Fail.cpp @@ -0,0 +1,14 @@ +#include "elfspy/Fail.h" + +#include + +namespace spy +{ + +Fail::~Fail() +{ + show(); + std::exit(-1); +} + +} // namespace spy diff --git a/Fail.h b/Fail.h new file mode 100644 index 0000000..201bbf2 --- /dev/null +++ b/Fail.h @@ -0,0 +1,17 @@ +#ifndef ELFSPY_FAIL_H +#define ELFSPY_FAIL_H + +#include "elfspy/Report.h" + +namespace spy +{ + +class Fail : public Report +{ +public: + ~Fail(); +}; + +} // namespace spy + +#endif diff --git a/Fake.h b/Fake.h new file mode 100644 index 0000000..9ceccc7 --- /dev/null +++ b/Fake.h @@ -0,0 +1,60 @@ +#ifndef ELFSPY_FAKE_H +#define ELFSPY_FAKE_H + +#include "elfspy/Lambda.h" + +namespace spy +{ + +/** + * @namespace spy + * @class Fake + * This replaces an existing function with another function, so that all calls + * to the program are made to the other function. + * The constructor installs the new function in place of the function, the + * destructor uninstalls it + */ + +template +class Fake +{ +public: + Fake(H& hook, ReturnType (*func)(ArgTypes...)); + ~Fake(); + +private: + H& hook_; + ReturnType (*func_)(ArgTypes...); +}; + +template +inline Fake:: +Fake(H& hook, ReturnType (*func)(ArgTypes...)) + :hook_(hook) +{ + func_ = hook_.patch(func); +} + +template +inline Fake::~Fake() +{ + hook_.patch(func_); +} + +template +inline auto fake(H& hook, ReturnType (*patch)(ArgTypes...)) + -> Fake +{ + return { hook, patch }; +} + +template +inline auto new_fake(H& hook, ReturnType (*patch)(ArgTypes...)) + -> Fake* +{ + return new Fake(hook, patch); +} + +} // namespace elfspy + +#endif diff --git a/Function.h b/Function.h new file mode 100644 index 0000000..d2fd68f --- /dev/null +++ b/Function.h @@ -0,0 +1,22 @@ +#ifndef ELFSPY_FUNCTION_H +#define ELFSPY_FUNCTION_H + +namespace spy +{ + +/** + * @namespace spy + * @union Function + * Conversion helper from function to address + */ + +template +union Function +{ + R (*address_)(Args...); + void* pointer_; +}; + +} // namespace spy + +#endif diff --git a/GOTEntry.cpp b/GOTEntry.cpp new file mode 100644 index 0000000..2c1cfff --- /dev/null +++ b/GOTEntry.cpp @@ -0,0 +1,243 @@ +#include "GOTEntry.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "elfspy/MFile.h" +#include "elfspy/ELFInfo.h" +#include "elfspy/Fail.h" +#include "elfspy/Report.h" + +#ifdef __x86_64__ +#define Elf_Phdr Elf64_Phdr +#define Elf_Ehdr Elf64_Ehdr +#else +#define Elf_Phdr Elf32_Phdr +#define Elf_Ehdr Elf32_Ehdr +#endif + +namespace +{ +int argc; +char** argv; +std::string program_name; +bool debug = getenv("ELFSPY_DEBUG") != nullptr; +// TODO: just copy the char it's in memory +std::unordered_map vtables; +std::vector elf_objects; + +void resolve_program_name() +{ + struct stat file_info; + std::string file_name = argv[0]; + const char* path = getenv("PATH"); + if (!path || argv[0][0] == '/') { + path = ""; + } + char* path_copy = strdup(path); + char* path_token = path_copy; + char* save; + for ( ; ; ) { + int rv = stat(file_name.c_str(), &file_info); + if (rv == 0) { + program_name = file_name; + break; + } + const char* dir = strtok_r(path_token, ":", &save); + if (dir == nullptr) { + break; + } + path_token = nullptr; // for second call to strtok_r + file_name = dir; + file_name.push_back('/'); + file_name.append(argv[0]); + } + free(path_copy); +} + +int read_shared_object(struct dl_phdr_info* info, size_t size, void* data) +{ + // the ELF header for each loaded object + unsigned char* elf_root; + const char* name; + if (!info->dlpi_addr) { + // this is the executable itself - sadly the name was not included in info + elf_root = nullptr; + name = program_name.c_str(); + } else { + elf_root = reinterpret_cast(info->dlpi_addr); + name = info->dlpi_name; + } + if (name && *name) { + spy::MFile file(name); + spy::ELFInfo elf(file.address(), name); + elf.base_ = elf_root; + elf.size_ = file.size(); + elf.unprotect(elf_root); + elf_objects.push_back(elf); + auto entries = elf.get_vtables(elf_root); + vtables.insert(entries.begin(), entries.end()); + } + return 0; +} + +} // namespace + +namespace spy +{ +// arguments left in for future compatibility +void GOTEntry::initialise(int argc, char** argv) +{ + ::argc = argc; + ::argv = argv; + resolve_program_name(); + if (program_name.empty()) { + Fail() << "Cannot determine absolute file name of executable"; + } + dl_iterate_phdr(&read_shared_object, nullptr); +} + +// base + rela.plt if symbol is found and defined +// base + rela.dyn if symbol is found and undefined +void* GOTEntry::set(void* function) +{ + if (entries_.empty()) { + auto address = reinterpret_cast(function); + // first find ELF object where it is defined + ELFInfo::Symbol symbol; + std::string symbol_name; + ELFObject* defined = nullptr; + for (auto& object : elf_objects) { + // check if it is even possible for the function to exist in the object + size_t offset = address - object.base_; + if (offset < object.size_) { + // that the offset is inside the size is a tell-tale the symbol can be + // found + MFile elf_file(object.name_); + ELFInfo elf(elf_file.address(), object.name_); + // find name in elf file where it is defined + symbol = elf.get_symbol_rela(offset); + if (symbol.rela_offset_) { + // symbol was defined and used, offset is from the .rela.plt section + make_entry(object.base_ + symbol.rela_offset_); + } + if (symbol.name_) { + // symbol was defined + symbol_name = symbol.name_; + defined = &object; + break; + } + } + } + if (!defined) { + // rare case of looking for an STT_IFUNC when not found at all + for (auto& object : elf_objects) { + MFile elf_file(object.name_); + ELFInfo elf(elf_file.address(), object.name_); + symbol = elf.get_indirect_symbol_rela(object.base_, function); + if (symbol.rela_offset_) { + // symbol was defined, offset is from the .rela.plt section + make_entry(object.base_ + symbol.rela_offset_); + symbol_name = symbol.name_; + defined = &object; + break; + } + } + } + if (!defined) { + Report() << "cannot find definition of function " << function; + return nullptr; + } + // find entries where the symbol is used in other ELF functions + for (auto& object : elf_objects) { + if (&object == defined) { + continue; + } + MFile elf_file(object.name_); + ELFInfo elf(elf_file.address(), object.name_); + size_t rela_offset = elf.get_symbol_rela_dyn(symbol_name.c_str()); + if (rela_offset) { + // symbol was undefined, offset is from the .rela.dyn section + make_entry(object.base_ + rela_offset); + } + } + } + // in file where it is defined find the .rela.plt + // if undefined find the name in the .rela.dyn using .symtab index for name + // and with strcmp + if (original_ == nullptr) { + original_ = function; + } + current_ = function; + return original_; +} + +void GOTEntry::make_entry(void** address) +{ + // store the previous value of the entry which points to @plt+6 as it has been + // resolved, @plt there is a indirect jump to the contents of the entry, so + // if it is set to @plt instead @plt+6 an infinite loop will occur + Entry entry; + entry.address_ = address; + entry.restore_ = *entry.address_; + entries_.push_back(entry); +} + +void** GOTEntry::get_vtable_entry(const std::type_info& type, + const MethodPointer& method) +{ + if (method.is_virtual()) { + // this is a virtual function + auto seek = vtables.find(type.name()); + if (seek != vtables.end()) { + unsigned char* vtable = seek->second; + // there seems to be two entries that are not accounted for in the index + auto location = vtable + sizeof(void*) * 2 + (method.index_ & ~1UL); + return reinterpret_cast(location); + } + Report() << "Could not find virtual function"; + return nullptr; + } + // non virtual function + return nullptr; +} + +void* GOTEntry::spy_with(void* function) +{ + if (!spy_count_) { + patch(function); + } + ++spy_count_; + return original_; +} + +void* GOTEntry::patch(void* function) +{ + for (auto entry : entries_) { + *entry.address_ = function; + } + void* previous = current_; + current_ = function; + return previous; +} + +void GOTEntry::restore() +{ + --spy_count_; + if (!spy_count_) { + for (auto entry : entries_) { + *entry.address_ = entry.restore_; + } + } + current_ = original_; +} + +} // namespace spy diff --git a/GOTEntry.h b/GOTEntry.h new file mode 100644 index 0000000..56137f1 --- /dev/null +++ b/GOTEntry.h @@ -0,0 +1,94 @@ +#ifndef ELFSPY_GOTENTRY_H +#define ELFSPY_GOTENTRY_H + +#include +#include + +#include "elfspy/MethodPointer.h" + +namespace spy +{ + +/** + * @namespace spy + * @class GOTEntry + * Handle a function of interest and all its references in all Global Offset + * Tables + */ + +class GOTEntry +{ +public: + GOTEntry() = default; + /** + * @param function address of function + * @return original unaltered address of function + */ + void* set(void* function); + /** + * replace function with other function and reference count + * @param function to enter in Global Offset Tables instead of original + * @return original entry in Global Offset Table instead of original + */ + void* spy_with(void* function); + /** + * count down reference count from spy_with and restore original entry in + * Global Offset Tables if count is zero + */ + void restore(); + /** + * replace function with other function + * @param function to enter in Global Offset Tables instead of original + * @return original entry in Global Offset Table instead of original + */ + void* patch(void* function); + /** + * get the address of a virtual function + * @param type type info of class + * @param method method function pointer + * @return address of address of virtual function for that class + */ + static void** get_vtable_entry(const std::type_info& type, + const MethodPointer& method); + + static void initialise(int argc, char** argv); + + const void* current() const; + const void* original() const; + /** + * add an entry to the function pointer addresses + * @param address address of address of function + */ + void make_entry(void** address); + +private: + struct Entry + { + void** address_; + void* restore_; + }; + std::vector entries_; // all entries + size_t spy_count_ = 0; + void* current_ = nullptr; + void* original_ = nullptr; + void make_entry(unsigned char* address); +}; + +inline const void* GOTEntry::current() const +{ + return original_; +} + +inline const void* GOTEntry::original() const +{ + return original_; +} + +inline void GOTEntry::make_entry(unsigned char* address) +{ + make_entry(reinterpret_cast(address)); +} + +} // namespace spy + +#endif diff --git a/Hook.h b/Hook.h new file mode 100644 index 0000000..fb3b3e5 --- /dev/null +++ b/Hook.h @@ -0,0 +1,115 @@ +#ifndef ELFSPY_HOOK_H +#define ELFSPY_HOOK_H + +#include "elfspy/HookImpl.h" +#include "elfspy/GOTEntry.h" +#include "elfspy/Function.h" +#include "elfspy/MethodInfo.h" +#include "elfspy/Thunk.h" + +namespace spy +{ + +/** + * @namespace spy + * @class Hook + * An instance is a function that is executed after a call to a + * function/method and before it is run, essentially injected between the two. + * It is very lightweight as it only holds reference(s) to static data members. + * Calling convention is unified between methods and functions so that methods + * are functions where the first type in Args... is the class type. + */ + +template +class Hook : public HookImpl +{ +public: + using Result = ReturnType; + /** + * typedefs to re-export variadic ArgTypes to other variadic templates + */ + template