2022-11-26 22:45:45 +08:00
|
|
|
#include "include/lsplt.hpp"
|
|
|
|
|
|
|
|
#include <regex.h>
|
|
|
|
#include <sys/mman.h>
|
2022-11-27 00:59:56 +08:00
|
|
|
#include <sys/sysmacros.h>
|
2022-11-26 22:45:45 +08:00
|
|
|
|
|
|
|
#include <array>
|
|
|
|
#include <cinttypes>
|
|
|
|
#include <list>
|
|
|
|
#include <map>
|
|
|
|
#include <mutex>
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
#include "elf_util.hpp"
|
2022-11-27 03:05:35 +08:00
|
|
|
#include "logging.hpp"
|
2022-11-26 22:45:45 +08:00
|
|
|
|
|
|
|
namespace {
|
2022-11-27 01:47:12 +08:00
|
|
|
|
|
|
|
inline auto PageStart(uintptr_t addr) { return reinterpret_cast<char *>(((addr)&PAGE_MASK)); }
|
|
|
|
|
|
|
|
inline auto PageEnd(uintptr_t addr) {
|
|
|
|
return reinterpret_cast<char *>(reinterpret_cast<uintptr_t>(PageStart(addr)) + PAGE_SIZE);
|
|
|
|
}
|
|
|
|
|
2022-11-27 00:29:32 +08:00
|
|
|
struct RegisterInfo {
|
2022-11-26 22:45:45 +08:00
|
|
|
ino_t inode;
|
|
|
|
std::string symbol;
|
|
|
|
void *callback;
|
|
|
|
void **backup;
|
|
|
|
};
|
|
|
|
|
2022-11-27 00:59:56 +08:00
|
|
|
struct HookInfo : public lsplt::MapInfo {
|
2022-11-26 22:45:45 +08:00
|
|
|
std::map<uintptr_t, uintptr_t> hooks;
|
|
|
|
uintptr_t backup;
|
|
|
|
std::unique_ptr<Elf> elf;
|
2022-11-27 06:36:16 +08:00
|
|
|
bool self;
|
2022-11-27 00:29:32 +08:00
|
|
|
[[nodiscard]] bool Match(const RegisterInfo &info) const { return info.inode == inode; }
|
2022-11-26 22:45:45 +08:00
|
|
|
};
|
|
|
|
|
2022-11-27 00:59:56 +08:00
|
|
|
class HookInfos : public std::map<uintptr_t, HookInfo, std::greater<>> {
|
2022-11-26 22:45:45 +08:00
|
|
|
public:
|
2022-11-27 00:59:56 +08:00
|
|
|
static auto ScanHookInfo() {
|
2022-11-27 06:36:16 +08:00
|
|
|
static ino_t kSelfInode = 0;
|
2022-11-27 00:59:56 +08:00
|
|
|
HookInfos info;
|
2022-11-27 06:36:16 +08:00
|
|
|
auto maps = lsplt::MapInfo::Scan();
|
|
|
|
if (kSelfInode == 0) {
|
|
|
|
auto self = reinterpret_cast<uintptr_t>(__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) {
|
2022-11-27 01:47:12 +08:00
|
|
|
// we basically only care about r-?p entry
|
2022-11-27 00:59:56 +08:00
|
|
|
// 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;
|
2022-11-27 06:36:16 +08:00
|
|
|
auto inode = map.inode;
|
|
|
|
info.emplace(start, HookInfo{{std::move(map)}, {}, 0, nullptr, inode == kSelfInode});
|
2022-11-26 22:45:45 +08:00
|
|
|
}
|
|
|
|
return info;
|
|
|
|
}
|
|
|
|
|
|
|
|
// fiter out ignored
|
2022-11-27 00:29:32 +08:00
|
|
|
void Filter(const std::list<RegisterInfo> ®ister_info) {
|
2022-11-26 22:45:45 +08:00
|
|
|
for (auto iter = begin(); iter != end();) {
|
|
|
|
const auto &info = iter->second;
|
|
|
|
bool matched = false;
|
|
|
|
for (const auto ® : register_info) {
|
2022-11-27 00:29:32 +08:00
|
|
|
if (info.Match(reg)) {
|
2022-11-26 22:45:45 +08:00
|
|
|
matched = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (matched) {
|
2022-11-27 01:47:12 +08:00
|
|
|
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);
|
2022-11-26 22:45:45 +08:00
|
|
|
++iter;
|
|
|
|
} else {
|
|
|
|
iter = erase(iter);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-27 00:59:56 +08:00
|
|
|
void Merge(HookInfos &old) {
|
2022-11-26 22:45:45 +08:00
|
|
|
// 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);
|
2022-11-27 03:05:35 +08:00
|
|
|
} else if (info.second.backup) {
|
2022-11-26 22:45:45 +08:00
|
|
|
emplace(info.first, std::move(info.second));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool DoHook(uintptr_t addr, uintptr_t callback, uintptr_t *backup) {
|
2022-11-27 03:05:35 +08:00
|
|
|
LOGV("Hooking %p", reinterpret_cast<void *>(addr));
|
2022-11-26 22:45:45 +08:00
|
|
|
auto iter = lower_bound(addr);
|
|
|
|
if (iter == end()) return false;
|
|
|
|
// iter.first < addr
|
|
|
|
auto &info = iter->second;
|
|
|
|
if (info.end <= addr) return false;
|
2022-11-27 06:36:16 +08:00
|
|
|
if (!iter->second.backup && !info.self) {
|
2022-11-26 22:45:45 +08:00
|
|
|
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);
|
2022-11-27 03:05:35 +08:00
|
|
|
LOGD("Backup %p to %p", reinterpret_cast<void *>(addr), backup_addr);
|
2022-11-26 22:45:45 +08:00
|
|
|
if (backup_addr == MAP_FAILED) return false;
|
|
|
|
if (auto *new_addr = mremap(reinterpret_cast<void *>(info.start), len, len,
|
|
|
|
MREMAP_FIXED | MREMAP_MAYMOVE, backup_addr);
|
|
|
|
new_addr == MAP_FAILED || new_addr != backup_addr) {
|
|
|
|
return false;
|
|
|
|
}
|
2022-11-27 01:47:12 +08:00
|
|
|
if (auto *new_addr = mmap(reinterpret_cast<void *>(info.start), len,
|
|
|
|
PROT_READ | PROT_WRITE | info.perms,
|
|
|
|
MAP_PRIVATE | MAP_FIXED | MAP_ANON, -1, 0);
|
2022-11-26 22:45:45 +08:00
|
|
|
new_addr == MAP_FAILED) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
memcpy(reinterpret_cast<void *>(info.start), backup_addr, len);
|
|
|
|
info.backup = reinterpret_cast<uintptr_t>(backup_addr);
|
|
|
|
}
|
2022-11-27 06:36:16 +08:00
|
|
|
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<void *>(info.start), len, info.perms);
|
|
|
|
}
|
|
|
|
}
|
2022-11-26 22:45:45 +08:00
|
|
|
auto *the_addr = reinterpret_cast<uintptr_t *>(addr);
|
2022-11-27 03:05:35 +08:00
|
|
|
auto the_backup = *the_addr;
|
2022-11-27 01:47:12 +08:00
|
|
|
if (*the_addr != addr) {
|
|
|
|
*the_addr = callback;
|
|
|
|
if (backup) *backup = the_backup;
|
|
|
|
__builtin___clear_cache(PageStart(addr), PageEnd(addr));
|
|
|
|
}
|
2022-11-26 22:45:45 +08:00
|
|
|
if (auto hook_iter = info.hooks.find(addr); hook_iter != info.hooks.end()) {
|
|
|
|
if (hook_iter->second == callback) info.hooks.erase(hook_iter);
|
|
|
|
} else {
|
2022-11-27 03:05:35 +08:00
|
|
|
info.hooks.emplace(addr, the_backup);
|
2022-11-26 22:45:45 +08:00
|
|
|
}
|
2022-11-27 06:36:16 +08:00
|
|
|
if (info.hooks.empty() && !info.self) {
|
2022-11-26 22:45:45 +08:00
|
|
|
auto len = info.end - info.start;
|
2022-11-27 03:05:35 +08:00
|
|
|
LOGD("Restore %p from %p", reinterpret_cast<void *>(info.start),
|
|
|
|
reinterpret_cast<void *>(info.backup));
|
2022-11-26 22:45:45 +08:00
|
|
|
if (auto *new_addr =
|
|
|
|
mremap(reinterpret_cast<void *>(info.backup), len, len,
|
|
|
|
MREMAP_FIXED | MREMAP_MAYMOVE, reinterpret_cast<void *>(info.start));
|
|
|
|
new_addr == MAP_FAILED || reinterpret_cast<uintptr_t>(new_addr) != info.start) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
info.backup = 0;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-11-27 00:29:32 +08:00
|
|
|
bool DoHook(std::list<RegisterInfo> ®ister_info) {
|
2022-11-26 22:45:45 +08:00
|
|
|
bool res = true;
|
|
|
|
for (auto &[_, info] : *this) {
|
|
|
|
for (auto iter = register_info.begin(); iter != register_info.end();) {
|
|
|
|
const auto ® = *iter;
|
2022-11-27 03:05:35 +08:00
|
|
|
if (info.offset != 0 || !info.Match(reg)) {
|
|
|
|
++iter;
|
|
|
|
continue;
|
|
|
|
}
|
2022-11-27 00:29:32 +08:00
|
|
|
if (!info.elf) info.elf = std::make_unique<Elf>(info.start);
|
2022-11-26 22:45:45 +08:00
|
|
|
if (info.elf && info.elf->Valid()) {
|
2022-11-27 03:05:35 +08:00
|
|
|
LOGD("Hooking %s", iter->symbol.data());
|
2022-11-27 00:29:32 +08:00
|
|
|
for (auto addr : info.elf->FindPltAddr(reg.symbol)) {
|
2022-11-26 22:45:45 +08:00
|
|
|
res = DoHook(addr, reinterpret_cast<uintptr_t>(reg.callback),
|
2022-11-27 00:29:32 +08:00
|
|
|
reinterpret_cast<uintptr_t *>(reg.backup)) &&
|
|
|
|
res;
|
2022-11-26 22:45:45 +08:00
|
|
|
}
|
|
|
|
}
|
2022-11-27 00:29:32 +08:00
|
|
|
iter = register_info.erase(iter);
|
2022-11-26 22:45:45 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool InvalidateBackup() {
|
|
|
|
bool res = true;
|
|
|
|
for (auto &[_, info] : *this) {
|
2022-11-27 00:29:32 +08:00
|
|
|
if (!info.backup) continue;
|
|
|
|
for (auto &[addr, backup] : info.hooks) {
|
|
|
|
// store new address to backup since we don't need backup
|
|
|
|
backup = *reinterpret_cast<uintptr_t *>(addr);
|
|
|
|
}
|
|
|
|
auto len = info.end - info.start;
|
|
|
|
if (auto *new_addr =
|
|
|
|
mremap(reinterpret_cast<void *>(info.backup), len, len,
|
|
|
|
MREMAP_FIXED | MREMAP_MAYMOVE, reinterpret_cast<void *>(info.start));
|
|
|
|
new_addr == MAP_FAILED || reinterpret_cast<uintptr_t>(new_addr) != info.start) {
|
|
|
|
res = false;
|
2022-11-26 22:45:45 +08:00
|
|
|
info.hooks.clear();
|
2022-11-27 00:29:32 +08:00
|
|
|
continue;
|
|
|
|
}
|
2022-11-27 01:47:12 +08:00
|
|
|
if (!mprotect(PageStart(info.start), len, PROT_WRITE)) {
|
2022-11-27 01:06:31 +08:00
|
|
|
for (auto &[addr, backup] : info.hooks) {
|
|
|
|
*reinterpret_cast<uintptr_t *>(addr) = backup;
|
|
|
|
}
|
2022-11-27 01:47:12 +08:00
|
|
|
mprotect(PageStart(info.start), len, info.perms);
|
2022-11-26 22:45:45 +08:00
|
|
|
}
|
2022-11-27 00:29:32 +08:00
|
|
|
info.hooks.clear();
|
|
|
|
info.backup = 0;
|
2022-11-26 22:45:45 +08:00
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-11-27 00:29:32 +08:00
|
|
|
std::mutex hook_mutex;
|
2022-11-26 22:45:45 +08:00
|
|
|
std::list<RegisterInfo> register_info;
|
2022-11-27 00:59:56 +08:00
|
|
|
HookInfos hook_info;
|
2022-11-26 22:45:45 +08:00
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace lsplt {
|
2022-11-27 01:47:29 +08:00
|
|
|
inline namespace v1 {
|
2022-11-27 00:59:56 +08:00
|
|
|
[[maybe_unused]] std::vector<MapInfo> MapInfo::Scan() {
|
2022-11-27 00:43:46 +08:00
|
|
|
constexpr static auto kPermLength = 5;
|
2022-11-27 00:59:56 +08:00
|
|
|
constexpr static auto kMapEntry = 7;
|
2022-11-27 00:43:46 +08:00
|
|
|
std::vector<MapInfo> info;
|
|
|
|
auto maps = std::unique_ptr<FILE, decltype(&fclose)>{fopen("/proc/self/maps", "r"), &fclose};
|
|
|
|
if (maps) {
|
|
|
|
char *line = nullptr;
|
|
|
|
size_t len = 0;
|
|
|
|
ssize_t read;
|
2022-11-27 03:05:35 +08:00
|
|
|
while ((read = getline(&line, &len, maps.get())) > 0) {
|
|
|
|
line[read - 1] = '\0';
|
2022-11-27 00:43:46 +08:00
|
|
|
uintptr_t start = 0;
|
|
|
|
uintptr_t end = 0;
|
|
|
|
uintptr_t off = 0;
|
|
|
|
ino_t inode = 0;
|
2022-11-27 00:59:56 +08:00
|
|
|
unsigned int dev_major = 0;
|
|
|
|
unsigned int dev_minor = 0;
|
2022-11-27 00:43:46 +08:00
|
|
|
std::array<char, kPermLength> perm{'\0'};
|
|
|
|
int path_off;
|
2022-11-27 00:59:56 +08:00
|
|
|
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) {
|
2022-11-27 00:43:46 +08:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
while (path_off < read && isspace(line[path_off])) path_off++;
|
2022-11-27 01:24:21 +08:00
|
|
|
auto &ref = info.emplace_back(MapInfo{start, end, 0, perm[3] == 'p', off,
|
2022-11-27 00:59:56 +08:00
|
|
|
static_cast<dev_t>(makedev(dev_major, dev_minor)),
|
|
|
|
inode, line + path_off});
|
2022-11-27 01:24:21 +08:00
|
|
|
if (perm[0] == 'r') ref.perms |= PROT_READ;
|
|
|
|
if (perm[1] == 'w') ref.perms |= PROT_WRITE;
|
|
|
|
if (perm[2] == 'x') ref.perms |= PROT_EXEC;
|
2022-11-27 00:43:46 +08:00
|
|
|
}
|
|
|
|
free(line);
|
|
|
|
}
|
|
|
|
return info;
|
|
|
|
}
|
2022-11-27 01:47:29 +08:00
|
|
|
|
2022-11-27 03:05:35 +08:00
|
|
|
[[maybe_unused]] bool RegisterHook(ino_t inode, std::string_view symbol, void *callback,
|
2022-11-26 22:45:45 +08:00
|
|
|
void **backup) {
|
2022-11-27 03:05:35 +08:00
|
|
|
if (inode == 0 || symbol.empty() || !callback) return false;
|
|
|
|
LOGV("RegisterHook %lu %s", inode, symbol.data());
|
2022-11-26 22:45:45 +08:00
|
|
|
|
2022-11-27 00:29:32 +08:00
|
|
|
std::unique_lock lock(hook_mutex);
|
2022-11-27 03:05:35 +08:00
|
|
|
register_info.emplace_back(RegisterInfo{inode, std::string{symbol}, callback, backup});
|
2022-11-26 22:45:45 +08:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
[[maybe_unused]] bool CommitHook() {
|
2022-11-27 00:29:32 +08:00
|
|
|
std::unique_lock lock(hook_mutex);
|
2022-11-26 22:45:45 +08:00
|
|
|
if (register_info.empty()) return true;
|
|
|
|
|
2022-11-27 00:59:56 +08:00
|
|
|
auto new_hook_info = HookInfos::ScanHookInfo();
|
|
|
|
if (new_hook_info.empty()) return false;
|
2022-11-26 22:45:45 +08:00
|
|
|
|
2022-11-27 00:59:56 +08:00
|
|
|
new_hook_info.Filter(register_info);
|
2022-11-26 22:45:45 +08:00
|
|
|
|
2022-11-27 00:59:56 +08:00
|
|
|
new_hook_info.Merge(hook_info);
|
2022-11-26 22:45:45 +08:00
|
|
|
// update to new map info
|
2022-11-27 00:59:56 +08:00
|
|
|
hook_info = std::move(new_hook_info);
|
2022-11-26 22:45:45 +08:00
|
|
|
|
2022-11-27 00:59:56 +08:00
|
|
|
return hook_info.DoHook(register_info);
|
2022-11-26 22:45:45 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
[[gnu::destructor]] [[maybe_unused]] bool InvalidateBackup() {
|
2022-11-27 00:29:32 +08:00
|
|
|
std::unique_lock lock(hook_mutex);
|
2022-11-27 00:59:56 +08:00
|
|
|
return hook_info.InvalidateBackup();
|
2022-11-26 22:45:45 +08:00
|
|
|
}
|
2022-11-27 01:47:29 +08:00
|
|
|
} // namespace v1
|
2022-11-26 22:45:45 +08:00
|
|
|
} // namespace lsplt
|