blob: 3e3b7230d1f08c922494264b221d43084486a4af [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.h"
#include "base/native_library.h"
#include "base/path_service.h"
#include "base/files/file_path.h"
#include "base/strings/string_util.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "syzygy/core/unittest_util.h"
#include "syzygy/pe/unittest_util.h"
namespace pe {
namespace {
using core::AbsoluteAddress;
using core::FileOffsetAddress;
using core::RelativeAddress;
class PEFileTest: public testing::PELibUnitTest {
typedef testing::PELibUnitTest Super;
public:
PEFileTest() : test_dll_(NULL) {
}
virtual void SetUp() OVERRIDE {
Super::SetUp();
base::FilePath test_dll =
testing::GetExeRelativePath(testing::kTestDllName);
base::NativeLibraryLoadError error;
test_dll_ = base::LoadNativeLibrary(test_dll, &error);
ASSERT_TRUE(image_file_.Init(test_dll));
base::FilePath test_dll_64 =
testing::GetExeRelativePath(testing::kTestDllName64);
test_dll_64_ = base::LoadNativeLibrary(test_dll_64, &error);
ASSERT_TRUE(image_file_64_.Init(test_dll_64));
}
virtual void TearDown() OVERRIDE {
base::UnloadNativeLibrary(test_dll_);
base::UnloadNativeLibrary(test_dll_64_);
Super::TearDown();
}
void TestAddressesAreConsistent(RelativeAddress rel,
AbsoluteAddress abs,
FileOffsetAddress off) {
AbsoluteAddress abs2;
RelativeAddress rel2;
FileOffsetAddress off2;
ASSERT_TRUE(image_file_.Translate(rel, &abs2));
ASSERT_EQ(abs, abs2);
ASSERT_TRUE(image_file_.Translate(abs, &rel2));
ASSERT_EQ(rel, rel2);
ASSERT_TRUE(image_file_.Translate(off, &rel2));
ASSERT_EQ(rel, rel2);
ASSERT_TRUE(image_file_.Translate(rel, &off2));
ASSERT_EQ(off, off2);
}
protected:
pe::PEFile image_file_;
pe::PEFile64 image_file_64_;
base::NativeLibrary test_dll_;
base::NativeLibrary test_dll_64_;
};
// Functor for comparing import infos.
struct CompareImportInfo {
bool operator()(const PEFile::ImportInfo& ii1,
const PEFile::ImportInfo& ii2) {
if (ii1.hint < ii2.hint)
return true;
if (ii1.hint > ii2.hint)
return false;
if (ii1.ordinal < ii2.ordinal)
return true;
if (ii1.ordinal > ii2.ordinal)
return false;
return ii1.function < ii2.function;
}
};
} // namespace
TEST_F(PEFileTest, Create) {
PEFile image_file;
ASSERT_EQ(NULL, image_file.dos_header());
ASSERT_EQ(NULL, image_file.nt_headers());
ASSERT_EQ(NULL, image_file.section_headers());
}
TEST_F(PEFileTest, Init) {
EXPECT_TRUE(image_file_.dos_header() != NULL);
EXPECT_TRUE(image_file_.nt_headers() != NULL);
EXPECT_TRUE(image_file_.section_headers() != NULL);
}
TEST_F(PEFileTest, GetImageData) {
const IMAGE_NT_HEADERS* nt_headers = image_file_.nt_headers();
ASSERT_TRUE(nt_headers != NULL);
const IMAGE_DATA_DIRECTORY* exports =
&nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
// We should be able to read the export directory.
ASSERT_TRUE(image_file_.GetImageData(RelativeAddress(exports->VirtualAddress),
exports->Size) != NULL);
// We should be able to read it using an absolute address as well.
AbsoluteAddress abs_addr;
ASSERT_TRUE(image_file_.Translate(RelativeAddress(exports->VirtualAddress),
&abs_addr));
ASSERT_TRUE(image_file_.GetImageData(abs_addr, exports->Size) != NULL);
// But there ought to be a gap in the image data past the header size.
ASSERT_TRUE(image_file_.GetImageData(
RelativeAddress(nt_headers->OptionalHeader.SizeOfHeaders), 1) == NULL);
}
TEST_F(PEFileTest, ReadImage) {
const IMAGE_NT_HEADERS* nt_headers = image_file_.nt_headers();
ASSERT_TRUE(nt_headers != NULL);
const IMAGE_DATA_DIRECTORY* exports =
&nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
// We should be able to read the export directory.
IMAGE_EXPORT_DIRECTORY export_dir = {};
ASSERT_TRUE(image_file_.ReadImage(RelativeAddress(exports->VirtualAddress),
&export_dir,
sizeof(export_dir)));
// Check that we actually read something.
IMAGE_EXPORT_DIRECTORY zero_export_dir = {};
ASSERT_NE(0, memcmp(&export_dir, &zero_export_dir, sizeof(export_dir)));
// Now test the ReadImageString function.
std::vector<RelativeAddress> names(export_dir.NumberOfNames);
ASSERT_TRUE(image_file_.ReadImage(RelativeAddress(export_dir.AddressOfNames),
&names.at(0),
sizeof(names[0]) * names.size()));
// Test the same thing using an absolute address.
AbsoluteAddress abs_names_addr;
ASSERT_TRUE(image_file_.Translate(RelativeAddress(export_dir.AddressOfNames),
&abs_names_addr));
std::vector<RelativeAddress> names2(export_dir.NumberOfNames);
ASSERT_TRUE(image_file_.ReadImage(abs_names_addr, &names2.at(0),
sizeof(names2[0]) * names2.size()));
ASSERT_EQ(names, names2);
// Read all the export name strings.
for (size_t i = 0; i < names.size(); ++i) {
std::string name1;
ASSERT_TRUE(image_file_.ReadImageString(names[i], &name1));
ASSERT_TRUE(name1 == "function1" ||
name1 == "function3" ||
name1 == "DllMain" ||
name1 == "CreateFileW" ||
name1 == "TestUnusedFuncs" ||
name1 == "TestExport" ||
name1 == "LabelTestFunc" ||
name1 == "BringInOle32DelayLib" ||
name1 == "TestFunctionWithNoPrivateSymbols" ||
name1 == "FuncWithOffsetOutOfImage" ||
name1 == "EndToEndTest");
std::string name2;
AbsoluteAddress abs_addr;
ASSERT_TRUE(image_file_.Translate(names[i], &abs_addr));
ASSERT_TRUE(image_file_.ReadImageString(abs_addr, &name2));
ASSERT_EQ(name1, name2);
}
}
TEST_F(PEFileTest, Contains) {
RelativeAddress relative_base(0);
AbsoluteAddress absolute_base;
size_t image_size = image_file_.nt_headers()->OptionalHeader.SizeOfImage;
RelativeAddress relative_end(image_size);
AbsoluteAddress absolute_end(
image_file_.nt_headers()->OptionalHeader.ImageBase + image_size);
ASSERT_TRUE(image_file_.Translate(relative_base, &absolute_base));
ASSERT_TRUE(image_file_.Contains(relative_base, 1));
ASSERT_TRUE(image_file_.Contains(absolute_base, 1));
ASSERT_FALSE(image_file_.Contains(absolute_base - 1, 1));
ASSERT_FALSE(image_file_.Contains(absolute_end, 1));
ASSERT_FALSE(image_file_.Contains(relative_end, 1));
// TODO(rogerm): test for inclusion at the end of the address space
// The way the address space is built only captures the ranges
// specified as sections in the headers, not the overall image size.
// Either the test needs to be more invasive or the data structure
// needs to be more broadly representative. Note sure which, but
// it's not critical.
// ASSERT_TRUE(image_file_.Contains(absolute_end - 1, 1));
}
TEST_F(PEFileTest, Translate) {
// Try an address inside the headers (outside of any section).
AbsoluteAddress abs(image_file_.nt_headers()->OptionalHeader.ImageBase + 3);
RelativeAddress rel(3);
FileOffsetAddress off(3);
ASSERT_NO_FATAL_FAILURE(TestAddressesAreConsistent(rel, abs, off));
// Now try an address in each of the sections.
size_t i = 0;
for (; i < image_file_.nt_headers()->FileHeader.NumberOfSections; ++i) {
const IMAGE_SECTION_HEADER* section = image_file_.section_header(i);
AbsoluteAddress abs(section->VirtualAddress +
image_file_.nt_headers()->OptionalHeader.ImageBase + i);
RelativeAddress rel(section->VirtualAddress + i);
FileOffsetAddress off(section->PointerToRawData + i);
ASSERT_NO_FATAL_FAILURE(TestAddressesAreConsistent(rel, abs, off));
}
}
TEST_F(PEFileTest, TranslateOffImageFails) {
const IMAGE_SECTION_HEADER* section = image_file_.section_header(
image_file_.nt_headers()->FileHeader.NumberOfSections - 1);
AbsoluteAddress abs_end(image_file_.nt_headers()->OptionalHeader.ImageBase +
image_file_.nt_headers()->OptionalHeader.SizeOfImage);
RelativeAddress rel_end(image_file_.nt_headers()->OptionalHeader.SizeOfImage);
FileOffsetAddress off_end(section->PointerToRawData + section->SizeOfRawData);
AbsoluteAddress abs;
RelativeAddress rel;
FileOffsetAddress off;
ASSERT_FALSE(image_file_.Translate(rel_end, &abs));
ASSERT_FALSE(image_file_.Translate(abs_end, &rel));
ASSERT_FALSE(image_file_.Translate(off_end, &rel));
ASSERT_FALSE(image_file_.Translate(rel_end, &off));
}
TEST_F(PEFileTest, TranslateFileOffsetSpaceNotContiguous) {
size_t data_index = image_file_.GetSectionIndex(".data");
ASSERT_NE(kInvalidSection, data_index);
const IMAGE_SECTION_HEADER* data =
image_file_.section_header(data_index);
ASSERT_TRUE(data != NULL);
RelativeAddress rel1, rel2;
rel1.set_value(data->VirtualAddress + data->SizeOfRawData - 1);
rel2.set_value(data->VirtualAddress + data->SizeOfRawData);
FileOffsetAddress off1, off2;
ASSERT_TRUE(image_file_.Translate(rel1, &off1));
ASSERT_FALSE(image_file_.Translate(rel2, &off2));
RelativeAddress rel3;
off2 = off1 + 1;
ASSERT_TRUE(image_file_.Translate(off2, &rel3));
ASSERT_LT(1, rel3 - rel2);
}
TEST_F(PEFileTest, DecodeRelocs) {
PEFile::RelocSet relocs;
ASSERT_TRUE(image_file_.DecodeRelocs(&relocs));
PEFile::RelocMap reloc_values;
ASSERT_TRUE(image_file_.ReadRelocs(relocs, &reloc_values));
// We expect to have some relocs to validate and we expect that
// all relocation table entries and their corresponding values
// fall within the image's address space
ASSERT_TRUE(!reloc_values.empty());
PEFile::RelocMap::const_iterator i = reloc_values.begin();
for (; i != reloc_values.end(); ++i) {
// Note:
// i->first is a relative pointer yielded by the relocation table
// i->second is the absolute value of that pointer (i.e., the relocation)
const RelativeAddress &pointer_location(i->first);
const AbsoluteAddress &pointer_value(i->second);
ASSERT_TRUE(image_file_.Contains(pointer_location, sizeof(pointer_value)));
}
}
TEST_F(PEFileTest, DecodeExports) {
PEFile::ExportInfoVector exports;
ASSERT_TRUE(image_file_.DecodeExports(&exports));
// This must match the information in the test_dll.def file.
PEFile::ExportInfo expected[] = {
{ RelativeAddress(0), "", "", 1 },
{ RelativeAddress(0), "BringInOle32DelayLib", "", 2 },
{ RelativeAddress(0), "TestExport", "", 3 },
{ RelativeAddress(0), "TestUnusedFuncs", "", 4 },
{ RelativeAddress(0), "LabelTestFunc", "", 5 },
{ RelativeAddress(0), "TestFunctionWithNoPrivateSymbols", "", 6 },
{ RelativeAddress(0), "DllMain", "", 7 },
{ RelativeAddress(0), "function3", "", 9 },
{ RelativeAddress(0), "CreateFileW", "kernel32.CreateFileW", 13 },
{ RelativeAddress(0), "function1", "", 17 },
{ RelativeAddress(0), "FuncWithOffsetOutOfImage", "", 18 },
};
ASSERT_EQ(ARRAYSIZE(expected), exports.size());
const uint8* module_base = reinterpret_cast<const uint8*>(test_dll_);
// Resolve the exports and compare.
for (size_t i = 0; i < arraysize(expected); ++i) {
if (expected[i].forward.empty()) {
// Look up the functions by ordinal.
const uint8* function = reinterpret_cast<const uint8*>(
base::GetFunctionPointerFromNativeLibrary(
test_dll_, reinterpret_cast<const char*>(expected[i].ordinal)));
EXPECT_TRUE(function != NULL);
expected[i].function = RelativeAddress(function - module_base);
}
EXPECT_EQ(expected[i].function, exports.at(i).function);
EXPECT_EQ(expected[i].name, exports.at(i).name);
EXPECT_EQ(expected[i].forward, exports.at(i).forward);
EXPECT_EQ(expected[i].ordinal, exports.at(i).ordinal);
}
}
TEST_F(PEFileTest, DecodeImports) {
PEFile::ImportDllVector imports;
ASSERT_TRUE(image_file_.DecodeImports(&imports));
// Validation the read imports section.
// The test image imports at least kernel32 and the export_dll.
ASSERT_LE(2U, imports.size());
for (size_t i = 0; i < imports.size(); ++i) {
PEFile::ImportDll& dll = imports[i];
if (0 == base::strcasecmp("export_dll.dll", dll.name.c_str())) {
ASSERT_EQ(4, dll.functions.size());
// Depending on the optimization settings the order of these elements can
// actually be different.
ASSERT_THAT(dll.functions, testing::WhenSortedBy(
CompareImportInfo(),
testing::ElementsAre(
PEFile::ImportInfo(0, 0, "function1"),
PEFile::ImportInfo(0, 7, ""),
PEFile::ImportInfo(1, 0, "function3"),
PEFile::ImportInfo(2, 0, "kExportedData"))));
}
}
}
TEST_F(PEFileTest, DecodeImportsX64) {
PEFile64::ImportDllVector imports;
ASSERT_TRUE(image_file_64_.DecodeImports(&imports));
// Validate the read imports section.
// The test image imports at least kernel32 and user32.
ASSERT_LE(2U, imports.size());
int expected_imports = 0;
for (size_t i = 0; i < imports.size(); ++i) {
PEFile64::ImportDll& dll = imports[i];
if (base::strcasecmp("kernel32.dll", dll.name.c_str()) == 0 ||
base::strcasecmp("user32.dll", dll.name.c_str()) == 0) {
expected_imports++;
}
}
EXPECT_EQ(2, expected_imports);
}
TEST_F(PEFileTest, GetSectionIndexByRelativeAddress) {
size_t num_sections = image_file_.nt_headers()->FileHeader.NumberOfSections;
for (size_t i = 0; i < num_sections; ++i) {
RelativeAddress section_start(
image_file_.section_header(i)->VirtualAddress);
EXPECT_EQ(i, image_file_.GetSectionIndex(section_start, 1));
}
RelativeAddress off_end(image_file_.nt_headers()->OptionalHeader.SizeOfImage +
0x10000);
EXPECT_EQ(kInvalidSection, image_file_.GetSectionIndex(off_end, 1));
}
TEST_F(PEFileTest, GetSectionIndexByAbsoluteAddress) {
size_t image_base = image_file_.nt_headers()->OptionalHeader.ImageBase;
size_t num_sections = image_file_.nt_headers()->FileHeader.NumberOfSections;
for (size_t i = 0; i < num_sections; ++i) {
AbsoluteAddress section_start(
image_file_.section_header(i)->VirtualAddress + image_base);
EXPECT_EQ(i, image_file_.GetSectionIndex(section_start, 1));
}
AbsoluteAddress off_end(image_file_.nt_headers()->OptionalHeader.SizeOfImage +
0x10000 + image_base);
EXPECT_EQ(kInvalidSection, image_file_.GetSectionIndex(off_end, 1));
}
TEST_F(PEFileTest, GetSectionIndexByName) {
size_t num_sections = image_file_.nt_headers()->FileHeader.NumberOfSections;
for (size_t i = 0; i < num_sections; ++i) {
std::string name = image_file_.GetSectionName(i);
EXPECT_EQ(i, image_file_.GetSectionIndex(name.c_str()));
}
EXPECT_EQ(kInvalidSection, image_file_.GetSectionIndex(".foobar"));
}
TEST_F(PEFileTest, GetSectionHeaderByRelativeAddress) {
size_t num_sections = image_file_.nt_headers()->FileHeader.NumberOfSections;
for (size_t i = 0; i < num_sections; ++i) {
RelativeAddress section_start(
image_file_.section_header(i)->VirtualAddress);
EXPECT_EQ(image_file_.section_header(i),
image_file_.GetSectionHeader(section_start, 1));
}
RelativeAddress off_end(image_file_.nt_headers()->OptionalHeader.SizeOfImage +
0x10000);
EXPECT_EQ(kInvalidSection, image_file_.GetSectionIndex(off_end, 1));
}
TEST_F(PEFileTest, GetSectionHeaderByAbsoluteAddress) {
size_t image_base = image_file_.nt_headers()->OptionalHeader.ImageBase;
size_t num_sections = image_file_.nt_headers()->FileHeader.NumberOfSections;
for (size_t i = 0; i < num_sections; ++i) {
AbsoluteAddress section_start(
image_file_.section_header(i)->VirtualAddress + image_base);
EXPECT_EQ(image_file_.section_header(i),
image_file_.GetSectionHeader(section_start, 1));
}
AbsoluteAddress off_end(image_file_.nt_headers()->OptionalHeader.SizeOfImage +
0x10000 + image_base);
EXPECT_EQ(kInvalidSection, image_file_.GetSectionIndex(off_end, 1));
}
TEST_F(PEFileTest, GetSectionHeaderByName) {
size_t num_sections = image_file_.nt_headers()->FileHeader.NumberOfSections;
for (size_t i = 0; i < num_sections; ++i) {
std::string name = image_file_.GetSectionName(i);
EXPECT_EQ(image_file_.section_header(i),
image_file_.GetSectionHeader(name.c_str()));
}
EXPECT_EQ(NULL, image_file_.GetSectionHeader(".foobar"));
}
TEST(PEFileSignatureTest, Serialization) {
PEFile::Signature sig;
sig.path = L"C:\foo\bar.dll";
sig.base_address = AbsoluteAddress(0x1000000);
sig.module_size = 12345;
sig.module_time_date_stamp = 9999999;
sig.module_checksum = 0xbaadf00d;
EXPECT_TRUE(testing::TestSerialization(sig));
}
TEST(PEFileSignatureTest, Consistency) {
PEFile::Signature sig1;
sig1.path = L"C:\\foo\\bar.dll";
sig1.base_address = AbsoluteAddress(0x1000000);
sig1.module_size = 12345;
sig1.module_time_date_stamp = 9999999;
sig1.module_checksum = 0xbaadf00d;
// sig2 is the same, but with a different module path.
PEFile::Signature sig2(sig1);
sig2.path = L"C:\\foo\\bar.exe";
EXPECT_FALSE(sig1 == sig2);
EXPECT_TRUE(sig1.IsConsistent(sig2));
EXPECT_TRUE(sig1.IsConsistentExceptForChecksum(sig2));
sig2.module_checksum = sig1.module_checksum + 100;
EXPECT_FALSE(sig1.IsConsistent(sig2));
EXPECT_TRUE(sig1.IsConsistentExceptForChecksum(sig2));
sig2.module_checksum = sig1.module_checksum;
sig2.base_address = sig1.base_address + 0x1000;
EXPECT_FALSE(sig1.IsConsistent(sig2));
EXPECT_FALSE(sig1.IsConsistentExceptForChecksum(sig2));
sig2.base_address = sig1.base_address;
sig2.module_size = sig2.module_size + 0x1000;
EXPECT_FALSE(sig1.IsConsistent(sig2));
EXPECT_FALSE(sig1.IsConsistentExceptForChecksum(sig2));
}
} // namespace pe