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