From 5df2bb2c0bdf91ed078e3cd3fafb04bdec5333c8 Mon Sep 17 00:00:00 2001 From: Davide Beatrici Date: Sun, 27 Sep 2020 20:12:02 +0200 Subject: [PATCH] FEAT(client): add getExportedSymbol() function for positional audio plugins This function is of critical importance for the Source Engine plugin, let me elaborate on why. Most games consist in an executable and maybe one or more libraries, but they don't have particular exported symbols we can use to easily access (existing) resources inside the process, which means we have to rely on hardcoded offsets and hex pattern scanning. Source Engine games are special: the executable is nothing more than a manager that takes care of loading the core libraries, which are engine(.dll|.so) and client(.dll|.so) (or server(.dll|.so) in the case of a dedicated server). Those libraries have a common exported symbol, which is CreateInterface(). The function takes the interface's name as argument (char *), creates the interface object if it doesn't exist yet and returns a pointer (void *) to it. The interfaces objects are stored in a list called s_pInterfaceList, which is usually an exported symbol on Linux. getExportedSymbol() allows us to get the address to CreateInterface() (different for each loaded library) inside the process and read the function's assembly code in order to reach s_pInterfaceList. If s_pInterfaceList is exported we can get its address with getExportedSymbol(), without the need of CreateInterface(). --- plugins/mumble_plugin_linux.h | 102 ++++++++++++++++++++++++++++++++++ plugins/mumble_plugin_main.h | 46 +++++++++++++++ 2 files changed, 148 insertions(+) diff --git a/plugins/mumble_plugin_linux.h b/plugins/mumble_plugin_linux.h index ad499c96c..461d58bb4 100644 --- a/plugins/mumble_plugin_linux.h +++ b/plugins/mumble_plugin_linux.h @@ -11,6 +11,7 @@ #endif #include +#include #include #include #include @@ -212,6 +213,107 @@ static inline bool peekProc(const procptr_t &addr, void *dest, const size_t &len return (nread != -1 && static_cast< size_t >(nread) == in.iov_len); } +static inline procptr_t getExportedSymbol(const std::string &symbol, const procptr_t module) { + if (isWin32) { + return getWin32ExportedSymbol(symbol, module); + } + + procptr_t hashTable = 0; + procptr_t strTable = 0; + procptr_t symTable = 0; + + if (is64Bit) { + const auto ehdr = peekProc< Elf64_Ehdr >(module); + const auto phdr = peekProcVector< Elf64_Phdr >(module + ehdr.e_phoff, ehdr.e_phnum); + + for (size_t i = 0; i < phdr.size(); ++i) { + if (phdr[i].p_type == PT_DYNAMIC) { + const auto dyn = + peekProcVector< Elf64_Dyn >(module + phdr[i].p_vaddr, phdr[i].p_memsz / sizeof(Elf64_Dyn)); + + for (size_t j = 0; j < dyn.size(); ++j) { + switch (dyn[j].d_tag) { + case DT_HASH: + hashTable = dyn[j].d_un.d_ptr; + break; + case DT_STRTAB: + strTable = dyn[j].d_un.d_ptr; + break; + case DT_SYMTAB: + symTable = dyn[j].d_un.d_ptr; + break; + } + + if (hashTable && strTable && symTable) { + break; + } + } + + break; + } + } + } else { + const auto ehdr = peekProc< Elf32_Ehdr >(module); + const auto phdr = peekProcVector< Elf32_Phdr >(module + ehdr.e_phoff, ehdr.e_phnum); + + for (size_t i = 0; i < phdr.size(); ++i) { + if (phdr[i].p_type == PT_DYNAMIC) { + const auto dyn = + peekProcVector< Elf32_Dyn >(module + phdr[i].p_vaddr, phdr[i].p_memsz / sizeof(Elf32_Dyn)); + + for (size_t j = 0; j < dyn.size(); ++j) { + switch (dyn[j].d_tag) { + case DT_HASH: + hashTable = dyn[j].d_un.d_ptr; + break; + case DT_STRTAB: + strTable = dyn[j].d_un.d_ptr; + break; + case DT_SYMTAB: + symTable = dyn[j].d_un.d_ptr; + break; + } + + if (hashTable && strTable && symTable) { + break; + } + } + + break; + } + } + } + + // Hash table pseudo-struct: + // uint32_t nBucket; + // uint32_t nChain; + // uint32_t bucket[nBucket]; + // uint32_t chain[nChain]; + const auto nChain = peekProc< uint32_t >(hashTable + sizeof(uint32_t)); + + if (is64Bit) { + for (uint32_t i = 0; i < nChain; ++i) { + const auto sym = peekProc< Elf64_Sym >(symTable + sizeof(Elf64_Sym) * i); + const auto name = peekProcString(strTable + sym.st_name, symbol.size()); + + if (name == symbol) { + return module + sym.st_value; + } + } + } else { + for (uint32_t i = 0; i < nChain; ++i) { + const auto sym = peekProc< Elf32_Sym >(symTable + sizeof(Elf32_Sym) * i); + const auto name = peekProcString(strTable + sym.st_name, symbol.size()); + + if (name == symbol) { + return module + sym.st_value; + } + } + } + + return 0; +} + static void generic_unlock() { pModule = 0; pPid = 0; diff --git a/plugins/mumble_plugin_main.h b/plugins/mumble_plugin_main.h index f4b5be5b1..6adcd5d1f 100644 --- a/plugins/mumble_plugin_main.h +++ b/plugins/mumble_plugin_main.h @@ -151,6 +151,52 @@ static inline procptr_t getVirtualFunction(const procptr_t classObject, const si return peekProcPtr(vTable + (index * GET_POINTER_SIZE)); } +#ifdef OS_WINDOWS +static inline procptr_t getExportedSymbol(const std::string &symbol, const procptr_t module) { +#else +/// We use a different name because the function is called by the Linux version +/// of getExportedSymbol() in case the process is running through Wine. +static inline procptr_t getWin32ExportedSymbol(const std::string &symbol, const procptr_t module) { +#endif + const auto dos = peekProc< ImageDosHeader >(module); + if (!(dos.magic[0] == 'M' && dos.magic[1] == 'Z')) { + // Invalid DOS signature + return -1; + } + + procptr_t dataAddress; + + if (is64Bit) { + const auto nt = peekProc< ImageNtHeaders64 >(module + dos.addressOfNtHeader); + dataAddress = nt.optionalHeader.dataDirectory[0].virtualAddress; + } else { + const auto nt = peekProc< ImageNtHeaders32 >(module + dos.addressOfNtHeader); + dataAddress = nt.optionalHeader.dataDirectory[0].virtualAddress; + } + + if (!dataAddress) { + return 0; + } + + const auto exportDir = peekProc< ImageExportDirectory >(module + dataAddress); + + const auto funcs = peekProcVector< uint32_t >(module + exportDir.addressOfFunctions, exportDir.numberOfFunctions); + const auto names = peekProcVector< uint32_t >(module + exportDir.addressOfNames, exportDir.numberOfNames); + const auto ords = peekProcVector< uint16_t >(module + exportDir.addressOfNameOrdinals, exportDir.numberOfNames); + + for (uint32_t i = 0; i < exportDir.numberOfNames; ++i) { + if (names[i]) { + const auto name = peekProcString(module + names[i], symbol.size()); + + if (name == symbol) { + return module + funcs[ords[i]]; + } + } + } + + return 0; +} + // This function returns: // -1 in case of failure. // 0 if the process is 32-bit.