diff options
Diffstat (limited to 'build/unix/elfhack/elfhack.cpp')
-rw-r--r-- | build/unix/elfhack/elfhack.cpp | 823 |
1 files changed, 823 insertions, 0 deletions
diff --git a/build/unix/elfhack/elfhack.cpp b/build/unix/elfhack/elfhack.cpp new file mode 100644 index 000000000..8c1184237 --- /dev/null +++ b/build/unix/elfhack/elfhack.cpp @@ -0,0 +1,823 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#undef NDEBUG +#include <assert.h> +#include <cstring> +#include <cstdlib> +#include <cstdio> +#include "elfxx.h" + +#define ver "0" +#define elfhack_data ".elfhack.data.v" ver +#define elfhack_text ".elfhack.text.v" ver + +#ifndef R_ARM_V4BX +#define R_ARM_V4BX 0x28 +#endif +#ifndef R_ARM_CALL +#define R_ARM_CALL 0x1c +#endif +#ifndef R_ARM_JUMP24 +#define R_ARM_JUMP24 0x1d +#endif +#ifndef R_ARM_THM_JUMP24 +#define R_ARM_THM_JUMP24 0x1e +#endif + +char *rundir = nullptr; + +template <typename T> +struct wrapped { + T value; +}; + +class Elf_Addr_Traits { +public: + typedef wrapped<Elf32_Addr> Type32; + typedef wrapped<Elf64_Addr> Type64; + + template <class endian, typename R, typename T> + static inline void swap(T &t, R &r) { + r.value = endian::swap(t.value); + } +}; + +typedef serializable<Elf_Addr_Traits> Elf_Addr; + +class Elf_RelHack_Traits { +public: + typedef Elf32_Rel Type32; + typedef Elf32_Rel Type64; + + template <class endian, typename R, typename T> + static inline void swap(T &t, R &r) { + r.r_offset = endian::swap(t.r_offset); + r.r_info = endian::swap(t.r_info); + } +}; + +typedef serializable<Elf_RelHack_Traits> Elf_RelHack; + +class ElfRelHack_Section: public ElfSection { +public: + ElfRelHack_Section(Elf_Shdr &s) + : ElfSection(s, nullptr, nullptr) + { + name = elfhack_data; + }; + + void serialize(std::ofstream &file, char ei_class, char ei_data) + { + for (std::vector<Elf_RelHack>::iterator i = rels.begin(); + i != rels.end(); ++i) + (*i).serialize(file, ei_class, ei_data); + } + + bool isRelocatable() { + return true; + } + + void push_back(Elf_RelHack &r) { + rels.push_back(r); + shdr.sh_size = rels.size() * shdr.sh_entsize; + } +private: + std::vector<Elf_RelHack> rels; +}; + +class ElfRelHackCode_Section: public ElfSection { +public: + ElfRelHackCode_Section(Elf_Shdr &s, Elf &e, unsigned int init) + : ElfSection(s, nullptr, nullptr), parent(e), init(init) { + std::string file(rundir); + file += "/inject/"; + switch (parent.getMachine()) { + case EM_386: + file += "x86"; + break; + case EM_X86_64: + file += "x86_64"; + break; + case EM_ARM: + file += "arm"; + break; + default: + throw std::runtime_error("unsupported architecture"); + } + file += ".o"; + std::ifstream inject(file.c_str(), std::ios::in|std::ios::binary); + elf = new Elf(inject); + if (elf->getType() != ET_REL) + throw std::runtime_error("object for injected code is not ET_REL"); + if (elf->getMachine() != parent.getMachine()) + throw std::runtime_error("architecture of object for injected code doesn't match"); + + ElfSymtab_Section *symtab = nullptr; + + // Find the symbol table. + for (ElfSection *section = elf->getSection(1); section != nullptr; + section = section->getNext()) { + if (section->getType() == SHT_SYMTAB) + symtab = (ElfSymtab_Section *) section; + } + if (symtab == nullptr) + throw std::runtime_error("Couldn't find a symbol table for the injected code"); + + // Find the init symbol + entry_point = -1; + Elf_SymValue *sym = symtab->lookup(init ? "init" : "init_noinit"); + if (!sym) + throw std::runtime_error("Couldn't find an 'init' symbol in the injected code"); + + entry_point = sym->value.getValue(); + + // Get all relevant sections from the injected code object. + add_code_section(sym->value.getSection()); + + // Adjust code sections offsets according to their size + std::vector<ElfSection *>::iterator c = code.begin(); + (*c)->getShdr().sh_addr = 0; + for(ElfSection *last = *(c++); c != code.end(); c++) { + unsigned int addr = last->getShdr().sh_addr + last->getSize(); + if (addr & ((*c)->getAddrAlign() - 1)) + addr = (addr | ((*c)->getAddrAlign() - 1)) + 1; + (*c)->getShdr().sh_addr = addr; + // We need to align this section depending on the greater + // alignment required by code sections. + if (shdr.sh_addralign < (*c)->getAddrAlign()) + shdr.sh_addralign = (*c)->getAddrAlign(); + } + shdr.sh_size = code.back()->getAddr() + code.back()->getSize(); + data = new char[shdr.sh_size]; + char *buf = data; + for (c = code.begin(); c != code.end(); c++) { + memcpy(buf, (*c)->getData(), (*c)->getSize()); + buf += (*c)->getSize(); + } + name = elfhack_text; + } + + ~ElfRelHackCode_Section() { + delete elf; + } + + void serialize(std::ofstream &file, char ei_class, char ei_data) + { + // Readjust code offsets + for (std::vector<ElfSection *>::iterator c = code.begin(); c != code.end(); c++) + (*c)->getShdr().sh_addr += getAddr(); + + // Apply relocations + for (std::vector<ElfSection *>::iterator c = code.begin(); c != code.end(); c++) { + for (ElfSection *rel = elf->getSection(1); rel != nullptr; rel = rel->getNext()) + if (((rel->getType() == SHT_REL) || + (rel->getType() == SHT_RELA)) && + (rel->getInfo().section == *c)) { + if (rel->getType() == SHT_REL) + apply_relocations((ElfRel_Section<Elf_Rel> *)rel, *c); + else + apply_relocations((ElfRel_Section<Elf_Rela> *)rel, *c); + } + } + + ElfSection::serialize(file, ei_class, ei_data); + } + + bool isRelocatable() { + return true; + } + + unsigned int getEntryPoint() { + return entry_point; + } +private: + void add_code_section(ElfSection *section) + { + if (section) { + /* Don't add section if it's already been added in the past */ + for (auto s = code.begin(); s != code.end(); ++s) { + if (section == *s) + return; + } + code.push_back(section); + find_code(section); + } + } + + /* Look at the relocations associated to the given section to find other + * sections that it requires */ + void find_code(ElfSection *section) + { + for (ElfSection *s = elf->getSection(1); s != nullptr; + s = s->getNext()) { + if (((s->getType() == SHT_REL) || + (s->getType() == SHT_RELA)) && + (s->getInfo().section == section)) { + if (s->getType() == SHT_REL) + scan_relocs_for_code((ElfRel_Section<Elf_Rel> *)s); + else + scan_relocs_for_code((ElfRel_Section<Elf_Rela> *)s); + } + } + } + + template <typename Rel_Type> + void scan_relocs_for_code(ElfRel_Section<Rel_Type> *rel) + { + ElfSymtab_Section *symtab = (ElfSymtab_Section *)rel->getLink(); + for (auto r = rel->rels.begin(); r != rel->rels.end(); r++) { + ElfSection *section = symtab->syms[ELF32_R_SYM(r->r_info)].value.getSection(); + add_code_section(section); + } + } + + class pc32_relocation { + public: + Elf32_Addr operator()(unsigned int base_addr, Elf32_Off offset, + Elf32_Word addend, unsigned int addr) + { + return addr + addend - offset - base_addr; + } + }; + + class arm_plt32_relocation { + public: + Elf32_Addr operator()(unsigned int base_addr, Elf32_Off offset, + Elf32_Word addend, unsigned int addr) + { + // We don't care about sign_extend because the only case where this is + // going to be used only jumps forward. + Elf32_Addr tmp = (Elf32_Addr) (addr - offset - base_addr) >> 2; + tmp = (addend + tmp) & 0x00ffffff; + return (addend & 0xff000000) | tmp; + } + }; + + class arm_thm_jump24_relocation { + public: + Elf32_Addr operator()(unsigned int base_addr, Elf32_Off offset, + Elf32_Word addend, unsigned int addr) + { + /* Follows description of b.w and bl instructions as per + ARM Architecture Reference Manual ARMĀ® v7-A and ARMĀ® v7-R edition, A8.6.16 + We limit ourselves to Encoding T4 of b.w and Encoding T1 of bl. + We don't care about sign_extend because the only case where this is + going to be used only jumps forward. */ + Elf32_Addr tmp = (Elf32_Addr) (addr - offset - base_addr); + unsigned int word0 = addend & 0xffff, + word1 = addend >> 16; + + /* Encoding T4 of B.W is 10x1 ; Encoding T1 of BL is 11x1. */ + unsigned int type = (word1 & 0xd000) >> 12; + if (((word0 & 0xf800) != 0xf000) || ((type & 0x9) != 0x9)) + throw std::runtime_error("R_ARM_THM_JUMP24/R_ARM_THM_CALL relocation only supported for B.W <label> and BL <label>"); + + /* When the target address points to ARM code, switch a BL to a + * BLX. This however can't be done with a B.W without adding a + * trampoline, which is not supported as of now. */ + if ((addr & 0x1) == 0) { + if (type == 0x9) + throw std::runtime_error("R_ARM_THM_JUMP24/R_ARM_THM_CALL relocation only supported for BL <label> when label points to ARM code"); + /* The address of the target is always relative to a 4-bytes + * aligned address, so if the address of the BL instruction is + * not 4-bytes aligned, adjust for it. */ + if ((base_addr + offset) & 0x2) + tmp += 2; + /* Encoding T2 of BLX is 11x0. */ + type = 0xc; + } + + unsigned int s = (word0 & (1 << 10)) >> 10; + unsigned int j1 = (word1 & (1 << 13)) >> 13; + unsigned int j2 = (word1 & (1 << 11)) >> 11; + unsigned int i1 = j1 ^ s ? 0 : 1; + unsigned int i2 = j2 ^ s ? 0 : 1; + + tmp += ((s << 24) | (i1 << 23) | (i2 << 22) | ((word0 & 0x3ff) << 12) | ((word1 & 0x7ff) << 1)); + + s = (tmp & (1 << 24)) >> 24; + j1 = ((tmp & (1 << 23)) >> 23) ^ !s; + j2 = ((tmp & (1 << 22)) >> 22) ^ !s; + + return 0xf000 | (s << 10) | ((tmp & (0x3ff << 12)) >> 12) | + (type << 28) | (j1 << 29) | (j2 << 27) | ((tmp & 0xffe) << 15); + } + }; + + class gotoff_relocation { + public: + Elf32_Addr operator()(unsigned int base_addr, Elf32_Off offset, + Elf32_Word addend, unsigned int addr) + { + return addr + addend; + } + }; + + template <class relocation_type> + void apply_relocation(ElfSection *the_code, char *base, Elf_Rel *r, unsigned int addr) + { + relocation_type relocation; + Elf32_Addr value; + memcpy(&value, base + r->r_offset, 4); + value = relocation(the_code->getAddr(), r->r_offset, value, addr); + memcpy(base + r->r_offset, &value, 4); + } + + template <class relocation_type> + void apply_relocation(ElfSection *the_code, char *base, Elf_Rela *r, unsigned int addr) + { + relocation_type relocation; + Elf32_Addr value = relocation(the_code->getAddr(), r->r_offset, r->r_addend, addr); + memcpy(base + r->r_offset, &value, 4); + } + + template <typename Rel_Type> + void apply_relocations(ElfRel_Section<Rel_Type> *rel, ElfSection *the_code) + { + assert(rel->getType() == Rel_Type::sh_type); + char *buf = data + (the_code->getAddr() - code.front()->getAddr()); + // TODO: various checks on the sections + ElfSymtab_Section *symtab = (ElfSymtab_Section *)rel->getLink(); + for (typename std::vector<Rel_Type>::iterator r = rel->rels.begin(); r != rel->rels.end(); r++) { + // TODO: various checks on the symbol + const char *name = symtab->syms[ELF32_R_SYM(r->r_info)].name; + unsigned int addr; + if (symtab->syms[ELF32_R_SYM(r->r_info)].value.getSection() == nullptr) { + if (strcmp(name, "relhack") == 0) { + addr = getNext()->getAddr(); + } else if (strcmp(name, "elf_header") == 0) { + // TODO: change this ungly hack to something better + ElfSection *ehdr = parent.getSection(1)->getPrevious()->getPrevious(); + addr = ehdr->getAddr(); + } else if (strcmp(name, "original_init") == 0) { + addr = init; + } else if (strcmp(name, "_GLOBAL_OFFSET_TABLE_") == 0) { + // We actually don't need a GOT, but need it as a reference for + // GOTOFF relocations. We'll just use the start of the ELF file + addr = 0; + } else if (strcmp(name, "") == 0) { + // This is for R_ARM_V4BX, until we find something better + addr = -1; + } else { + throw std::runtime_error("Unsupported symbol in relocation"); + } + } else { + ElfSection *section = symtab->syms[ELF32_R_SYM(r->r_info)].value.getSection(); + assert((section->getType() == SHT_PROGBITS) && (section->getFlags() & SHF_EXECINSTR)); + addr = symtab->syms[ELF32_R_SYM(r->r_info)].value.getValue(); + } + // Do the relocation +#define REL(machine, type) (EM_ ## machine | (R_ ## machine ## _ ## type << 8)) + switch (elf->getMachine() | (ELF32_R_TYPE(r->r_info) << 8)) { + case REL(X86_64, PC32): + case REL(386, PC32): + case REL(386, GOTPC): + case REL(ARM, GOTPC): + case REL(ARM, REL32): + apply_relocation<pc32_relocation>(the_code, buf, &*r, addr); + break; + case REL(ARM, CALL): + case REL(ARM, JUMP24): + case REL(ARM, PLT32): + apply_relocation<arm_plt32_relocation>(the_code, buf, &*r, addr); + break; + case REL(ARM, THM_PC22 /* THM_CALL */): + case REL(ARM, THM_JUMP24): + apply_relocation<arm_thm_jump24_relocation>(the_code, buf, &*r, addr); + break; + case REL(386, GOTOFF): + case REL(ARM, GOTOFF): + apply_relocation<gotoff_relocation>(the_code, buf, &*r, addr); + break; + case REL(ARM, V4BX): + // Ignore R_ARM_V4BX relocations + break; + default: + throw std::runtime_error("Unsupported relocation type"); + } + } + } + + Elf *elf, &parent; + std::vector<ElfSection *> code; + unsigned int init; + int entry_point; +}; + +unsigned int get_addend(Elf_Rel *rel, Elf *elf) { + ElfLocation loc(rel->r_offset, elf); + Elf_Addr addr(loc.getBuffer(), Elf_Addr::size(elf->getClass()), elf->getClass(), elf->getData()); + return addr.value; +} + +unsigned int get_addend(Elf_Rela *rel, Elf *elf) { + return rel->r_addend; +} + +void set_relative_reloc(Elf_Rel *rel, Elf *elf, unsigned int value) { + ElfLocation loc(rel->r_offset, elf); + Elf_Addr addr; + addr.value = value; + addr.serialize(const_cast<char *>(loc.getBuffer()), Elf_Addr::size(elf->getClass()), elf->getClass(), elf->getData()); +} + +void set_relative_reloc(Elf_Rela *rel, Elf *elf, unsigned int value) { + // ld puts the value of relocated relocations both in the addend and + // at r_offset. For consistency, keep it that way. + set_relative_reloc((Elf_Rel *)rel, elf, value); + rel->r_addend = value; +} + +void maybe_split_segment(Elf *elf, ElfSegment *segment, bool fill) +{ + std::list<ElfSection *>::iterator it = segment->begin(); + for (ElfSection *last = *(it++); it != segment->end(); last = *(it++)) { + // When two consecutive non-SHT_NOBITS sections are apart by more + // than the alignment of the section, the second can be moved closer + // to the first, but this requires the segment to be split. + if (((*it)->getType() != SHT_NOBITS) && (last->getType() != SHT_NOBITS) && + ((*it)->getOffset() - last->getOffset() - last->getSize() > segment->getAlign())) { + // Probably very wrong. + Elf_Phdr phdr; + phdr.p_type = PT_LOAD; + phdr.p_vaddr = 0; + phdr.p_paddr = phdr.p_vaddr + segment->getVPDiff(); + phdr.p_flags = segment->getFlags(); + phdr.p_align = segment->getAlign(); + phdr.p_filesz = (unsigned int)-1; + phdr.p_memsz = (unsigned int)-1; + ElfSegment *newSegment = new ElfSegment(&phdr); + elf->insertSegmentAfter(segment, newSegment); + ElfSection *section = *it; + for (; it != segment->end(); ++it) { + newSegment->addSection(*it); + } + for (it = newSegment->begin(); it != newSegment->end(); it++) { + segment->removeSection(*it); + } + // Fill the virtual address space gap left between the two PT_LOADs + // with a new PT_LOAD with no permissions. This avoids the linker + // (especially bionic's) filling the gap with anonymous memory, + // which breakpad doesn't like. + // /!\ running strip on a elfhacked binary will break this filler + // PT_LOAD. + if (!fill) + break; + // Insert dummy segment to normalize the entire Elf with the header + // sizes adjusted, before inserting a filler segment. + { + memset(&phdr, 0, sizeof(phdr)); + ElfSegment dummySegment(&phdr); + elf->insertSegmentAfter(segment, &dummySegment); + elf->normalize(); + elf->removeSegment(&dummySegment); + } + ElfSection *previous = section->getPrevious(); + phdr.p_type = PT_LOAD; + phdr.p_vaddr = (previous->getAddr() + previous->getSize() + segment->getAlign() - 1) & ~(segment->getAlign() - 1); + phdr.p_paddr = phdr.p_vaddr + segment->getVPDiff(); + phdr.p_flags = 0; + phdr.p_align = 0; + phdr.p_filesz = (section->getAddr() & ~(newSegment->getAlign() - 1)) - phdr.p_vaddr; + phdr.p_memsz = phdr.p_filesz; + if (phdr.p_filesz) { + newSegment = new ElfSegment(&phdr); + assert(newSegment->isElfHackFillerSegment()); + elf->insertSegmentAfter(segment, newSegment); + } else { + elf->normalize(); + } + break; + } + } +} + +template <typename Rel_Type> +int do_relocation_section(Elf *elf, unsigned int rel_type, unsigned int rel_type2, bool force, bool fill) +{ + ElfDynamic_Section *dyn = elf->getDynSection(); + if (dyn == nullptr) { + fprintf(stderr, "Couldn't find SHT_DYNAMIC section\n"); + return -1; + } + + ElfSegment *relro = elf->getSegmentByType(PT_GNU_RELRO); + + ElfRel_Section<Rel_Type> *section = (ElfRel_Section<Rel_Type> *)dyn->getSectionForType(Rel_Type::d_tag); + assert(section->getType() == Rel_Type::sh_type); + + Elf32_Shdr relhack32_section = + { 0, SHT_PROGBITS, SHF_ALLOC, 0, (Elf32_Off)-1, 0, SHN_UNDEF, 0, + Elf_RelHack::size(elf->getClass()), Elf_RelHack::size(elf->getClass()) }; // TODO: sh_addralign should be an alignment, not size + Elf32_Shdr relhackcode32_section = + { 0, SHT_PROGBITS, SHF_ALLOC | SHF_EXECINSTR, 0, (Elf32_Off)-1, 0, + SHN_UNDEF, 0, 1, 0 }; + + unsigned int entry_sz = Elf_Addr::size(elf->getClass()); + + // The injected code needs to be executed before any init code in the + // binary. There are three possible cases: + // - The binary has no init code at all. In this case, we will add a + // DT_INIT entry pointing to the injected code. + // - The binary has a DT_INIT entry. In this case, we will interpose: + // we change DT_INIT to point to the injected code, and have the + // injected code call the original DT_INIT entry point. + // - The binary has no DT_INIT entry, but has a DT_INIT_ARRAY. In this + // case, we interpose as well, by replacing the first entry in the + // array to point to the injected code, and have the injected code + // call the original first entry. + // The binary may have .ctors instead of DT_INIT_ARRAY, for its init + // functions, but this falls into the second case above, since .ctors + // are actually run by DT_INIT code. + ElfValue *value = dyn->getValueForType(DT_INIT); + unsigned int original_init = value ? value->getValue() : 0; + ElfSection *init_array = nullptr; + if (!value || !value->getValue()) { + value = dyn->getValueForType(DT_INIT_ARRAYSZ); + if (value && value->getValue() >= entry_sz) + init_array = dyn->getSectionForType(DT_INIT_ARRAY); + } + + Elf_Shdr relhack_section(relhack32_section); + Elf_Shdr relhackcode_section(relhackcode32_section); + ElfRelHack_Section *relhack = new ElfRelHack_Section(relhack_section); + + ElfSymtab_Section *symtab = (ElfSymtab_Section *) section->getLink(); + Elf_SymValue *sym = symtab->lookup("__cxa_pure_virtual"); + + std::vector<Rel_Type> new_rels; + Elf_RelHack relhack_entry; + relhack_entry.r_offset = relhack_entry.r_info = 0; + size_t init_array_reloc = 0; + for (typename std::vector<Rel_Type>::iterator i = section->rels.begin(); + i != section->rels.end(); i++) { + // We don't need to keep R_*_NONE relocations + if (!ELF32_R_TYPE(i->r_info)) + continue; + ElfLocation loc(i->r_offset, elf); + // __cxa_pure_virtual is a function used in vtables to point at pure + // virtual methods. The __cxa_pure_virtual function usually abort()s. + // These functions are however normally never called. In the case + // where they would, jumping to the null address instead of calling + // __cxa_pure_virtual is going to work just as well. So we can remove + // relocations for the __cxa_pure_virtual symbol and null out the + // content at the offset pointed by the relocation. + if (sym) { + if (sym->defined) { + // If we are statically linked to libstdc++, the + // __cxa_pure_virtual symbol is defined in our lib, and we + // have relative relocations (rel_type) for it. + if (ELF32_R_TYPE(i->r_info) == rel_type) { + Elf_Addr addr(loc.getBuffer(), entry_sz, elf->getClass(), elf->getData()); + if (addr.value == sym->value.getValue()) { + memset((char *)loc.getBuffer(), 0, entry_sz); + continue; + } + } + } else { + // If we are dynamically linked to libstdc++, the + // __cxa_pure_virtual symbol is undefined in our lib, and we + // have absolute relocations (rel_type2) for it. + if ((ELF32_R_TYPE(i->r_info) == rel_type2) && + (sym == &symtab->syms[ELF32_R_SYM(i->r_info)])) { + memset((char *)loc.getBuffer(), 0, entry_sz); + continue; + } + } + } + // Keep track of the relocation associated with the first init_array entry. + if (init_array && i->r_offset == init_array->getAddr()) { + if (init_array_reloc) { + fprintf(stderr, "Found multiple relocations for the first init_array entry. Skipping\n"); + return -1; + } + new_rels.push_back(*i); + init_array_reloc = new_rels.size(); + } else if (!(loc.getSection()->getFlags() & SHF_WRITE) || (ELF32_R_TYPE(i->r_info) != rel_type) || + (relro && (i->r_offset >= relro->getAddr()) && + (i->r_offset < relro->getAddr() + relro->getMemSize()))) { + // Don't pack relocations happening in non writable sections. + // Our injected code is likely not to be allowed to write there. + new_rels.push_back(*i); + } else { + // TODO: check that i->r_addend == *i->r_offset + if (i->r_offset == relhack_entry.r_offset + relhack_entry.r_info * entry_sz) { + relhack_entry.r_info++; + } else { + if (relhack_entry.r_offset) + relhack->push_back(relhack_entry); + relhack_entry.r_offset = i->r_offset; + relhack_entry.r_info = 1; + } + } + } + if (relhack_entry.r_offset) + relhack->push_back(relhack_entry); + // Last entry must be nullptr + relhack_entry.r_offset = relhack_entry.r_info = 0; + relhack->push_back(relhack_entry); + + unsigned int old_end = section->getOffset() + section->getSize(); + + if (init_array) { + if (! init_array_reloc) { + fprintf(stderr, "Didn't find relocation for DT_INIT_ARRAY's first entry. Skipping\n"); + return -1; + } + Rel_Type *rel = &new_rels[init_array_reloc - 1]; + unsigned int addend = get_addend(rel, elf); + // Use relocated value of DT_INIT_ARRAY's first entry for the + // function to be called by the injected code. + if (ELF32_R_TYPE(rel->r_info) == rel_type) { + original_init = addend; + } else if (ELF32_R_TYPE(rel->r_info) == rel_type2) { + ElfSymtab_Section *symtab = (ElfSymtab_Section *)section->getLink(); + original_init = symtab->syms[ELF32_R_SYM(rel->r_info)].value.getValue() + addend; + } else { + fprintf(stderr, "Unsupported relocation type for DT_INIT_ARRAY's first entry. Skipping\n"); + return -1; + } + } + + section->rels.assign(new_rels.begin(), new_rels.end()); + section->shrink(new_rels.size() * section->getEntSize()); + + ElfRelHackCode_Section *relhackcode = new ElfRelHackCode_Section(relhackcode_section, *elf, original_init); + relhackcode->insertBefore(section); + relhack->insertAfter(relhackcode); + if (section->getOffset() + section->getSize() >= old_end) { + fprintf(stderr, "No gain. Skipping\n"); + return -1; + } + + // Adjust PT_LOAD segments + for (ElfSegment *segment = elf->getSegmentByType(PT_LOAD); segment; + segment = elf->getSegmentByType(PT_LOAD, segment)) { + maybe_split_segment(elf, segment, fill); + } + + // Ensure Elf sections will be at their final location. + elf->normalize(); + ElfLocation *init = new ElfLocation(relhackcode, relhackcode->getEntryPoint()); + if (init_array) { + // Adjust the first DT_INIT_ARRAY entry to point at the injected code + // by transforming its relocation into a relative one pointing to the + // address of the injected code. + Rel_Type *rel = §ion->rels[init_array_reloc - 1]; + rel->r_info = ELF32_R_INFO(0, rel_type); // Set as a relative relocation + set_relative_reloc(§ion->rels[init_array_reloc - 1], elf, init->getValue()); + } else if (!dyn->setValueForType(DT_INIT, init)) { + fprintf(stderr, "Can't grow .dynamic section to set DT_INIT. Skipping\n"); + return -1; + } + // TODO: adjust the value according to the remaining number of relative relocations + if (dyn->getValueForType(Rel_Type::d_tag_count)) + dyn->setValueForType(Rel_Type::d_tag_count, new ElfPlainValue(0)); + + return 0; +} + +static inline int backup_file(const char *name) +{ + std::string fname(name); + fname += ".bak"; + return rename(name, fname.c_str()); +} + +void do_file(const char *name, bool backup = false, bool force = false, bool fill = false) +{ + std::ifstream file(name, std::ios::in|std::ios::binary); + Elf elf(file); + unsigned int size = elf.getSize(); + fprintf(stderr, "%s: ", name); + if (elf.getType() != ET_DYN) { + fprintf(stderr, "Not a shared object. Skipping\n"); + return; + } + + for (ElfSection *section = elf.getSection(1); section != nullptr; + section = section->getNext()) { + if (section->getName() && + (strncmp(section->getName(), ".elfhack.", 9) == 0)) { + fprintf(stderr, "Already elfhacked. Skipping\n"); + return; + } + } + + int exit = -1; + switch (elf.getMachine()) { + case EM_386: + exit = do_relocation_section<Elf_Rel>(&elf, R_386_RELATIVE, R_386_32, force, fill); + break; + case EM_X86_64: + exit = do_relocation_section<Elf_Rela>(&elf, R_X86_64_RELATIVE, R_X86_64_64, force, fill); + break; + case EM_ARM: + exit = do_relocation_section<Elf_Rel>(&elf, R_ARM_RELATIVE, R_ARM_ABS32, force, fill); + break; + } + if (exit == 0) { + if (!force && (elf.getSize() >= size)) { + fprintf(stderr, "No gain. Skipping\n"); + } else if (backup && backup_file(name) != 0) { + fprintf(stderr, "Couln't create backup file\n"); + } else { + std::ofstream ofile(name, std::ios::out|std::ios::binary|std::ios::trunc); + elf.write(ofile); + fprintf(stderr, "Reduced by %d bytes\n", size - elf.getSize()); + } + } +} + +void undo_file(const char *name, bool backup = false) +{ + std::ifstream file(name, std::ios::in|std::ios::binary); + Elf elf(file); + unsigned int size = elf.getSize(); + fprintf(stderr, "%s: ", name); + if (elf.getType() != ET_DYN) { + fprintf(stderr, "Not a shared object. Skipping\n"); + return; + } + + ElfSection *data = nullptr, *text = nullptr; + for (ElfSection *section = elf.getSection(1); section != nullptr; + section = section->getNext()) { + if (section->getName() && + (strcmp(section->getName(), elfhack_data) == 0)) + data = section; + if (section->getName() && + (strcmp(section->getName(), elfhack_text) == 0)) + text = section; + } + + if (!data || !text) { + fprintf(stderr, "Not elfhacked. Skipping\n"); + return; + } + if (data != text->getNext()) { + fprintf(stderr, elfhack_data " section not following " elfhack_text ". Skipping\n"); + return; + } + + ElfSegment *first = elf.getSegmentByType(PT_LOAD); + ElfSegment *second = elf.getSegmentByType(PT_LOAD, first); + ElfSegment *filler = nullptr; + // If the second PT_LOAD is a filler from elfhack --fill, check the third. + if (second->isElfHackFillerSegment()) { + filler = second; + second = elf.getSegmentByType(PT_LOAD, filler); + } + if (second->getFlags() != first->getFlags()) { + fprintf(stderr, "Couldn't identify elfhacked PT_LOAD segments. Skipping\n"); + return; + } + // Move sections from the second PT_LOAD to the first, and remove the + // second PT_LOAD segment. + for (std::list<ElfSection *>::iterator section = second->begin(); + section != second->end(); ++section) + first->addSection(*section); + + elf.removeSegment(second); + if (filler) + elf.removeSegment(filler); + + if (backup && backup_file(name) != 0) { + fprintf(stderr, "Couln't create backup file\n"); + } else { + std::ofstream ofile(name, std::ios::out|std::ios::binary|std::ios::trunc); + elf.write(ofile); + fprintf(stderr, "Grown by %d bytes\n", elf.getSize() - size); + } +} + +int main(int argc, char *argv[]) +{ + int arg; + bool backup = false; + bool force = false; + bool revert = false; + bool fill = false; + char *lastSlash = rindex(argv[0], '/'); + if (lastSlash != nullptr) + rundir = strndup(argv[0], lastSlash - argv[0]); + for (arg = 1; arg < argc; arg++) { + if (strcmp(argv[arg], "-f") == 0) + force = true; + else if (strcmp(argv[arg], "-b") == 0) + backup = true; + else if (strcmp(argv[arg], "-r") == 0) + revert = true; + else if (strcmp(argv[arg], "--fill") == 0) + fill = true; + else if (revert) { + undo_file(argv[arg], backup); + } else + do_file(argv[arg], backup, force, fill); + } + + free(rundir); + return 0; +} |