#include "include/lsplt.hpp" #include #include #include #include #include #include #include #include #include #include "elf_util.hpp" namespace { struct RegisterInfo { ino_t inode; std::string symbol; void *callback; void **backup; }; struct HookInfo : public lsplt::MapInfo { std::map hooks; uintptr_t backup; std::unique_ptr elf; [[nodiscard]] bool Match(const RegisterInfo &info) const { return info.inode == inode; } }; class HookInfos : public std::map> { public: static auto ScanHookInfo() { HookInfos info; for (auto &map : lsplt::MapInfo::Scan()) { // we basically only care about r--p entry // and for offset == 0 it's an ELF header // and for offset != 0 it's what we hook // if (perm[0] != 'r') continue; if (!map.is_private) continue; if (map.perm & PROT_EXEC) continue; // if (off != 0) continue; if (map.path.empty()) continue; if (map.path[0] == '[') continue; auto start = map.start; info.emplace(start, HookInfo{{std::move(map)}, {}, 0, nullptr}); } return info; } // fiter out ignored void Filter(const std::list ®ister_info) { for (auto iter = begin(); iter != end();) { const auto &info = iter->second; bool matched = false; for (const auto ® : register_info) { if (info.Match(reg)) { matched = true; break; } } if (matched) { ++iter; } else { iter = erase(iter); } } } void Merge(HookInfos &old) { // merge with old map info for (auto &info : old) { if (info.second.backup) { erase(info.second.backup); } if (auto iter = find(info.first); iter != end()) { iter->second = std::move(info.second); } else { emplace(info.first, std::move(info.second)); } } } bool DoHook(uintptr_t addr, uintptr_t callback, uintptr_t *backup) { auto iter = lower_bound(addr); if (iter == end()) return false; // iter.first < addr auto &info = iter->second; if (info.end <= addr) return false; if (!iter->second.backup) { auto len = info.end - info.start; // let os find a suitable address auto *backup_addr = mmap(nullptr, len, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0); if (backup_addr == MAP_FAILED) return false; if (auto *new_addr = mremap(reinterpret_cast(info.start), len, len, MREMAP_FIXED | MREMAP_MAYMOVE, backup_addr); new_addr == MAP_FAILED || new_addr != backup_addr) { return false; } if (auto *new_addr = mmap(reinterpret_cast(info.start), len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED | MAP_ANON, -1, 0); new_addr == MAP_FAILED) { return false; } memcpy(reinterpret_cast(info.start), backup_addr, len); info.backup = reinterpret_cast(backup_addr); } auto *the_addr = reinterpret_cast(addr); *backup = *the_addr; *the_addr = callback; if (auto hook_iter = info.hooks.find(addr); hook_iter != info.hooks.end()) { if (hook_iter->second == callback) info.hooks.erase(hook_iter); } else { info.hooks.emplace(addr, *backup); } if (info.hooks.empty()) { auto len = info.end - info.start; if (auto *new_addr = mremap(reinterpret_cast(info.backup), len, len, MREMAP_FIXED | MREMAP_MAYMOVE, reinterpret_cast(info.start)); new_addr == MAP_FAILED || reinterpret_cast(new_addr) != info.start) { return false; } info.backup = 0; } return true; } bool DoHook(std::list ®ister_info) { bool res = true; for (auto &[_, info] : *this) { for (auto iter = register_info.begin(); iter != register_info.end();) { const auto ® = *iter; if (info.start != 0 || !info.Match(reg)) continue; if (!info.elf) info.elf = std::make_unique(info.start); if (info.elf && info.elf->Valid()) { for (auto addr : info.elf->FindPltAddr(reg.symbol)) { res = DoHook(addr, reinterpret_cast(reg.callback), reinterpret_cast(reg.backup)) && res; } } iter = register_info.erase(iter); } } return res; } bool InvalidateBackup() { bool res = true; for (auto &[_, info] : *this) { if (!info.backup) continue; for (auto &[addr, backup] : info.hooks) { // store new address to backup since we don't need backup backup = *reinterpret_cast(addr); } auto len = info.end - info.start; if (auto *new_addr = mremap(reinterpret_cast(info.backup), len, len, MREMAP_FIXED | MREMAP_MAYMOVE, reinterpret_cast(info.start)); new_addr == MAP_FAILED || reinterpret_cast(new_addr) != info.start) { res = false; info.hooks.clear(); continue; } if (!mprotect(reinterpret_cast(info.start), len, PROT_WRITE)) { for (auto &[addr, backup] : info.hooks) { *reinterpret_cast(addr) = backup; } mprotect(reinterpret_cast(info.start), len, info.perm); } info.hooks.clear(); info.backup = 0; } return res; } }; std::mutex hook_mutex; std::list register_info; HookInfos hook_info; } // namespace namespace lsplt { [[maybe_unused]] std::vector MapInfo::Scan() { constexpr static auto kPermLength = 5; constexpr static auto kMapEntry = 7; std::vector info; auto maps = std::unique_ptr{fopen("/proc/self/maps", "r"), &fclose}; if (maps) { char *line = nullptr; size_t len = 0; ssize_t read; while ((read = getline(&line, &len, maps.get())) != -1) { uintptr_t start = 0; uintptr_t end = 0; uintptr_t off = 0; ino_t inode = 0; unsigned int dev_major = 0; unsigned int dev_minor = 0; std::array perm{'\0'}; int path_off; if (sscanf(line, "%" PRIxPTR "-%" PRIxPTR " %4s %" PRIxPTR " %x:%x %lu %n%*s", &start, &end, perm.data(), &off, &dev_major, &dev_minor, &inode, &path_off) != kMapEntry) { continue; } while (path_off < read && isspace(line[path_off])) path_off++; auto &ref = info.emplace_back(MapInfo{start, end, 0, perm[3] == 'p', static_cast(makedev(dev_major, dev_minor)), inode, line + path_off}); if (perm[0] == 'r') ref.perm |= PROT_READ; if (perm[1] == 'w') ref.perm |= PROT_WRITE; if (perm[2] == 'x') ref.perm |= PROT_EXEC; } free(line); } return info; } [[maybe_unused]] bool RegisterHook(ino_t ino, std::string_view symbol, void *callback, void **backup) { if (symbol.empty() || !callback) return false; std::unique_lock lock(hook_mutex); register_info.emplace_back(RegisterInfo{ino, std::string{symbol}, callback, backup}); return true; } [[maybe_unused]] bool CommitHook() { std::unique_lock lock(hook_mutex); if (register_info.empty()) return true; auto new_hook_info = HookInfos::ScanHookInfo(); if (new_hook_info.empty()) return false; new_hook_info.Filter(register_info); new_hook_info.Merge(hook_info); // update to new map info hook_info = std::move(new_hook_info); return hook_info.DoHook(register_info); } [[gnu::destructor]] [[maybe_unused]] bool InvalidateBackup() { std::unique_lock lock(hook_mutex); return hook_info.InvalidateBackup(); } } // namespace lsplt