Android so库Hook技术

来源:互联网 发布:免费炒股软件 编辑:程序博客网 时间:2024/05/29 19:15

在有些情况下,可能遇到需要改变目标进程的执行流程的问题,替换原函数转而指向我们自己的函数,而Hook就是指的改变待Hook函数的地址,替换地址而达到更换原函数的功能。本文主要介绍Android上对native层中的so库Hook的方法,核心技术其实是对GOT表的Hook,获取目标函数地址需要对so文件进行解析,而so文件实际上是ELF文件,所以在此之前需要对ELF文件格式有一个很好的了解。

关键

解析so文件,其实主要目的就是获得ELF文件中以下三个表的信息:

  • 字符串表:包含以空字符结尾的字符序列,使用这些字符来描绘符号和节名;
  • 符号表:保存了一个程序在定位和重定位时需要的定义和引用的信息;
  • 重定位表:保存了需要重定位的符号的信息;

准备工作

#include <stdio.h>#include <stdlib.h>#include <elf.h>#include <fcntl.h>#include <android/log.h>#include <sys/mman.h>#include <sys/stat.h>#include <utime.h>#include <dirent.h>#ifdef __x86_64    #define Elf_Eh Elf64_Ehdr    #define Elf_Shdr Elf64_Shdr    #define Elf_Sym Elf64_Sym    #define Elf_Rel Elf64_Rela    #define ELF_R_SYM ELF64_R_SYM#else    #define Elf_Ehdr Elf32_Ehdr    #define Elf_Shdr Elf32_Shdr    #define Elf_Sym Elf32_Sym    #define Elf_Rel Elf32_Rel    #define ELF_R_SYM ELF32_R_SYM#endif#define module_path "/system/lib/libjavacore.so"#define LOG_TAG    "native_hook_log"#define LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)

读取ELF文件头(ELF文件头起始于 ELF 文件开始的第一字节),取出:

  • 节头表的文件偏移(shdr_base),获取节头表在文件中的位置;
  • 节头表的项数(shnum),获取节头表的项数;
  • 与节名称字符串表关联的项的节头表索引(shstr_base),并将其缓存起来(shstr);
int fd;    fd = open(modulepath,O_RDONLY);    if (fd == -1){        LOGD("[-] open the so file fail");    }    else{        LOGD("[+] open the so file success");    }    Elf_Ehdr *ehdr = (Elf_Ehdr *)malloc(sizeof(Elf_Ehdr));    read(fd, ehdr, sizeof(Elf_Ehdr));    if(fd == -1){        LOGD("[-] read the elf header fail!!");    }    else{        LOGD("[+] read the elf header success!!!");    }    //LOGD("[+] the elf header information:");    uint32_t shdr_base = ehdr -> e_shoff;    uint16_t shnum = ehdr -> e_shnum;    uint32_t shstr_base = shdr_base + ehdr -> e_shstrndx * sizeof(Elf32_Shdr);    /*LOGD("[+] shstrndx = %d",ehdr -> e_shstrndx);    LOGD("[+] shoff = %d",shdr_base);    LOGD("[+] shnum = %d",shnum);    LOGD("[+] shstr_base = %d",shstr_base);*/    Elf_Shdr *shstr = (Elf_Shdr *)malloc(sizeof(Elf_Shdr));    lseek(fd, shstr_base, SEEK_SET);    read(fd, shstr, sizeof(Elf_Shdr));    if(fd == -1){        LOGD("[-] read the shdr section str fail!!");    }    else{        LOGD("[+] read the shdr section str success!!");    }

读取节头表索引,取出: 节的大小(sh_size)、节的名称(sh_name);
遍历节名,分别将节名为.dynsym、.dynstr、.got、.rel.plt的节缓存起来(dynsym_shdr、dynstr_shdr、got_shdr、relplt_shdr);
通过上一步获取的节,分别获得对应的表,并将其缓存;

char *shstrtab = (char *)malloc(shstr -> sh_size);    lseek(fd, shstr -> sh_offset, SEEK_SET);    read(fd, shstrtab, shstr -> sh_size);    if(fd == -1){        LOGD("[-] read section str fail!!");    }    else{        LOGD("[+] read section str success!!");    }    Elf_Shdr *shdr = (Elf_Shdr *)malloc(sizeof(Elf_Shdr));    lseek(fd, shdr_base, SEEK_SET);    uint16_t i;    char *str = NULL;    Elf_Shdr *relplt_shdr = (Elf_Shdr *) malloc(sizeof(Elf_Shdr));    Elf_Shdr *dynsym_shdr = (Elf_Shdr *) malloc(sizeof(Elf_Shdr));    Elf_Shdr *dynstr_shdr = (Elf_Shdr *) malloc(sizeof(Elf_Shdr));    Elf_Shdr *got_shdr = (Elf_Shdr *) malloc(sizeof(Elf_Shdr));    for(i = 0; i < shnum; ++i) {        read(fd, shdr, sizeof(Elf_Shdr));        if(fd == -1){            LOGD("[-] read fail!!");        }        str = shstrtab + shdr -> sh_name;        if(strcmp(str, ".dynsym") == 0)            memcpy(dynsym_shdr, shdr, sizeof(Elf_Shdr));        else if(strcmp(str, ".dynstr") == 0)            memcpy(dynstr_shdr, shdr, sizeof(Elf_Shdr));        else if(strcmp(str, ".got") == 0)            memcpy(got_shdr, shdr, sizeof(Elf_Shdr));        else if(strcmp(str, ".rel.plt") == 0)            memcpy(relplt_shdr, shdr, sizeof(Elf_Shdr));    }    char *got = (char *) malloc(sizeof(char) * got_shdr->sh_size);    lseek(fd, got_shdr->sh_offset,SEEK_SET);    if(read(fd, got, got_shdr->sh_size) != got_shdr->sh_size)        LOGD("[-] read the got fail!!");    else{        LOGD("[+] read the GOT success!!");    }    char *dynstr = (char *) malloc(sizeof(char) * dynstr_shdr->sh_size);    lseek(fd, dynstr_shdr->sh_offset, SEEK_SET);    if(read(fd, dynstr, dynstr_shdr->sh_size) != dynstr_shdr->sh_size)        LOGD("[-] read the dynstr fail!!");    else{        LOGD("[+] read the dynstr success!!");    }    Elf_Sym *dynsymtab = (Elf_Sym *) malloc(dynsym_shdr->sh_size);    //LOGD("[+] dynsym_shdr->sh_size\t0x%x\n", dynsym_shdr->sh_size);    lseek(fd, dynsym_shdr->sh_offset, SEEK_SET);    if(read(fd, dynsymtab, dynsym_shdr->sh_size) != dynsym_shdr->sh_size)        LOGD("[-] read the dynsym fail!!");    else{        LOGD("[+] read the dynsym success");    }    Elf_Rel *rel_ent = (Elf_Rel *) malloc(sizeof(Elf_Rel));    lseek(fd, relplt_shdr->sh_offset, SEEK_SET);    if(read(fd, rel_ent, sizeof(Elf_Rel)) != sizeof(Elf_Rel))        LOGD("[-] read the rel_ent fail!!");    else{        LOGD("[+] read the rel_ent success");    }

获取指定符号在got表的偏移地址:

uint32_t offset = 0;    for (i = 0; i < relplt_shdr->sh_size / sizeof(Elf_Rel); i++)    {        uint16_t ndx = ELF_R_SYM(rel_ent->r_info);        //LOGD("[+] ndx = %d, str = %s", ndx, dynstr + dynsymtab[ndx].st_name);        if (strcmp(dynstr + dynsymtab[ndx].st_name, symbolname) == 0)        {            LOGD("[+] The offset of %s int the dyn GOT table: 0x%x", symbolname, rel_ent->r_offset);            offset = rel_ent->r_offset;            break;        }        if(read(fd, rel_ent, sizeof(Elf_Rel)) != sizeof(Elf_Rel))        {            LOGD("Fail to get the address of %s", symbolname);        }    }    if(offset == 0)    {        LOGD("[-] can't find the address of %s,maybe it's static", symbolname);        for(i = 0; i < (dynsym_shdr->sh_size) / sizeof(Elf_Sym); ++i)        {            if(strcmp(dynstr + dynsymtab[i].st_name, symbolname) == 0)            {                LOGD("[+] The address of %s为 int the static GOT table: 0x%x", symbolname, dynsymtab[i].st_value);                offset = dynsymtab[i].st_value;                break;            }        }    }    if(offset == 0)    {        LOGD("[-] Fail to get the address of %s", symbolname);    }    close(fd);    return offset;

获取so文件的基地址

static uint32_t get_module_base(pid_t pid,char *modulepath){    FILE *fp = NULL;    char *pch = NULL;    char filename[32];    char line[512];    uint32_t addr = 0;    LOGD("[+] get libc base...");    if (pid < 0) {        snprintf(filename, sizeof(filename), "/proc/self/maps");        LOGD("pid < 0");    }    else {        snprintf(filename, sizeof(filename), "/proc/%d/maps", pid);        LOGD("pid > 0");    }    if ((fp = fopen(filename, "r")) == NULL) {        LOGD("[-]open %s failed!", filename);        return 0;    }    LOGD("[+]open %s success!", filename);    while (fgets(line, sizeof(line), fp)) {        if (strstr(line, modulepath)) {            pch = strtok(line, "-");            addr = strtoul(pch, NULL, 16);            if(addr==0x8000)                addr = 0;            break;        }    }    fclose(fp);    LOGD("[+] libc base:0x%x...\n",addr);    return addr;}

最后,基地址加上函数符号在GOT表中的地址就是这个函数在内存中的实际地址。

首先写一个open函数用于替换旧的open函数,之后将这个函数的地址替换到目标so的open函数地址。

int *hook_open(char * path, int flag) {    //char * retu = "0X00000000";    LOGD("[+] start hook open()");    LOGD("[+] hook_open path:%s",path);    LOGD("[+] hook_open flag:%d",flag);    LOGD("[+] ++++++++++++++++++++++++++++++++++");    return open(path,flag);}

地址替换

void gotHook(char *modulepath,char *symbolname,char *hookfunname){    uint32_t old_addr = get_fun_addr(modulepath,symbolname);    mprotect((void *)((uint32_t)old_addr & ~4095), 4096 , PROT_READ | PROT_WRITE);    uint32_t old_fun_addr = *(uint32_t *)old_addr;    *(uint32_t *)old_addr = (uint32_t)hookfunname;}

当运行目标so库中的open函数时,执行流程会跳转到我们上面自定义的hook_open函数。

原创粉丝点击