blob: 8f5d687fd573f3769baee2fbbcd2c8e5b1ef79fc [file] [log] [blame]
// Copyright 2012 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_parser.h"
#include "base/bind.h"
#include "base/native_library.h"
#include "base/path_service.h"
#include "base/files/file_path.h"
#include "base/memory/scoped_ptr.h"
#include "base/strings/string_util.h"
#include "base/win/pe_image.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "syzygy/core/unittest_util.h"
#include "syzygy/pe/unittest_util.h"
namespace pe {
using block_graph::BlockGraph;
using core::RelativeAddress;
using testing::ContainerEq;
using testing::Contains;
namespace {
// Exposes the protected methods for testing.
class TestPEFileParser: public PEFileParser {
public:
TestPEFileParser(const PEFile& image_file,
BlockGraph::AddressSpace* address_space,
AddReferenceCallback add_reference)
: PEFileParser(image_file, address_space, add_reference) {
}
// Expose as public for testing.
using PEFileParser::ParseArchitectureDir;
using PEFileParser::ParseBoundImportDir;
using PEFileParser::ParseComDescriptorDir;
using PEFileParser::ParseDebugDir;
using PEFileParser::ParseDelayImportDir;
using PEFileParser::ParseExceptionDir;
using PEFileParser::ParseExportDir;
using PEFileParser::ParseGlobalDir;
using PEFileParser::ParseIatDir;
using PEFileParser::ParseImageHeader;
using PEFileParser::ParseImportDir;
using PEFileParser::ParseLoadConfigDir;
using PEFileParser::ParseRelocDir;
using PEFileParser::ParseResourceDir;
using PEFileParser::ParseSecurityDir;
using PEFileParser::ParseTlsDir;
};
class PEFileParserTest: public testing::PELibUnitTest {
typedef testing::PELibUnitTest Super;
public:
PEFileParserTest() : address_space_(&image_), loaded_image_(NULL) {
}
virtual void SetUp() {
Super::SetUp();
add_reference_ = base::Bind(&PEFileParserTest::AddReference,
base::Unretained(this));
on_import_thunk_ = base::Bind(&PEFileParserTest::OnImportThunk,
base::Unretained(this));
ASSERT_TRUE(image_file_.Init(testing::GetExeRelativePath(
testing::kTestDllName)));
}
virtual void TearDown() {
if (loaded_image_ != NULL)
base::UnloadNativeLibrary(loaded_image_);
loaded_image_ = NULL;
Super::TearDown();
}
bool AddReference(RelativeAddress src,
BlockGraph::ReferenceType type,
BlockGraph::Size size,
RelativeAddress dst) {
Reference ref = { type, size, dst };
bool inserted = references_.insert(std::make_pair(src, ref)).second;
EXPECT_TRUE(inserted);
return inserted;
}
bool OnImportThunk(const char* module_name,
const char* symbol_name,
BlockGraph::Block* thunk) {
EXPECT_TRUE(module_name != NULL);
EXPECT_TRUE(symbol_name != NULL);
EXPECT_TRUE(thunk != NULL);
import_map_[module_name]++;
EXPECT_TRUE(import_set_.insert(
std::make_pair(std::string(module_name),
std::string(symbol_name))).second);
return true;
}
// Assert that an exported function in the test_dll is referenced
// in the image.
bool ExportIsReferenced(const char* function_name_or_ordinal) {
if (loaded_image_ == NULL) {
base::NativeLibraryLoadError error;
loaded_image_ = base::LoadNativeLibrary(
testing::GetExeRelativePath(testing::kTestDllName), &error);
}
EXPECT_TRUE(loaded_image_ != NULL);
if (loaded_image_ == NULL)
return false;
void* function = base::GetFunctionPointerFromNativeLibrary(
loaded_image_, function_name_or_ordinal);
RelativeAddress addr(reinterpret_cast<const char*>(function) -
reinterpret_cast<const char*>(loaded_image_));
ReferenceMap::const_iterator it(references_.begin());
for (; it != references_.end(); ++it) {
if (it->second.dst == addr)
return true;
}
return false;
}
void AssertDataDirectoryEntryValid(BlockGraph::Block* block) {
ASSERT_TRUE(block != NULL);
ASSERT_NE(0u, block->size());
ASSERT_EQ(block->size(), block->data_size());
ASSERT_TRUE(block->data() != NULL);
}
// Locate block pointed to by the reference at @p offset into @p block.
// @returns the block in question, or NULL if no such block.
BlockGraph::Block* FindReferencedBlock(BlockGraph::Block* block,
BlockGraph::Offset offset) {
ReferenceMap::const_iterator it(references_.find(block->addr() + offset));
if (it == references_.end())
return NULL;
return address_space_.GetBlockByAddress(it->second.dst);
}
protected:
struct Reference {
BlockGraph::ReferenceType type;
BlockGraph::Size size;
RelativeAddress dst;
};
typedef std::map<RelativeAddress, Reference> ReferenceMap;
ReferenceMap references_;
// This is used to count the number of imported symbols per imported module,
// and is populated by the OnImportThunk callback.
typedef std::map<std::string, size_t> ImportMap;
typedef std::set<std::pair<std::string, std::string>> ImportSet;
ImportMap import_map_;
ImportSet import_set_;
PEFileParser::AddReferenceCallback add_reference_;
PEFileParser::OnImportThunkCallback on_import_thunk_;
PEFile image_file_;
BlockGraph image_;
BlockGraph::AddressSpace address_space_;
base::NativeLibrary loaded_image_;
};
} // namespace
TEST_F(PEFileParserTest, ParseImageHeader) {
TestPEFileParser parser(image_file_, &address_space_, add_reference_);
PEFileParser::PEHeader header;
EXPECT_TRUE(parser.ParseImageHeader(&header));
// Check that the DOS header was read successfully.
ASSERT_TRUE(header.dos_header != NULL);
ASSERT_GE(header.dos_header->size(), sizeof(IMAGE_DOS_HEADER));
ASSERT_EQ(BlockGraph::DATA_BLOCK, header.dos_header->type());
// Check the underlying data.
ASSERT_GE(header.dos_header->data_size(), sizeof(IMAGE_DOS_HEADER));
const IMAGE_DOS_HEADER* dos_header =
reinterpret_cast<const IMAGE_DOS_HEADER*>(header.dos_header->data());
ASSERT_TRUE(dos_header != NULL);
ASSERT_EQ(IMAGE_DOS_SIGNATURE, dos_header->e_magic);
// Check that the DOS header references the NT headers.
ASSERT_EQ(header.nt_headers,
FindReferencedBlock(header.dos_header,
offsetof(IMAGE_DOS_HEADER, e_lfanew)));
// Check the NT headers.
ASSERT_TRUE(header.nt_headers != NULL);
ASSERT_GT(header.nt_headers->size(), sizeof(IMAGE_NT_HEADERS));
ASSERT_EQ(header.nt_headers->data_size(), header.nt_headers->size());
ASSERT_EQ(BlockGraph::DATA_BLOCK, header.nt_headers->type());
const IMAGE_NT_HEADERS* nt_headers =
reinterpret_cast<const IMAGE_NT_HEADERS*>(header.nt_headers->data());
ASSERT_TRUE(nt_headers != NULL);
ASSERT_EQ(IMAGE_NT_OPTIONAL_HDR32_MAGIC, nt_headers->OptionalHeader.Magic);
const IMAGE_SECTION_HEADER* section_headers = NULL;
// Check that the data accounts for the image section headers.
ASSERT_EQ(nt_headers->FileHeader.NumberOfSections * sizeof(*section_headers) +
sizeof(*nt_headers), header.nt_headers->data_size());
}
TEST_F(PEFileParserTest, ParseExportDir) {
TestPEFileParser parser(image_file_, &address_space_, add_reference_);
PEFileParser::PEHeader header;
EXPECT_TRUE(parser.ParseImageHeader(&header));
const IMAGE_NT_HEADERS* nt_headers =
reinterpret_cast<const IMAGE_NT_HEADERS*>(header.nt_headers->data());
const IMAGE_DATA_DIRECTORY& dir =
nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
EXPECT_TRUE(parser.ParseExportDir(dir) != NULL);
base::NativeLibraryLoadError error;
loaded_image_ = base::LoadNativeLibrary(
testing::GetExeRelativePath(testing::kTestDllName), &error);
ASSERT_TRUE(loaded_image_ != NULL);
ASSERT_TRUE(ExportIsReferenced("function1"));
// function2 is exported by ordinal only.
ASSERT_TRUE(ExportIsReferenced(reinterpret_cast<const char*>(7)));
ASSERT_TRUE(ExportIsReferenced("function3"));
}
TEST_F(PEFileParserTest, ParseEmptyExportDir) {
base::FilePath no_exports_dll_path = testing::GetOutputRelativePath(
testing::kNoExportsDllName);
pe::PEFile no_exports_dll_image;
ASSERT_TRUE(no_exports_dll_image.Init(no_exports_dll_path));
TestPEFileParser parser(no_exports_dll_image,
&address_space_, add_reference_);
PEFileParser::PEHeader header;
EXPECT_TRUE(parser.ParseImageHeader(&header));
const IMAGE_NT_HEADERS* nt_headers =
reinterpret_cast<const IMAGE_NT_HEADERS*>(header.nt_headers->data());
const IMAGE_DATA_DIRECTORY& dir =
nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
EXPECT_TRUE(parser.ParseExportDir(dir) != NULL);
}
TEST_F(PEFileParserTest, ParseImportDir) {
TestPEFileParser parser(image_file_, &address_space_, add_reference_);
parser.set_on_import_thunk(on_import_thunk_);
PEFileParser::PEHeader header;
EXPECT_TRUE(parser.ParseImageHeader(&header));
const IMAGE_NT_HEADERS* nt_headers =
reinterpret_cast<const IMAGE_NT_HEADERS*>(header.nt_headers->data());
const IMAGE_DATA_DIRECTORY& dir =
nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
BlockGraph::Block* block = parser.ParseImportDir(dir);
ASSERT_TRUE(block != NULL);
// Test that we have the two import descriptors we expect, plus the sentinel.
size_t num_descriptors = block->size() / sizeof(IMAGE_IMPORT_DESCRIPTOR);
ASSERT_EQ(3, num_descriptors);
ASSERT_TRUE(block->data() != NULL);
ASSERT_EQ(block->size(), block->data_size());
std::set<std::string> import_names;
for (size_t i = 0; i < num_descriptors - 1; ++i) {
size_t element_offset = sizeof(IMAGE_IMPORT_DESCRIPTOR) * i;
BlockGraph::Block* name_block =
FindReferencedBlock(block, element_offset +
offsetof(IMAGE_IMPORT_DESCRIPTOR, Name));
ASSERT_TRUE(name_block != NULL);
const char* name =
reinterpret_cast<const char*>(name_block->data());
EXPECT_TRUE(import_names.insert(name).second);
// Now retrieve the IAT and INT blocks.
BlockGraph::Block* iat_block =
FindReferencedBlock(block, element_offset +
offsetof(IMAGE_IMPORT_DESCRIPTOR, FirstThunk));
BlockGraph::Block* int_block =
FindReferencedBlock(block, element_offset +
offsetof(IMAGE_IMPORT_DESCRIPTOR, OriginalFirstThunk));
ASSERT_TRUE(iat_block != NULL);
ASSERT_TRUE(int_block != NULL);
ASSERT_EQ(iat_block->size(), int_block->size());
ASSERT_EQ(iat_block->data_size(), int_block->data_size());
ASSERT_EQ(0,
memcmp(iat_block->data(), int_block->data(), iat_block->data_size()));
// Now check that each slot, save for the last one, in the IAT/INT
// points to a name block or else is an ordinal.
size_t num_thunks = iat_block->data_size() / sizeof(IMAGE_THUNK_DATA) - 1;
const IMAGE_THUNK_DATA* iat =
reinterpret_cast<const IMAGE_THUNK_DATA*>(iat_block->data());
for (size_t i = 0; i < num_thunks; ++i) {
if (!IMAGE_ORDINAL(iat[i].u1.Ordinal)) {
size_t thunk_offset = sizeof(IMAGE_THUNK_DATA) * i;
ASSERT_TRUE(FindReferencedBlock(iat_block, thunk_offset) != NULL);
ASSERT_TRUE(FindReferencedBlock(int_block, thunk_offset) != NULL);
}
}
}
// Check that the sentinel is all zero.
IMAGE_IMPORT_DESCRIPTOR zero = {};
const IMAGE_IMPORT_DESCRIPTOR* sentinel =
reinterpret_cast<const IMAGE_IMPORT_DESCRIPTOR*>(block->data()) +
num_descriptors - 1;
EXPECT_EQ(0, memcmp(sentinel, &zero, sizeof(zero)));
std::set<std::string> expected;
expected.insert("KERNEL32.dll");
expected.insert("export_dll.dll");
EXPECT_THAT(import_names, ContainerEq(expected));
// The number of expected symbols imported from kernel32.dll.
#if defined(NDEBUG)
// VC++ 2013 Release Build.
static size_t kNumKernel32Symbols = 63;
#else
// VC++ 2013 Debug/Coverage build.
static size_t kNumKernel32Symbols = 64;
#endif
// The number of expected symbols imported from export_dll.dll.
static const size_t kNumExportDllSymbols = 3;
ImportMap expected_import_map;
expected_import_map["KERNEL32.dll"] = kNumKernel32Symbols;
expected_import_map["export_dll.dll"] = kNumExportDllSymbols;
EXPECT_THAT(import_map_, ContainerEq(expected_import_map));
EXPECT_EQ(kNumKernel32Symbols + kNumExportDllSymbols, import_set_.size());
EXPECT_THAT(import_set_, Contains(std::make_pair(
std::string("KERNEL32.dll"), std::string("ExitProcess"))));
EXPECT_THAT(import_set_, Contains(std::make_pair(
std::string("export_dll.dll"), std::string("function1"))));
}
TEST_F(PEFileParserTest, ParseDelayImportDir) {
TestPEFileParser parser(image_file_, &address_space_, add_reference_);
PEFileParser::PEHeader header;
EXPECT_TRUE(parser.ParseImageHeader(&header));
const IMAGE_NT_HEADERS* nt_headers =
reinterpret_cast<const IMAGE_NT_HEADERS*>(header.nt_headers->data());
const IMAGE_DATA_DIRECTORY& dir =
nt_headers->OptionalHeader.DataDirectory[
IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT];
BlockGraph::Block* block = parser.ParseDelayImportDir(dir);
ASSERT_TRUE(block != NULL);
// Test that we have the import descriptors we expect - we expect
// the one delay import, plus the sentinel import descriptor to be
// chunked out.
size_t num_descriptors = block->size() / sizeof(ImgDelayDescr);
ASSERT_EQ(2, num_descriptors);
ASSERT_TRUE(block->data() != NULL);
ASSERT_EQ(block->size(), block->data_size());
std::set<std::string> import_names;
for (size_t i = 0; i < num_descriptors - 1; ++i) {
size_t element_offset = sizeof(ImgDelayDescr) * i;
BlockGraph::Block* name_block =
FindReferencedBlock(block, element_offset +
offsetof(ImgDelayDescr, rvaDLLName));
ASSERT_TRUE(name_block != NULL);
const char* name =
reinterpret_cast<const char*>(name_block->data());
EXPECT_TRUE(import_names.insert(name).second);
// Now retrieve the IAT, INT and BoundIAT blocks.
BlockGraph::Block* iat_block =
FindReferencedBlock(block, element_offset +
offsetof(ImgDelayDescr, rvaIAT));
BlockGraph::Block* int_block =
FindReferencedBlock(block, element_offset +
offsetof(ImgDelayDescr, rvaINT));
BlockGraph::Block* bound_iat_block =
FindReferencedBlock(block, element_offset +
offsetof(ImgDelayDescr, rvaBoundIAT));
ASSERT_TRUE(iat_block != NULL);
ASSERT_TRUE(int_block != NULL);
ASSERT_TRUE(bound_iat_block != NULL);
ASSERT_EQ(iat_block->size(), int_block->size());
ASSERT_EQ(iat_block->size(), bound_iat_block->size());
ASSERT_EQ(iat_block->data_size(), int_block->data_size());
ASSERT_EQ(iat_block->data_size(), bound_iat_block->data_size());
// Now check that each slot, save for the last one, in the INT
// points to a name block or else is an ordinal.
size_t num_thunks = iat_block->data_size() / sizeof(IMAGE_THUNK_DATA) - 1;
const IMAGE_THUNK_DATA* iat =
reinterpret_cast<const IMAGE_THUNK_DATA*>(int_block->data());
for (size_t i = 0; i < num_thunks; ++i) {
if (!IMAGE_ORDINAL(iat[i].u1.Ordinal)) {
size_t thunk_offset = sizeof(IMAGE_THUNK_DATA) * i;
ASSERT_TRUE(FindReferencedBlock(int_block, thunk_offset) != NULL);
}
}
}
// Check that the sentinel is all zero.
ImgDelayDescr zero = {};
const ImgDelayDescr* sentinel =
reinterpret_cast<const ImgDelayDescr*>(block->data()) +
num_descriptors - 1;
EXPECT_EQ(0, memcmp(sentinel, &zero, sizeof(zero)));
std::set<std::string> expected;
expected.insert("ole32.dll");
EXPECT_THAT(import_names, ContainerEq(expected));
}
TEST_F(PEFileParserTest, ParseImage) {
TestPEFileParser parser(image_file_, &address_space_, add_reference_);
PEFileParser::PEHeader header;
EXPECT_TRUE(parser.ParseImage(&header));
// Check that the DOS header was read successfully.
ASSERT_TRUE(header.dos_header != NULL);
ASSERT_GE(header.dos_header->size(), sizeof(IMAGE_DOS_HEADER));
ASSERT_EQ(BlockGraph::DATA_BLOCK, header.dos_header->type());
// Check the underlying data.
ASSERT_GE(header.dos_header->data_size(), sizeof(IMAGE_DOS_HEADER));
const IMAGE_DOS_HEADER* dos_header =
reinterpret_cast<const IMAGE_DOS_HEADER*>(header.dos_header->data());
ASSERT_TRUE(dos_header != NULL);
ASSERT_EQ(IMAGE_DOS_SIGNATURE, dos_header->e_magic);
// Check the NT headers.
ASSERT_TRUE(header.nt_headers != NULL);
ASSERT_GT(header.nt_headers->size(), sizeof(IMAGE_NT_HEADERS));
ASSERT_EQ(header.nt_headers->data_size(), header.nt_headers->size());
ASSERT_EQ(BlockGraph::DATA_BLOCK, header.nt_headers->type());
const IMAGE_NT_HEADERS* nt_headers =
reinterpret_cast<const IMAGE_NT_HEADERS*>(header.nt_headers->data());
ASSERT_TRUE(nt_headers != NULL);
ASSERT_EQ(IMAGE_NT_OPTIONAL_HDR32_MAGIC, nt_headers->OptionalHeader.Magic);
const IMAGE_SECTION_HEADER* section_headers = NULL;
// Check that the data accounts for the image section headers.
ASSERT_EQ(nt_headers->FileHeader.NumberOfSections * sizeof(*section_headers) +
sizeof(*nt_headers), header.nt_headers->data_size());
section_headers =
reinterpret_cast<const IMAGE_SECTION_HEADER*>(nt_headers + 1);
// Now check the various data directory sections we expect to be non NULL.
// We know the test dll has exports.
EXPECT_NO_FATAL_FAILURE(AssertDataDirectoryEntryValid(
header.data_directory[IMAGE_DIRECTORY_ENTRY_EXPORT]));
// And imports.
EXPECT_NO_FATAL_FAILURE(AssertDataDirectoryEntryValid(
header.data_directory[IMAGE_DIRECTORY_ENTRY_IMPORT]));
// And resources.
EXPECT_NO_FATAL_FAILURE(AssertDataDirectoryEntryValid(
header.data_directory[IMAGE_DIRECTORY_ENTRY_RESOURCE]));
// And relocs.
EXPECT_NO_FATAL_FAILURE(AssertDataDirectoryEntryValid(
header.data_directory[IMAGE_DIRECTORY_ENTRY_BASERELOC]));
// And a debug directory.
EXPECT_NO_FATAL_FAILURE(AssertDataDirectoryEntryValid(
header.data_directory[IMAGE_DIRECTORY_ENTRY_DEBUG]));
// And a tls directory?
// TODO(siggi): add some TLS data to the test DLL.
// EXPECT_NO_FATAL_FAILURE(AssertDataDirectoryEntryValid(
// header.data_directory[IMAGE_DIRECTORY_ENTRY_TLS]));
// And a load configuration directory.
EXPECT_NO_FATAL_FAILURE(AssertDataDirectoryEntryValid(
header.data_directory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG]));
// And a delay import directory.
EXPECT_NO_FATAL_FAILURE(AssertDataDirectoryEntryValid(
header.data_directory[IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT]));
}
} // namespace pe