blob: eb3f3878eacc78e294e9cf0ce1c0d3f8e68828d8 [file] [log] [blame]
// Copyright 2011 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/pe/pe_file_writer.h"
#include "base/path_service.h"
#include "base/files/file_util.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "syzygy/core/unittest_util.h"
#include "syzygy/pe/decomposer.h"
#include "syzygy/pe/pe_file.h"
#include "syzygy/pe/pe_utils.h"
#include "syzygy/pe/unittest_util.h"
namespace pe {
namespace {
using block_graph::BlockGraph;
using core::RelativeAddress;
class PEFileWriterTest: public testing::PELibUnitTest {
// Add customizations here.
};
} // namespace
TEST_F(PEFileWriterTest, LoadOriginalImage) {
// This test baselines the other test(s) that operate on mutated, copied
// versions of the DLLs.
base::FilePath image_path(testing::GetExeRelativePath(testing::kTestDllName));
ASSERT_NO_FATAL_FAILURE(CheckTestDll(image_path));
}
TEST_F(PEFileWriterTest, RewriteAndLoadImage) {
// Create a temporary file we can write the new image to.
base::FilePath temp_dir;
ASSERT_NO_FATAL_FAILURE(CreateTemporaryDir(&temp_dir));
base::FilePath temp_file = temp_dir.Append(testing::kTestDllName);
// Decompose the original test image.
PEFile image_file;
base::FilePath image_path(testing::GetExeRelativePath(testing::kTestDllName));
ASSERT_TRUE(image_file.Init(image_path));
Decomposer decomposer(image_file);
block_graph::BlockGraph block_graph;
pe::ImageLayout image_layout(&block_graph);
ASSERT_TRUE(decomposer.Decompose(&image_layout));
PEFileWriter writer(image_layout);
ASSERT_TRUE(writer.WriteImage(temp_file));
ASSERT_NO_FATAL_FAILURE(CheckTestDll(temp_file));
}
TEST_F(PEFileWriterTest, UpdateFileChecksum) {
base::FilePath temp_dir;
ASSERT_NO_FATAL_FAILURE(CreateTemporaryDir(&temp_dir));
// Verify that the function fails on non-existent paths.
base::FilePath executable = temp_dir.Append(L"executable_file.exe");
EXPECT_FALSE(PEFileWriter::UpdateFileChecksum(executable));
// Verify that the function fails for non-image files.
base::ScopedFILE file(base::OpenFile(executable, "wb"));
// Grow the file to 16K.
ASSERT_EQ(0, fseek(file.get(), 16 * 1024, SEEK_SET));
file.reset();
EXPECT_FALSE(PEFileWriter::UpdateFileChecksum(executable));
// Make a copy of our test DLL and check that we work on that.
base::FilePath input_path(testing::GetExeRelativePath(testing::kTestDllName));
base::FilePath image_path(temp_dir.Append(testing::kTestDllName));
EXPECT_TRUE(base::CopyFile(input_path, image_path));
EXPECT_TRUE(PEFileWriter::UpdateFileChecksum(image_path));
}
namespace {
bool WriteImageLayout(const ImageLayout& image_layout,
const base::FilePath& path) {
PEFileWriter pe_file_writer(image_layout);
return pe_file_writer.WriteImage(path);
}
} // namespace
TEST_F(PEFileWriterTest, FailsForInconsistentImage) {
base::FilePath temp_dir;
ASSERT_NO_FATAL_FAILURE(CreateTemporaryDir(&temp_dir));
base::FilePath temp_file = temp_dir.Append(L"foo.dll");
PEFile image_file;
base::FilePath image_path(testing::GetExeRelativePath(testing::kTestDllName));
ASSERT_TRUE(image_file.Init(image_path));
Decomposer decomposer(image_file);
block_graph::BlockGraph block_graph;
pe::ImageLayout image_layout(&block_graph);
ASSERT_TRUE(decomposer.Decompose(&image_layout));
BlockGraph::Block* dos_header_block =
image_layout.blocks.GetBlockByAddress(RelativeAddress(0));
BlockGraph::Block* nt_headers_block =
GetNtHeadersBlockFromDosHeaderBlock(dos_header_block);
ASSERT_TRUE(nt_headers_block != NULL);
IMAGE_DOS_HEADER* dos_header =
reinterpret_cast<IMAGE_DOS_HEADER*>(dos_header_block->GetMutableData());
IMAGE_NT_HEADERS* nt_headers =
reinterpret_cast<IMAGE_NT_HEADERS*>(nt_headers_block->GetMutableData());
IMAGE_SECTION_HEADER* section_headers = IMAGE_FIRST_SECTION(nt_headers);
ASSERT_TRUE(nt_headers != NULL);
// To start with, the image should be perfectly writable.
EXPECT_TRUE(WriteImageLayout(image_layout, temp_file));
// Invalid DOS header.
dos_header->e_magic ^= 0xF00D;
EXPECT_FALSE(WriteImageLayout(image_layout, temp_file));
dos_header->e_magic ^= 0xF00D;
// Section count mismatch.
--nt_headers->FileHeader.NumberOfSections;
EXPECT_FALSE(WriteImageLayout(image_layout, temp_file));
++nt_headers->FileHeader.NumberOfSections;
// Inconsistent section headers and image layout.
section_headers[0].SizeOfRawData += 10 * 1024;
EXPECT_FALSE(WriteImageLayout(image_layout, temp_file));
// Make the section headers and image layout consistent again, while making
// the first section overlap the second on disk.
image_layout.sections[0].data_size += 10 * 1024;
// Overlapping sections on disk.
EXPECT_FALSE(WriteImageLayout(image_layout, temp_file));
section_headers[0].SizeOfRawData -= 10 * 1024;
image_layout.sections[0].data_size -= 10 * 1024;
// Overlapping sections in memory.
section_headers[0].Misc.VirtualSize += 10 * 1024;
image_layout.sections[0].size += 10 * 1024;
EXPECT_FALSE(WriteImageLayout(image_layout, temp_file));
section_headers[0].Misc.VirtualSize -= 10 * 1024;
image_layout.sections[0].size -= 10 * 1024;
// Unaligned section start on disk.
section_headers[0].PointerToRawData++;
EXPECT_FALSE(WriteImageLayout(image_layout, temp_file));
section_headers[0].PointerToRawData--;
// Unaligned section start in memory.
section_headers[0].VirtualAddress++;
image_layout.sections[0].addr += 1;
EXPECT_FALSE(WriteImageLayout(image_layout, temp_file));
section_headers[0].VirtualAddress--;
image_layout.sections[0].addr -= 1;
size_t last_section_id = nt_headers->FileHeader.NumberOfSections - 1;
size_t file_alignment = nt_headers->OptionalHeader.FileAlignment;
RelativeAddress last_section_start(
section_headers[last_section_id].VirtualAddress);
RelativeAddress last_section_end = last_section_start +
section_headers[last_section_id].Misc.VirtualSize;
RelativeAddress new_block_addr = last_section_end.AlignUp(file_alignment);
BlockGraph::Block* new_block = block_graph.AddBlock(
BlockGraph::DATA_BLOCK, 2 * file_alignment, "new block");
new_block->set_section(last_section_id);
// Block that is outside its section entirely.
ASSERT_TRUE(image_layout.blocks.InsertBlock(new_block_addr, new_block));
EXPECT_FALSE(WriteImageLayout(image_layout, temp_file));
// Block in virtual portion of section with explicit data.
section_headers[last_section_id].SizeOfRawData =
new_block_addr - last_section_start;
section_headers[last_section_id].Misc.VirtualSize =
new_block_addr + new_block->size() - last_section_start;
image_layout.sections.back().data_size =
section_headers[last_section_id].SizeOfRawData;
image_layout.sections.back().size =
section_headers[last_section_id].Misc.VirtualSize;
ASSERT_TRUE(new_block->AllocateData(file_alignment) != NULL);
::memset(new_block->GetMutableData(), 0xCC, new_block->data_size());
EXPECT_FALSE(WriteImageLayout(image_layout, temp_file));
// Extending the initialized data size of the section to cover the initialized
// portion of the block should allow it to be written, even though half of it
// is implicit.
section_headers[last_section_id].SizeOfRawData += file_alignment;
image_layout.sections.back().data_size += file_alignment;
EXPECT_TRUE(WriteImageLayout(image_layout, temp_file));
// Finally, having a FileOffsetAddress reference to a location in implicit
// data should fail.
ASSERT_TRUE(new_block->SetReference(
0,
BlockGraph::Reference(BlockGraph::FILE_OFFSET_REF,
4,
new_block,
file_alignment,
file_alignment)));
EXPECT_FALSE(WriteImageLayout(image_layout, temp_file));
}
} // namespace pe