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