commit a4d59d09f719c46fb0a107bc60f5d7e15548bb2e Author: mollismerx Date: Fri Sep 16 17:45:35 2016 +0200 Initial version 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