Nagram/TMessagesProj/jni/integrity/plt.c
xtaodada d7905c9eaa
feat: signature check
Co-authored-by: tehcneko <chsqwyx@gmail.com>
2024-08-15 16:36:22 +08:00

356 lines
11 KiB
C

//
// Created by Thom on 2019/2/16.
//
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <stdlib.h>
#include <dlfcn.h>
#include "plt.h"
#include "common.h"
#include "path.h"
/*
* reference: https://android.googlesource.com/platform/bionic/+/master/linker/linker_soinfo.cpp
*/
static uint32_t gnu_hash(const uint8_t *name) {
uint32_t h = 5381;
while (*name) {
h += (h << 5) + *name++;
}
return h;
}
static uint32_t elf_hash(const uint8_t *name) {
uint32_t h = 0, g;
while (*name) {
h = (h << 4) + *name++;
g = h & 0xf0000000;
h ^= g;
h ^= g >> 24;
}
return h;
}
static ElfW(Dyn) *find_dyn_by_tag(ElfW(Dyn) *dyn, ElfW(Sxword) tag) {
while (dyn->d_tag != DT_NULL) {
if (dyn->d_tag == tag) {
return dyn;
}
++dyn;
}
return NULL;
}
static inline bool is_global(ElfW(Sym) *sym) {
unsigned char stb = ELF_ST_BIND(sym->st_info);
if (stb == STB_GLOBAL || stb == STB_WEAK) {
return sym->st_shndx != SHN_UNDEF;
} else {
return false;
}
}
static ElfW(Addr) *
find_symbol(struct dl_phdr_info *info, ElfW(Dyn) *base_addr, const char *symbol) {
ElfW(Dyn) *dyn;
dyn = find_dyn_by_tag(base_addr, DT_SYMTAB);
ElfW(Sym) *dynsym = (ElfW(Sym) *) (info->dlpi_addr + dyn->d_un.d_ptr);
dyn = find_dyn_by_tag(base_addr, DT_STRTAB);
char *dynstr = (char *) (info->dlpi_addr + dyn->d_un.d_ptr);
dyn = find_dyn_by_tag(base_addr, DT_GNU_HASH);
if (dyn != NULL) {
ElfW(Word) *dt_gnu_hash = (ElfW(Word) *) (info->dlpi_addr + dyn->d_un.d_ptr);
size_t gnu_nbucket_ = dt_gnu_hash[0];
uint32_t gnu_maskwords_ = dt_gnu_hash[2];
uint32_t gnu_shift2_ = dt_gnu_hash[3];
ElfW(Addr) *gnu_bloom_filter_ = (ElfW(Addr) *) (dt_gnu_hash + 4);
uint32_t *gnu_bucket_ = (uint32_t *) (gnu_bloom_filter_ + gnu_maskwords_);
uint32_t *gnu_chain_ = gnu_bucket_ + gnu_nbucket_ - dt_gnu_hash[1];
--gnu_maskwords_;
uint32_t hash = gnu_hash((uint8_t *) symbol);
uint32_t h2 = hash >> gnu_shift2_;
uint32_t bloom_mask_bits = sizeof(ElfW(Addr)) * 8;
uint32_t word_num = (hash / bloom_mask_bits) & gnu_maskwords_;
ElfW(Addr) bloom_word = gnu_bloom_filter_[word_num];
if ((1 & (bloom_word >> (hash % bloom_mask_bits)) &
(bloom_word >> (h2 % bloom_mask_bits))) == 0) {
return NULL;
}
uint32_t n = gnu_bucket_[hash % gnu_nbucket_];
if (n == 0) {
return NULL;
}
do {
ElfW(Sym) *sym = dynsym + n;
if (((gnu_chain_[n] ^ hash) >> 1) == 0
&& is_global(sym)
&& strcmp(dynstr + sym->st_name, symbol) == 0) {
ElfW(Addr) *symbol_sym = (ElfW(Addr) *) (info->dlpi_addr + sym->st_value);
#ifdef DEBUG_PLT
LOGI("found %s(gnu+%u) in %s, %p", symbol, n, info->dlpi_name, symbol_sym);
#endif
return symbol_sym;
}
} while ((gnu_chain_[n++] & 1) == 0);
return NULL;
}
dyn = find_dyn_by_tag(base_addr, DT_HASH);
if (dyn != NULL) {
ElfW(Word) *dt_hash = (ElfW(Word) *) (info->dlpi_addr + dyn->d_un.d_ptr);
size_t nbucket_ = dt_hash[0];
uint32_t *bucket_ = dt_hash + 2;
uint32_t *chain_ = bucket_ + nbucket_;
uint32_t hash = elf_hash((uint8_t *) (symbol));
for (uint32_t n = bucket_[hash % nbucket_]; n != 0; n = chain_[n]) {
ElfW(Sym) *sym = dynsym + n;
if (is_global(sym) &&
strcmp(dynstr + sym->st_name, symbol) == 0) {
ElfW(Addr) *symbol_sym = (ElfW(Addr) *) (info->dlpi_addr + sym->st_value);
#ifdef DEBUG_PLT
LOGI("found %s(elf+%u) in %s, %p", symbol, n, info->dlpi_name, symbol_sym);
#endif
return symbol_sym;
}
}
return NULL;
}
return NULL;
}
#if defined(__LP64__)
#define Elf_Rela ElfW(Rela)
#define ELF_R_SYM ELF64_R_SYM
#else
#define Elf_Rela ElfW(Rel)
#define ELF_R_SYM ELF32_R_SYM
#endif
#ifdef DEBUG_PLT
#if defined(__x86_64__)
#define R_JUMP_SLOT R_X86_64_JUMP_SLOT
#define ELF_R_TYPE ELF64_R_TYPE
#elif defined(__i386__)
#define R_JUMP_SLOT R_386_JMP_SLOT
#define ELF_R_TYPE ELF32_R_TYPE
#elif defined(__arm__)
#define R_JUMP_SLOT R_ARM_JUMP_SLOT
#define ELF_R_TYPE ELF32_R_TYPE
#elif defined(__aarch64__)
#define R_JUMP_SLOT R_AARCH64_JUMP_SLOT
#define ELF_R_TYPE ELF64_R_TYPE
#else
#error unsupported OS
#endif
#endif
static ElfW(Addr) *find_plt(struct dl_phdr_info *info, ElfW(Dyn) *base_addr, const char *symbol) {
ElfW(Dyn) *dyn = find_dyn_by_tag(base_addr, DT_JMPREL);
if (dyn == NULL) {
return NULL;
}
Elf_Rela *dynplt = (Elf_Rela *) (info->dlpi_addr + dyn->d_un.d_ptr);
dyn = find_dyn_by_tag(base_addr, DT_SYMTAB);
ElfW(Sym) *dynsym = (ElfW(Sym) *) (info->dlpi_addr + dyn->d_un.d_ptr);
dyn = find_dyn_by_tag(base_addr, DT_STRTAB);
char *dynstr = (char *) (info->dlpi_addr + dyn->d_un.d_ptr);
dyn = find_dyn_by_tag(base_addr, DT_PLTRELSZ);
if (dyn == NULL) {
return NULL;
}
size_t count = dyn->d_un.d_val / sizeof(Elf_Rela);
for (size_t i = 0; i < count; ++i) {
Elf_Rela *plt = dynplt + i;
#ifdef DEBUG_PLT
if (ELF_R_TYPE(plt->r_info) != R_JUMP_SLOT) {
LOGW("invalid type for plt+%zu in %s", i, info->dlpi_name);
continue;
}
#endif
size_t idx = ELF_R_SYM(plt->r_info);
idx = dynsym[idx].st_name;
if (strcmp(dynstr + idx, symbol) == 0) {
ElfW(Addr) *symbol_plt = (ElfW(Addr) *) (info->dlpi_addr + plt->r_offset);
#ifdef DEBUG_PLT
ElfW(Addr) *symbol_plt_value = (ElfW(Addr) *) *symbol_plt;
LOGI("found %s(plt+%zu) in %s, %p -> %p", symbol, i, info->dlpi_name, symbol_plt,
symbol_plt_value);
#endif
return symbol_plt;
}
}
return NULL;
}
static inline bool isso(const char *str) {
if (str == NULL) {
return false;
}
const char *dot = strrchr(str, '.');
return dot != NULL
&& *++dot == 's'
&& *++dot == 'o'
&& (*++dot == '\0' || *dot == '\r' || *dot == '\n');
}
static inline bool should_check_plt(Symbol *symbol, struct dl_phdr_info *info) {
const char *path = info->dlpi_name;
if (symbol->check & PLT_CHECK_PLT_ALL) {
return true;
} else if (symbol->check & PLT_CHECK_PLT_APP) {
return *path != '/' || isThirdParty(path);
} else {
return false;
}
}
static int callback(struct dl_phdr_info *info, __unused size_t size, void *data) {
if (!isso(info->dlpi_name)) {
#ifdef DEBUG_PLT
LOGW("ignore non-so: %s", info->dlpi_name);
#endif
return 0;
}
Symbol *symbol = (Symbol *) data;
#if 0
LOGI("Name: \"%s\" (%d segments)", info->dlpi_name, info->dlpi_phnum);
#endif
++symbol->total;
for (ElfW(Half) phdr_idx = 0; phdr_idx < info->dlpi_phnum; ++phdr_idx) {
ElfW(Phdr) phdr = info->dlpi_phdr[phdr_idx];
if (phdr.p_type != PT_DYNAMIC) {
continue;
}
ElfW(Dyn) *base_addr = (ElfW(Dyn) *) (info->dlpi_addr + phdr.p_vaddr);
ElfW(Addr) *addr;
addr = should_check_plt(symbol, info) ? find_plt(info, base_addr, symbol->symbol_name) : NULL;
if (addr != NULL) {
if (symbol->symbol_plt != NULL) {
ElfW(Addr) *addr_value = (ElfW(Addr) *) *addr;
ElfW(Addr) *symbol_plt_value = (ElfW(Addr) *) *symbol->symbol_plt;
if (addr_value != symbol_plt_value) {
#ifdef DEBUG_PLT
LOGW("%s, plt %p -> %p != %p", symbol->symbol_name, addr, addr_value,
symbol_plt_value);
#endif
return 1;
}
}
symbol->symbol_plt = addr;
if (symbol->check & PLT_CHECK_NAME) {
if (symbol->size == 0) {
symbol->size = 1;
symbol->names = calloc(1, sizeof(char *));
} else {
++symbol->size;
symbol->names = realloc(symbol->names, symbol->size * sizeof(char *));
}
#ifdef DEBUG_PLT
LOGI("[%d]: %s", symbol->size - 1, info->dlpi_name);
#endif
symbol->names[symbol->size - 1] = strdup(info->dlpi_name);
}
}
addr = find_symbol(info, base_addr, symbol->symbol_name);
if (addr != NULL) {
symbol->symbol_sym = addr;
if (symbol->check == PLT_CHECK_SYM_ONE) {
return PLT_CHECK_SYM_ONE;
}
}
if (symbol->symbol_plt != NULL && symbol->symbol_sym != NULL) {
ElfW(Addr) *symbol_plt_value = (ElfW(Addr) *) *symbol->symbol_plt;
// stop if unmatch
if (symbol_plt_value != symbol->symbol_sym) {
#ifdef DEBUG_PLT
LOGW("%s, plt: %p -> %p != %p", symbol->symbol_name, symbol->symbol_plt,
symbol_plt_value, symbol->symbol_sym);
#endif
return 1;
}
}
}
return 0;
}
void *plt_dlsym(const char *name, size_t *total) {
Symbol symbol;
memset(&symbol, 0, sizeof(Symbol));
if (total == NULL) {
symbol.check = PLT_CHECK_SYM_ONE;
}
symbol.symbol_name = name;
dl_iterate_phdr_symbol(&symbol);
if (total != NULL) {
*total = symbol.total;
}
return symbol.symbol_sym;
}
bool isPltHooked(const char *name, bool all) {
Symbol symbol;
memset(&symbol, 0, sizeof(Symbol));
symbol.check = all ? PLT_CHECK_PLT_ALL : PLT_CHECK_PLT_APP;
symbol.symbol_name = name;
return dl_iterate_phdr_symbol(&symbol) ? true : false;
}
/**
* symbol->check PLT_CHECK_PLT | PLT_CHECK_NAME
* @param symbol
* @return
*/
int dl_iterate_phdr_symbol(Symbol *symbol) {
int result;
#ifdef DEBUG_PLT
LOGI("start dl_iterate_phdr: %s", symbol->symbol_name);
#endif
#if __ANDROID_API__ >= 21 || !defined(__arm__)
result = dl_iterate_phdr(callback, symbol);
#else
int (*dl_iterate_phdr)(int (*)(struct dl_phdr_info *, size_t, void *), void *);
dl_iterate_phdr = dlsym(RTLD_NEXT, "dl_iterate_phdr");
if (dl_iterate_phdr != NULL) {
result = dl_iterate_phdr(callback, symbol);
} else {
result = 0;
void *handle = dlopen("libdl.so", RTLD_NOW);
dl_iterate_phdr = dlsym(handle, "dl_iterate_phdr");
if (dl_iterate_phdr != NULL) {
result = dl_iterate_phdr(callback, symbol);
} else {
LOGW("cannot dlsym dl_iterate_phdr");
}
dlclose(handle);
}
#endif
#ifdef DEBUG_PLT
LOGI("complete dl_iterate_phdr: %s", symbol->symbol_name);
#endif
return result;
}