From 06f1ae9aaca969ee95ef840f22b6b461c304542d Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 13 Mar 2018 18:19:34 +0000 Subject: [Zucchini] Move Zucchini from /chrome/installer/ to /components/. (Use "git log --follow" to see older revisions of files). /components/ is the most logical place to put Zucchini, which only depends on /base and /testing/gtest. This move also enables Zucchini to be used by the Component Updater. Details: - Move all files; run the following to change deps and guards: sed 's/chrome\/installer/components/' *.cc *.h -i sed 's/CHROME_INSTALLER/COMPONENTS/' *.cc *.h -i - Sorting works out pretty well! - Change all 'chrome/installer/zucchini' to 'components/zucchini' throughout other parts of the repo; sort if necessary. - Fix 6 'git cl lint' errors. - Change 1 Bind() usage to BindRepeated(). - Update OWNER. Bug: 729154 Change-Id: I50c5a7d411ea85f707b5994ab319dfb2a1acccf7 Reviewed-on: https://chromium-review.googlesource.com/954923 Reviewed-by: Greg Thompson Reviewed-by: Jochen Eisinger Reviewed-by: Samuel Huang Commit-Queue: Samuel Huang Cr-Commit-Position: refs/heads/master@{#542857} NOKEYCHECK=True GitOrigin-RevId: 577ef6c435e8d43be6e3e60ccbcbd1881780f4ec --- disassembler_win32.cc | 392 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 392 insertions(+) create mode 100644 disassembler_win32.cc (limited to 'disassembler_win32.cc') diff --git a/disassembler_win32.cc b/disassembler_win32.cc new file mode 100644 index 0000000..5bdc503 --- /dev/null +++ b/disassembler_win32.cc @@ -0,0 +1,392 @@ +// Copyright 2017 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_win32.h" + +#include + +#include + +#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" +#include "components/zucchini/rel32_finder.h" +#include "components/zucchini/rel32_utils.h" +#include "components/zucchini/reloc_utils.h" + +namespace zucchini { + +namespace { + +// Decides whether |image| points to a Win32 PE file. If this is a possibility, +// assigns |source| to enable further parsing, and returns true. Otherwise +// leaves |source| at an undefined state and returns false. +template +bool ReadWin32Header(ConstBufferView image, BufferSource* source) { + *source = BufferSource(image); + + // Check "MZ" magic of DOS header. + if (!source->CheckNextBytes({'M', 'Z'})) + return false; + + const auto* dos_header = source->GetPointer(); + if (!dos_header || (dos_header->e_lfanew & 7) != 0) + return false; + + // Offset to PE header is in DOS header. + *source = std::move(BufferSource(image).Skip(dos_header->e_lfanew)); + // Check 'PE\0\0' magic from PE header. + if (!source->ConsumeBytes({'P', 'E', 0, 0})) + return false; + + return true; +} + +template +const pe::ImageDataDirectory* ReadDataDirectory( + const typename Traits::ImageOptionalHeader* optional_header, + size_t index) { + if (index >= optional_header->number_of_rva_and_sizes) + return nullptr; + return &optional_header->data_directory[index]; +} + +// Decides whether |section| (assumed value) is a section that contains code. +template +bool IsWin32CodeSection(const pe::ImageSectionHeader& section) { + return (section.characteristics & kCodeCharacteristics) == + kCodeCharacteristics; +} + +} // namespace + +/******** Win32X86Traits ********/ + +// static +constexpr Bitness Win32X86Traits::kBitness; +constexpr ExecutableType Win32X86Traits::kExeType; +const char Win32X86Traits::kExeTypeString[] = "Windows PE x86"; + +/******** Win32X64Traits ********/ + +// static +constexpr Bitness Win32X64Traits::kBitness; +constexpr ExecutableType Win32X64Traits::kExeType; +const char Win32X64Traits::kExeTypeString[] = "Windows PE x64"; + +/******** DisassemblerWin32 ********/ + +// static. +template +bool DisassemblerWin32::QuickDetect(ConstBufferView image) { + BufferSource source; + return ReadWin32Header(image, &source); +} + +template +DisassemblerWin32::DisassemblerWin32() = default; + +template +DisassemblerWin32::~DisassemblerWin32() = default; + +template +ExecutableType DisassemblerWin32::GetExeType() const { + return Traits::kExeType; +} + +template +std::string DisassemblerWin32::GetExeTypeString() const { + return Traits::kExeTypeString; +} + +template +std::vector DisassemblerWin32::MakeReferenceGroups() + const { + return { + {ReferenceTypeTraits{2, TypeTag(kReloc), PoolTag(kReloc)}, + &DisassemblerWin32::MakeReadRelocs, &DisassemblerWin32::MakeWriteRelocs}, + {ReferenceTypeTraits{Traits::kVAWidth, TypeTag(kAbs32), PoolTag(kAbs32)}, + &DisassemblerWin32::MakeReadAbs32, &DisassemblerWin32::MakeWriteAbs32}, + {ReferenceTypeTraits{4, TypeTag(kRel32), PoolTag(kRel32)}, + &DisassemblerWin32::MakeReadRel32, &DisassemblerWin32::MakeWriteRel32}, + }; +} + +template +std::unique_ptr DisassemblerWin32::MakeReadRelocs( + offset_t lo, + offset_t hi) { + ParseAndStoreRelocBlocks(); + + RelocRvaReaderWin32 reloc_rva_reader(image_, reloc_region_, + reloc_block_offsets_, lo, hi); + CHECK_GE(image_.size(), Traits::kVAWidth); + offset_t offset_bound = + base::checked_cast(image_.size() - Traits::kVAWidth + 1); + return std::make_unique(std::move(reloc_rva_reader), + Traits::kRelocType, offset_bound, + translator_); +} + +template +std::unique_ptr DisassemblerWin32::MakeReadAbs32( + offset_t lo, + offset_t hi) { + ParseAndStoreAbs32(); + Abs32RvaExtractorWin32 abs_rva_extractor( + image_, {Traits::kBitness, image_base_}, abs32_locations_, lo, hi); + return std::make_unique(std::move(abs_rva_extractor), + translator_); +} + +template +std::unique_ptr DisassemblerWin32::MakeReadRel32( + offset_t lo, + offset_t hi) { + ParseAndStoreRel32(); + return std::make_unique(image_, lo, hi, &rel32_locations_, + translator_); +} + +template +std::unique_ptr DisassemblerWin32::MakeWriteRelocs( + MutableBufferView image) { + ParseAndStoreRelocBlocks(); + return std::make_unique(Traits::kRelocType, image, + reloc_region_, reloc_block_offsets_, + translator_); +} + +template +std::unique_ptr DisassemblerWin32::MakeWriteAbs32( + MutableBufferView image) { + return std::make_unique( + image, AbsoluteAddress(Traits::kBitness, image_base_), translator_); +} + +template +std::unique_ptr DisassemblerWin32::MakeWriteRel32( + MutableBufferView image) { + return std::make_unique(image, translator_); +} + +template +bool DisassemblerWin32::Parse(ConstBufferView image) { + image_ = image; + return ParseHeader(); +} + +template +bool DisassemblerWin32::ParseHeader() { + BufferSource source; + + if (!ReadWin32Header(image_, &source)) + return false; + + auto* coff_header = source.GetPointer(); + if (!coff_header || + coff_header->size_of_optional_header < + offsetof(typename Traits::ImageOptionalHeader, data_directory)) { + return false; + } + + auto* optional_header = + source.GetPointer(); + if (!optional_header || optional_header->magic != Traits::kMagic) + return false; + + const size_t kDataDirBase = + offsetof(typename Traits::ImageOptionalHeader, data_directory); + size_t size_of_optional_header = coff_header->size_of_optional_header; + if (size_of_optional_header < kDataDirBase) + return false; + + const size_t data_dir_bound = + (size_of_optional_header - kDataDirBase) / sizeof(pe::ImageDataDirectory); + if (optional_header->number_of_rva_and_sizes > data_dir_bound) + return false; + + base_relocation_table_ = ReadDataDirectory( + optional_header, pe::kIndexOfBaseRelocationTable); + if (!base_relocation_table_) + return false; + + image_base_ = optional_header->image_base; + + // |optional_header->size_of_image| is the size of the image when loaded into + // memory, and not the actual size on disk. + rva_t rva_bound = optional_header->size_of_image; + if (rva_bound >= kRvaBound) + return false; + + // An exclusive upper bound of all offsets used in the image. This gets + // updated as sections get visited. + offset_t offset_bound = + base::checked_cast(source.begin() - image_.begin()); + + // Extract |sections_|. + size_t sections_count = coff_header->number_of_sections; + auto* sections_array = + source.GetArray(sections_count); + if (!sections_array) + return false; + sections_.assign(sections_array, sections_array + sections_count); + + // Prepare |units| for offset-RVA translation. + std::vector units; + units.reserve(sections_count); + + // Visit each section, validate, and add address translation data to |units|. + bool has_text_section = false; + decltype(pe::ImageSectionHeader::virtual_address) prev_virtual_address = 0; + for (size_t i = 0; i < sections_count; ++i) { + const pe::ImageSectionHeader& section = sections_[i]; + // Apply strict checks on section bounds. + if (!image_.covers( + {section.file_offset_of_raw_data, section.size_of_raw_data})) { + return false; + } + if (!RangeIsBounded(section.virtual_address, section.virtual_size, + rva_bound)) { + return false; + } + + // PE sections should be sorted by RVAs. For robustness, we don't rely on + // this, so even if unsorted we don't care. Output warning though. + if (prev_virtual_address > section.virtual_address) + LOG(WARNING) << "RVA anomaly found for Section " << i; + prev_virtual_address = section.virtual_address; + + // Add |section| data for offset-RVA translation. + units.push_back({section.file_offset_of_raw_data, section.size_of_raw_data, + section.virtual_address, section.virtual_size}); + + offset_t end_offset = + section.file_offset_of_raw_data + section.size_of_raw_data; + offset_bound = std::max(end_offset, offset_bound); + if (IsWin32CodeSection(section)) + has_text_section = true; + } + + if (offset_bound > image_.size()) + return false; + if (!has_text_section) + return false; + + // Initialize |translator_| for offset-RVA translations. Any inconsistency + // (e.g., 2 offsets correspond to the same RVA) would invalidate the PE file. + if (translator_.Initialize(std::move(units)) != AddressTranslator::kSuccess) + return false; + + // Resize |image_| to include only contents claimed by sections. Note that + // this may miss digital signatures at end of PE files, but for patching this + // is of minor concern. + image_.shrink(offset_bound); + + return true; +} + +template +bool DisassemblerWin32::ParseAndStoreRelocBlocks() { + if (has_parsed_relocs_) + return true; + has_parsed_relocs_ = true; + DCHECK(reloc_block_offsets_.empty()); + + offset_t relocs_offset = + translator_.RvaToOffset(base_relocation_table_->virtual_address); + size_t relocs_size = base_relocation_table_->size; + reloc_region_ = {relocs_offset, relocs_size}; + // Reject bogus relocs. Note that empty relocs are allowed! + if (!image_.covers(reloc_region_)) + return false; + + // Precompute offsets of all reloc blocks. + return RelocRvaReaderWin32::FindRelocBlocks(image_, reloc_region_, + &reloc_block_offsets_); +} + +// TODO(huangs): Print warning if too few abs32 references are found. +// Empirically, file size / # relocs is < 100, so take 200 as the +// threshold for warning. +template +bool DisassemblerWin32::ParseAndStoreAbs32() { + if (has_parsed_abs32_) + return true; + has_parsed_abs32_ = true; + + ParseAndStoreRelocBlocks(); + + std::unique_ptr relocs = MakeReadRelocs(0, offset_t(size())); + for (auto ref = relocs->GetNext(); ref.has_value(); ref = relocs->GetNext()) + 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_); + LOG_IF(WARNING, num_removed) << "Found and removed " << num_removed + << " abs32 locations with overlapping bodies."; + return true; +} + +template +bool DisassemblerWin32::ParseAndStoreRel32() { + if (has_parsed_rel32_) + return true; + has_parsed_rel32_ = true; + + ParseAndStoreAbs32(); + + AddressTranslator::OffsetToRvaCache location_offset_to_rva(translator_); + AddressTranslator::RvaToOffsetCache target_rva_checker(translator_); + + for (const pe::ImageSectionHeader& section : sections_) { + if (!IsWin32CodeSection(section)) + continue; + + rva_t start_rva = section.virtual_address; + rva_t end_rva = start_rva + section.virtual_size; + + ConstBufferView region = + image_[{section.file_offset_of_raw_data, section.size_of_raw_data}]; + Abs32GapFinder gap_finder(image_, region, abs32_locations_, + Traits::kVAWidth); + typename Traits::RelFinder finder(image_); + // Iterate over gaps between abs32 references, to avoid collision. + for (auto gap = gap_finder.GetNext(); gap.has_value(); + gap = gap_finder.GetNext()) { + finder.Reset(gap.value()); + // Iterate over heuristically detected rel32 references, validate, and add + // to |rel32_locations_|. + 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 = location_offset_to_rva.Convert(rel32_offset); + rva_t target_rva = rel32_rva + 4 + image_.read(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); + } + } + } + } + rel32_locations_.shrink_to_fit(); + // |sections_| entries are usually sorted by offset, but there's no guarantee. + // So sort explicitly, to be sure. + std::sort(rel32_locations_.begin(), rel32_locations_.end()); + return true; +} + +// Explicit instantiation for supported classes. +template class DisassemblerWin32; +template class DisassemblerWin32; + +} // namespace zucchini -- cgit v1.2.3