#include "include/lsplt.hpp" #include #include #include #include #include #include #include #include #include #include "elf_util.hpp" #include "logging.hpp" namespace { inline auto PageStart(uintptr_t addr) { return reinterpret_cast(((addr)&PAGE_MASK)); } inline auto PageEnd(uintptr_t addr) { return reinterpret_cast(reinterpret_cast(PageStart(addr)) + PAGE_SIZE); } 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; bool self; [[nodiscard]] bool Match(const RegisterInfo &info) const { return info.inode == inode; } }; class HookInfos : public std::map> { public: static auto ScanHookInfo() { static ino_t kSelfInode = 0; HookInfos info; auto maps = lsplt::MapInfo::Scan(); if (kSelfInode == 0) { auto self = reinterpret_cast(__builtin_return_address(0)); for (auto &map : maps) { if (self >= map.start && self < map.end) { kSelfInode = map.inode; LOGV("self inode = %lu", kSelfInode); break; } } } for (auto &map : maps) { // 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 (!map.is_private) continue; if (map.path.empty()) continue; if (map.path[0] == '[') continue; auto start = map.start; auto inode = map.inode; info.emplace(start, HookInfo{{std::move(map)}, {}, 0, nullptr, inode == kSelfInode}); } 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) { LOGV("Match hook info %s:%lu %" PRIxPTR " %" PRIxPTR "-%" PRIxPTR, iter->second.path.data(), iter->second.inode, iter->second.start, iter->second.end, iter->second.offset); ++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 if (info.second.backup) { emplace(info.first, std::move(info.second)); } } } bool DoHook(uintptr_t addr, uintptr_t callback, uintptr_t *backup) { LOGV("Hooking %p", reinterpret_cast(addr)); 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 && !info.self) { 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); LOGD("Backup %p to %p", reinterpret_cast(addr), backup_addr); 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 | info.perms, 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); } if (info.self) { // self hooking, no need backup since we are always dirty auto len = info.end - info.start; if (!(info.perms & PROT_WRITE)) { info.perms |= PROT_WRITE; mprotect(reinterpret_cast(info.start), len, info.perms); } } auto *the_addr = reinterpret_cast(addr); auto the_backup = *the_addr; if (*the_addr != addr) { *the_addr = callback; if (backup) *backup = the_backup; __builtin___clear_cache(PageStart(addr), PageEnd(addr)); } 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, the_backup); } if (info.hooks.empty() && !info.self) { auto len = info.end - info.start; LOGD("Restore %p from %p", reinterpret_cast(info.start), reinterpret_cast(info.backup)); 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.offset != 0 || !info.Match(reg)) { ++iter; continue; } if (!info.elf) info.elf = std::make_unique(info.start); if (info.elf && info.elf->Valid()) { LOGD("Hooking %s", iter->symbol.data()); 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(PageStart(info.start), len, PROT_WRITE)) { for (auto &[addr, backup] : info.hooks) { *reinterpret_cast(addr) = backup; } mprotect(PageStart(info.start), len, info.perms); } info.hooks.clear(); info.backup = 0; } return res; } }; std::mutex hook_mutex; std::list register_info; HookInfos hook_info; } // namespace namespace lsplt { inline namespace v1 { [[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())) > 0) { line[read - 1] = '\0'; 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', off, static_cast(makedev(dev_major, dev_minor)), inode, line + path_off}); if (perm[0] == 'r') ref.perms |= PROT_READ; if (perm[1] == 'w') ref.perms |= PROT_WRITE; if (perm[2] == 'x') ref.perms |= PROT_EXEC; } free(line); } return info; } [[maybe_unused]] bool RegisterHook(ino_t inode, std::string_view symbol, void *callback, void **backup) { if (inode == 0 || symbol.empty() || !callback) return false; LOGV("RegisterHook %lu %s", inode, symbol.data()); std::unique_lock lock(hook_mutex); register_info.emplace_back(RegisterInfo{inode, 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 v1 } // namespace lsplt