// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "courgette/disassembler_elf_32.h" #include #include #include #include #include #include "base/logging.h" #include "base/memory/scoped_vector.h" #include "courgette/assembly_program.h" #include "courgette/courgette.h" #include "courgette/encoded_program.h" namespace courgette { DisassemblerElf32::DisassemblerElf32(const void* start, size_t length) : Disassembler(start, length), header_(NULL), section_header_table_(NULL), section_header_table_size_(0), program_header_table_(NULL), program_header_table_size_(0), default_string_section_(NULL) { } bool DisassemblerElf32::ParseHeader() { if (length() < sizeof(Elf32_Ehdr)) return Bad("Too small"); header_ = (Elf32_Ehdr *)start(); // Have magic for elf header? if (header_->e_ident[0] != 0x7f || header_->e_ident[1] != 'E' || header_->e_ident[2] != 'L' || header_->e_ident[3] != 'F') return Bad("No Magic Number"); if (header_->e_type != ET_EXEC && header_->e_type != ET_DYN) return Bad("Not an executable file or shared library"); if (header_->e_machine != ElfEM()) return Bad("Not a supported architecture"); if (header_->e_version != 1) return Bad("Unknown file version"); if (header_->e_shentsize != sizeof(Elf32_Shdr)) return Bad("Unexpected section header size"); if (!IsArrayInBounds(header_->e_shoff, header_->e_shnum, sizeof(Elf32_Shdr))) return Bad("Out of bounds section header table"); section_header_table_ = (Elf32_Shdr *)OffsetToPointer(header_->e_shoff); section_header_table_size_ = header_->e_shnum; if (!IsArrayInBounds(header_->e_phoff, header_->e_phnum, sizeof(Elf32_Phdr))) return Bad("Out of bounds program header table"); program_header_table_ = (Elf32_Phdr *)OffsetToPointer(header_->e_phoff); program_header_table_size_ = header_->e_phnum; if (header_->e_shstrndx >= header_->e_shnum) return Bad("Out of bounds string section index"); default_string_section_ = (const char *)SectionBody((int)header_->e_shstrndx); if (!UpdateLength()) { return Bad("Out of bounds section or segment"); } return Good(); } bool DisassemblerElf32::Disassemble(AssemblyProgram* target) { if (!ok()) return false; // The Image Base is always 0 for ELF Executables target->set_image_base(0); if (!ParseAbs32Relocs()) return false; if (!ParseRel32RelocsFromSections()) return false; if (!ParseFile(target)) return false; target->DefaultAssignIndexes(); return true; } bool DisassemblerElf32::UpdateLength() { Elf32_Off result = 0; // Find the end of the last section for (int section_id = 0; section_id < SectionHeaderCount(); section_id++) { const Elf32_Shdr *section_header = SectionHeader(section_id); if (section_header->sh_type == SHT_NOBITS) continue; if (!IsArrayInBounds(section_header->sh_offset, section_header->sh_size, 1)) return false; Elf32_Off section_end = section_header->sh_offset + section_header->sh_size; result = std::max(result, section_end); } // Find the end of the last segment for (int i = 0; i < ProgramSegmentHeaderCount(); i++) { const Elf32_Phdr *segment_header = ProgramSegmentHeader(i); if (!IsArrayInBounds(segment_header->p_offset, segment_header->p_filesz, 1)) return false; Elf32_Off segment_end = segment_header->p_offset + segment_header->p_filesz; result = std::max(result, segment_end); } Elf32_Off section_table_end = header_->e_shoff + (header_->e_shnum * sizeof(Elf32_Shdr)); result = std::max(result, section_table_end); Elf32_Off segment_table_end = header_->e_phoff + (header_->e_phnum * sizeof(Elf32_Phdr)); result = std::max(result, segment_table_end); ReduceLength(result); return true; } CheckBool DisassemblerElf32::IsValidRVA(RVA rva) const { // It's valid if it's contained in any program segment for (int i = 0; i < ProgramSegmentHeaderCount(); i++) { const Elf32_Phdr *segment_header = ProgramSegmentHeader(i); if (segment_header->p_type != PT_LOAD) continue; Elf32_Addr begin = segment_header->p_vaddr; Elf32_Addr end = segment_header->p_vaddr + segment_header->p_memsz; if (rva >= begin && rva < end) return true; } return false; } // Returns RVA for an in memory address, or NULL. CheckBool DisassemblerElf32::RVAToFileOffset(Elf32_Addr addr, size_t* result) const { for (int i = 0; i < ProgramSegmentHeaderCount(); i++) { Elf32_Addr begin = ProgramSegmentMemoryBegin(i); Elf32_Addr end = begin + ProgramSegmentMemorySize(i); if (addr >= begin && addr < end) { Elf32_Addr offset = addr - begin; if (offset < ProgramSegmentFileSize(i)) { *result = ProgramSegmentFileOffset(i) + offset; return true; } } } return false; } RVA DisassemblerElf32::FileOffsetToRVA(size_t offset) const { // File offsets can be 64 bit values, but we are dealing with 32 // bit executables and so only need to support 32bit file sizes. uint32_t offset32 = (uint32_t)offset; for (int i = 0; i < SectionHeaderCount(); i++) { const Elf32_Shdr *section_header = SectionHeader(i); // These can appear to have a size in the file, but don't. if (section_header->sh_type == SHT_NOBITS) continue; Elf32_Off section_begin = section_header->sh_offset; Elf32_Off section_end = section_begin + section_header->sh_size; if (offset32 >= section_begin && offset32 < section_end) { return section_header->sh_addr + (offset32 - section_begin); } } return 0; } CheckBool DisassemblerElf32::RVAsToOffsets(std::vector* rvas, std::vector* offsets) { offsets->clear(); for (std::vector::iterator rva = rvas->begin(); rva != rvas->end(); rva++) { size_t offset; if (!RVAToFileOffset(*rva, &offset)) return false; offsets->push_back(offset); } return true; } CheckBool DisassemblerElf32::RVAsToOffsets(ScopedVector* rvas) { for (ScopedVector::iterator rva = rvas->begin(); rva != rvas->end(); rva++) { size_t offset; if (!RVAToFileOffset((*rva)->rva(), &offset)) return false; (*rva)->set_offset(offset); } return true; } CheckBool DisassemblerElf32::ParseFile(AssemblyProgram* program) { // Walk all the bytes in the file, whether or not in a section. uint32_t file_offset = 0; std::vector abs_offsets; if (!RVAsToOffsets(&abs32_locations_, &abs_offsets)) return false; if (!RVAsToOffsets(&rel32_locations_)) return false; std::vector::iterator current_abs_offset = abs_offsets.begin(); ScopedVector::iterator current_rel = rel32_locations_.begin(); std::vector::iterator end_abs_offset = abs_offsets.end(); ScopedVector::iterator end_rel = rel32_locations_.end(); for (int section_id = 0; section_id < SectionHeaderCount(); section_id++) { const Elf32_Shdr *section_header = SectionHeader(section_id); if (section_header->sh_type == SHT_NOBITS) continue; if (!ParseSimpleRegion(file_offset, section_header->sh_offset, program)) return false; file_offset = section_header->sh_offset; switch (section_header->sh_type) { case SHT_REL: if (!ParseRelocationSection(section_header, program)) return false; file_offset = section_header->sh_offset + section_header->sh_size; break; case SHT_PROGBITS: if (!ParseProgbitsSection(section_header, ¤t_abs_offset, end_abs_offset, ¤t_rel, end_rel, program)) return false; file_offset = section_header->sh_offset + section_header->sh_size; break; case SHT_INIT_ARRAY: // Fall through case SHT_FINI_ARRAY: while (current_abs_offset != end_abs_offset && *current_abs_offset >= section_header->sh_offset && *current_abs_offset < (section_header->sh_offset + section_header->sh_size)) { // Skip any abs_offsets appear in the unsupported INIT_ARRAY section VLOG(1) << "Skipping relocation entry for unsupported section: " << section_header->sh_type; current_abs_offset++; } break; default: if (current_abs_offset != end_abs_offset && *current_abs_offset >= section_header->sh_offset && *current_abs_offset < (section_header->sh_offset + section_header->sh_size)) VLOG(1) << "Relocation address in unrecognized ELF section: " << \ section_header->sh_type; break; } } // Rest of the file past the last section if (!ParseSimpleRegion(file_offset, length(), program)) return false; // Make certain we consume all of the relocations as expected return (current_abs_offset == end_abs_offset); } CheckBool DisassemblerElf32::ParseProgbitsSection( const Elf32_Shdr *section_header, std::vector::iterator* current_abs_offset, std::vector::iterator end_abs_offset, ScopedVector::iterator* current_rel, ScopedVector::iterator end_rel, AssemblyProgram* program) { // Walk all the bytes in the file, whether or not in a section. size_t file_offset = section_header->sh_offset; size_t section_end = section_header->sh_offset + section_header->sh_size; Elf32_Addr origin = section_header->sh_addr; size_t origin_offset = section_header->sh_offset; if (!program->EmitOriginInstruction(origin)) return false; while (file_offset < section_end) { if (*current_abs_offset != end_abs_offset && file_offset > **current_abs_offset) return false; while (*current_rel != end_rel && file_offset > (**current_rel)->get_offset()) { (*current_rel)++; } size_t next_relocation = section_end; if (*current_abs_offset != end_abs_offset && next_relocation > **current_abs_offset) next_relocation = **current_abs_offset; // Rel offsets are heuristically derived, and might (incorrectly) overlap // an Abs value, or the end of the section, so +3 to make sure there is // room for the full 4 byte value. if (*current_rel != end_rel && next_relocation > ((**current_rel)->get_offset() + 3)) next_relocation = (**current_rel)->get_offset(); if (next_relocation > file_offset) { if (!ParseSimpleRegion(file_offset, next_relocation, program)) return false; file_offset = next_relocation; continue; } if (*current_abs_offset != end_abs_offset && file_offset == **current_abs_offset) { const uint8_t* p = OffsetToPointer(file_offset); RVA target_rva = Read32LittleEndian(p); if (!program->EmitAbs32(program->FindOrMakeAbs32Label(target_rva))) return false; file_offset += sizeof(RVA); (*current_abs_offset)++; continue; } if (*current_rel != end_rel && file_offset == (**current_rel)->get_offset()) { uint32_t relative_target = (**current_rel)->relative_target(); // This cast is for 64 bit systems, and is only safe because we // are working on 32 bit executables. RVA target_rva = (RVA)(origin + (file_offset - origin_offset) + relative_target); if (! (**current_rel)->EmitInstruction(program, target_rva)) return false; file_offset += (**current_rel)->op_size(); (*current_rel)++; continue; } } // Rest of the section (if any) return ParseSimpleRegion(file_offset, section_end, program); } CheckBool DisassemblerElf32::ParseSimpleRegion( size_t start_file_offset, size_t end_file_offset, AssemblyProgram* program) { // Callers don't guarantee start < end if (start_file_offset >= end_file_offset) return true; const size_t len = end_file_offset - start_file_offset; if (!program->EmitBytesInstruction(OffsetToPointer(start_file_offset), len)) return false; return true; } CheckBool DisassemblerElf32::ParseAbs32Relocs() { abs32_locations_.clear(); // Loop through sections for relocation sections for (int section_id = 0; section_id < SectionHeaderCount(); section_id++) { const Elf32_Shdr *section_header = SectionHeader(section_id); if (section_header->sh_type == SHT_REL) { Elf32_Rel *relocs_table = (Elf32_Rel *)SectionBody(section_id); int relocs_table_count = section_header->sh_size / section_header->sh_entsize; // Elf32_Word relocation_section_id = section_header->sh_info; // Loop through relocation objects in the relocation section for (int rel_id = 0; rel_id < relocs_table_count; rel_id++) { RVA rva; // Quite a few of these conversions fail, and we simply skip // them, that's okay. if (RelToRVA(relocs_table[rel_id], &rva) && CheckSection(rva)) abs32_locations_.push_back(rva); } } } std::sort(abs32_locations_.begin(), abs32_locations_.end()); return true; } CheckBool DisassemblerElf32::CheckSection(RVA rva) { size_t offset; if (!RVAToFileOffset(rva, &offset)) { return false; } for (int section_id = 0; section_id < SectionHeaderCount(); section_id++) { const Elf32_Shdr *section_header = SectionHeader(section_id); if (offset >= section_header->sh_offset && offset < (section_header->sh_offset + section_header->sh_size)) { switch (section_header->sh_type) { case SHT_REL: // Fall-through case SHT_PROGBITS: return true; } } } return false; } CheckBool DisassemblerElf32::ParseRel32RelocsFromSections() { rel32_locations_.clear(); // Loop through sections for relocation sections for (int section_id = 0; section_id < SectionHeaderCount(); section_id++) { const Elf32_Shdr *section_header = SectionHeader(section_id); if (section_header->sh_type != SHT_PROGBITS) continue; if (!ParseRel32RelocsFromSection(section_header)) return false; } std::sort(rel32_locations_.begin(), rel32_locations_.end(), TypedRVA::IsLessThan); return true; } } // namespace courgette