Swap out an ELF executable's main function with another function from its symbol table, without touching its code at all.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

260 lines
7.9 KiB

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <elf.h>
#include <link.h>
/*#if sizeof(ElfW(Ehdr)) != sizeof(Elf64_Ehdr)
#error "Need 64-bit ELF for now, sorry"
#endif*/
static bool manip_elf(ElfW(Ehdr)* elf, const char* symname) {
if (elf->e_ident[0] != ELFMAG0 || elf->e_ident[1] != ELFMAG1
|| elf->e_ident[2] != ELFMAG2 || elf->e_ident[3] != ELFMAG3) {
printf("bad elf magic\n");
return true;
}
// TODO: port to other architectures
// what would be needed for a port:
// * fix code looking for 'lea rdi, [rel main]'
// * fix relocation type
// * thats it i think??? (well also fix this validation stuff)
if (elf->e_ident[EI_CLASS] != ELFCLASS64 || elf->e_ident[EI_DATA] != ELFDATA2LSB) {
printf("not a 64-bit little-endian elf\n");
return true;
}
if (elf->e_type != ET_DYN || elf->e_machine != EM_X86_64) {
printf("need dynamic x86_64 elf\n");
return true;
}
ElfW(Phdr)* phdr = (ElfW(Phdr)*)(elf->e_phoff + (size_t)elf), *ph_loadexec;
ElfW(Dyn)* dyn = NULL;
bool textrel = false;
enum {
libc_unk, libc_g /* glibc */, libc_musl, libc_uc /* uclibc */
} libc = libc_unk;
// first, try to determine the libc used
for (size_t i = 0; i < elf->e_phnum;
++i, phdr = (ElfW(Phdr)*)((size_t)phdr + elf->e_phentsize)) {
if (phdr->p_type == PT_INTERP) {
const char* str = (char*)elf + phdr->p_offset;
if (!strcmp(str, "/lib64/ld-linux-x86-64.so.2")) {
libc = libc_g;
printf("\tglibc\n");
} else if (!strcmp(str, "/lib/ld-musl-x86_64.so.1")) {
libc = libc_musl;
printf("\tmusl\n");
} else {
printf("\tunknown libc: '%s'\n", str);
}
}
if (phdr->p_type == PT_DYNAMIC) {
dyn = (ElfW(Dyn)*)((size_t)elf + phdr->p_offset);
printf("dyn=%zu\n", phdr->p_offset);
}
}
if (libc == libc_unk) {
ElfW(Dyn)* dd = dyn;
const char* ddstr = NULL;
for (size_t i = 0; dd->d_tag != DT_NULL; ++i, ++dd) {
if (dd->d_tag == DT_STRTAB)
ddstr = (const char*)((size_t)elf + dd->d_un.d_ptr);
}
dd = dyn;
for (size_t i = 0; dd->d_tag != DT_NULL; ++i, ++dd) {
if (dd->d_tag == DT_NEEDED) {
const char* s = ddstr + dd->d_un.d_val;
if (strstr(s, "libc.so") != s) continue;
if (!strcmp(s, "libc.so.6")) {
libc = libc_g;
printf("\tglibc\n");
break;
} else if (!strcmp(s, "libc.so.0")) {
libc = libc_uc;
printf("\tuclibc\n");
break;
} else if (!strcmp(s, "libc.so")) {
libc = libc_musl;
printf("\tmusl\n");
break;
} else {
printf("\tunknown libc: '%s'\n", s);
}
}
}
}
phdr = (ElfW(Phdr)*)(elf->e_phoff + (size_t)elf);
for (size_t i = 0; i < elf->e_phnum;
++i, phdr = (ElfW(Phdr)*)((size_t)phdr + elf->e_phentsize)) {
if (phdr->p_type == PT_LOAD) {
// need this at runtime for the relocs to work (yeah, sorry)
if (phdr->p_flags & PF_X) {
ph_loadexec = phdr;
if (libc == libc_g || libc == libc_uc) {
textrel = true;
} else {
phdr->p_flags |= PF_W;
}
}
}
}
if (dyn == NULL) {
fprintf(stderr, "No DYNAMIC table! bailing out...\n");
return true;
}
ElfW(Addr)* relasz = NULL, *relaent = NULL, *jmprel = NULL;
ElfW(Rela)* relatab = NULL;
ElfW(Sym)* dynsym = NULL;
const char* dynstr = NULL;
size_t strsz = 0, syment = 0;
ElfW(Addr) relaoff = 0;
for (size_t i = 0; dyn->d_tag != DT_NULL; ++i, ++dyn) {
if (dyn->d_tag == DT_TEXTREL) textrel = false;
if (dyn->d_tag == DT_RELA) {
relatab = (ElfW(Rela)*)((size_t)elf + dyn->d_un.d_ptr);
relaoff = dyn->d_un.d_ptr;
}
if (dyn->d_tag == DT_RELASZ) relasz = (ElfW(Addr)*)(&dyn->d_un.d_ptr);
if (dyn->d_tag == DT_RELAENT) relaent = (ElfW(Addr)*)(&dyn->d_un.d_ptr);
if (dyn->d_tag == DT_JMPREL) jmprel = (ElfW(Addr)*)(&dyn->d_un.d_ptr);
if (dyn->d_tag == DT_REL) {
printf("WARN: REL table present in DYN\n");
}
if (dyn->d_tag == DT_SYMTAB) dynsym = (ElfW(Sym)*)((size_t)elf + dyn->d_un.d_ptr);
if (dyn->d_tag == DT_STRTAB) dynstr = (const char*)((size_t)elf + dyn->d_un.d_ptr);
if (dyn->d_tag == DT_SYMENT) syment = dyn->d_un.d_val;
if (dyn->d_tag == DT_STRSZ ) strsz = dyn->d_un.d_val;
}
if (textrel) {
dyn->d_tag = DT_TEXTREL; // need this one
dyn[1].d_tag = DT_NULL;
}
if (!relasz || !relaent || !relatab) {
fprintf(stderr, "No RELA/RELASZ/RELAENT in DYN!\n");
return true;
}
if (!dynsym || !dynstr || !strsz || !syment) {
fprintf(stderr, "no SYMTAB/STRTAB/STRSZ/SYMENT in DYN!\n");
return true;
}
// now we have everything to start patching stuff
static const uint8_t magic_code[] = {0x48,0x8d,0x3d}; // lea rdi, [rel <pcrel32>]
// assumption: the first matching instruction loads 'main' into rdi
// both glibc and musl load main this way, they could do it in a different
// way, but apparently it's not the case
ElfW(Addr) reloctarget = 0;
ElfW(Addr) entry_vma = elf->e_entry;
ElfW(Addr) entry_lma = elf->e_entry - ph_loadexec->p_vaddr + ph_loadexec->p_offset;
printf("entry_vma=0x%zx, entry_lma=0x%zx\n", entry_vma, entry_lma);
void* entry_mem = (void*)(entry_lma + (size_t)elf);
for (size_t i = 0; entry_lma+i - ph_loadexec->p_offset < ph_loadexec->p_filesz; ++i) {
const uint8_t* tocmp = (const uint8_t*)((size_t)entry_mem + i);
for (size_t j = 0; j < sizeof(magic_code)/sizeof(*magic_code); ++j) {
if (tocmp[j] != magic_code[j]) goto next;
}
// found it!
reloctarget = entry_vma + i + sizeof(magic_code)/sizeof(*magic_code);
printf("target = 0x%zx\n", reloctarget);
break;
next:;
}
if (!reloctarget) {
printf("couldn't find relocation target...\n");
return true;
}
ElfW(Word) sym = 0;
for (size_t i = 0; (size_t)dynsym < (size_t)dynstr; ++i, dynsym = (ElfW(Sym)*)((size_t)dynsym + syment)) {
if (i && dynsym->st_name != 0 && dynsym->st_name < strsz
&& !strcmp(symname, dynstr + dynsym->st_name)) {
sym = i;
break;
}
}
if (!sym) {
printf("replacement symbol '%s' not found!\n", symname);
return true;
}
printf("symbol '%s' -> index %zu\n", symname, sym);
ElfW(Rela) extrarel;
extrarel.r_offset = reloctarget;
extrarel.r_info = ELF64_R_INFO(sym, R_X86_64_PC32); // TODO: make portable
extrarel.r_addend = -4; // TODO: make portable
printf("extrarel: *0x%016zx = 0x%016zx + %d\n", extrarel.r_offset, extrarel.r_info, extrarel.r_addend);
// resize the phdr containing the relocations
phdr = (ElfW(Phdr)*)(elf->e_phoff + (size_t)elf);
for (size_t i = 0; i < elf->e_phnum;
++i, phdr = (ElfW(Phdr)*)((size_t)phdr + elf->e_phentsize)) {
if (phdr->p_offset >= relaoff && phdr->p_offset + phdr->p_filesz < relaoff) {
printf("rela phdr index = %zu\n", i);
phdr->p_filesz += *relaent;
phdr->p_memsz += *relaent;
break;
}
}
// resize the shdr containing the relocations
// FIXME: ASSUMPTION: the relocation tables end up at the very end of this
// phdr, first .rela.dyn and then .rela.plt, and no others
// things will break BADLY if this isn't true, and it's hard to detect
// if this will happen, so watch out!
ElfW(Shdr)* shdr = (ElfW(Shdr)*)(elf->e_shoff + (size_t)elf);
for (size_t i = 0; i < elf->e_shnum;
++i, shdr = (ElfW(Shdr)*)((size_t)shdr + elf->e_shentsize)) {
if (shdr->sh_type == SHT_REL) {
printf("WARN: REL table present\n");
} else if (shdr->sh_type == SHT_RELA) {
if (shdr->sh_offset == relaoff) { // .rela.dyn
//shdr->sh_size += shdr->sh_entsize; // one extra entry
} else { // .rela.plt
if (jmprel && *jmprel != shdr->sh_offset) {
printf("WARN: RELA tables look fishy\n");
}
// move one entry below to make room
memmove((void*)((size_t)elf + shdr->sh_offset + shdr->sh_entsize),
(void*)((size_t)elf + shdr->sh_offset),
shdr->sh_size);
shdr->sh_offset += shdr->sh_entsize;
if (jmprel) *jmprel += shdr->sh_entsize;
}
}
}
// FINALLY write back the actually relevant stuff
printf("relasz = %zu, index = %zu\n", *relasz, *relasz / *relaent);
relatab[*relasz / *relaent] = extrarel;
*relasz += *relaent;
printf("relasz now %zu\n", *relasz);
return false;
}