Inject a DT_NEEDED entry with libelf

Viewed 349

I'm tryin to inject a new DT_NEEDED entry into an arbitrary elf executable.

Until now, I only managed to edit existing DT_NEEDED entries with strings that already exist in the .dynstr section. Here is my code:

#define _GNU_SOURCE
#include <assert.h>
#include <err.h>
#include <fcntl.h>
#include <gelf.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

char g_target_path[] = "/tmp/new_elf";

void copy_elf(const char *arg_path) {
  char source_path[PATH_MAX];
  realpath(arg_path, source_path);

  FILE *source, *target;

  source = fopen(source_path, "rb");
  assert(source != NULL);
  target = fopen(g_target_path, "wb");
  assert(target != NULL);

  size_t n, m;
  unsigned char buff[8192];
  do {
    n = fread(buff, 1, sizeof buff, source);
    if (n != 0)
      m = fwrite(buff, 1, n, target);
    else
      m = 0;
  } while ((n > 0) && (n == m));
  assert(m == 0);

  assert(fclose(target) == 0);
  assert(fclose(source) == 0);
}

void inject_dt_needed(const char *elf_path) {
  assert(elf_version(EV_CURRENT) != EV_NONE);

  int fd = open(elf_path, O_RDWR, 0);
  assert(fd >= 0);

  Elf *elf = elf_begin(fd, ELF_C_RDWR, NULL);
  assert(elf != NULL);
  assert(elf_kind(elf) == ELF_K_ELF);

  elf_flagelf(elf, ELF_C_SET, ELF_F_LAYOUT);

  Elf_Scn *scn = NULL;
  while ((scn = elf_nextscn(elf, scn)) != NULL) {
    GElf_Shdr shdr = {0};
    assert(gelf_getshdr(scn, &shdr) == &shdr);

    if (shdr.sh_type == SHT_DYNAMIC) {
      Elf_Data *data = NULL;
      data = elf_getdata(scn, data);
      assert(data != NULL);

      size_t sh_entsize = gelf_fsize(elf, ELF_T_DYN, 1, EV_CURRENT);

      for (size_t i = 0; i < shdr.sh_size / sh_entsize; i++) {
        GElf_Dyn dyn = {0};
        assert(gelf_getdyn(data, (int)i, &dyn) == &dyn);

        if (dyn.d_tag == DT_NEEDED) {
          printf("DT_NEEDED detected: %s string offset: %ld\n",
                 elf_strptr(elf, shdr.sh_link, dyn.d_un.d_val), dyn.d_un.d_val);

          // Update a DT_NEEDED inplace.
          dyn.d_un.d_val = 1;
          assert(gelf_update_dyn(data, (int)i, &dyn) != 0);
        }
      }
    }
  }

  assert(elf_update(elf, ELF_C_WRITE) >= 0);

  assert(elf_end(elf) == 0);
  assert(close(fd) == 0);
}

int main(int argc, char const *argv[]) {
  if (argc != 2)
    errx(EXIT_FAILURE, "Please give a path to an elf.");

  copy_elf(argv[1]);
  inject_dt_needed(g_target_path);
  return 0;
}

As an input I provide a simple Hello World program under ASan just to test that libasan is the only DT_NEEDED entry: gcc -fsanitize=address test.c -o elftarget. Example:

$ readelf -d /tmp/new_elf
Dynamic section at offset 0xd78 contains 30 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: [libasan.so.4]
 0x0000000000000001 (NEEDED)             Shared library: [libasan.so.4]
...

How can I add an arbitrary extra DT_NEEDED entry?

I tried to create a new .dynamic section like this:

void inject_dt_needed(const char *elf_path) {
  assert(elf_version(EV_CURRENT) != EV_NONE);

  int fd = open(elf_path, O_RDWR, 0);
  assert(fd >= 0);

  Elf *elf = elf_begin(fd, ELF_C_RDWR, NULL);
  assert(elf != NULL);
  assert(elf_kind(elf) == ELF_K_ELF);

  elf_flagelf(elf, ELF_C_SET, ELF_F_LAYOUT);

  Elf_Scn *scn = NULL;
  while ((scn = elf_nextscn(elf, scn)) != NULL) {
    GElf_Shdr shdr = {};
    assert(gelf_getshdr(scn, &shdr) == &shdr);

    if (shdr.sh_type == SHT_DYNAMIC) {
      Elf_Data *data = NULL;
      data = elf_getdata(scn, data);
      assert(data != NULL);

      Elf_Scn *n_scn = elf_newscn(elf);
      assert(n_scn != NULL);

      Elf_Data *n_data = elf_newdata(n_scn);
      assert(n_data != NULL);

      *n_data = *data;

      GElf_Shdr n_shdr = {};
      assert(gelf_getshdr(n_scn, &n_shdr) == &n_shdr);

      n_shdr = shdr;

      assert(gelf_update_shdr(n_scn, &n_shdr) != 0);

      shdr.sh_name = 0;
      assert(gelf_update_shdr(scn, &shdr) != 0);

      elf_flagscn(n_scn, ELF_C_SET, ELF_F_DIRTY);
      elf_flagshdr(n_scn, ELF_C_SET, ELF_F_DIRTY);
      elf_flagdata(n_data, ELF_C_SET, ELF_F_DIRTY);
      assert(elf_update(elf, ELF_C_NULL) >= 0);
      break;
    }
  }

  assert(elf_update(elf, ELF_C_WRITE) >= 0);

  assert(elf_end(elf) == 0);
  assert(close(fd) == 0);
}

But for some reason when I compare the original elf with the newly generated one, the extra section has no header information, while I see the old .dynamic section been deleted:

$ diff <(readelf -a /tmp/new_elf) <(readelf -a ./elftarget)
readelf: Error: no .dynamic section in the dynamic segment
19c19
<   Number of section headers:         36
---
>   Number of section headers:         35
69c69
<   [22]                   DYNAMIC          0000000000200d78  00000d78
---
>   [22] .dynamic          DYNAMIC          0000000000200d78  00000d78
95,96d94
<   [35]                   NULL             0000001400000001  00000000
<        000000000160c260  0000000000000000 WSIx       2     1     544
133,134c131,132
<    03     .preinit_array .init_array .fini_array  .got .data .bss
<    04
---
>    03     .preinit_array .init_array .fini_array .dynamic .got .data .bss
>    04     .dynamic
138c136
<    08     .preinit_array .init_array .fini_array  .got
---
>    08     .preinit_array .init_array .fini_array .dynamic .got
0 Answers
Related