diff options
author | Etienne Pierre-doray <etiennep@chromium.org> | 2018-08-10 17:44:37 +0000 |
---|---|---|
committer | Copybara-Service <copybara-worker@google.com> | 2021-07-25 20:34:00 -0700 |
commit | e57c4e6bb4c122686c16f40e0b9d50a2e683d42b (patch) | |
tree | 2cf002c3499a23f698d92eb0ae2b51bfe9bc1606 | |
parent | a88cad0485f1c73d63ba0a1bcfccc8a68bd300c6 (diff) | |
download | zucchini-e57c4e6bb4c122686c16f40e0b9d50a2e683d42b.tar.gz |
[Zucchini] Create elf disassembler.
Creates Disassembler that recognises and parses ELF format. For now, it only supports Intel architeture. Support for Arm will be added in follow-up CLs.
Change-Id: Ibdcf113b573f22844b6a1611c5ff6df46829b9b3
Reviewed-on: https://chromium-review.googlesource.com/1136841
Commit-Queue: Etienne Pierre-Doray <etiennep@chromium.org>
Reviewed-by: Greg Thompson <grt@chromium.org>
Reviewed-by: Samuel Huang <huangs@chromium.org>
Cr-Commit-Position: refs/heads/master@{#582233}
NOKEYCHECK=True
GitOrigin-RevId: 3c64e078fea9f23e44939c25ca02cf05b72b2c40
-rw-r--r-- | BUILD.gn | 5 | ||||
-rw-r--r-- | abs32_utils.cc | 10 | ||||
-rw-r--r-- | disassembler.cc | 6 | ||||
-rw-r--r-- | disassembler.h | 6 | ||||
-rw-r--r-- | disassembler_elf.cc | 424 | ||||
-rw-r--r-- | disassembler_elf.h | 189 | ||||
-rw-r--r-- | disassembler_elf_unittest.cc | 94 | ||||
-rw-r--r-- | element_detection.cc | 24 | ||||
-rw-r--r-- | type_elf.h | 26 |
9 files changed, 778 insertions, 6 deletions
@@ -11,12 +11,14 @@ buildflag_header("buildflags") { # Disable DEX on Windows Official Builds. _enable_dex = !(is_win && is_official_build) + _enable_elf = true _enable_win = true # Disable ZTF (Zucchini Text Format) on official builds it is for testing only. _enable_ztf = !is_official_build flags = [ "ENABLE_DEX=$_enable_dex", + "ENABLE_ELF=$_enable_elf", "ENABLE_WIN=$_enable_win", "ENABLE_ZTF=$_enable_ztf", ] @@ -42,6 +44,8 @@ static_library("zucchini_lib") { "disassembler.h", "disassembler_dex.cc", "disassembler_dex.h", + "disassembler_elf.cc", + "disassembler_elf.h", "disassembler_no_op.cc", "disassembler_no_op.h", "disassembler_win32.cc", @@ -158,6 +162,7 @@ test("zucchini_unittests") { "buffer_view_unittest.cc", "crc32_unittest.cc", "disassembler_dex_unittest.cc", + "disassembler_elf_unittest.cc", "disassembler_ztf_unittest.cc", "element_detection_unittest.cc", "encoded_view_unittest.cc", diff --git a/abs32_utils.cc b/abs32_utils.cc index ab4372c..b3fd9f4 100644 --- a/abs32_utils.cc +++ b/abs32_utils.cc @@ -136,10 +136,12 @@ base::Optional<Reference> Abs32ReaderWin32::GetNext() { for (auto unit = abs32_rva_extractor_.GetNext(); unit.has_value(); unit = abs32_rva_extractor_.GetNext()) { offset_t location = unit->location; - offset_t target = target_rva_to_offset_.Convert(unit->target_rva); - if (target == kInvalidOffset) - continue; - return Reference{location, target}; + // |target| will not be dereferenced, so we don't worry about it + // exceeding |image_.size()| (in fact, there are valid cases where it + // does). + offset_t unsafe_target = target_rva_to_offset_.Convert(unit->target_rva); + if (unsafe_target < kOffsetBound) + return Reference{location, unsafe_target}; } return base::nullopt; } diff --git a/disassembler.cc b/disassembler.cc index 012e5ef..b6a2748 100644 --- a/disassembler.cc +++ b/disassembler.cc @@ -8,6 +8,12 @@ namespace zucchini { +/******** EmptyReferenceReader ********/ + +base::Optional<Reference> EmptyReferenceReader::GetNext() { + return base::nullopt; +} + /******** ReferenceGroup ********/ std::unique_ptr<ReferenceReader> ReferenceGroup::GetReader( diff --git a/disassembler.h b/disassembler.h index 4f528ed..ab1dafb 100644 --- a/disassembler.h +++ b/disassembler.h @@ -17,6 +17,12 @@ namespace zucchini { +// A vacuous ReferenceReader that produces no references. +class EmptyReferenceReader : public ReferenceReader { + public: + base::Optional<Reference> GetNext() override; +}; + // Disassembler needs to be declared before ReferenceGroup because the latter // contains member pointers based on the former, and we use a compiler flag, // -fcomplete-member-pointers, which enforces that member pointer base types are diff --git a/disassembler_elf.cc b/disassembler_elf.cc new file mode 100644 index 0000000..75690e9 --- /dev/null +++ b/disassembler_elf.cc @@ -0,0 +1,424 @@ +// Copyright 2018 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 "components/zucchini/disassembler_elf.h" + +#include <stddef.h> + +#include <algorithm> +#include <utility> + +#include "base/logging.h" +#include "base/numerics/safe_conversions.h" +#include "components/zucchini/abs32_utils.h" +#include "components/zucchini/algorithm.h" +#include "components/zucchini/buffer_source.h" + +namespace zucchini { + +namespace { + +// Determines whether |section| is a reloc section. +template <class Traits> +bool IsRelocSection(const typename Traits::Elf_Shdr& section) { + if (section.sh_size == 0) + return false; + if (section.sh_type == elf::SHT_REL) { + // Also validate |section.sh_entsize|, which gets used later. + return section.sh_entsize == sizeof(typename Traits::Elf_Rel); + } + if (section.sh_type == elf::SHT_RELA) + return section.sh_entsize == sizeof(typename Traits::Elf_Rela); + return false; +} + +// Determines whether |section| is a section with executable code. +template <class Traits> +bool IsExecSection(const typename Traits::Elf_Shdr& section) { + return (section.sh_flags & elf::SHF_EXECINSTR) != 0; +} + +} // namespace + +/******** ELF32Traits ********/ + +// static +constexpr Bitness Elf32Traits::kBitness; +constexpr elf::FileClass Elf32Traits::kIdentificationClass; + +/******** ELF32IntelTraits ********/ + +// static +constexpr ExecutableType Elf32IntelTraits::kExeType; +const char Elf32IntelTraits::kExeTypeString[] = "ELF x86"; +constexpr elf::MachineArchitecture Elf32IntelTraits::kMachineValue; +constexpr uint32_t Elf32IntelTraits::kRelType; + +/******** ELF64Traits ********/ + +// static +constexpr Bitness Elf64Traits::kBitness; +constexpr elf::FileClass Elf64Traits::kIdentificationClass; + +/******** ELF64IntelTraits ********/ + +// static +constexpr ExecutableType Elf64IntelTraits::kExeType; +const char Elf64IntelTraits::kExeTypeString[] = "ELF x64"; +constexpr elf::MachineArchitecture Elf64IntelTraits::kMachineValue; +constexpr uint32_t Elf64IntelTraits::kRelType; + +/******** DisassemblerElf ********/ + +// static. +template <class Traits> +bool DisassemblerElf<Traits>::QuickDetect(ConstBufferView image) { + BufferSource source(image); + + // Do not consume the bytes for the magic value, as they are part of the + // header. + if (!source.CheckNextBytes({0x7F, 'E', 'L', 'F'})) + return false; + + auto* header = source.GetPointer<typename Traits::Elf_Ehdr>(); + if (!header) + return false; + + if (header->e_ident[elf::EI_CLASS] != Traits::kIdentificationClass) + return false; + + if (header->e_ident[elf::EI_DATA] != 1) // Only ELFDATA2LSB is supported. + return false; + + if (header->e_type != elf::ET_EXEC && header->e_type != elf::ET_DYN) + return false; + + if (header->e_version != 1 || header->e_ident[elf::EI_VERSION] != 1) + return false; + + if (header->e_machine != supported_architecture()) + return false; + + if (header->e_shentsize != sizeof(typename Traits::Elf_Shdr)) + return false; + + return true; +} + +template <class Traits> +DisassemblerElf<Traits>::~DisassemblerElf() = default; + +template <class Traits> +ExecutableType DisassemblerElf<Traits>::GetExeType() const { + return Traits::kExeType; +} + +template <class Traits> +std::string DisassemblerElf<Traits>::GetExeTypeString() const { + return Traits::kExeTypeString; +} + +// |num_equivalence_iterations_| = 2 for reloc -> abs32. +template <class Traits> +DisassemblerElf<Traits>::DisassemblerElf() : Disassembler(2) {} + +template <class Traits> +bool DisassemblerElf<Traits>::Parse(ConstBufferView image) { + image_ = image; + if (!ParseHeader()) + return false; + ParseSections(); + return true; +} + +template <class Traits> +std::unique_ptr<ReferenceReader> DisassemblerElf<Traits>::MakeReadRelocs( + offset_t lo, + offset_t hi) { + DCHECK_LE(lo, hi); + DCHECK_LE(hi, image_.size()); + + if (reloc_section_dims_.empty()) + return std::make_unique<EmptyReferenceReader>(); + + return std::make_unique<RelocReaderElf>( + image_, Traits::kBitness, reloc_section_dims_, + supported_relocation_type(), lo, hi, translator_); +} + +template <class Traits> +std::unique_ptr<ReferenceWriter> DisassemblerElf<Traits>::MakeWriteRelocs( + MutableBufferView image) { + return std::make_unique<RelocWriterElf>(image, Traits::kBitness, translator_); +} + +template <class Traits> +std::unique_ptr<ReferenceReader> DisassemblerElf<Traits>::MakeReadAbs32( + offset_t lo, + offset_t hi) { + Abs32RvaExtractorWin32 abs_rva_extractor(image_, {Traits::kBitness, 0}, + abs32_locations_, lo, hi); + return std::make_unique<Abs32ReaderWin32>(std::move(abs_rva_extractor), + translator_); +} + +template <class Traits> +std::unique_ptr<ReferenceWriter> DisassemblerElf<Traits>::MakeWriteAbs32( + MutableBufferView image) { + return std::make_unique<Abs32WriterWin32>( + image, AbsoluteAddress(Traits::kBitness, 0), translator_); +} + +template <class Traits> +bool DisassemblerElf<Traits>::ParseHeader() { + BufferSource source(image_); + + // Ensures |header_| is valid later on. + if (!QuickDetect(image_)) + return false; + + header_ = source.GetPointer<typename Traits::Elf_Ehdr>(); + + sections_count_ = header_->e_shnum; + source = std::move(BufferSource(image_).Skip(header_->e_shoff)); + sections_ = source.GetArray<typename Traits::Elf_Shdr>(sections_count_); + if (!sections_) + return false; + offset_t section_table_end = + base::checked_cast<offset_t>(source.begin() - image_.begin()); + + segments_count_ = header_->e_phnum; + source = std::move(BufferSource(image_).Skip(header_->e_phoff)); + segments_ = source.GetArray<typename Traits::Elf_Phdr>(segments_count_); + if (!segments_) + return false; + offset_t segment_table_end = + base::checked_cast<offset_t>(source.begin() - image_.begin()); + + // Check string section -- even though we've stopped using them. + elf::Elf32_Half string_section_id = header_->e_shstrndx; + if (string_section_id >= sections_count_) + return false; + size_t section_names_size = sections_[string_section_id].sh_size; + if (section_names_size > 0) { + // If nonempty, then last byte of string section must be null. + const char* section_names = nullptr; + source = std::move( + BufferSource(image_).Skip(sections_[string_section_id].sh_offset)); + section_names = source.GetArray<char>(section_names_size); + if (!section_names || section_names[section_names_size - 1] != '\0') + return false; + } + + // Establish bound on encountered offsets. + offset_t offset_bound = std::max(section_table_end, segment_table_end); + + // Visit each section, validate, and add address translation data to |units|. + std::vector<AddressTranslator::Unit> units; + units.reserve(sections_count_); + + for (int i = 0; i < sections_count_; ++i) { + const typename Traits::Elf_Shdr* section = §ions_[i]; + + // Skip empty sections. These don't affect |offset_bound|, and don't + // contribute to RVA-offset mapping. + if (section->sh_size == 0) + continue; + + // Be lax with RVAs: Assume they fit in int32_t, even for 64-bit. If + // assumption fails, simply skip the section with warning. + if (!RangeIsBounded(section->sh_addr, section->sh_size, kRvaBound) || + !RangeIsBounded(section->sh_offset, section->sh_size, kOffsetBound)) { + LOG(WARNING) << "Section " << i << " does not fit in int32_t."; + continue; + } + + // Extract dimensions to 32-bit integers to facilitate conversion. Range of + // values was ensured above when checking that the section is bounded. + uint32_t sh_size = base::checked_cast<uint32_t>(section->sh_size); + offset_t sh_offset = base::checked_cast<offset_t>(section->sh_offset); + rva_t sh_addr = base::checked_cast<rva_t>(section->sh_addr); + + // Update |offset_bound|. + if (section->sh_type != elf::SHT_NOBITS) { + // Be strict with offsets: Any size overflow invalidates the file. + if (!image_.covers({sh_offset, sh_size})) + return false; + + offset_t section_end = sh_offset + sh_size; + offset_bound = std::max(offset_bound, section_end); + } + + // Compute mappings to translate between RVA and offset. As a heuristic, + // sections with RVA == 0 (i.e., |sh_addr == 0|) are ignored because these + // tend to be duplicates (which cause problems during lookup), and tend to + // be uninteresting. + if (section->sh_addr > 0) { + // Add |section| data for offset-RVA translation. + units.push_back({sh_offset, sh_size, sh_addr, sh_size}); + } + } + + // Initialize |translator_| for offset-RVA translations. Any inconsistency + // (e.g., 2 offsets correspond to the same RVA) would invalidate the ELF file. + if (translator_.Initialize(std::move(units)) != AddressTranslator::kSuccess) + return false; + + // Visits |segments_| to get better estimate on |offset_bound|. + for (const typename Traits::Elf_Phdr* segment = segments_; + segment != segments_ + segments_count_; ++segment) { + if (!RangeIsBounded(segment->p_offset, segment->p_filesz, kOffsetBound)) + return false; + offset_t segment_end = segment->p_offset + segment->p_filesz; + offset_bound = std::max(offset_bound, segment_end); + } + + if (offset_bound > image_.size()) + return false; + image_.shrink(offset_bound); + + return true; +} + +template <class Traits> +void DisassemblerElf<Traits>::ExtractInterestingSectionHeaders() { + DCHECK(reloc_section_dims_.empty()); + DCHECK(exec_headers_.empty()); + for (elf::Elf32_Half i = 0; i < sections_count_; ++i) { + const typename Traits::Elf_Shdr* section = sections_ + i; + if (IsRelocSection<Traits>(*section)) + reloc_section_dims_.emplace_back(*section); + else if (IsExecSection<Traits>(*section)) + exec_headers_.push_back(section); + } + auto comp = [](const typename Traits::Elf_Shdr* a, + const typename Traits::Elf_Shdr* b) { + return a->sh_offset < b->sh_offset; + }; + std::sort(reloc_section_dims_.begin(), reloc_section_dims_.end()); + std::sort(exec_headers_.begin(), exec_headers_.end(), comp); +} + +template <class Traits> +void DisassemblerElf<Traits>::GetAbs32FromRelocSections() { + constexpr int kAbs32Width = 4; + DCHECK(abs32_locations_.empty()); + auto relocs = MakeReadRelocs(0, offset_t(size())); + for (auto ref = relocs->GetNext(); ref; ref = relocs->GetNext()) { + // Reject null targets and targets outside |image_|. Note that here we + // assume abs32 targets are never "fake offsets". + if (ref->target > 0 && image_.covers({ref->target, kAbs32Width})) + abs32_locations_.push_back(ref->target); + } + abs32_locations_.shrink_to_fit(); + std::sort(abs32_locations_.begin(), abs32_locations_.end()); + + // Abs32 reference bodies must not overlap. If found, simply remove them. + size_t num_removed = + RemoveOverlappingAbs32Locations(Traits::kBitness, &abs32_locations_); + if (num_removed) { + LOG(WARNING) << "Warning: Found and removed " << num_removed + << " abs32 locations with overlapping bodies."; + } +} + +template <class Traits> +void DisassemblerElf<Traits>::GetRel32FromCodeSections() { + for (const typename Traits::Elf_Shdr* section : exec_headers_) + ParseExecSection(*section); + PostProcessRel32(); +} + +template <class Traits> +void DisassemblerElf<Traits>::ParseSections() { + ExtractInterestingSectionHeaders(); + GetAbs32FromRelocSections(); + GetRel32FromCodeSections(); +} + +/******** DisassemblerElfIntel ********/ + +template <class Traits> +DisassemblerElfIntel<Traits>::DisassemblerElfIntel() = default; + +template <class Traits> +DisassemblerElfIntel<Traits>::~DisassemblerElfIntel() = default; + +template <class Traits> +std::vector<ReferenceGroup> DisassemblerElfIntel<Traits>::MakeReferenceGroups() + const { + return {{ReferenceTypeTraits{4, TypeTag(kReloc), PoolTag(kReloc)}, + &DisassemblerElfIntel<Traits>::MakeReadRelocs, + &DisassemblerElfIntel<Traits>::MakeWriteRelocs}, + {ReferenceTypeTraits{4, TypeTag(kAbs32), PoolTag(kAbs32)}, + &DisassemblerElfIntel<Traits>::MakeReadAbs32, + &DisassemblerElfIntel<Traits>::MakeWriteAbs32}, + {ReferenceTypeTraits{4, TypeTag(kRel32), PoolTag(kRel32)}, + &DisassemblerElfIntel<Traits>::MakeReadRel32, + &DisassemblerElfIntel<Traits>::MakeWriteRel32}}; +} + +template <class Traits> +void DisassemblerElfIntel<Traits>::ParseExecSection( + const typename Traits::Elf_Shdr& section) { + ConstBufferView& image_ = this->image_; + auto& abs32_locations_ = this->abs32_locations_; + + std::ptrdiff_t from_offset_to_rva = section.sh_addr - section.sh_offset; + rva_t start_rva = section.sh_addr; + rva_t end_rva = start_rva + section.sh_size; + + AddressTranslator::RvaToOffsetCache target_rva_checker(this->translator_); + + ConstBufferView region(image_.begin() + section.sh_offset, section.sh_size); + Abs32GapFinder gap_finder(image_, region, abs32_locations_, 4); + std::unique_ptr<Rel32FinderIntel> finder = + std::make_unique<typename Traits::Rel32FinderUse>(image_); + for (auto gap = gap_finder.GetNext(); gap.has_value(); + gap = gap_finder.GetNext()) { + finder->Reset(gap.value()); + for (auto rel32 = finder->GetNext(); rel32.has_value(); + rel32 = finder->GetNext()) { + offset_t rel32_offset = offset_t(rel32->location - image_.begin()); + rva_t rel32_rva = rva_t(rel32_offset + from_offset_to_rva); + rva_t target_rva = rel32_rva + 4 + image_.read<uint32_t>(rel32_offset); + if (target_rva_checker.IsValid(target_rva) && + (rel32->can_point_outside_section || + (start_rva <= target_rva && target_rva < end_rva))) { + finder->Accept(); + rel32_locations_.push_back(rel32_offset); + } + } + } +} + +template <class Traits> +void DisassemblerElfIntel<Traits>::PostProcessRel32() { + rel32_locations_.shrink_to_fit(); + std::sort(rel32_locations_.begin(), rel32_locations_.end()); +} + +template <class Traits> +std::unique_ptr<ReferenceReader> DisassemblerElfIntel<Traits>::MakeReadRel32( + offset_t lo, + offset_t hi) { + return std::make_unique<Rel32ReaderX86>(this->image_, lo, hi, + &rel32_locations_, this->translator_); +} + +template <class Traits> +std::unique_ptr<ReferenceWriter> DisassemblerElfIntel<Traits>::MakeWriteRel32( + MutableBufferView image) { + return std::make_unique<Rel32WriterX86>(image, this->translator_); +} + +// Explicit instantiation for supported classes. +template class DisassemblerElfIntel<Elf32IntelTraits>; +template class DisassemblerElfIntel<Elf64IntelTraits>; +template bool DisassemblerElf<Elf32IntelTraits>::QuickDetect( + ConstBufferView image); +template bool DisassemblerElf<Elf64IntelTraits>::QuickDetect( + ConstBufferView image); + +} // namespace zucchini diff --git a/disassembler_elf.h b/disassembler_elf.h new file mode 100644 index 0000000..a5b0a72 --- /dev/null +++ b/disassembler_elf.h @@ -0,0 +1,189 @@ +// Copyright 2018 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. + +#ifndef COMPONENTS_ZUCCHINI_DISASSEMBLER_ELF_H_ +#define COMPONENTS_ZUCCHINI_DISASSEMBLER_ELF_H_ + +#include <stdint.h> + +#include <memory> +#include <string> +#include <vector> + +#include "base/macros.h" +#include "components/zucchini/address_translator.h" +#include "components/zucchini/buffer_view.h" +#include "components/zucchini/disassembler.h" +#include "components/zucchini/image_utils.h" +#include "components/zucchini/rel32_finder.h" +#include "components/zucchini/rel32_utils.h" +#include "components/zucchini/reloc_elf.h" +#include "components/zucchini/type_elf.h" + +namespace zucchini { + +struct Elf32Traits { + static constexpr Bitness kBitness = kBit32; + static constexpr elf::FileClass kIdentificationClass = elf::ELFCLASS32; + using Elf_Shdr = elf::Elf32_Shdr; + using Elf_Phdr = elf::Elf32_Phdr; + using Elf_Ehdr = elf::Elf32_Ehdr; + using Elf_Rel = elf::Elf32_Rel; + using Elf_Rela = elf::Elf32_Rela; +}; + +// Architecture-specific definitions. +struct Elf32IntelTraits : public Elf32Traits { + static constexpr ExecutableType kExeType = kExeTypeElfX86; + static const char kExeTypeString[]; + static constexpr elf::MachineArchitecture kMachineValue = elf::EM_386; + static constexpr uint32_t kRelType = elf::R_386_RELATIVE; + using Rel32FinderUse = Rel32FinderX86; +}; + +struct Elf64Traits { + static constexpr Bitness kBitness = kBit64; + static constexpr elf::FileClass kIdentificationClass = elf::ELFCLASS64; + using Elf_Shdr = elf::Elf64_Shdr; + using Elf_Phdr = elf::Elf64_Phdr; + using Elf_Ehdr = elf::Elf64_Ehdr; + using Elf_Rel = elf::Elf64_Rel; + using Elf_Rela = elf::Elf64_Rela; +}; + +// Architecture-specific definitions. +struct Elf64IntelTraits : public Elf64Traits { + static constexpr ExecutableType kExeType = kExeTypeElfX64; + static const char kExeTypeString[]; + static constexpr elf::MachineArchitecture kMachineValue = elf::EM_X86_64; + static constexpr uint32_t kRelType = elf::R_X86_64_RELATIVE; + using Rel32FinderUse = Rel32FinderX64; +}; + +// Disassembler for ELF. +template <class Traits> +class DisassemblerElf : public Disassembler { + public: + using HeaderVector = std::vector<const typename Traits::Elf_Shdr*>; + + // Applies quick checks to determine whether |image| *may* point to the start + // of an executable. Returns true iff the check passes. + static bool QuickDetect(ConstBufferView image); + + ~DisassemblerElf() override; + + // Disassembler: + ExecutableType GetExeType() const override; + std::string GetExeTypeString() const override; + std::vector<ReferenceGroup> MakeReferenceGroups() const override = 0; + + // Find/Receive functions that are common among different architectures. + std::unique_ptr<ReferenceReader> MakeReadRelocs(offset_t lo, offset_t hi); + std::unique_ptr<ReferenceWriter> MakeWriteRelocs(MutableBufferView image); + std::unique_ptr<ReferenceReader> MakeReadAbs32(offset_t lo, offset_t hi); + std::unique_ptr<ReferenceWriter> MakeWriteAbs32(MutableBufferView image); + + const AddressTranslator& translator() const { return translator_; } + + protected: + friend Disassembler; + + DisassemblerElf(); + + bool Parse(ConstBufferView image) override; + + // Returns the supported Elf_Ehdr::e_machine enum. + static constexpr elf::MachineArchitecture supported_architecture() { + return Traits::kMachineValue; + } + + // Returns the type to look for in the reloc section. + static constexpr uint32_t supported_relocation_type() { + return Traits::kRelType; + } + + // Performs architecture-specific parsing of an executable section, to extract + // rel32 references. + virtual void ParseExecSection(const typename Traits::Elf_Shdr& section) = 0; + + // Processes rel32 data after they are extracted from executable sections. + virtual void PostProcessRel32() = 0; + + // The parsing routines below return true on success, and false on failure. + + // Parses ELF header and section headers, and performs basic validation. + bool ParseHeader(); + + // Extracts and stores section headers that we need. + void ExtractInterestingSectionHeaders(); + + // Parsing functions that extract references from various sections. + void GetAbs32FromRelocSections(); + void GetRel32FromCodeSections(); + void ParseSections(); + + // Main ELF header. + const typename Traits::Elf_Ehdr* header_ = nullptr; + + // Section header table, ordered by section id. + elf::Elf32_Half sections_count_ = 0; + const typename Traits::Elf_Shdr* sections_ = nullptr; + + // Program header table. + elf::Elf32_Half segments_count_ = 0; + const typename Traits::Elf_Phdr* segments_ = nullptr; + + // Translator between offsets and RVAs. + AddressTranslator translator_; + + // Identity translator for abs32 translation. + AddressTranslator identity_translator_; + + // Extracted relocation section dimensions data, sorted by file offsets. + std::vector<SectionDimensionsElf> reloc_section_dims_; + + // Headers of executable sections, sorted by file offsets of the data each + // header points to. + std::vector<const typename Traits::Elf_Shdr*> exec_headers_; + + // Sorted file offsets of abs32 locations. + std::vector<offset_t> abs32_locations_; + + private: + DISALLOW_COPY_AND_ASSIGN(DisassemblerElf); +}; + +// Disassembler for ELF with Intel architectures. +template <class Traits> +class DisassemblerElfIntel : public DisassemblerElf<Traits> { + public: + enum ReferenceType : uint8_t { kReloc, kAbs32, kRel32, kTypeCount }; + + DisassemblerElfIntel(); + ~DisassemblerElfIntel() override; + + // Disassembler: + std::vector<ReferenceGroup> MakeReferenceGroups() const override; + + // DisassemblerElf: + void ParseExecSection(const typename Traits::Elf_Shdr& section) override; + void PostProcessRel32() override; + + // Specialized Find/Receive functions. + std::unique_ptr<ReferenceReader> MakeReadRel32(offset_t lo, offset_t hi); + std::unique_ptr<ReferenceWriter> MakeWriteRel32(MutableBufferView image); + + private: + // Sorted file offsets of rel32 locations. + std::vector<offset_t> rel32_locations_; + + DISALLOW_COPY_AND_ASSIGN(DisassemblerElfIntel); +}; + +using DisassemblerElfX86 = DisassemblerElfIntel<Elf32IntelTraits>; +using DisassemblerElfX64 = DisassemblerElfIntel<Elf64IntelTraits>; + +} // namespace zucchini + +#endif // COMPONENTS_ZUCCHINI_DISASSEMBLER_ELF_H_ diff --git a/disassembler_elf_unittest.cc b/disassembler_elf_unittest.cc new file mode 100644 index 0000000..2db8635 --- /dev/null +++ b/disassembler_elf_unittest.cc @@ -0,0 +1,94 @@ +// Copyright 2018 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 "components/zucchini/disassembler_elf.h" + +#include <stddef.h> +#include <stdint.h> + +#include <algorithm> +#include <random> +#include <vector> + +#include "components/zucchini/test_utils.h" +#include "components/zucchini/type_elf.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace zucchini { + +TEST(DisassemblerElfTest, QuickDetect) { + std::vector<uint8_t> image_data; + ConstBufferView image; + + // Empty. + EXPECT_FALSE(DisassemblerElfX86::QuickDetect(image)); + EXPECT_FALSE(DisassemblerElfX64::QuickDetect(image)); + + // Unrelated. + image_data = ParseHexString("DE AD"); + image = {image_data.data(), image_data.size()}; + EXPECT_FALSE(DisassemblerElfX86::QuickDetect(image)); + EXPECT_FALSE(DisassemblerElfX64::QuickDetect(image)); + + // Only Magic. + image_data = ParseHexString("7F 45 4C 46"); + image = {image_data.data(), image_data.size()}; + EXPECT_FALSE(DisassemblerElfX86::QuickDetect(image)); + EXPECT_FALSE(DisassemblerElfX64::QuickDetect(image)); + + // Only identification. + image_data = + ParseHexString("7F 45 4C 46 01 01 01 00 00 00 00 00 00 00 00 00"); + image = {image_data.data(), image_data.size()}; + EXPECT_FALSE(DisassemblerElfX86::QuickDetect(image)); + EXPECT_FALSE(DisassemblerElfX64::QuickDetect(image)); + + // Large enough, filled with zeros. + image_data.assign(sizeof(elf::Elf32_Ehdr), 0); + image = {image_data.data(), image_data.size()}; + EXPECT_FALSE(DisassemblerElfX86::QuickDetect(image)); + EXPECT_FALSE(DisassemblerElfX64::QuickDetect(image)); + + // Random. + std::random_device rd; + std::mt19937 gen{rd()}; + std::generate(image_data.begin(), image_data.end(), gen); + image = {image_data.data(), image_data.size()}; + EXPECT_FALSE(DisassemblerElfX86::QuickDetect(image)); + EXPECT_FALSE(DisassemblerElfX64::QuickDetect(image)); + + // Typical x86 elf header. + { + elf::Elf32_Ehdr header = {}; + auto e_ident = + ParseHexString("7F 45 4C 46 01 01 01 00 00 00 00 00 00 00 00 00"); + std::copy(e_ident.begin(), e_ident.end(), header.e_ident); + header.e_type = elf::ET_EXEC; + header.e_machine = elf::EM_386; + header.e_version = 1; + header.e_shentsize = sizeof(elf::Elf32_Shdr); + ConstBufferView image(reinterpret_cast<const uint8_t*>(&header), + sizeof(header)); + EXPECT_TRUE(DisassemblerElfX86::QuickDetect(image)); + EXPECT_FALSE(DisassemblerElfX64::QuickDetect(image)); + } + + // Typical x64 elf header. + { + elf::Elf64_Ehdr header = {}; + auto e_ident = + ParseHexString("7F 45 4C 46 02 01 01 00 00 00 00 00 00 00 00 00"); + std::copy(e_ident.begin(), e_ident.end(), header.e_ident); + header.e_type = elf::ET_EXEC; + header.e_machine = elf::EM_X86_64; + header.e_version = 1; + header.e_shentsize = sizeof(elf::Elf64_Shdr); + ConstBufferView image(reinterpret_cast<const uint8_t*>(&header), + sizeof(header)); + EXPECT_FALSE(DisassemblerElfX86::QuickDetect(image)); + EXPECT_TRUE(DisassemblerElfX64::QuickDetect(image)); + } +} + +} // namespace zucchini diff --git a/element_detection.cc b/element_detection.cc index 6b31f61..fa5adf4 100644 --- a/element_detection.cc +++ b/element_detection.cc @@ -19,6 +19,10 @@ #include "components/zucchini/disassembler_win32.h" #endif // BUILDFLAG(ENABLE_WIN) +#if BUILDFLAG(ENABLE_ELF) +#include "components/zucchini/disassembler_elf.h" +#endif // BUILDFLAG(ENABLE_ELF) + #if BUILDFLAG(ENABLE_ZTF) #include "components/zucchini/disassembler_ztf.h" #endif // BUILDFLAG(ENABLE_ZTF) @@ -50,6 +54,20 @@ std::unique_ptr<Disassembler> MakeDisassemblerWithoutFallback( } #endif // BUILDFLAG(ENABLE_WIN) +#if BUILDFLAG(ENABLE_ELF) + if (DisassemblerElfX86::QuickDetect(image)) { + auto disasm = Disassembler::Make<DisassemblerElfX86>(image); + if (disasm && disasm->size() >= kMinProgramSize) + return disasm; + } + + if (DisassemblerElfX64::QuickDetect(image)) { + auto disasm = Disassembler::Make<DisassemblerElfX64>(image); + if (disasm && disasm->size() >= kMinProgramSize) + return disasm; + } +#endif // BUILDFLAG(ENABLE_ELF) + #if BUILDFLAG(ENABLE_DEX) if (DisassemblerDex::QuickDetect(image)) { auto disasm = Disassembler::Make<DisassemblerDex>(image); @@ -79,6 +97,12 @@ std::unique_ptr<Disassembler> MakeDisassemblerOfType(ConstBufferView image, case kExeTypeWin32X64: return Disassembler::Make<DisassemblerWin32X64>(image); #endif // BUILDFLAG(ENABLE_WIN) +#if BUILDFLAG(ENABLE_ELF) + case kExeTypeElfX86: + return Disassembler::Make<DisassemblerElfX86>(image); + case kExeTypeElfX64: + return Disassembler::Make<DisassemblerElfX64>(image); +#endif // BUILDFLAG(ENABLE_ELF) #if BUILDFLAG(ENABLE_DEX) case kExeTypeDex: return Disassembler::Make<DisassemblerDex>(image); @@ -67,8 +67,30 @@ struct Elf64_Ehdr { Elf64_Half e_shstrndx; }; +// Identification Indexes in header->e_ident. +enum IdentificationIndex { + EI_MAG0 = 0, // File identification. + EI_MAG1 = 1, // File identification. + EI_MAG2 = 2, // File identification. + EI_MAG3 = 3, // File identification. + EI_CLASS = 4, // File class. + EI_DATA = 5, // Data encoding. + EI_VERSION = 6, // File version. + EI_OSABI = 7, // Operating system/ABI identification. + EI_ABIVERSION = 8, // ABI version. + EI_PAD = 9, // Start of padding bytes. + EI_NIDENT = 16 // Size of e_ident[]. +}; + +// Values for header->e_ident[EI_CLASS]. +enum FileClass { + ELFCLASSNONE = 0, // Invalid class. + ELFCLASS32 = 1, // 32-bit objects. + ELFCLASS64 = 2 // 64-bit objects. +}; + // Values for header->e_type. -enum e_type_values { +enum FileType { ET_NONE = 0, // No file type ET_REL = 1, // Relocatable file ET_EXEC = 2, // Executable file @@ -79,7 +101,7 @@ enum e_type_values { }; // Values for header->e_machine. -enum e_machine_values { +enum MachineArchitecture { EM_NONE = 0, // No machine. EM_386 = 3, // Intel Architecture. EM_ARM = 40, // ARM Architecture. |