Android so库Hook技术

男娘i 2022-06-08 00:54 400阅读 0赞

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

关键

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

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

准备工作

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <elf.h>
  4. #include <fcntl.h>
  5. #include <android/log.h>
  6. #include <sys/mman.h>
  7. #include <sys/stat.h>
  8. #include <utime.h>
  9. #include <dirent.h>
  10. #ifdef __x86_64
  11. #define Elf_Eh Elf64_Ehdr
  12. #define Elf_Shdr Elf64_Shdr
  13. #define Elf_Sym Elf64_Sym
  14. #define Elf_Rel Elf64_Rela
  15. #define ELF_R_SYM ELF64_R_SYM
  16. #else
  17. #define Elf_Ehdr Elf32_Ehdr
  18. #define Elf_Shdr Elf32_Shdr
  19. #define Elf_Sym Elf32_Sym
  20. #define Elf_Rel Elf32_Rel
  21. #define ELF_R_SYM ELF32_R_SYM
  22. #endif
  23. #define module_path "/system/lib/libjavacore.so"
  24. #define LOG_TAG "native_hook_log"
  25. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)

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

  • 节头表的文件偏移(shdr_base),获取节头表在文件中的位置;
  • 节头表的项数(shnum),获取节头表的项数;
  • 与节名称字符串表关联的项的节头表索引(shstr_base),并将其缓存起来(shstr);

    int fd;

    1. fd = open(modulepath,O_RDONLY);
    2. if (fd == -1){
    3. LOGD("[-] open the so file fail");
    4. }
    5. else{
    6. LOGD("[+] open the so file success");
    7. }
    8. Elf_Ehdr *ehdr = (Elf_Ehdr *)malloc(sizeof(Elf_Ehdr));
    9. read(fd, ehdr, sizeof(Elf_Ehdr));
    10. if(fd == -1){
    11. LOGD("[-] read the elf header fail!!");
    12. }
    13. else{
    14. LOGD("[+] read the elf header success!!!");
    15. }
    16. //LOGD("[+] the elf header information:");
    17. uint32_t shdr_base = ehdr -> e_shoff;
    18. uint16_t shnum = ehdr -> e_shnum;
    19. uint32_t shstr_base = shdr_base + ehdr -> e_shstrndx * sizeof(Elf32_Shdr);
    20. /*LOGD("[+] shstrndx = %d",ehdr -> e_shstrndx);
    21. LOGD("[+] shoff = %d",shdr_base);
    22. LOGD("[+] shnum = %d",shnum);
    23. LOGD("[+] shstr_base = %d",shstr_base);*/
    24. Elf_Shdr *shstr = (Elf_Shdr *)malloc(sizeof(Elf_Shdr));
    25. lseek(fd, shstr_base, SEEK_SET);
    26. read(fd, shstr, sizeof(Elf_Shdr));
    27. if(fd == -1){
    28. LOGD("[-] read the shdr section str fail!!");
    29. }
    30. else{
    31. LOGD("[+] read the shdr section str success!!");
    32. }

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

  1. char *shstrtab = (char *)malloc(shstr -> sh_size);
  2. lseek(fd, shstr -> sh_offset, SEEK_SET);
  3. read(fd, shstrtab, shstr -> sh_size);
  4. if(fd == -1){
  5. LOGD("[-] read section str fail!!");
  6. }
  7. else{
  8. LOGD("[+] read section str success!!");
  9. }
  10. Elf_Shdr *shdr = (Elf_Shdr *)malloc(sizeof(Elf_Shdr));
  11. lseek(fd, shdr_base, SEEK_SET);
  12. uint16_t i;
  13. char *str = NULL;
  14. Elf_Shdr *relplt_shdr = (Elf_Shdr *) malloc(sizeof(Elf_Shdr));
  15. Elf_Shdr *dynsym_shdr = (Elf_Shdr *) malloc(sizeof(Elf_Shdr));
  16. Elf_Shdr *dynstr_shdr = (Elf_Shdr *) malloc(sizeof(Elf_Shdr));
  17. Elf_Shdr *got_shdr = (Elf_Shdr *) malloc(sizeof(Elf_Shdr));
  18. for(i = 0; i < shnum; ++i) {
  19. read(fd, shdr, sizeof(Elf_Shdr));
  20. if(fd == -1){
  21. LOGD("[-] read fail!!");
  22. }
  23. str = shstrtab + shdr -> sh_name;
  24. if(strcmp(str, ".dynsym") == 0)
  25. memcpy(dynsym_shdr, shdr, sizeof(Elf_Shdr));
  26. else if(strcmp(str, ".dynstr") == 0)
  27. memcpy(dynstr_shdr, shdr, sizeof(Elf_Shdr));
  28. else if(strcmp(str, ".got") == 0)
  29. memcpy(got_shdr, shdr, sizeof(Elf_Shdr));
  30. else if(strcmp(str, ".rel.plt") == 0)
  31. memcpy(relplt_shdr, shdr, sizeof(Elf_Shdr));
  32. }
  33. char *got = (char *) malloc(sizeof(char) * got_shdr->sh_size);
  34. lseek(fd, got_shdr->sh_offset,SEEK_SET);
  35. if(read(fd, got, got_shdr->sh_size) != got_shdr->sh_size)
  36. LOGD("[-] read the got fail!!");
  37. else{
  38. LOGD("[+] read the GOT success!!");
  39. }
  40. char *dynstr = (char *) malloc(sizeof(char) * dynstr_shdr->sh_size);
  41. lseek(fd, dynstr_shdr->sh_offset, SEEK_SET);
  42. if(read(fd, dynstr, dynstr_shdr->sh_size) != dynstr_shdr->sh_size)
  43. LOGD("[-] read the dynstr fail!!");
  44. else{
  45. LOGD("[+] read the dynstr success!!");
  46. }
  47. Elf_Sym *dynsymtab = (Elf_Sym *) malloc(dynsym_shdr->sh_size);
  48. //LOGD("[+] dynsym_shdr->sh_size\t0x%x\n", dynsym_shdr->sh_size);
  49. lseek(fd, dynsym_shdr->sh_offset, SEEK_SET);
  50. if(read(fd, dynsymtab, dynsym_shdr->sh_size) != dynsym_shdr->sh_size)
  51. LOGD("[-] read the dynsym fail!!");
  52. else{
  53. LOGD("[+] read the dynsym success");
  54. }
  55. Elf_Rel *rel_ent = (Elf_Rel *) malloc(sizeof(Elf_Rel));
  56. lseek(fd, relplt_shdr->sh_offset, SEEK_SET);
  57. if(read(fd, rel_ent, sizeof(Elf_Rel)) != sizeof(Elf_Rel))
  58. LOGD("[-] read the rel_ent fail!!");
  59. else{
  60. LOGD("[+] read the rel_ent success");
  61. }

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

  1. uint32_t offset = 0;
  2. for (i = 0; i < relplt_shdr->sh_size / sizeof(Elf_Rel); i++)
  3. {
  4. uint16_t ndx = ELF_R_SYM(rel_ent->r_info);
  5. //LOGD("[+] ndx = %d, str = %s", ndx, dynstr + dynsymtab[ndx].st_name);
  6. if (strcmp(dynstr + dynsymtab[ndx].st_name, symbolname) == 0)
  7. {
  8. LOGD("[+] The offset of %s int the dyn GOT table: 0x%x", symbolname, rel_ent->r_offset);
  9. offset = rel_ent->r_offset;
  10. break;
  11. }
  12. if(read(fd, rel_ent, sizeof(Elf_Rel)) != sizeof(Elf_Rel))
  13. {
  14. LOGD("Fail to get the address of %s", symbolname);
  15. }
  16. }
  17. if(offset == 0)
  18. {
  19. LOGD("[-] can't find the address of %s,maybe it's static", symbolname);
  20. for(i = 0; i < (dynsym_shdr->sh_size) / sizeof(Elf_Sym); ++i)
  21. {
  22. if(strcmp(dynstr + dynsymtab[i].st_name, symbolname) == 0)
  23. {
  24. LOGD("[+] The address of %s为 int the static GOT table: 0x%x", symbolname, dynsymtab[i].st_value);
  25. offset = dynsymtab[i].st_value;
  26. break;
  27. }
  28. }
  29. }
  30. if(offset == 0)
  31. {
  32. LOGD("[-] Fail to get the address of %s", symbolname);
  33. }
  34. close(fd);
  35. return offset;

获取so文件的基地址

  1. static uint32_t get_module_base(pid_t pid,char *modulepath){
  2. FILE *fp = NULL;
  3. char *pch = NULL;
  4. char filename[32];
  5. char line[512];
  6. uint32_t addr = 0;
  7. LOGD("[+] get libc base...");
  8. if (pid < 0) {
  9. snprintf(filename, sizeof(filename), "/proc/self/maps");
  10. LOGD("pid < 0");
  11. }
  12. else {
  13. snprintf(filename, sizeof(filename), "/proc/%d/maps", pid);
  14. LOGD("pid > 0");
  15. }
  16. if ((fp = fopen(filename, "r")) == NULL) {
  17. LOGD("[-]open %s failed!", filename);
  18. return 0;
  19. }
  20. LOGD("[+]open %s success!", filename);
  21. while (fgets(line, sizeof(line), fp)) {
  22. if (strstr(line, modulepath)) {
  23. pch = strtok(line, "-");
  24. addr = strtoul(pch, NULL, 16);
  25. if(addr==0x8000)
  26. addr = 0;
  27. break;
  28. }
  29. }
  30. fclose(fp);
  31. LOGD("[+] libc base:0x%x...\n",addr);
  32. return addr;
  33. }

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

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

  1. int *hook_open(char * path, int flag) {
  2. //char * retu = "0X00000000";
  3. LOGD("[+] start hook open()");
  4. LOGD("[+] hook_open path:%s",path);
  5. LOGD("[+] hook_open flag:%d",flag);
  6. LOGD("[+] ++++++++++++++++++++++++++++++++++");
  7. return open(path,flag);
  8. }

地址替换

  1. void gotHook(char *modulepath,char *symbolname,char *hookfunname){
  2. uint32_t old_addr = get_fun_addr(modulepath,symbolname);
  3. mprotect((void *)((uint32_t)old_addr & ~4095), 4096 , PROT_READ | PROT_WRITE);
  4. uint32_t old_fun_addr = *(uint32_t *)old_addr;
  5. *(uint32_t *)old_addr = (uint32_t)hookfunname;
  6. }

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

发表评论

表情:
评论列表 (有 0 条评论,400人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Android加载so

    1. 说明    早期的Android系统几乎只支持ARMv5的CPU架构,你知道现在它支持多少种吗?7种! Android系统目前支持以下七种不同的CPU架构:AR

    相关 Android soHook技术

    在有些情况下,可能遇到需要改变目标进程的执行流程的问题,替换原函数转而指向我们自己的函数,而Hook就是指的改变待Hook函数的地址,替换地址而达到更换原函数的功能。本文主要介