fix: remote strlen fail in dlopen path; add: GNU ifunc handling to elf_util.c

This commit fixes the remote call to "strlen" fail because ReZygisk ELF utils would not handle GNU indirect functions, making it call the in-between function instead of the actual function.
This commit is contained in:
ThePedroo
2025-08-10 17:15:43 -03:00
parent 08513b17e8
commit f9fcf1c2e7
3 changed files with 183 additions and 71 deletions

View File

@@ -5,6 +5,7 @@
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/auxv.h>
#include <unistd.h>
@@ -159,14 +160,14 @@ ElfImg *ElfImg_create(const char *elf, void *base) {
}
if (base) {
/* LOGI: Due to the use in zygisk-ptracer, we need to allow pre-
/* INFO: Due to the use in zygisk-ptracer, we need to allow pre-
fetched bases to be passed, as the linker (Android 7.1
and below) is not loaded from dlopen, which makes it not
be visible with dl_iterate_phdr.
*/
img->base = base;
LOGI("Using provided base address 0x%p for %s", base, elf);
LOGD("Using provided base address 0x%p for %s", base, elf);
} else {
if (!_find_module_base(img)) {
LOGE("Failed to find module base for %s using dl_iterate_phdr", elf);
@@ -387,7 +388,7 @@ ElfImg *ElfImg_create(const char *elf, void *base) {
img->symstr_offset_for_symtab = 0;
}
} else {
LOGI("No .symtab section found or section headers missing");
LOGD("No .symtab section found or section headers missing");
img->symtab_start = NULL;
img->symtab_count = 0;
@@ -403,45 +404,24 @@ ElfImg *ElfImg_create(const char *elf, void *base) {
img->bias = phdr[i].p_vaddr - phdr[i].p_offset;
bias_calculated = true;
LOGI("Calculated bias %ld from PT_LOAD segment %d (vaddr %lx)", (long)img->bias, i, (unsigned long)phdr[i].p_vaddr);
LOGD("Calculated bias %ld from PT_LOAD segment %d (vaddr %lx)", (long)img->bias, i, (unsigned long)phdr[i].p_vaddr);
break;
}
}
if (!bias_calculated) {
for (int i = 0; i < img->header->e_phnum; ++i) {
if (phdr[i].p_type == PT_LOAD) {
if (!bias_calculated) for (int i = 0; i < img->header->e_phnum; ++i) {
if (phdr[i].p_type != PT_LOAD) continue;
img->bias = phdr[i].p_vaddr - phdr[i].p_offset;
bias_calculated = true;
LOGI("Calculated bias %ld from first PT_LOAD segment %d (vaddr %lx, offset %lx)",
LOGD("Calculated bias %ld from first PT_LOAD segment %d (vaddr %lx, offset %lx)",
(long)img->bias, i, (unsigned long)phdr[i].p_vaddr, (unsigned long)phdr[i].p_offset);
break;
}
}
}
}
if (!bias_calculated && shdr_base) {
LOGW("Could not calculate bias from program headers, falling back to section method.");
uintptr_t shoff_for_bias = (uintptr_t)shdr_base;
for (int i = 0; i < img->header->e_shnum; i++, shoff_for_bias += img->header->e_shentsize) {
ElfW(Shdr) *section_h = (ElfW(Shdr *))shoff_for_bias;
if ((section_h->sh_flags & SHF_ALLOC) && section_h->sh_addr != 0) {
img->bias = (off_t)section_h->sh_addr - (off_t)section_h->sh_offset;
bias_calculated = true;
char *sname = section_str ? (section_h->sh_name + section_str) : "<?>";
LOGI("Calculated bias %ld from first allocated section %s (addr %lx, offset %lx)",
(long)img->bias, sname, (unsigned long)section_h->sh_addr, (unsigned long)section_h->sh_offset);
break;
}
}
}
if (!bias_calculated)
LOGE("Failed to calculate bias for %s. Assuming bias is 0.", elf);
@@ -523,7 +503,7 @@ bool _load_symtabs(ElfImg *img) {
return true;
}
ElfW(Addr) GnuLookup(ElfImg *restrict img, const char *name, uint32_t hash) {
ElfW(Addr) GnuLookup(ElfImg *restrict img, const char *name, uint32_t hash, unsigned char *sym_type) {
if (img->gnu_nbucket_ == 0 || img->gnu_bloom_size_ == 0 || !img->gnu_bloom_filter_ || !img->gnu_bucket_ || !img->gnu_chain_ || !img->dynsym_start || !img->strtab_start)
return 0;
@@ -545,7 +525,7 @@ ElfW(Addr) GnuLookup(ElfImg *restrict img, const char *name, uint32_t hash) {
uint32_t sym_index = img->gnu_bucket_[hash % img->gnu_nbucket_];
if (sym_index < img->gnu_symndx_) {
LOGI("Symbol %s hash %u maps to bucket %u index %u (below gnu_symndx %u), not exported?", name, hash, hash % img->gnu_nbucket_, sym_index, img->gnu_symndx_);
LOGW("Symbol %s hash %u maps to bucket %u index %u (below gnu_symndx %u), not exported?", name, hash, hash % img->gnu_nbucket_, sym_index, img->gnu_symndx_);
return 0;
}
@@ -568,8 +548,12 @@ ElfW(Addr) GnuLookup(ElfImg *restrict img, const char *name, uint32_t hash) {
return 0;
}
if ((((chain_val ^ hash) >> 1) == 0 && strcmp(name, strings + sym->st_name) == 0) && sym->st_shndx != SHN_UNDEF)
if ((((chain_val ^ hash) >> 1) == 0 && strcmp(name, strings + sym->st_name) == 0) && sym->st_shndx != SHN_UNDEF) {
unsigned int type = ELF_ST_TYPE(sym->st_info);
if (sym_type) *sym_type = type;
return sym->st_value;
}
while ((chain_val & 1) == 0) {
sym_index++;
@@ -589,14 +573,18 @@ ElfW(Addr) GnuLookup(ElfImg *restrict img, const char *name, uint32_t hash) {
break;
}
if ((((chain_val ^ hash) >> 1) == 0 && strcmp(name, strings + sym->st_name) == 0) && sym->st_shndx != SHN_UNDEF)
if ((((chain_val ^ hash) >> 1) == 0 && strcmp(name, strings + sym->st_name) == 0) && sym->st_shndx != SHN_UNDEF) {
unsigned int type = ELF_ST_TYPE(sym->st_info);
if (sym_type) *sym_type = type;
return sym->st_value;
}
}
return 0;
}
ElfW(Addr) ElfLookup(ElfImg *restrict img, const char *restrict name, uint32_t hash) {
ElfW(Addr) ElfLookup(ElfImg *restrict img, const char *restrict name, uint32_t hash, unsigned char *sym_type) {
if (img->nbucket_ == 0 || !img->bucket_ || !img->chain_ || !img->dynsym_start || !img->strtab_start)
return 0;
@@ -605,14 +593,18 @@ ElfW(Addr) ElfLookup(ElfImg *restrict img, const char *restrict name, uint32_t h
for (size_t n = img->bucket_[hash % img->nbucket_]; n != STN_UNDEF; n = img->chain_[n]) {
ElfW(Sym) *sym = img->dynsym_start + n;
if (strcmp(name, strings + sym->st_name) == 0 && sym->st_shndx != SHN_UNDEF)
if (strcmp(name, strings + sym->st_name) == 0 && sym->st_shndx != SHN_UNDEF) {
unsigned int type = ELF_ST_TYPE(sym->st_info);
if (sym_type) *sym_type = type;
return sym->st_value;
}
}
return 0;
}
ElfW(Addr) LinearLookup(ElfImg *img, const char *restrict name) {
ElfW(Addr) LinearLookup(ElfImg *img, const char *restrict name, unsigned char *sym_type) {
if (!_load_symtabs(img)) {
LOGE("Failed to load symtabs for linear lookup of %s", name);
@@ -623,7 +615,7 @@ ElfW(Addr) LinearLookup(ElfImg *img, const char *restrict name) {
if (valid_symtabs_amount == 0) {
LOGW("No valid symbols (FUNC/OBJECT with size > 0) found in .symtab for %s", img->elf);
return false;
return 0;
}
for (size_t i = 0; i < valid_symtabs_amount; i++) {
@@ -633,13 +625,16 @@ ElfW(Addr) LinearLookup(ElfImg *img, const char *restrict name) {
if (img->symtabs_[i].sym->st_shndx == SHN_UNDEF)
continue;
unsigned int type = ELF_ST_TYPE(img->symtabs_[i].sym->st_info);
if (sym_type) *sym_type = type;
return img->symtabs_[i].sym->st_value;
}
return 0;
}
ElfW(Addr) LinearLookupByPrefix(ElfImg *img, const char *prefix) {
ElfW(Addr) LinearLookupByPrefix(ElfImg *img, const char *prefix, unsigned char *sym_type) {
if (!_load_symtabs(img)) {
LOGE("Failed to load symtabs for linear lookup by prefix of %s", prefix);
@@ -650,7 +645,7 @@ ElfW(Addr) LinearLookupByPrefix(ElfImg *img, const char *prefix) {
if (valid_symtabs_amount == 0) {
LOGW("No valid symbols (FUNC/OBJECT with size > 0) found in .symtab for %s", img->elf);
return false;
return 0;
}
size_t prefix_len = strlen(prefix);
@@ -666,45 +661,161 @@ ElfW(Addr) LinearLookupByPrefix(ElfImg *img, const char *prefix) {
if (img->symtabs_[i].sym->st_shndx == SHN_UNDEF)
continue;
unsigned int type = ELF_ST_TYPE(img->symtabs_[i].sym->st_info);
if (sym_type) *sym_type = type;
return img->symtabs_[i].sym->st_value;
}
return 0;
}
ElfW(Addr) getSymbOffset(ElfImg *img, const char *name) {
ElfW(Addr) getSymbOffset(ElfImg *img, const char *name, unsigned char *sym_type) {
ElfW(Addr) offset = 0;
offset = GnuLookup(img, name, GnuHash(name));
offset = GnuLookup(img, name, GnuHash(name), sym_type);
if (offset != 0) return offset;
offset = ElfLookup(img, name, ElfHash(name));
offset = ElfLookup(img, name, ElfHash(name), sym_type);
if (offset != 0) return offset;
offset = LinearLookup(img, name);
offset = LinearLookup(img, name, sym_type);
if (offset != 0) return offset;
return 0;
}
#ifdef __aarch64__
/* INFO: Struct containing information about hardware capabilities used in resolver. This
struct information is pulled directly from the AOSP code.
SOURCES:
- https://android.googlesource.com/platform/bionic/+/refs/tags/android-16.0.0_r1/libc/include/sys/ifunc.h#53
*/
struct __ifunc_arg_t {
unsigned long _size;
unsigned long _hwcap;
unsigned long _hwcap2;
};
/* INFO: This is a constant used in the AOSP code to indicate that the struct __ifunc_arg_t
contains hardware capabilities.
SOURCES:
- https://android.googlesource.com/platform/bionic/+/refs/tags/android-16.0.0_r1/libc/include/sys/ifunc.h#74
*/
#define _IFUNC_ARG_HWCAP (1ULL << 62)
#elif defined(__riscv)
/* INFO: Struct used in Linux RISC-V architecture to probe hardware capabilities.
SOURCES:
- https://android.googlesource.com/platform/bionic/+/refs/tags/android-16.0.0_r1/libc/kernel/uapi/asm-riscv/asm/hwprobe.h#10
*/
struct riscv_hwprobe {
int64_t key;
uint64_t value;
};
/* INFO: This function is used in the AOSP code to probe hardware capabilities on RISC-V architecture
by calling the syscall __NR_riscv_hwprobe and passing the parameters that will filled with
the device hardware capabilities.
SOURCES:
- https://android.googlesource.com/platform/bionic/+/refs/tags/android-16.0.0_r1/libc/bionic/vdso.cpp#86
*/
int __riscv_hwprobe(struct riscv_hwprobe *pairs, size_t pair_count, size_t cpu_count, unsigned long *cpus, unsigned flags) {
register long a0 __asm__("a0") = (long)pairs;
register long a1 __asm__("a1") = pair_count;
register long a2 __asm__("a2") = cpu_count;
register long a3 __asm__("a3") = (long)cpus;
register long a4 __asm__("a4") = flags;
register long a7 __asm__("a7") = __NR_riscv_hwprobe;
__asm__ volatile(
"ecall"
: "=r"(a0)
: "r"(a0), "r"(a1), "r"(a2), "r"(a3), "r"(a4), "r"(a7)
);
return -a0;
}
/* INFO: This is a function pointer type that points how the signature of the __riscv_hwprobe
function is.
SOURCES:
- https://android.googlesource.com/platform/bionic/+/refs/tags/android-16.0.0_r1/libc/include/sys/hwprobe.h#62
*/
typedef int (*__riscv_hwprobe_t)(struct riscv_hwprobe *__pairs, size_t __pair_count, size_t __cpu_count, unsigned long *__cpus, unsigned __flags);
#endif
/* INFO: GNU ifuncs (indirect functions) are functions that does not execute the code by itself,
but instead lead to other functions that may very according to hardware capabilities,
or other reasons, depending of the architecture.
This function is based on AOSP's (Android Open Source Project) code, and resolves the
indirect symbol, leading to the correct, most appropriate for the hardware, symbol.
SOURCES:
- https://android.googlesource.com/platform/bionic/+/refs/tags/android-16.0.0_r1/linker/linker.cpp#2594
- https://android.googlesource.com/platform/bionic/+/tags/android-16.0.0_r1/libc/bionic/bionic_call_ifunc_resolver.cpp#41
*/
static ElfW(Addr) handle_indirect_symbol(ElfImg *img, ElfW(Off) offset) {
ElfW(Addr) resolver_addr = (ElfW(Addr))((uintptr_t)img->base + offset - img->bias);
#ifdef __aarch64__
typedef ElfW(Addr) (*ifunc_resolver_t)(uint64_t, struct __ifunc_arg_t *);
struct __ifunc_arg_t args = {
._size = sizeof(struct __ifunc_arg_t),
._hwcap = getauxval(AT_HWCAP),
._hwcap2 = getauxval(AT_HWCAP2)
};
return ((ifunc_resolver_t)resolver_addr)(args._hwcap | _IFUNC_ARG_HWCAP, &args);
#elif defined(__arm__)
typedef ElfW(Addr) (*ifunc_resolver_t)(unsigned long);
return ((ifunc_resolver_t)resolver_addr)(getauxval(AT_HWCAP));
#elif defined(__riscv)
typedef ElfW(Addr) (*ifunc_resolver_t)(uint64_t, __riscv_hwprobe_t, void *);
return ((ifunc_resolver_t)resolver_addr)(getauxval(AT_HWCAP), __riscv_hwprobe, NULL);
#else
typedef ElfW(Addr) (*ifunc_resolver_t)(void);
return ((ifunc_resolver_t)resolver_addr)();
#endif
}
ElfW(Addr) getSymbAddress(ElfImg *img, const char *name) {
ElfW(Addr) offset = getSymbOffset(img, name);
unsigned char sym_type = 0;
ElfW(Addr) offset = getSymbOffset(img, name, &sym_type);
if (offset == 0 || !img->base) return 0;
ElfW(Addr) address = (ElfW(Addr))((uintptr_t)img->base + offset - img->bias);
if (sym_type == STT_GNU_IFUNC) {
LOGD("Resolving STT_GNU_IFUNC symbol %s", name);
return address;
return handle_indirect_symbol(img, offset);
}
return (ElfW(Addr))((uintptr_t)img->base + offset - img->bias);
}
ElfW(Addr) getSymbAddressByPrefix(ElfImg *img, const char *prefix) {
ElfW(Addr) offset = LinearLookupByPrefix(img, prefix);
unsigned char sym_type = 0;
ElfW(Addr) offset = LinearLookupByPrefix(img, prefix, &sym_type);
if (offset == 0 || !img->base) return 0;
ElfW(Addr) address = (ElfW(Addr))((uintptr_t)img->base + offset - img->bias);
if (sym_type == STT_GNU_IFUNC) {
LOGD("Resolving STT_GNU_IFUNC symbol by prefix %s", prefix);
return address;
return handle_indirect_symbol(img, offset);
}
return (ElfW(Addr))((uintptr_t)img->base + offset - img->bias);
}
void *getSymbValueByPrefix(ElfImg *img, const char *prefix) {

View File

@@ -1,13 +1,20 @@
#ifndef ELF_UTIL_H
#define ELF_UTIL_H
#include <stdbool.h>
#include <string.h>
#include <link.h>
#include <linux/elf.h>
#include <sys/types.h>
#include <pthread.h> // Added for threading primitives
#define SHT_GNU_HASH 0x6ffffff6
// Function pointer types for constructors and destructors
typedef void (*linker_simple_func_t)(void);
typedef void (*linker_ctor_function_t)(int, char**, char**);
typedef void (*linker_dtor_function_t)(void);
struct symtabs {
char *name;
ElfW(Sym) *sym;
@@ -54,17 +61,7 @@ void ElfImg_destroy(ElfImg *img);
ElfImg *ElfImg_create(const char *elf, void *base);
ElfW(Addr) ElfLookup(ElfImg *restrict img, const char *restrict name, uint32_t hash);
ElfW(Addr) GnuLookup(ElfImg *restrict img, const char *restrict name, uint32_t hash);
ElfW(Addr) LinearLookup(ElfImg *restrict img, const char *restrict name);
ElfW(Addr) LinearLookupByPrefix(ElfImg *restrict img, const char *name);
int dl_cb(struct dl_phdr_info *info, size_t size, void *data);
ElfW(Addr) getSymbOffset(ElfImg *img, const char *name);
ElfW(Addr) getSymbOffset(ElfImg *img, const char *name, unsigned char *sym_type);
ElfW(Addr) getSymbAddress(ElfImg *img, const char *name);

View File

@@ -161,13 +161,15 @@ bool inject_on_main(int pid, const char *lib_path) {
const char *libdl_path = NULL;
const char *libc_path = NULL;
for (size_t i = 0; i < local_map->size; i++) {
if (local_map->maps[i].path == NULL) continue;
for (size_t i = 0; i < map->size; i++) {
if (map->maps[i].path == NULL) continue;
const char *filename = position_after(local_map->maps[i].path, '/');
const char *filename = position_after(map->maps[i].path, '/');
if (strcmp(filename, "libdl.so") == 0) {
libdl_path = local_map->maps[i].path;
if (!libdl_path && strcmp(filename, "libdl.so") == 0) {
libdl_path = map->maps[i].path;
LOGD("found libdl.so at %s", libdl_path);
/* INFO: If we had found libc.so too, no need to continue searching */
if (libc_path) break;
@@ -175,8 +177,10 @@ bool inject_on_main(int pid, const char *lib_path) {
continue;
}
if (strcmp(filename, "libc.so") == 0) {
libc_path = local_map->maps[i].path;
if (!libc_path && strcmp(filename, "libc.so") == 0) {
libc_path = map->maps[i].path;
LOGD("found libc.so at %s", libc_path);
/* INFO: If we had found libdl.so too, no need to continue searching */
if (libdl_path) break;