/* 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/. */
class ElfRelHack_Section : public ElfSection { public:
ElfRelHack_Section(Elf_Shdr& s)
: ElfSection(s, nullptr, nullptr),
block_size((8 * s.sh_entsize - 1) * s.sh_entsize) {
name = elfhack_data;
};
void serialize(std::ofstream& file, unsignedchar ei_class, unsignedchar ei_data) { if (bitmap) {
relr.push_back((bitmap << 1) | 1);
} for (std::vector<Elf64_Addr>::iterator i = relr.begin(); i != relr.end();
++i) {
Elf_Addr out;
out.value = *i;
out.serialize(file, ei_class, ei_data);
}
}
bool isRelocatable() { returntrue; }
void push_back(Elf64_Addr offset) { // The format used for the packed relocations is SHT_RELR, described in // https://groups.google.com/g/generic-abi/c/bX460iggiKg/m/Jnz1lgLJAgAJ // The gist of it is that an address is recorded, and the following words, // if their LSB is 1, represent a bitmap of word-size-spaced relocations // at the addresses that follow. There can be multiple such bitmaps, such // that very long streaks of (possibly spaced) relocations can be recorded // in a very compact way. for (;;) { // [block_start; block_start + block_size] represents the range of offsets // the current bitmap can record. If the offset doesn't fall in that // range, or if doesn't align properly to be recorded, we record the // bitmap, and slide the block corresponding to a new bitmap. If the // offset doesn't fall in the range for the new bitmap, or if there wasn't // an active bitmap in the first place, we record the offset and start a // new bitmap for the block that follows it. if (!block_start || offset < block_start ||
offset >= block_start + block_size ||
(offset - block_start) % shdr.sh_entsize) { if (bitmap) {
relr.push_back((bitmap << 1) | 1);
block_start += block_size;
bitmap = 0; continue;
}
relr.push_back(offset);
block_start = offset + shdr.sh_entsize; break;
}
bitmap |= 1ULL << ((offset - block_start) / shdr.sh_entsize); break;
}
shdr.sh_size = (relr.size() + (bitmap ? 1 : 0)) * shdr.sh_entsize;
}
class ElfRelHackCode_Section : public ElfSection { public:
ElfRelHackCode_Section(Elf_Shdr& s, Elf& e,
ElfRelHack_Section& relhack_section, unsignedint init, unsignedint mprotect_cb, unsignedint sysconf_cb)
: ElfSection(s, nullptr, nullptr),
parent(e),
relhack_section(relhack_section),
init(init),
init_trampoline(nullptr),
mprotect_cb(mprotect_cb),
sysconf_cb(sysconf_cb) {
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; case EM_AARCH64:
file += "aarch64"; 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");
relro = parent.getSegmentByType(PT_GNU_RELRO);
// Find the init symbol
entry_point = -1;
std::string symbol = "init"; if (!init) symbol += "_noinit"; if (relro) symbol += "_relro";
Elf_SymValue* sym = symtab->lookup(symbol.c_str()); 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());
// If the original init function is located too far away, we're going to // need to use a trampoline. See comment in inject.c. // Theoretically, we should check for (init - instr) > boundary, where // boundary is the platform-dependent limit, and instr is the virtual // address of the instruction that calls the original init, but we don't // have it at this point, so punt to just init. if ((init > 0xffffff && parent.getMachine() == EM_ARM) ||
(init > 0x07ffffff && parent.getMachine() == EM_AARCH64)) {
Elf_SymValue* trampoline = symtab->lookup("init_trampoline"); if (!trampoline) { throw std::runtime_error( "Couldn't find an 'init_trampoline' symbol in the injected code");
}
void insertBefore(ElfSection* section, bool dirty = true) override { // Adjust the address so that this section is adjacent to the one it's // being inserted before. This avoids creating holes which subsequently // might lead the PHDR-adjusting code to create unnecessary additional // PT_LOADs.
shdr.sh_addr =
(section->getAddr() - shdr.sh_size) & ~(shdr.sh_addralign - 1);
ElfSection::insertBefore(section, dirty);
}
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[ELF64_R_SYM(r->r_info)].value.getSection();
add_code_section(section);
}
}
// TODO: sort out which non-aarch64 relocation types should be using // `value` (even though in practice it's either 0 or the same as addend) class pc32_relocation { public:
Elf32_Addr operator()(unsignedint base_addr, Elf64_Off offset,
Elf64_Sxword addend, unsignedint addr,
Elf64_Word value) { return addr + addend - offset - base_addr;
}
};
class arm_plt32_relocation { public:
Elf32_Addr operator()(unsignedint base_addr, Elf64_Off offset,
Elf64_Sxword addend, unsignedint addr,
Elf64_Word value) { // 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()(unsignedint base_addr, Elf64_Off offset,
Elf64_Sxword addend, unsignedint addr,
Elf64_Word value) { /* 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); unsignedint word0 = addend & 0xffff, word1 = addend >> 16;
/* Encoding T4 of B.W is 10x1 ; Encoding T1 of BL is 11x1. */ unsignedint 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 " ");
/* 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 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;
}
template <int start, int end> class abs_lo12_nc_relocation { public:
Elf32_Addr operator()(unsignedint base_addr, Elf64_Off offset,
Elf64_Sxword addend, unsignedint addr,
Elf64_Word value) { // Fill the bits [end:start] of the immediate value in an ADD, LDR or STR // instruction, at bits [21:10]. // per ARM® Architecture Reference Manual ARMv8, for ARMv8-A architecture // profile C5.6.4, C5.6.83 or C5.6.178 and ELF for the ARM® 64-bit // Architecture (AArch64) 4.6.6, Table 4-9.
Elf64_Word mask = (1 << (end + 1)) - 1; return value | (((((addr + addend) & mask) >> start) & 0xfff) << 10);
}
};
class adr_prel_pg_hi21_relocation { public:
Elf32_Addr operator()(unsignedint base_addr, Elf64_Off offset,
Elf64_Sxword addend, unsignedint addr,
Elf64_Word value) { // Fill the bits [32:12] of the immediate value in a ADRP instruction, // at bits [23:5]+[30:29]. // per ARM® Architecture Reference Manual ARMv8, for ARMv8-A architecture // profile C5.6.10 and ELF for the ARM® 64-bit Architecture // (AArch64) 4.6.6, Table 4-9.
Elf64_Word imm = ((addr + addend) >> 12) - ((base_addr + offset) >> 12);
Elf64_Word immLo = (imm & 0x3) << 29;
Elf64_Word immHi = (imm & 0x1ffffc) << 3; return value & 0x9f00001f | immLo | immHi;
}
};
class call26_relocation { public:
Elf32_Addr operator()(unsignedint base_addr, Elf64_Off offset,
Elf64_Sxword addend, unsignedint addr,
Elf64_Word value) { // Fill the bits [27:2] of the immediate value in a BL instruction, // at bits [25:0]. // per ARM® Architecture Reference Manual ARMv8, for ARMv8-A architecture // profile C5.6.26 and ELF for the ARM® 64-bit Architecture // (AArch64) 4.6.6, Table 4-10. return value | (((addr + addend - offset - base_addr) & 0x0ffffffc) >> 2);
}
};
void set_relative_reloc(Elf_Rela* rel, Elf* elf, unsignedint 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) {
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 = (Elf64_Xword)-1LL;
phdr.p_memsz = (Elf64_Xword)-1LL;
ElfSegment* newSegment = new ElfSegment(&phdr);
elf->insertSegmentAfter(segment, newSegment); for (; it != segment->end(); ++it) {
newSegment->addSection(*it);
} for (it = newSegment->begin(); it != newSegment->end(); ++it) {
segment->removeSection(*it);
} break;
}
}
}
// Return the data size part of the encoding value staticunsignedchar encoding_data_size(unsignedchar encoding) { return encoding & 0x07;
}
// Advance `step` bytes in the buffer at `data` with size `size`, returning // the advanced buffer pointer and remaining size. // Returns true if step <= size. staticbool advance_buffer(char** data, size_t* size, size_t step) { if (step > *size) returnfalse;
*data += step;
*size -= step; returntrue;
}
// Advance in the given buffer, skipping the full length of the variable-length // encoded LEB128 type in CIE/FDE data. staticbool skip_LEB128(char** data, size_t* size) { if (!*size) returnfalse;
// Advance in the given buffer, skipping the full length of a pointer encoded // with the given encoding. staticbool skip_eh_frame_pointer(char** data, size_t* size, unsignedchar encoding) { switch (encoding_data_size(encoding)) { case DW_EH_PE_data2: return advance_buffer(data, size, 2); case DW_EH_PE_data4: return advance_buffer(data, size, 4); case DW_EH_PE_data8: return advance_buffer(data, size, 8); case DW_EH_PE_LEB128: return skip_LEB128(data, size);
} throw std::runtime_error("unreachable");
}
serializable<FixedSizeData<T>> pointer(*data, *size, elf->getClass(),
elf->getData());
mozilla::CheckedInt<T> value = pointer.value; if (origAddr < eh_frame->getAddr()) { unsignedint diff = eh_frame->getAddr() - origAddr;
value -= diff;
} else { unsignedint diff = origAddr - eh_frame->getAddr();
value += diff;
} if (!value.isValid()) throw std::runtime_error("Overflow while adjusting eh_frame");
pointer.value = value.value();
pointer.serialize(*data, *size, elf->getClass(), elf->getData()); return advance_buffer(data, size, sizeof(T));
}
// In the given eh_frame section, adjust the pointer with the given encoding, // pointed to by the given buffer (`data`, `size`), considering the eh_frame // section was originally at `origAddr`. Also advances in the buffer. staticbool adjust_eh_frame_pointer(char** data, size_t* size, unsignedchar encoding,
ElfSection* eh_frame, unsignedint origAddr,
Elf* elf) { if ((encoding & 0x70) != DW_EH_PE_pcrel) return skip_eh_frame_pointer(data, size, encoding);
if (encoding & DW_EH_PE_signed) { switch (encoding_data_size(encoding)) { case DW_EH_PE_data2: return adjust_eh_frame_sized_pointer<int16_t>(data, size, eh_frame,
origAddr, elf); case DW_EH_PE_data4: return adjust_eh_frame_sized_pointer<int32_t>(data, size, eh_frame,
origAddr, elf); case DW_EH_PE_data8: return adjust_eh_frame_sized_pointer<int64_t>(data, size, eh_frame,
origAddr, elf);
}
} else { switch (encoding_data_size(encoding)) { case DW_EH_PE_data2: return adjust_eh_frame_sized_pointer<uint16_t>(data, size, eh_frame,
origAddr, elf); case DW_EH_PE_data4: return adjust_eh_frame_sized_pointer<uint32_t>(data, size, eh_frame,
origAddr, elf); case DW_EH_PE_data8: return adjust_eh_frame_sized_pointer<uint64_t>(data, size, eh_frame,
origAddr, elf);
}
}
// The eh_frame section may contain "PC"-relative pointers. If we move the // section, those need to be adjusted. Other type of pointers are relative to // sections we don't touch. staticvoid adjust_eh_frame(ElfSection* eh_frame, unsignedint origAddr,
Elf* elf) { if (eh_frame->getAddr() == origAddr) // nothing to do; return;
if (id.value == 0) { // This is a Common Information Entry if (length < 2) goto malformed; // Reset LSDA and FDE encodings, and hasZ for subsequent FDEs.
LSDAencoding = DW_EH_PE_omit;
FDEencoding = DW_EH_PE_absptr;
hasZ = false; // CIE version. Should only be 1 or 3. char version = *cursor++;
length--; if (version != 1 && version != 3) { throw std::runtime_error("Unsupported eh_frame version");
} // NUL terminated string. constchar* augmentationString = cursor;
size_t l = strnlen(augmentationString, length - 1); if (l == length - 1) goto malformed; if (!advance_buffer(&cursor, &length, l + 1)) goto malformed; // Skip code alignment factor (LEB128) if (!skip_LEB128(&cursor, &length)) goto malformed; // Skip data alignment factor (LEB128) if (!skip_LEB128(&cursor, &length)) goto malformed; // Skip return address register (single byte in CIE version 1, LEB128 // in CIE version 3) if (version == 1) { if (!advance_buffer(&cursor, &length, 1)) goto malformed;
} else { if (!skip_LEB128(&cursor, &length)) goto malformed;
} // Past this, it's data driven by the contents of the augmentation string. for (size_t i = 0; i < l; i++) { if (!length) goto malformed; switch (augmentationString[i]) { case'z': if (!skip_LEB128(&cursor, &length)) goto malformed;
hasZ = true; break; case'L':
LSDAencoding = *cursor++;
length--; break; case'R':
FDEencoding = *cursor++;
length--; break; case'P': { unsignedchar encoding = (unsignedchar)*cursor++;
length--; if (!adjust_eh_frame_pointer(&cursor, &length, encoding, eh_frame,
origAddr, elf)) goto malformed;
} break; default: goto malformed;
}
}
} else { // This is a Frame Description Entry // Starting address if (!adjust_eh_frame_pointer(&cursor, &length, FDEencoding, eh_frame,
origAddr, elf)) goto malformed;
if (LSDAencoding != DW_EH_PE_omit) { // Skip number of bytes, same size as the starting address. if (!skip_eh_frame_pointer(&cursor, &length, FDEencoding)) goto malformed; if (hasZ) { if (!skip_LEB128(&cursor, &length)) goto malformed;
} // pointer to the LSDA. if (!adjust_eh_frame_pointer(&cursor, &length, LSDAencoding, eh_frame,
origAddr, elf)) goto malformed;
}
}
data += entryLength.value;
size -= entryLength.value;
} return;
// 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); unsignedint 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(relhack64_section);
Elf_Shdr relhackcode_section(relhackcode64_section); auto relhack_ptr = std::make_unique<ElfRelHack_Section>(relhack_section); auto relhack = relhack_ptr.get();
std::vector<Rel_Type> new_rels;
std::vector<Rel_Type> init_array_relocs;
size_t init_array_insert = 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 (!ELF64_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 (ELF64_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 ((ELF64_R_TYPE(i->r_info) == rel_type2) &&
(sym == &symtab->syms[ELF64_R_SYM(i->r_info)])) {
memset((char*)loc.getBuffer(), 0, entry_sz); continue;
}
}
} // Keep track of the relocations associated with the init_array section. if (init_array && i->r_offset >= init_array->getAddr() &&
i->r_offset < init_array->getAddr() + init_array->getSize()) {
init_array_relocs.push_back(*i);
init_array_insert = new_rels.size();
} elseif (!(loc.getSection()->getFlags() & SHF_WRITE) ||
(ELF64_R_TYPE(i->r_info) != rel_type)) { // 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);
} elseif (i->r_offset & 1) { // RELR packing doesn't support relocations at an odd address, but // there shouldn't be any.
new_rels.push_back(*i);
} else { // With Elf_Rel, the value pointed by the relocation offset is the addend. // With Elf_Rela, the addend is in the relocation entry, but the elfhacked // relocation info doesn't contain it. Elfhack relies on the value pointed // by the relocation offset to also contain the addend. Which is true with // BFD ld and gold, but not lld, which leaves that nulled out. So if that // value is nulled out, we update it to the addend.
Elf_Addr addr(loc.getBuffer(), entry_sz, elf->getClass(), elf->getData()); unsignedint addend = get_addend(&*i, elf); if (addr.value == 0) {
addr.value = addend;
addr.serialize(const_cast<char*>(loc.getBuffer()), entry_sz,
elf->getClass(), elf->getData());
} elseif (addr.value != addend) {
fprintf(stderr, "Relocation addend inconsistent with content. Skipping\n"); return -1;
}
relhack->push_back(i->r_offset);
}
}
if (init_array) { // Some linkers create a DT_INIT_ARRAY section that, for all purposes, // is empty: it only contains 0x0 or 0xffffffff pointers with no // relocations. In some other cases, there can be null pointers with no // relocations in the middle of the section. Example: crtend_so.o in the // Android NDK contains a sized .init_array with a null pointer and no // relocation, which ends up in all Android libraries, and in some cases it // ends up in the middle of the final .init_array section. If we have such a // reusable slot at the beginning of .init_array, we just use it. It we have // one in the middle of .init_array, we slide its content to move the "hole" // at the beginning and use it there (we need our injected code to run // before any other). Otherwise, replace the first entry and keep the // original pointer.
std::sort(init_array_relocs.begin(), init_array_relocs.end(),
[](Rel_Type& a, Rel_Type& b) { return a.r_offset < b.r_offset; });
size_t expected = init_array->getAddr(); const size_t zero = 0; const size_t all = SIZE_MAX; constchar* data = init_array->getData();
size_t length = Elf_Addr::size(elf->getClass());
size_t off = 0; for (; off < init_array_relocs.size(); off++) { auto& r = init_array_relocs[off]; if (r.r_offset >= expected + length &&
(memcmp(data + off * length, &zero, length) == 0 ||
memcmp(data + off * length, &all, length) == 0)) { // We found a hole, move the preceding entries. while (off) { auto& p = init_array_relocs[--off]; if (ELF64_R_TYPE(p.r_info) == rel_type) { unsignedint addend = get_addend(&p, elf);
p.r_offset += length;
set_relative_reloc(&p, elf, addend);
} else {
fprintf(stderr, "Unsupported relocation type in DT_INIT_ARRAY. Skipping\n"); return -1;
}
} break;
}
expected = r.r_offset + length;
}
if (off == 0) { // We either found a hole above, and can now use the first entry, // or the init_array section is effectively empty (see further above) // and we also can use the first entry. // Either way, code further below will take care of actually setting // the right r_info and r_added for the relocation.
Rel_Type rel;
rel.r_offset = init_array->getAddr();
init_array_relocs.insert(init_array_relocs.begin(), rel);
} else { // Use relocated value of DT_INIT_ARRAY's first entry for the // function to be called by the injected code. auto& rel = init_array_relocs[0]; unsignedint addend = get_addend(&rel, elf); if (ELF64_R_TYPE(rel.r_info) == rel_type) {
original_init = addend;
} elseif (ELF64_R_TYPE(rel.r_info) == rel_type2) {
ElfSymtab_Section* symtab = (ElfSymtab_Section*)section->getLink();
original_init =
symtab->syms[ELF64_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;
}
}
unsignedint mprotect_cb = 0; unsignedint sysconf_cb = 0; // If there is a relro segment, our injected code will run after the linker // sets the corresponding pages read-only. We need to make our code change // that to read-write before applying relocations, which means it needs to // call mprotect. To do that, we need to find a reference to the mprotect // symbol. In case the library already has one, we use that, but otherwise, we // add the symbol. Then the injected code needs to be able to call the // corresponding function, which means it needs access to a pointer to it. We // get such a pointer by making the linker apply a relocation for the symbol // at an address our code can read. The problem here is that there is not much // relocated space where we can put such a pointer, so we abuse the bss // section temporarily (it will be restored to a null value before any code // can actually use it) if (elf->getSegmentByType(PT_GNU_RELRO)) {
ElfSection* gnu_versym = dyn->getSectionForType(DT_VERSYM); auto ensure_symbol = [&symtab, &gnu_versym](constchar* symbol) {
Elf_SymValue* sym_value = symtab->lookup(symbol, STT(FUNC)); if (!sym_value) {
symtab->syms.emplace_back();
sym_value = &symtab->syms.back();
symtab->grow(symtab->syms.size() * symtab->getEntSize());
sym_value->name =
((ElfStrtab_Section*)symtab->getLink())->getStr(symbol);
sym_value->info = ELF64_ST_INFO(STB_GLOBAL, STT_FUNC);
sym_value->other = STV_DEFAULT; new (&sym_value->value) ElfLocation(nullptr, 0, ElfLocation::ABSOLUTE);
sym_value->size = 0;
sym_value->defined = false;
// The DT_VERSYM data (in the .gnu.version section) has the same number // of entries as the symbols table. Since we added one entry there, we // need to add one entry here. Zeroes in the extra data means no version // for that symbol, which is the simplest thing to do. if (gnu_versym) {
gnu_versym->grow(gnu_versym->getSize() + gnu_versym->getEntSize());
}
}
}; // ensure_symbol may trigger a symbol table vector resize, so only lookup // the symbols after we're done touching the symbol table.
ensure_symbol("mprotect");
ensure_symbol("sysconf");
Elf_SymValue* mprotect = symtab->lookup("mprotect", STT(FUNC));
Elf_SymValue* sysconf = symtab->lookup("sysconf", STT(FUNC));
// Add relocations for the mprotect and sysconf symbols. auto add_relocation_to = [&new_rels, &symtab, rel_type2](
Elf_SymValue* symbol, unsignedint location) {
new_rels.emplace_back();
Rel_Type& rel = new_rels.back();
memset(&rel, 0, sizeof(rel));
rel.r_info = ELF64_R_INFO(
std::distance(symtab->syms.begin(),
std::vector<Elf_SymValue>::iterator(symbol)),
rel_type2);
rel.r_offset = location; return location;
};
// Find the beginning of the bss section, and use an aligned location in // there for the relocation. for (ElfSection* s = elf->getSection(1); s != nullptr; s = s->getNext()) { if (s->getType() != SHT_NOBITS ||
(s->getFlags() & (SHF_TLS | SHF_WRITE)) != SHF_WRITE) { continue;
}
size_t ptr_size = Elf_Addr::size(elf->getClass());
size_t usable_start = (s->getAddr() + ptr_size - 1) & ~(ptr_size - 1);
size_t usable_end = (s->getAddr() + s->getSize()) & ~(ptr_size - 1); if (usable_end - usable_start >= 2 * ptr_size) {
mprotect_cb = add_relocation_to(mprotect, usable_start);
sysconf_cb = add_relocation_to(sysconf, usable_start + ptr_size); break;
}
}
auto relhackcode_ptr = std::make_unique<ElfRelHackCode_Section>(
relhackcode_section, *elf, *relhack, original_init, mprotect_cb,
sysconf_cb); auto relhackcode = relhackcode_ptr.get(); // Find the first executable section, and insert the relhack code before // that. The relhack data is inserted between .rel.dyn and .rel.plt.
ElfSection* first_executable = nullptr; for (ElfSection* s = elf->getSection(1); s != nullptr; s = s->getNext()) { if (s->getFlags() & SHF_EXECINSTR) {
first_executable = s; break;
}
}
// Once the pointers for relhack, relhackcode, and init are inserted, // their ownership is transferred to the Elf object, which will free // them when itself is freed. Hence the .release() calls here (and // the init.release() call later on). Please note that the raw // pointers will continue to be used after .release(), which is why // we are caching them (since .release() will end up setting the // smart pointer's internal raw pointer to nullptr).
// Don't try further if we can't gain from the relocation section size change. // We account for the fact we're going to split the PT_LOAD before the // injected code section, so the overhead of the page alignment for section // needs to be accounted for.
size_t align = first_executable->getSegmentByType(PT_LOAD)->getAlign();
size_t new_size = relhack->getSize() + section->getSize() +
relhackcode->getSize() +
(relhackcode->getAddr() & (align - 1)); if (!force && (new_size >= old_size || old_size - new_size < align)) {
fprintf(stderr, "No gain. Skipping\n"); return -1;
}
// .eh_frame/.eh_frame_hdr may be between the relocation sections and the // executable sections. When that happens, we may end up creating a separate // PT_LOAD for just both of them because they are not considered relocatable. // But they are, in fact, kind of relocatable, albeit with some manual work. // Which we'll do here.
ElfSegment* eh_frame_segment = elf->getSegmentByType(PT_GNU_EH_FRAME);
ElfSection* eh_frame_hdr =
eh_frame_segment ? eh_frame_segment->getFirstSection() : nullptr; // The .eh_frame section usually follows the eh_frame_hdr section.
ElfSection* eh_frame = eh_frame_hdr ? eh_frame_hdr->getNext() : nullptr;
ElfSection* first = eh_frame_hdr;
ElfSection* second = eh_frame; if (eh_frame && strcmp(eh_frame->getName(), ".eh_frame")) { // But sometimes it appears *before* the eh_frame_hdr section.
eh_frame = eh_frame_hdr->getPrevious();
first = eh_frame;
second = eh_frame_hdr;
} if (eh_frame_hdr && (!eh_frame || strcmp(eh_frame->getName(), ".eh_frame"))) { throw std::runtime_error( "Expected to find an .eh_frame section adjacent to .eh_frame_hdr");
} if (eh_frame && first->getAddr() > relhack->getAddr() &&
second->getAddr() < first_executable->getAddr()) { // The distance between both sections needs to be preserved because // eh_frame_hdr contains relative offsets to eh_frame. Well, they could be // relocated too, but it's not worth the effort for the few number of bytes // this would save.
Elf64_Off distance = second->getAddr() - first->getAddr();
Elf64_Addr origAddr = eh_frame->getAddr();
ElfSection* previous = first->getPrevious();
first->getShdr().sh_addr = (previous->getAddr() + previous->getSize() +
first->getAddrAlign() - 1) &
~(first->getAddrAlign() - 1);
second->getShdr().sh_addr =
(first->getAddr() + std::min(first->getSize(), distance) +
second->getAddrAlign() - 1) &
~(second->getAddrAlign() - 1); // Re-adjust to keep the original distance. // If the first section has a smaller alignment requirement than the second, // the second will be farther away, so we need to adjust the first. // If the second section has a smaller alignment requirement than the first, // it will already be at the right distance.
first->getShdr().sh_addr = second->getAddr() - distance;
assert(distance == second->getAddr() - first->getAddr());
first->markDirty();
adjust_eh_frame(eh_frame, origAddr, elf);
}
// Ensure Elf sections will be at their final location.
elf->normalize(); auto init =
std::make_unique<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_insert];
rel->r_info = ELF64_R_INFO(0, rel_type); // Set as a relative relocation
set_relative_reloc(rel, elf, init->getValue());
} else { if (dyn->setValueForType(DT_INIT, init.get())) {
init.release();
} else {
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));
// When both elfhack sections are in the same segment, try to merge // the segment that contains them both and the following segment. // When the elfhack sections are in separate segments, try to merge // those segments.
ElfSegment* first = data->getSegmentByType(PT_LOAD);
ElfSegment* second = text->getSegmentByType(PT_LOAD); if (first == second) {
second = elf.getSegmentByType(PT_LOAD, first);
}
// Only merge the segments when their flags match. if (second->getFlags() != first->getFlags()) {
fprintf(stderr, "Couldn't merge 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);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.