improve: cache scanned virtual maps

Reading the file `/proc/self/maps` is detectable by the target process.
Hence, we should cache scanned virtual maps after `libart.so` is loaded for later plt hooks in the target process.
This commit is contained in:
JingMatrix
2024-12-15 01:05:23 +01:00
parent 2814aaf67f
commit 5e072bd919
3 changed files with 28 additions and 17 deletions

2
.gitmodules vendored
View File

@@ -1,3 +1,3 @@
[submodule "loader/src/external/lsplt"]
path = loader/src/external/lsplt
url = https://github.com/LSPosed/lsplt
url = https://github.com/JingMatrix/LSPlt

View File

@@ -122,6 +122,7 @@ struct ZygiskContext {
vector<tuple<dev_t, ino_t, const char *, void **>> *plt_hook_list;
map<string, vector<JNINativeMethod>, StringCmp> *jni_hook_list;
bool should_unmap_zygisk = false;
std::vector<lsplt::MapInfo> cached_map_infos = {};
} // namespace
@@ -187,6 +188,7 @@ DCL_HOOK_FUNC(int, pthread_attr_setstacksize, void *target, size_t size) {
if (should_unmap_zygisk) {
unhook_functions();
cached_map_infos.clear();
if (should_unmap_zygisk) {
// Because both `pthread_attr_setstacksize` and `dlclose` have the same function signature,
// we can use `musttail` to let the compiler reuse our stack frame and thus
@@ -205,6 +207,8 @@ DCL_HOOK_FUNC(char *, strdup, const char *s) {
if (strcmp(s, "com.android.internal.os.ZygoteInit") == 0) {
LOGV("strdup %s", s);
initialize_jni_hook();
cached_map_infos = lsplt::MapInfo::Scan();
LOGD("cached_map_infos updated");
}
return old_strdup(s);
@@ -268,7 +272,7 @@ void initialize_jni_hook() {
auto get_created_java_vms = reinterpret_cast<jint (*)(JavaVM **, jsize, jsize *)>(
dlsym(RTLD_DEFAULT, "JNI_GetCreatedJavaVMs"));
if (!get_created_java_vms) {
for (auto &map: lsplt::MapInfo::Scan()) {
for (auto &map: cached_map_infos) {
if (!map.path.ends_with("/libnativehelper.so")) continue;
void *h = dlopen(map.path.data(), RTLD_LAZY);
if (!h) {
@@ -349,7 +353,7 @@ bool ZygiskModule::RegisterModuleImpl(ApiTable *api, long *module) {
api->v2.getFlags = [](auto) { return ZygiskModule::getFlags(); };
}
if (api_version >= 4) {
api->v4.pltHookCommit = lsplt::CommitHook;
api->v4.pltHookCommit = []() { return lsplt::CommitHook(cached_map_infos); };
api->v4.pltHookRegister = [](dev_t dev, ino_t inode, const char *symbol, void *fn, void **backup) {
if (dev == 0 || inode == 0 || symbol == nullptr || fn == nullptr)
return;
@@ -383,7 +387,7 @@ void ZygiskContext::plt_hook_exclude(const char *regex, const char *symbol) {
void ZygiskContext::plt_hook_process_regex() {
if (register_info.empty())
return;
for (auto &map : lsplt::MapInfo::Scan()) {
for (auto &map : cached_map_infos) {
if (map.offset != 0 || !map.is_private || !(map.perms & PROT_READ)) continue;
for (auto &reg: register_info) {
if (regexec(&reg.regex, map.path.data(), 0, nullptr, 0) != 0)
@@ -411,7 +415,7 @@ bool ZygiskContext::plt_hook_commit() {
register_info.clear();
ignore_info.clear();
}
return lsplt::CommitHook();
return lsplt::CommitHook(cached_map_infos);
}
@@ -719,8 +723,8 @@ ZygiskContext::~ZygiskContext() {
} // namespace
static bool hook_commit() {
if (lsplt::CommitHook()) {
static bool hook_commit(std::vector<lsplt::MapInfo> &map_infos = cached_map_infos) {
if (lsplt::CommitHook(map_infos)) {
return true;
} else {
LOGE("plt_hook failed");
@@ -752,23 +756,23 @@ void clean_trace(const char* path, bool spoof_maps) {
LOGD("spoofing virtual maps for %s", path);
// spoofing map names is futile in Android, we do it simply
// to avoid Zygisk detections based on string comparison
for (auto &info : lsplt::MapInfo::Scan()) {
if (strstr(info.path.c_str(), path))
for (auto &map : lsplt::MapInfo::Scan()) {
if (strstr(map.path.c_str(), path))
{
void *addr = (void *)info.start;
size_t size = info.end - info.start;
void *addr = (void *)map.start;
size_t size = map.end - map.start;
void *copy = mmap(nullptr, size, PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0);
if (copy == MAP_FAILED) {
LOGE("failed to backup block %s [%p, %p]", info.path.c_str(), addr, (void*)info.end);
LOGE("failed to backup block %s [%p, %p]", map.path.c_str(), addr, (void*)map.end);
continue;
}
if ((info.perms & PROT_READ) == 0) {
if ((map.perms & PROT_READ) == 0) {
mprotect(addr, size, PROT_READ);
}
memcpy(copy, addr, size);
mremap(copy, size, size, MREMAP_MAYMOVE | MREMAP_FIXED, addr);
mprotect(addr, size, info.perms);
mprotect(addr, size, map.perms);
}
}
}
@@ -783,7 +787,8 @@ void hook_functions() {
// ino_t native_bridge_inode = 0;
// dev_t native_bridge_dev = 0;
for (auto &map : lsplt::MapInfo::Scan()) {
cached_map_infos = lsplt::MapInfo::Scan();
for (auto &map : cached_map_infos) {
if (map.path.ends_with("libandroid_runtime.so")) {
android_runtime_inode = map.inode;
android_runtime_dev = map.dev;
@@ -809,7 +814,7 @@ static void hook_unloader() {
ino_t art_inode = 0;
dev_t art_dev = 0;
for (auto &map : lsplt::MapInfo::Scan()) {
for (auto &map : cached_map_infos) {
if (map.path.ends_with("/libart.so")) {
art_inode = map.inode;
art_dev = map.dev;
@@ -817,6 +822,12 @@ static void hook_unloader() {
}
}
if (art_dev == 0 || art_inode == 0) {
LOGE("virtual map for libart.so is not cached");
return;
} else {
LOGD("hook_unloader called with libart.so [%zu:%lu]", art_dev, art_inode);
}
PLT_HOOK_REGISTER(art_dev, art_inode, pthread_attr_setstacksize);
hook_commit();
}