From f96d7d0afa64693c000442271a2c418f28dc4aa2 Mon Sep 17 00:00:00 2001 From: JingMatrix Date: Fri, 1 Aug 2025 20:27:56 +0200 Subject: [PATCH] Make module base address parsing robust Resolves a `SIGSEGV` crash that occurs when co-instrumenting with recent versions of Frida. The root cause was that the previous parsing logic would select the first memory mapping matching the library name. When Frida is active, it can temporarily create a transient, read-only mapping at a lower address than the real library. This would cause our parser to select the wrong base address. This commit refactors the `findModuleBase` function to be structurally aware. It now filters all mappings for the target library and specifically searches for the pattern of a read-only (`r--p`) segment immediately followed by an executable (`r-xp`) segment. This allows it to correctly identify the real library mapping and ignore transient artifacts from other instrumentation frameworks. --- core/src/main/jni/src/elf_util.cpp | 109 +++++++++++++++++++++-------- external/lsplant | 2 +- 2 files changed, 81 insertions(+), 30 deletions(-) diff --git a/core/src/main/jni/src/elf_util.cpp b/core/src/main/jni/src/elf_util.cpp index 85f0c6cdf..fb6d2759b 100644 --- a/core/src/main/jni/src/elf_util.cpp +++ b/core/src/main/jni/src/elf_util.cpp @@ -27,6 +27,9 @@ #include #include +#include +#include +#include #include "linux/xz.h" #include "logging.h" @@ -373,47 +376,95 @@ constexpr inline bool contains(std::string_view a, std::string_view b) { return a.find(b) != std::string_view::npos; } +// A clean and simple struct to hold parsed map entry data. +struct MapEntry { + uintptr_t start_addr; + char perms[5] = {0}; // Assured null-termination + std::string pathname; +}; + bool ElfImg::findModuleBase() { - off_t load_addr; - bool found = false; + // Open the maps file using standard C file I/O. FILE *maps = fopen("/proc/self/maps", "r"); + if (!maps) { + LOGE("failed to open /proc/self/maps"); + return false; + } - char *buff = nullptr; - size_t len = 0; - ssize_t nread; - - while ((nread = getline(&buff, &len, maps)) != -1) { - std::string_view line{buff, static_cast(nread)}; - - if ((contains(line, "r-xp") || contains(line, "r--p")) && contains(line, elf)) { - LOGD("found: {}", line); - if (auto begin = line.find_last_of(' '); - begin != std::string_view::npos && line[++begin] == '/') { - found = true; - elf = line.substr(begin); - if (elf.back() == '\n') elf.pop_back(); - LOGD("update path: {}", elf); - break; - } + char line_buffer[512]; // A reasonable fixed-size buffer for map lines. + std::vector filtered_list; + + // Step 1: Filter all entries containing `elf` in its path. + while (fgets(line_buffer, sizeof(line_buffer), maps)) { + // Use an intermediate variable of a known, large type to avoid format warnings. + // `unsigned long long` and `%llx` are standard and portable. + unsigned long long temp_start; + char path_buffer[256] = {0}; + char p[5] = {0}; + + // Use the portable `%llx` specifier. + int items_parsed = + sscanf(line_buffer, "%llx-%*x %4s %*x %*s %*d %255s", &temp_start, p, path_buffer); + + // The filter condition: must parse the path, and it must contain the elf name. + if (items_parsed == 3 && strstr(path_buffer, elf.c_str()) != nullptr) { + MapEntry entry; + // Safely assign the parsed value to the uintptr_t. + entry.start_addr = static_cast(temp_start); + strncpy(entry.perms, p, 4); + entry.pathname = path_buffer; + filtered_list.push_back(std::move(entry)); } } - if (!found) { - if (buff) free(buff); - LOGE("failed to read load address for {}", elf); - fclose(maps); + fclose(maps); + + if (filtered_list.empty()) { + LOGE("Could not find any mappings for {}", elf.data()); return false; } - if (char *next = buff; load_addr = strtoul(buff, &next, 16), next == buff) { - LOGE("failed to read load address for {}", elf); + // Also part of Step 1: Print the filtered list for debugging. + LOGD("Found {} filtered map entries for {}:", filtered_list.size(), elf.data()); + for (const auto &entry : filtered_list) { + LOGD(" {:#x} {} {}", entry.start_addr, entry.perms, entry.pathname); } - if (buff) free(buff); + const MapEntry *found_block = nullptr; - fclose(maps); + // Step 2: In the filtered list, search for the first `r--p` whose next entry is `r-xp`. + for (size_t i = 0; i < filtered_list.size() - 1; ++i) { + if (strcmp(filtered_list[i].perms, "r--p") == 0 && + strcmp(filtered_list[i + 1].perms, "r-xp") == 0) { + found_block = &filtered_list[i]; + LOGD("Found `r--p` -> `r-xp` pattern. Choosing base from `r--p` block at {:#x}", + found_block->start_addr); + break; // Pattern found, exit loop. + } + } + + // Step 2 (Fallback): If the pattern was not found, find the first `r-xp` entry. + if (!found_block) { + LOGD("`r--p` -> `r-xp` pattern not found. Falling back to first `r-xp` entry."); + for (const auto &entry : filtered_list) { + if (strcmp(entry.perms, "r-xp") == 0) { + found_block = &entry; + LOGD("Found first `r-xp` block at {:#x}", found_block->start_addr); + break; // Fallback found, exit loop. + } + } + } + + if (!found_block) { + LOGE("Fatal: Could not determine a base address for {}", elf.data()); + return false; + } + + // Step 3: Use the starting address of the found block as the base address. + base = reinterpret_cast(found_block->start_addr); + elf = found_block->pathname; // Update elf path to the canonical one. - LOGD("get module base {}: {:#x}", elf, load_addr); + LOGD("get module base {}: {:#x}", elf, found_block->start_addr); + LOGD("update path: {}", elf); - base = reinterpret_cast(load_addr); return true; } diff --git a/external/lsplant b/external/lsplant index 4e9ad800e..a0990196c 160000 --- a/external/lsplant +++ b/external/lsplant @@ -1 +1 @@ -Subproject commit 4e9ad800eba97550a38a1b4f9d06e4c3386aaf1c +Subproject commit a0990196c26e3fad57213a03af22dbf993396c8a