|
37 | 37 | #include <sys/types.h> |
38 | 38 | #include <sys/syscall.h> |
39 | 39 | #include <dlfcn.h> |
| 40 | +#include <link.h> |
| 41 | +#include <elf.h> |
40 | 42 | #include <limits.h> |
41 | 43 |
|
42 | 44 | #include "log.h" |
43 | 45 | #include "il2cpp_api.h" |
44 | 46 | #include "il2cpp_dump.h" |
| 47 | +#include "elf_sym_find.h" |
45 | 48 |
|
46 | 49 | #ifndef PATH_MAX |
47 | 50 | #define PATH_MAX 4096 |
@@ -214,7 +217,9 @@ static void read_package_name(char *buf, size_t sz) { |
214 | 217 | * ActivityThread.currentApplication().getFilesDir().getAbsolutePath() |
215 | 218 | * |
216 | 219 | * The call may block until the Application object is created (up to |
217 | | - * 30 seconds). |
| 220 | + * 30 seconds). Any pending JNI exceptions are explicitly cleared |
| 221 | + * after each call to avoid spurious failures in certain container |
| 222 | + * environments. |
218 | 223 | * |
219 | 224 | * @param out_buf Buffer to receive the path. |
220 | 225 | * @param out_sz Size of out_buf. |
@@ -261,6 +266,9 @@ static int get_files_dir_jni(char *out_buf, size_t out_sz) { |
261 | 266 |
|
262 | 267 | cls_at = (*env)->FindClass(env, "android/app/ActivityThread"); |
263 | 268 | if (!cls_at) { CLR_EXC(); goto jni_done; } |
| 269 | + /* Avoid spurious failures in some |
| 270 | +container configurations. */ |
| 271 | + CLR_EXC(); |
264 | 272 |
|
265 | 273 | mid_ca = (*env)->GetStaticMethodID(env, cls_at, |
266 | 274 | "currentApplication", "()Landroid/app/Application;"); |
@@ -327,62 +335,242 @@ static int get_files_dir_jni(char *out_buf, size_t out_sz) { |
327 | 335 | } |
328 | 336 |
|
329 | 337 | /* ------------------------------------------------------------------------- |
330 | | - * Locate libil2cpp.so in the process memory map |
| 338 | + * IL2CPP library discovery – three‑tier strategy |
331 | 339 | * ------------------------------------------------------------------------- */ |
| 340 | + |
332 | 341 | /** |
333 | | - * @brief Parse /proc/self/maps to find the base address and path of |
334 | | - * libil2cpp.so. |
335 | | - * |
336 | | - * The function searches for a mapping where the file offset is 0 |
337 | | - * (the first loadable segment) and the path ends with "libil2cpp.so". |
| 342 | + * @brief Quick ELF magic check at a given address. |
338 | 343 | * |
339 | | - * @param out_path Buffer for the absolute path (may be empty). |
340 | | - * @param path_sz Size of out_path. |
341 | | - * @param out_base Receives the mapped base address. |
342 | | - * @return 1 if found, 0 otherwise. |
| 344 | + * @param addr potential base address of an ELF image. |
| 345 | + * @return 1 if the ELF magic (\x7fELF) is present, 0 otherwise. |
343 | 346 | */ |
344 | | -static int find_il2cpp_in_maps(char *out_path, size_t path_sz, |
345 | | - uintptr_t *out_base) { |
346 | | - *out_base = 0; |
347 | | - out_path[0] = '\0'; |
| 347 | +static int is_elf_at(uintptr_t addr) { |
| 348 | + if (!addr) return 0; |
| 349 | + const unsigned char *m = (const unsigned char *)addr; |
| 350 | + return (m[0] == 0x7f && m[1] == 'E' && m[2] == 'L' && m[3] == 'F'); |
| 351 | +} |
348 | 352 |
|
| 353 | +/** |
| 354 | + * @brief Tier A – scan /proc/self/maps for libil2cpp.so. |
| 355 | + * |
| 356 | + * Looks for readable entries whose path ends with "libil2cpp.so", |
| 357 | + * selects the one with the lowest start address, and verifies the |
| 358 | + * ELF signature. |
| 359 | + * |
| 360 | + * @param out_path buffer for the full path of the library. |
| 361 | + * @param psz size of out_path. |
| 362 | + * @param out_base receives the detected base address. |
| 363 | + * @return 1 on success, 0 on failure. |
| 364 | + */ |
| 365 | +static int find_il2cpp_by_maps(char *out_path, size_t psz, uintptr_t *out_base) { |
349 | 366 | FILE *maps = fopen("/proc/self/maps", "r"); |
350 | 367 | if (!maps) return 0; |
351 | 368 |
|
352 | | - char line[1024]; |
353 | | - int found = 0; |
| 369 | + char line[1024]; |
| 370 | + uintptr_t best = (uintptr_t)-1; |
| 371 | + char bpath[PATH_MAX] = {0}; |
354 | 372 |
|
355 | 373 | while (fgets(line, sizeof(line), maps)) { |
356 | | - if (!strstr(line, "libil2cpp.so")) continue; |
| 374 | + if (!strstr(line, "libil2cpp")) continue; |
357 | 375 |
|
358 | | - unsigned long addr_s = 0, addr_e = 0, offset = 0, inode = 0; |
359 | | - char perms[8] = {0}; |
360 | | - char dev[16] = {0}; |
361 | | - char path[PATH_MAX] = {0}; |
| 376 | + unsigned long s = 0, e = 0, off = 0, ino = 0; |
| 377 | + char perms[8] = {0}, dev[16] = {0}, path[PATH_MAX] = {0}; |
362 | 378 |
|
363 | 379 | int r = sscanf(line, "%lx-%lx %7s %lx %15s %lu %4095s", |
364 | | - &addr_s, &addr_e, perms, &offset, dev, &inode, path); |
| 380 | + &s, &e, perms, &off, dev, &ino, path); |
| 381 | + |
365 | 382 | if (r < 7 || !path[0] || path[0] == '[') continue; |
| 383 | + if (!strstr(path, "libil2cpp.so")) continue; |
| 384 | + if (perms[0] != 'r') continue; |
| 385 | + |
| 386 | + /* Track the lowest start address = ELF header location */ |
| 387 | + if ((uintptr_t)s < best) { |
| 388 | + best = (uintptr_t)s; |
| 389 | + strncpy(bpath, path, sizeof(bpath) - 1); |
| 390 | + bpath[sizeof(bpath) - 1] = '\0'; |
| 391 | + } |
| 392 | + } |
| 393 | + fclose(maps); |
| 394 | + |
| 395 | + if (best == (uintptr_t)-1 || !bpath[0]) return 0; |
| 396 | + if (!is_elf_at(best)) { |
| 397 | + LOGW("maps_scan: no ELF magic at 0x%lx (%s)", (unsigned long)best, bpath); |
| 398 | + return 0; |
| 399 | + } |
366 | 400 |
|
367 | | - const char *fn = strrchr(path, '/'); |
368 | | - fn = fn ? fn + 1 : path; |
369 | | - if (strcmp(fn, "libil2cpp.so") != 0) continue; |
370 | | - |
371 | | - /* The first loadable segment has file offset 0 */ |
372 | | - if (offset == 0) { |
373 | | - *out_base = (uintptr_t)addr_s; |
374 | | - strncpy(out_path, path, path_sz - 1); |
375 | | - out_path[path_sz - 1] = '\0'; |
376 | | - found = 1; |
377 | | - LOGI("libil2cpp.so: base=0x%lx path=%s", |
378 | | - (unsigned long)addr_s, out_path); |
379 | | - break; |
| 401 | + *out_base = best; |
| 402 | + strncpy(out_path, bpath, psz - 1); |
| 403 | + out_path[psz - 1] = '\0'; |
| 404 | + LOGI("Tier A found: base=0x%lx path=%s", (unsigned long)best, bpath); |
| 405 | + return 1; |
| 406 | +} |
| 407 | + |
| 408 | +/* ------------------------------------------------------------------ |
| 409 | + * Tier B: dl_iterate_phdr |
| 410 | + * ------------------------------------------------------------------ */ |
| 411 | +typedef struct { char path[PATH_MAX]; uintptr_t base; int found; } PhdrCtx; |
| 412 | + |
| 413 | +static int phdr_cb(struct dl_phdr_info *info, size_t size, void *arg) { |
| 414 | + (void)size; |
| 415 | + PhdrCtx *ctx = (PhdrCtx *)arg; |
| 416 | + if (!info || !info->dlpi_name) return 0; |
| 417 | + if (!strstr(info->dlpi_name, "libil2cpp.so")) return 0; |
| 418 | + |
| 419 | + uintptr_t base = (uintptr_t)info->dlpi_addr; |
| 420 | + |
| 421 | + /* dlpi_addr is load bias; for p_vaddr==0 SOs it equals the base address. |
| 422 | + * For non-zero p_vaddr, add the minimum PT_LOAD vaddr to get the header. */ |
| 423 | + if (!is_elf_at(base)) { |
| 424 | + uintptr_t min_vaddr = (uintptr_t)-1; |
| 425 | + ElfW(Half) i; |
| 426 | + for (i = 0; i < info->dlpi_phnum; ++i) { |
| 427 | + if (info->dlpi_phdr[i].p_type == PT_LOAD) { |
| 428 | + uintptr_t v = (uintptr_t)info->dlpi_phdr[i].p_vaddr; |
| 429 | + if (v < min_vaddr) min_vaddr = v; |
| 430 | + } |
380 | 431 | } |
| 432 | + if (min_vaddr != (uintptr_t)-1) |
| 433 | + base = (uintptr_t)info->dlpi_addr + min_vaddr; |
| 434 | + if (!is_elf_at(base)) return 0; |
381 | 435 | } |
| 436 | + |
| 437 | + ctx->base = base; |
| 438 | + strncpy(ctx->path, info->dlpi_name, sizeof(ctx->path) - 1); |
| 439 | + ctx->path[sizeof(ctx->path) - 1] = '\0'; |
| 440 | + ctx->found = 1; |
| 441 | + return 1; /* stop iteration */ |
| 442 | +} |
| 443 | + |
| 444 | +/** |
| 445 | + * @brief Locate libil2cpp.so via dl_iterate_phdr. |
| 446 | + * |
| 447 | + * Iterates over all loaded shared objects and finds the one whose |
| 448 | + * name contains "libil2cpp.so". It computes the runtime base by |
| 449 | + * adding the smallest PT_LOAD vaddr to the dlpi_addr reported by |
| 450 | + * the linker (as some containers mangle the reported base). |
| 451 | + * |
| 452 | + * @param out_path buffer for the library path. |
| 453 | + * @param psz size of out_path. |
| 454 | + * @param out_base receives the computed base address. |
| 455 | + * @return 1 on success, 0 on failure. |
| 456 | + */ |
| 457 | +static int find_il2cpp_by_phdr(char *out_path, size_t psz, uintptr_t *out_base) { |
| 458 | + PhdrCtx ctx; |
| 459 | + memset(&ctx, 0, sizeof(ctx)); |
| 460 | + dl_iterate_phdr(phdr_cb, &ctx); |
| 461 | + if (!ctx.found) return 0; |
| 462 | + *out_base = ctx.base; |
| 463 | + strncpy(out_path, ctx.path, psz - 1); |
| 464 | + out_path[psz - 1] = '\0'; |
| 465 | + LOGI("Tier B found: base=0x%lx path=%s", (unsigned long)ctx.base, ctx.path); |
| 466 | + return 1; |
| 467 | +} |
| 468 | + |
| 469 | +/** |
| 470 | + * @brief Tier C – brute‑force scan of all readable, offset‑0 mappings. |
| 471 | + * |
| 472 | + * Opens /proc/self/mem, scans every readable region with file offset 0, |
| 473 | + * checks for the ELF magic, and then resolves the `il2cpp_domain_get` |
| 474 | + * symbol to confirm that the region is indeed libil2cpp.so. This is |
| 475 | + * the final fallback and can be slow. |
| 476 | + * |
| 477 | + * @param out_path buffer for the library path (or a synthetic name). |
| 478 | + * @param psz size of out_path. |
| 479 | + * @param out_base receives the detected base address. |
| 480 | + * @return 1 on success, 0 on failure. |
| 481 | + */ |
| 482 | +static int find_il2cpp_by_brute(char *out_path, size_t psz, uintptr_t *out_base) { |
| 483 | + FILE *maps = fopen("/proc/self/maps", "r"); |
| 484 | + if (!maps) return 0; |
| 485 | + |
| 486 | + /* Open /proc/self/mem for safe pread()-based magic checks. |
| 487 | + * pread() returns -1 on unreadable/XOM pages — never faults. */ |
| 488 | + int memfd = open("/proc/self/mem", O_RDONLY); |
| 489 | + |
| 490 | + char line[1024]; |
| 491 | + int found = 0; |
| 492 | + |
| 493 | + while (fgets(line, sizeof(line), maps) && !found) { |
| 494 | + unsigned long s = 0, e = 0, off = 0, ino = 0; |
| 495 | + char perms[8] = {0}, dev[16] = {0}, path[PATH_MAX] = {0}; |
| 496 | + |
| 497 | + int r = sscanf(line, "%lx-%lx %7s %lx %15s %lu %4095s", |
| 498 | + &s, &e, perms, &off, dev, &ino, path); |
| 499 | + |
| 500 | + if (r < 6) continue; |
| 501 | + if (perms[0] != 'r') continue; |
| 502 | + if (off != 0) continue; |
| 503 | + if (path[0] == '[') continue; |
| 504 | + if (strstr(path, "/dev/") || |
| 505 | + strstr(path, "/proc/") || |
| 506 | + strstr(path, "/sys/")) continue; |
| 507 | + |
| 508 | + uintptr_t base = (uintptr_t)s; |
| 509 | + |
| 510 | + /* Safe ELF magic check via pread() — no fault on XOM pages */ |
| 511 | + int magic_ok = 0; |
| 512 | + if (memfd >= 0) { |
| 513 | + unsigned char magic[4] = {0}; |
| 514 | + ssize_t n = pread(memfd, magic, 4, (off_t)base); |
| 515 | + magic_ok = (n == 4 && |
| 516 | + magic[0] == 0x7f && magic[1] == 'E' && |
| 517 | + magic[2] == 'L' && magic[3] == 'F'); |
| 518 | + } else { |
| 519 | + /* Fallback: direct read protected by thread crash handler */ |
| 520 | + magic_ok = is_elf_at(base); |
| 521 | + } |
| 522 | + if (!magic_ok) continue; |
| 523 | + |
| 524 | + /* Confirm: probe il2cpp_domain_get via in-memory ELF parse. |
| 525 | + * Protected by thread-level crash handler if a bad page is hit. */ |
| 526 | + uintptr_t sym = elf_sym_find(base, "il2cpp_domain_get"); |
| 527 | + if (!sym) continue; |
| 528 | + |
| 529 | + *out_base = base; |
| 530 | + if (r >= 7 && path[0]) |
| 531 | + strncpy(out_path, path, psz - 1); |
| 532 | + else |
| 533 | + snprintf(out_path, psz, "<anon@0x%lx>", (unsigned long)base); |
| 534 | + out_path[psz - 1] = '\0'; |
| 535 | + LOGI("Tier C found: base=0x%lx path=%s", (unsigned long)base, out_path); |
| 536 | + found = 1; |
| 537 | + } |
| 538 | + |
| 539 | + if (memfd >= 0) close(memfd); |
382 | 540 | fclose(maps); |
383 | 541 | return found; |
384 | 542 | } |
385 | 543 |
|
| 544 | +/** |
| 545 | + * @brief Dispatch: try all three discovery tiers in order. |
| 546 | + * |
| 547 | + * Tier A – /proc/self/maps with ELF check. |
| 548 | + * Tier B – dl_iterate_phdr. |
| 549 | + * Tier C – brute‑force scan. |
| 550 | + * |
| 551 | + * The first successful tier sets `out_path` and `out_base`. |
| 552 | + * |
| 553 | + * @param out_path buffer for the library path. |
| 554 | + * @param path_sz size of out_path. |
| 555 | + * @param out_base receives the base address. |
| 556 | + * @return 1 if any tier succeeded, 0 otherwise. |
| 557 | + */ |
| 558 | +static int find_il2cpp_in_maps(char *out_path, size_t path_sz, |
| 559 | + uintptr_t *out_base) { |
| 560 | + *out_base = 0; |
| 561 | + out_path[0] = '\0'; |
| 562 | + |
| 563 | + if (find_il2cpp_by_maps(out_path, path_sz, out_base)) return 1; |
| 564 | + LOGW("Tier A failed; trying dl_iterate_phdr..."); |
| 565 | + |
| 566 | + if (find_il2cpp_by_phdr(out_path, path_sz, out_base)) return 1; |
| 567 | + LOGW("Tier B failed; trying brute ELF scan (slow)..."); |
| 568 | + |
| 569 | + if (find_il2cpp_by_brute(out_path, path_sz, out_base)) return 1; |
| 570 | + |
| 571 | + return 0; |
| 572 | +} |
| 573 | + |
386 | 574 | /* ------------------------------------------------------------------------- |
387 | 575 | * Background dump thread |
388 | 576 | * ------------------------------------------------------------------------- */ |
|
0 commit comments