| Index: syzygy/experimental/protect/protect_lib/integrity_check_layout_transform.cc
|
| diff --git a/syzygy/experimental/protect/protect_lib/integrity_check_layout_transform.cc b/syzygy/experimental/protect/protect_lib/integrity_check_layout_transform.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..9acaee6e79b164cab76eac6d8b84b8c6b420d516
|
| --- /dev/null
|
| +++ b/syzygy/experimental/protect/protect_lib/integrity_check_layout_transform.cc
|
| @@ -0,0 +1,366 @@
|
| +// Copyright 2015 Google Inc. All Rights Reserved.
|
| +//
|
| +// Licensed under the Apache License, Version 2.0 (the "License");
|
| +// you may not use this file except in compliance with the License.
|
| +// You may obtain a copy of the License at
|
| +//
|
| +// http://www.apache.org/licenses/LICENSE-2.0
|
| +//
|
| +// Unless required by applicable law or agreed to in writing, software
|
| +// distributed under the License is distributed on an "AS IS" BASIS,
|
| +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| +// See the License for the specific language governing permissions and
|
| +// limitations under the License.
|
| +#include "syzygy/experimental/protect/protect_lib/integrity_check_layout_transform.h"
|
| +
|
| +//TODO: remove this include
|
| +#include <inttypes.h>
|
| +
|
| +#include "syzygy/core/address.h"
|
| +#include "syzygy/experimental/protect/protect_lib/protect_utils.h"
|
| +#include "syzygy/pe/pe_file_writer.h"
|
| +#include "syzygy/pe/pe_utils.h"
|
| +
|
| +namespace protect {
|
| +
|
| +uint8_t IntegrityCheckLayoutTransform::ComputeAggregatedChunksHash(
|
| + const std::set<uint32_t> chunk_indexes){
|
| + uint8_t precomputed_xor = 0;
|
| + for (auto chunk_it = chunk_indexes.rbegin();
|
| + chunk_it != chunk_indexes.rend();
|
| + ++chunk_it) {
|
| + auto chunk_info = (*ic_block_reference_free_chunks)[*chunk_it];
|
| + precomputed_xor += chunk_info.hash_ + chunk_info.hash_of_next_instruction_;
|
| + precomputed_xor = -precomputed_xor;
|
| + }
|
| + return precomputed_xor;
|
| +}
|
| +
|
| +uint8_t
|
| +IntegrityCheckLayoutTransform::ComputeAggregatedBlocksHash(uint64_t bb_id){
|
| + uint8_t precomputed_hash = 0;
|
| + auto checkee_iter = (*this->checker_to_checkee_map_)[bb_id].begin();
|
| + for (; checkee_iter != (*this->checker_to_checkee_map_)[bb_id].end();
|
| + ++checkee_iter){
|
| + precomputed_hash += (*this->precomputed_hashes_)[checkee_iter->first] *
|
| + checkee_iter->second;
|
| + }
|
| + return precomputed_hash;
|
| +}
|
| +
|
| +bool IntegrityCheckLayoutTransform::RecomputePivot(
|
| + const uint64_t bb_id,
|
| + const uint8_t precomputed_hash,
|
| + const uint8_t precomputed_xor,
|
| + const size_t pivot_offset,
|
| + const size_t sub_offset,
|
| + block_graph::BlockGraph::Block *block){
|
| +
|
| + const uint8_t *pivot_byte = block->data() + pivot_offset;
|
| + DCHECK(*pivot_byte == 0x00);
|
| +
|
| + const uint8_t *sub_opcode = block->data() + sub_offset;
|
| + DCHECK(*sub_opcode == 0x2c);
|
| +
|
| + const uint8_t *hash_byte = sub_opcode + 1;
|
| + //+1 is the sub hash value
|
| + uint8_t new_bytes[3];
|
| + new_bytes[0] = *pivot_byte;
|
| + new_bytes[1] = *sub_opcode;
|
| + new_bytes[2] = *hash_byte;
|
| +
|
| + uint8_t old_hash = *hash_byte;
|
| + new_bytes[2] = precomputed_hash + precomputed_xor; // new hash
|
| + const uint8_t* data = block->data();
|
| + size_t data_size = block->data_size();
|
| + uint8_t* new_data = new uint8_t[data_size];
|
| + memcpy(new_data, data, data_size);
|
| +
|
| + // Set new pivot byte. Starts at offset 0
|
| + uint8_t new_pivot = old_hash - new_bytes[2];
|
| + new_bytes[0] = new_pivot;
|
| +
|
| + DCHECK_EQ((uint8_t)(new_pivot + precomputed_hash + precomputed_xor),
|
| + old_hash);
|
| + new_data[pivot_offset] = new_bytes[0];
|
| + new_data[sub_offset + 1] = new_bytes[2];
|
| +
|
| + block->CopyData(data_size, new_data);
|
| + delete[] new_data;
|
| +
|
| + return true;
|
| +}
|
| +
|
| +bool IntegrityCheckLayoutTransform::PatchPivot(BlockGraph::Label label) {
|
| + uint64_t bb_id = GetBasicBlockIdByLabel(label, this->id_to_label_);
|
| +
|
| + if (bb_id == -1)
|
| + return true;
|
| +
|
| + if ((*this->checker_to_checkee_map_)[bb_id].size() < 1)
|
| + return true;
|
| +
|
| + uint8_t precomputed_hash = ComputeAggregatedBlocksHash(bb_id);
|
| + uint8_t precomputed_xor = 0;
|
| + if (*perform_chunk_checks_) {
|
| + //recompute xor hash
|
| + auto checkee_chunks_it = ic_chunk_checker_to_checkee_map_->find(bb_id);
|
| + DCHECK(checkee_chunks_it != ic_chunk_checker_to_checkee_map_->end());
|
| +
|
| + DCHECK_NE(checkee_chunks_it->second.size(), static_cast<uint32_t>(0));
|
| + precomputed_xor = ComputeAggregatedChunksHash(checkee_chunks_it->second);
|
| + }
|
| + char *buffer = new char[50];
|
| + sprintf_s(buffer, 50, "Pivot:%llu", bb_id);
|
| + // offset of sub instruction after returning from hash function
|
| + size_t pivot_offset = (*label_name_to_block_)[buffer].second;
|
| + auto block = (*label_name_to_block_)[buffer].first;
|
| +
|
| + sprintf_s(buffer, 50, "sub %llu", bb_id);
|
| + // offset of sub instruction after returning from hash function
|
| + auto sub_instr_block = label_name_to_block_->find(buffer);
|
| + DCHECK(sub_instr_block != label_name_to_block_->end());
|
| + size_t sub_offset = sub_instr_block->second.second;
|
| + delete[] buffer;
|
| + if (RecomputePivot(bb_id, precomputed_hash, precomputed_xor,
|
| + pivot_offset, sub_offset, block)){
|
| + this->nr_hashes_patched_++;
|
| + }
|
| + return true;
|
| +}
|
| +
|
| +int IntegrityCheckLayoutTransform::PatchPrecomputedHashes(
|
| + const TransformPolicyInterface* policy,
|
| + BlockGraph::Block* block) {
|
| + if (!ShouldPostProcessBlock(block, this->id_to_label_))
|
| + return 0;
|
| +
|
| + // Iterate over every label in the block and patch the pivot
|
| + auto it = block->labels().begin();
|
| + for (; it != block->labels().end(); ++it) {
|
| + if (!PatchPivot(it->second))
|
| + return 0;
|
| + }
|
| +
|
| + return 0;
|
| +}
|
| +// This function adjusts the inter-block references that have shifted after
|
| +// code was inserted
|
| +bool IntegrityCheckLayoutTransform::CheckHash(
|
| + BasicCodeBlock* bb,
|
| + std::vector<uint8_t> new_block_buffer,
|
| + const core::AbsoluteAddress image_base) {
|
| + auto inst_iter = bb->instructions().begin();
|
| + uint64_t bb_id = (uint64_t)-1;
|
| + if ((inst_iter != bb->instructions().end()) && (inst_iter->has_label())) {
|
| + bb_id = GetBasicBlockIdByLabel(inst_iter->label(), this->id_to_label_);
|
| + if (bb_id != -1) {
|
| + auto buf_it = new_block_buffer.begin();
|
| + std::advance(buf_it, bb->offset());
|
| +
|
| + uint8_t hash = 0;
|
| + uint32_t block_size = (*this->basic_block_sizes_)[bb_id];
|
| + for (uint32_t i = 0; i < block_size; ++i) {
|
| + hash += *buf_it;
|
| + if (i % 16 == 0)
|
| + fprintf(phash, "\n");
|
| + else if (i % 8 == 0)
|
| + fprintf(phash, " ");
|
| + fprintf(phash, "%02X ", *buf_it);
|
| + ++buf_it;
|
| + }
|
| + // Compute hash of image_base.
|
| + uint8_t hash_image_base = 0;
|
| + for (uint8_t i = 0; i < 4; i++) {
|
| + hash_image_base += image_base.value() >> (i*8);
|
| + }
|
| +
|
| + // For each chunk or checkee subtract hash of image base.
|
| + uint8_t nr_checkees = (*this->checker_to_checkee_map_)[bb_id].size() +
|
| + (*ic_chunk_checker_to_checkee_map_)[bb_id].size();
|
| + hash -= hash_image_base * nr_checkees;
|
| +
|
| + uint8_t precompute_hash = (*this->precomputed_hashes_)[bb_id];
|
| + if (precompute_hash != hash) {
|
| + (*this->precomputed_hashes_)[bb_id] = hash;
|
| + }
|
| +
|
| + fprintf(phash, "\n%s,", bb->subgraph()->original_block()->name().c_str());
|
| + fprintf(phash, "%" PRIx64 ",", bb_id);
|
| + fprintf(phash, "%" PRIx32 ",",
|
| + bb->subgraph()->original_block()->addr().value() + bb->offset());
|
| + fprintf(phash, "%" PRIx8 "\n", hash);
|
| + } //end if
|
| + } //end if
|
| +
|
| + //We need to compute hash of the chunks whose last instruction has absolute
|
| + // address. If there is no chunk checking this step is not needed.
|
| + if (!*perform_chunk_checks_) return true;
|
| +
|
| + std::string chunk_pointerlabel = "n ";
|
| + auto end_block = bb->instructions().end();
|
| + uint64_t chunk_bb_id;
|
| + uint32_t chunk_index;
|
| + uint32_t offset = bb->offset();
|
| + for (; inst_iter != end_block; ++inst_iter)
|
| + {
|
| + offset += inst_iter->size();
|
| + if (!inst_iter->has_label()) continue;
|
| +
|
| + if (inst_iter->label().name()
|
| + .compare(0, chunk_pointerlabel.length(), chunk_pointerlabel) == 0){
|
| + // update last visited chunk index
|
| + GetChunkTokensFromlabel(inst_iter->label().name(),
|
| + &chunk_bb_id,
|
| + &chunk_index);
|
| +
|
| + size_t unique_key = GetChunkUniqueKey(chunk_bb_id, chunk_index);
|
| +
|
| + uint32_t vector_index = (*ic_block_chunk_index_map_)[unique_key];
|
| +
|
| + DCHECK_GE(vector_index, static_cast<uint32_t>(0));
|
| + DCHECK_LT(vector_index, ic_block_reference_free_chunks->size());
|
| +
|
| + auto chunk = (*ic_block_reference_free_chunks)[vector_index];
|
| + DCHECK(chunk.block_id_ == chunk_bb_id);
|
| + DCHECK(chunk.chunk_index_ == chunk_index);
|
| + //we need to recompute chunks whose last instruction has absoloute address
|
| + if (chunk.next_instruction_size_ == 0) continue;
|
| +
|
| + uint32_t chunk_offset = offset + chunk.size_ - inst_iter->size();
|
| +
|
| + auto buf_it = new_block_buffer.begin();
|
| + std::advance(buf_it, chunk_offset);
|
| +
|
| + uint8_t hash = 0;
|
| + for (uint32_t i = 0; i < chunk.next_instruction_size_; ++i) {
|
| + hash += *buf_it;
|
| + ++buf_it;
|
| + }
|
| +
|
| + chunk.hash_of_next_instruction_ = hash;
|
| + (*ic_block_reference_free_chunks)[vector_index] = chunk;
|
| + }
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| +bool IntegrityCheckLayoutTransform::FixPrecomputedHashes(
|
| + const TransformPolicyInterface* policy,
|
| + const core::AbsoluteAddress image_base,
|
| + BlockGraph::Block* block,
|
| + std::vector<uint8_t> new_block_buffer) {
|
| +
|
| + if (!ShouldPostProcessBlock(block, this->id_to_label_))
|
| + return false;
|
| +
|
| + // Use the decomposition policy to skip blocks that aren't eligible for
|
| + // basic-block decomposition.
|
| + if (!policy->BlockIsSafeToBasicBlockDecompose(block))
|
| + return false;
|
| +
|
| + // Decompose block to basic blocks.
|
| + BasicBlockSubGraph *subgraph = new BasicBlockSubGraph();
|
| + BasicBlockDecomposer bb_decomposer(block, subgraph);
|
| + if (!bb_decomposer.Decompose())
|
| + return false;
|
| +
|
| + BasicBlockSubGraph::BBCollection& basic_blocks =
|
| + subgraph->basic_blocks(); // set of BB to protect
|
| +
|
| + // Iterate over every basic block and recompute the hash
|
| + for (auto it = basic_blocks.begin(); it != basic_blocks.end(); ++it) {
|
| + BasicCodeBlock* bb = BasicCodeBlock::Cast(*it);
|
| +
|
| + if (bb == NULL)
|
| + continue;
|
| +
|
| + CheckHash(bb, new_block_buffer, image_base);
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| +bool IntegrityCheckLayoutTransform::TransformImageLayout(
|
| + const TransformPolicyInterface* policy,
|
| + const pe::ImageLayout* image_layout,
|
| + const OrderedBlockGraph* ordered_block_graph) {
|
| + pe::PEFileWriter writer(*image_layout);
|
| +
|
| + if (!writer.ValidateHeaders())
|
| + return false;
|
| +
|
| + if (!writer.CalculateSectionRanges())
|
| + return false;
|
| +
|
| + core::AbsoluteAddress* image_base = writer.GetImageBase();
|
| +
|
| + // Create the output buffer, reserving enough room for the whole file.
|
| + DCHECK(!image_layout->sections.empty());
|
| + size_t image_size = writer.GetImageSize();
|
| + std::vector<uint8_t> buffer;
|
| + buffer.reserve(image_size);
|
| +
|
| + // Iterate through all blocks in the address space writing them as we go.
|
| + BlockGraph::AddressSpace::RangeMap::const_iterator block_it2(
|
| + image_layout->blocks.address_space_impl().ranges().begin());
|
| + BlockGraph::AddressSpace::RangeMap::const_iterator block_end(
|
| + image_layout->blocks.address_space_impl().ranges().end());
|
| +
|
| + BlockGraph::AddressSpace::RangeMap::const_iterator block_it(
|
| + image_layout->blocks.address_space_impl().ranges().begin());
|
| +
|
| + BlockGraph::SectionId section_id = BlockGraph::kInvalidSectionId;
|
| + size_t section_index = BlockGraph::kInvalidSectionId;
|
| +
|
| + // TODO: remove file
|
| + phash = fopen("phash.txt", "w");
|
| + fprintf(phash, "Block name, BBid, Address, hash\n");
|
| +
|
| + for (; block_it != block_end; ++block_it) {
|
| + BlockGraph::Block* block =
|
| + const_cast<BlockGraph::Block*>(block_it->second);
|
| +
|
| + // If we're jumping to a new section output the necessary padding.
|
| + if (block->section() != section_id) {
|
| + writer.FlushSection(section_index, &buffer);
|
| + section_id = block->section();
|
| + section_index++;
|
| + DCHECK_GT(image_layout->sections.size(), section_index);
|
| + }
|
| +
|
| + core::FileOffsetAddress size_before(buffer.size());
|
| +
|
| + if (!writer.WriteOneBlock(*image_base, section_index, block,
|
| + &buffer, &size_before)) {
|
| + LOG(ERROR) << "Failed to write block \"" << block->name() << "\".";
|
| + return false;
|
| + }
|
| +
|
| + // compute new hash value of block
|
| + auto buf_it = buffer.begin();
|
| + std::advance(buf_it, size_before.value());
|
| + std::vector<uint8_t> new_block_buffer(buf_it, buffer.end());
|
| +
|
| + // Compute the new hash values inside buffer
|
| + FixPrecomputedHashes(policy, *image_base, block, new_block_buffer);
|
| + } // end for
|
| +
|
| + fclose(phash);
|
| +
|
| + block_it = image_layout->blocks.address_space_impl().ranges().begin();
|
| + for (; block_it != block_end; ++block_it) {
|
| + BlockGraph::Block* block =
|
| + const_cast<BlockGraph::Block*>(block_it->second);
|
| + // patch the hash values in-place
|
| + PatchPrecomputedHashes(policy, block);
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| +// static vars
|
| +const char IntegrityCheckLayoutTransform::kTransformName[] =
|
| + "IntegrityCheckLayoutTransform";
|
| +} // namespace protect
|
|
|