基于执行视图解析ELF

时间:2022-10-31 13:49:20
[《Redirecting functions in shared ELF libraries》](http://www.codeproject.com/Articles/70302/Redirecting-functions-in-shared-ELF-libraries#_Toc257815978)这篇文章所提供的例子,就是基于链接视图对ELF进行解析的,与基于执行视图进行解析相比,后面的逻辑基本是一样的,关键是要通过segment找到.dynsym、.dynstr、.rel.plt和rel.dyn,以及它们的项数。

首次通过Program Header Table找到类型为PT_DYNAMIC的段,该的内容其实对应.dynamic,这段的内容对应Elf32_Dyn类型的数组,其结构体如下所示:

```
/* Dynamic structure */
typedef struct {
  Elf32_Sword  d_tag;    /* controls meaning of d_val */
  union {
    Elf32_Word  d_val;  /* Multiple meanings - see d_tag */
    Elf32_Addr  d_ptr;  /* program virtual address */
  } d_un;
} Elf32_Dyn;
```

通过遍历这个数组,我们可以找到所有的需要的信息,我把它们的对应关系列出来:

- DT_HASH -> .hash
- DT_SYMTAB & DT_SYMENT -> .dynsym
- DT_STRTAB & DT_STRSZ -> .dynstr
- PLTREL(决定REL还是RELA) &(DT_REL | DT_RELA) & (DT_RELSZ | DT_RELASZ ) & (DT_RELENT | DT_RELAENT ) -> .rel.dyn
- DT_JMPREL & DT_PLTRELSZ & (DT_RELENT | DT_RELAENT) -> .rel.plt
- FINI_ARRAY & FINI_ARRAYSZ -> .fini_array
- INIT_ARRAY & INIT_ARRAYSZ -> .init_array

这是查找的相关代码:

```
void getElfInfoBySegmentView(ElfInfo &info, const ElfHandle *handle){

  info.handle = handle;
  info.elf_base = (uint8_t *) handle->base;
  info.ehdr = reinterpret_cast<Elf32_Ehdr *>(info.elf_base);

  // may be wrong
  info.shdr = reinterpret_cast<Elf32_Shdr *>(info.elf_base + info.ehdr->e_shoff);
  info.phdr = reinterpret_cast<Elf32_Phdr *>(info.elf_base + info.ehdr->e_phoff);

  info.shstr = NULL;

  Elf32_Phdr *dynamic = NULL;
  Elf32_Word size = 0;

  getSegmentInfo(info, PT_DYNAMIC, &dynamic, &size, &info.dyn);
  if(!dynamic){
    LOGE("[-] could't find PT_DYNAMIC segment");
    exit(-1);
  }
  info.dynsz = size / sizeof(Elf32_Dyn);

  Elf32_Dyn *dyn = info.dyn;
  for(int i=0; i<info.dynsz; i++, dyn++){

    switch(dyn->d_tag){

    case DT_SYMTAB:
      info.sym = reinterpret_cast<Elf32_Sym *>(info.elf_base + dyn->d_un.d_ptr);
      break;

    case DT_STRTAB:
      info.symstr = reinterpret_cast<const char *>(info.elf_base + dyn->d_un.d_ptr);
      break;

    case DT_REL:
      info.reldyn = reinterpret_cast<Elf32_Rel *>(info.elf_base + dyn->d_un.d_ptr);
      break;

    case DT_RELSZ:
      info.reldynsz = dyn->d_un.d_val / sizeof(Elf32_Rel);
      break;

    case DT_JMPREL:
      info.relplt = reinterpret_cast<Elf32_Rel *>(info.elf_base + dyn->d_un.d_ptr);
      break;

    case DT_PLTRELSZ:
      info.relpltsz = dyn->d_un.d_val / sizeof(Elf32_Rel);
      break;

    case DT_HASH:
      uint32_t *rawdata = reinterpret_cast<uint32_t *>(info.elf_base + dyn->d_un.d_ptr);
      info.nbucket = rawdata[0];
      info.nchain = rawdata[1];
      info.bucket = rawdata + 2;
      info.chain = info.bucket + info.nbucket;
      break;
    }
  }

  //because .dynsym is next to .dynstr, so we can caculate the symsz simply
  info.symsz = ((uint32_t)info.symstr - (uint32_t)info.sym)/sizeof(Elf32_Sym);
}

```
然而,有一个值我无法通过通过PT_DYNAMIC段得到的,那就是.dynsym的项数,我最后通过变通的方法得到的。由于.dynsym和.dynstr两个节区是相邻的,因此它们两个地址相减,即可得到的.dynsym总长度,再除了sizeof(Elf32_Sym)即可得到.dynsym的项数,如果你有更好的方法,请跟我说说。

#ELF Hook
有了上面的介绍之后,写个ELF Hook就很简单的,我把关键代码贴出来:

```
#define R_ARM_ABS32 0x02
#define R_ARM_GLOB_DAT 0x15
#define R_ARM_JUMP_SLOT 0x16

int elfHook(const char *soname, const char *symbol, void *replace_func, void **old_func){
  assert(old_func);
  assert(replace_func);
  assert(symbol);

  ElfHandle* handle = openElfBySoname(soname);
  ElfInfo info;

  getElfInfoBySegmentView(info, handle);

  Elf32_Sym *sym = NULL;
  int symidx = 0;

  findSymByName(info, symbol, &sym, &symidx);

  if(!sym){
    LOGE("[-] Could not find symbol %s", symbol);
    goto fails;
  }else{
    LOGI("[+] sym %p, symidx %d.", sym, symidx);
  }

  for (int i = 0; i < info.relpltsz; i++) {
    Elf32_Rel& rel = info.relplt[i];
    if (ELF32_R_SYM(rel.r_info) == symidx && ELF32_R_TYPE(rel.r_info) == R_ARM_JUMP_SLOT) {

      void *addr = (void *) (info.elf_base + rel.r_offset);
      if (replaceFunc(addr, replace_func, old_func))
        goto fails;

      //only once
      break;
    }
  }

  for (int i = 0; i < info.reldynsz; i++) {
    Elf32_Rel& rel = info.reldyn[i];
    if (ELF32_R_SYM(rel.r_info) == symidx &&
        (ELF32_R_TYPE(rel.r_info) == R_ARM_ABS32
            || ELF32_R_TYPE(rel.r_info) == R_ARM_GLOB_DAT)) {

      void *addr       = (void *) (info.elf_base + rel.r_offset);
      if (replaceFunc(addr, replace_func, old_func))
        goto fails;
    }
  }

  fails:
  closeElfBySoname(handle);
  return 0;
}
```

最后是测试的代码:

```
typedef int (*strlen_fun)(const char *);
strlen_fun old_strlen = NULL;

size_t my_strlen(const char *str){
  LOGI("strlen was called.");
  int len = old_strlen(str);
  return len * 2;
}


strlen_fun global_strlen1 = (strlen_fun)strlen;
strlen_fun global_strlen2 = (strlen_fun)strlen;

#define SHOW(x) LOGI("%s is %d", #x, x)

extern "C" jint Java_com_example_allhookinone_HookUtils_elfhook(JNIEnv *env, jobject thiz){
  const char *str = "helloworld";

  strlen_fun local_strlen1 = (strlen_fun)strlen;
  strlen_fun local_strlen2 = (strlen_fun)strlen;

  int len0 = global_strlen1(str);
  int len1 = global_strlen2(str);
  int len2 = local_strlen1(str);
  int len3 = local_strlen2(str);
  int len4 = strlen(str);
  int len5 = strlen(str);

  LOGI("hook before:");
  SHOW(len0);
  SHOW(len1);
  SHOW(len2);
  SHOW(len3);
  SHOW(len4);
  SHOW(len5);

  elfHook("libonehook.so", "strlen", (void *)my_strlen, (void **)&old_strlen);

  len0 = global_strlen1(str);
  len1 = global_strlen2(str);
  len2 = local_strlen1(str);
  len3 = local_strlen2(str);
  len4 = strlen(str);
  len5 = strlen(str);

  LOGI("hook after:");
  SHOW(len0);
  SHOW(len1);
  SHOW(len2);
  SHOW(len3);
  SHOW(len4);
  SHOW(len5);

  return 0;
}
```

从打印结果可以发现,local_strlen1和local_strlen2正所上面所说,并没有受影响,但如果函数再次被调用,则生效了,原因不解析了。测试结果就不发了,留给你们试吧。



#GitHup地址
完整代码,见https://github.com/boyliang/AllHookInOne.git*转载请注明来自看雪论坛@PEdiy.com