Source code
Revision control
Copy as Markdown
Other Tools
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
// This test makes sure mozilla::nt::PEExportSection can parse the export
// section of a local process, and a remote process even though it's
// modified by an external code.
#include "mozilla/CmdLineAndEnvUtils.h"
#include "mozilla/NativeNt.h"
#include "nsWindowsDllInterceptor.h"
#include <stdio.h>
#include <windows.h>
#define EXPORT_FUNCTION_EQ(name, func) \
(GetProcAddress(imageBase, name) == reinterpret_cast<void*>(func))
#define VERIFY_EXPORT_FUNCTION(tables, name, expected, errorMessage) \
do { \
if (tables.GetProcAddress(name) != reinterpret_cast<void*>(expected)) { \
printf("TEST-FAILED | TestPEExportSection | %s", errorMessage); \
return kTestFail; \
} \
} while (0)
using namespace mozilla::nt;
using mozilla::interceptor::MMPolicyInProcess;
using mozilla::interceptor::MMPolicyOutOfProcess;
using LocalPEExportSection = PEExportSection<MMPolicyInProcess>;
using RemotePEExportSection = PEExportSection<MMPolicyOutOfProcess>;
constexpr DWORD kEventTimeoutinMs = 5000;
const wchar_t kProcessControlEventName[] =
L"TestPEExportSection.Process.Control.Event";
enum TestResult : int {
kTestSuccess = 0,
kTestFail,
kTestSkip,
};
// These strings start with the same keyword to make sure we don't do substring
// match. Moreover, kSecretFunctionInvalid is purposely longer than the
// combination of the other two strings and located in between the other two
// strings to effectively test binary search.
const char kSecretFunction[] = "Secret";
const char kSecretFunctionInvalid[] = "Secret invalid long name";
const char kSecretFunctionWithSuffix[] = "Secret2";
const wchar_t* kNoModification = L"--NoModification";
const wchar_t* kNoExport = L"--NoExport";
const wchar_t* kModifyTableEntry = L"--ModifyTableEntry";
const wchar_t* kModifyTable = L"--ModifyTable";
const wchar_t* kModifyDirectoryEntry = L"--ModifyDirectoryEntry";
const wchar_t* kExportByOrdinal = L"--ExportByOrdinal";
// Use the global variable to pass the child process's error status to the
// parent process. We don't use a process's exit code to keep the test simple.
int gChildProcessStatus = 0;
// These functions are exported by linker or export section tampering at
// runtime. Each of function bodies needs to be different to avoid ICF.
extern "C" __declspec(dllexport) int Export1() { return 0; }
extern "C" __declspec(dllexport) int Export2() { return 1; }
int SecretFunction1() { return 100; }
int SecretFunction2() { return 101; }
// This class allocates a writable region downstream of the mapped image
// and prepares it as a valid export section.
class ExportDirectoryPatcher final {
static constexpr int kRegionAllocationTryLimit = 100;
static constexpr int kNumOfTableEntries = 2;
// VirtualAlloc sometimes fails if a desired base address is too small.
// Define a minimum desired base to reduce the number of allocation tries.
static constexpr uintptr_t kMinimumAllocationPoint = 0x8000000;
struct ExportDirectory {
IMAGE_EXPORT_DIRECTORY mDirectoryHeader;
DWORD mExportAddressTable[kNumOfTableEntries];
DWORD mExportNameTable[kNumOfTableEntries];
WORD mExportOrdinalTable[kNumOfTableEntries];
char mNameBuffer1[sizeof(kSecretFunction)];
char mNameBuffer2[sizeof(kSecretFunctionWithSuffix)];
template <typename T>
static DWORD PtrToRVA(T aPtr, uintptr_t aBase) {
return reinterpret_cast<uintptr_t>(aPtr) - aBase;
}
explicit ExportDirectory(uintptr_t aImageBase) : mDirectoryHeader{} {
mDirectoryHeader.Base = 1;
mExportAddressTable[0] = PtrToRVA(SecretFunction1, aImageBase);
mExportAddressTable[1] = PtrToRVA(SecretFunction2, aImageBase);
mExportNameTable[0] = PtrToRVA(mNameBuffer1, aImageBase);
mExportNameTable[1] = PtrToRVA(mNameBuffer2, aImageBase);
mExportOrdinalTable[0] = 0;
mExportOrdinalTable[1] = 1;
strcpy(mNameBuffer1, kSecretFunction);
strcpy(mNameBuffer2, kSecretFunctionWithSuffix);
}
};
uintptr_t mImageBase;
ExportDirectory* mNewExportDirectory;
DWORD PtrToRVA(const void* aPtr) const {
return reinterpret_cast<uintptr_t>(aPtr) - mImageBase;
}
public:
explicit ExportDirectoryPatcher(HMODULE aModule)
: mImageBase(PEHeaders::HModuleToBaseAddr<uintptr_t>(aModule)),
mNewExportDirectory(nullptr) {
SYSTEM_INFO si = {};
::GetSystemInfo(&si);
int numPagesRequired = ((sizeof(ExportDirectory) - 1) / si.dwPageSize) + 1;
uintptr_t desiredBase = mImageBase + si.dwAllocationGranularity;
desiredBase = std::max(desiredBase, kMinimumAllocationPoint);
for (int i = 0; i < kRegionAllocationTryLimit; ++i) {
void* allocated =
::VirtualAlloc(reinterpret_cast<void*>(desiredBase),
numPagesRequired * si.dwPageSize,
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (allocated) {
// Use the end of a allocated page as ExportDirectory in order to test
// the boundary between a commit page and a non-commited page.
allocated = reinterpret_cast<uint8_t*>(allocated) +
(numPagesRequired * si.dwPageSize) -
sizeof(ExportDirectory);
mNewExportDirectory = new (allocated) ExportDirectory(mImageBase);
return;
}
desiredBase += si.dwAllocationGranularity;
}
gChildProcessStatus = kTestSkip;
printf(
"TEST-SKIP | TestPEExportSection | "
"Giving up finding an allocatable space following the mapped image.\n");
}
~ExportDirectoryPatcher() {
// Intentionally leave mNewExportDirectory leaked to keep a patched data
// available until the process is terminated.
}
explicit operator bool() const { return !!mNewExportDirectory; }
void PopulateDirectory(IMAGE_EXPORT_DIRECTORY& aOutput) const {
aOutput.NumberOfFunctions = aOutput.NumberOfNames = kNumOfTableEntries;
aOutput.AddressOfFunctions =
PtrToRVA(mNewExportDirectory->mExportAddressTable);
aOutput.AddressOfNames = PtrToRVA(mNewExportDirectory->mExportNameTable);
aOutput.AddressOfNameOrdinals =
PtrToRVA(mNewExportDirectory->mExportOrdinalTable);
}
void PopulateDirectoryEntry(IMAGE_DATA_DIRECTORY& aOutput) const {
PopulateDirectory(mNewExportDirectory->mDirectoryHeader);
aOutput.VirtualAddress = PtrToRVA(&mNewExportDirectory->mDirectoryHeader);
aOutput.Size = sizeof(ExportDirectory);
}
};
// This exports SecretFunction1 as "Export1" by replacing an entry of the
// export address table.
void ModifyExportAddressTableEntry() {
MMPolicyInProcess policy;
HMODULE imageBase = ::GetModuleHandleW(nullptr);
auto ourExe = LocalPEExportSection::Get(imageBase, policy);
auto addressTableEntry =
const_cast<DWORD*>(ourExe.FindExportAddressTableEntry("Export1"));
if (!addressTableEntry) {
gChildProcessStatus = kTestFail;
return;
}
mozilla::AutoVirtualProtect protection(
addressTableEntry, sizeof(*addressTableEntry), PAGE_READWRITE);
if (!protection) {
gChildProcessStatus = kTestFail;
return;
}
*addressTableEntry = reinterpret_cast<uintptr_t>(SecretFunction1) -
PEHeaders::HModuleToBaseAddr<uintptr_t>(imageBase);
if (!EXPORT_FUNCTION_EQ("Export1", SecretFunction1) ||
!EXPORT_FUNCTION_EQ("Export2", Export2)) {
gChildProcessStatus = kTestFail;
}
}
// This switches the entire address table into one exporting SecretFunction1
// and SecretFunction2.
void ModifyExportAddressTable() {
MMPolicyInProcess policy;
HMODULE imageBase = ::GetModuleHandleW(nullptr);
auto ourExe = LocalPEExportSection::Get(imageBase, policy);
auto exportDirectory = ourExe.GetExportDirectory();
if (!exportDirectory) {
gChildProcessStatus = kTestFail;
return;
}
mozilla::AutoVirtualProtect protection(
exportDirectory, sizeof(*exportDirectory), PAGE_READWRITE);
if (!protection) {
gChildProcessStatus = kTestFail;
return;
}
ExportDirectoryPatcher patcher(imageBase);
if (!patcher) {
return;
}
patcher.PopulateDirectory(*exportDirectory);
if (GetProcAddress(imageBase, "Export1") ||
GetProcAddress(imageBase, "Export2") ||
!EXPORT_FUNCTION_EQ(kSecretFunction, SecretFunction1) ||
!EXPORT_FUNCTION_EQ(kSecretFunctionWithSuffix, SecretFunction2)) {
gChildProcessStatus = kTestFail;
}
}
// This hides all export functions by setting the table size to 0.
void HideExportSection() {
HMODULE imageBase = ::GetModuleHandleW(nullptr);
PEHeaders ourExe(imageBase);
auto sectionTable =
ourExe.GetImageDirectoryEntryPtr(IMAGE_DIRECTORY_ENTRY_EXPORT);
mozilla::AutoVirtualProtect protection(sectionTable, sizeof(*sectionTable),
PAGE_READWRITE);
if (!protection) {
gChildProcessStatus = kTestFail;
return;
}
sectionTable->VirtualAddress = sectionTable->Size = 0;
if (GetProcAddress(imageBase, "Export1") ||
GetProcAddress(imageBase, "Export2")) {
gChildProcessStatus = kTestFail;
}
}
// This makes the export directory entry point to a new export section
// which exports SecretFunction1 and SecretFunction2.
void ModifyExportDirectoryEntry() {
HMODULE imageBase = ::GetModuleHandleW(nullptr);
PEHeaders ourExe(imageBase);
auto sectionTable =
ourExe.GetImageDirectoryEntryPtr(IMAGE_DIRECTORY_ENTRY_EXPORT);
mozilla::AutoVirtualProtect protection(sectionTable, sizeof(*sectionTable),
PAGE_READWRITE);
if (!protection) {
gChildProcessStatus = kTestFail;
return;
}
ExportDirectoryPatcher patcher(imageBase);
if (!patcher) {
return;
}
patcher.PopulateDirectoryEntry(*sectionTable);
if (GetProcAddress(imageBase, "Export1") ||
GetProcAddress(imageBase, "Export2") ||
!EXPORT_FUNCTION_EQ(kSecretFunction, SecretFunction1) ||
!EXPORT_FUNCTION_EQ(kSecretFunctionWithSuffix, SecretFunction2)) {
gChildProcessStatus = kTestFail;
}
}
// This exports functions only by Ordinal by hiding the export name table.
void ExportByOrdinal() {
ModifyExportDirectoryEntry();
if (gChildProcessStatus != kTestSuccess) {
return;
}
MMPolicyInProcess policy;
HMODULE imageBase = ::GetModuleHandleW(nullptr);
auto ourExe = LocalPEExportSection::Get(imageBase, policy);
auto exportDirectory = ourExe.GetExportDirectory();
if (!exportDirectory) {
gChildProcessStatus = kTestFail;
return;
}
exportDirectory->NumberOfNames = 0;
if (GetProcAddress(imageBase, "Export1") ||
GetProcAddress(imageBase, "Export2") ||
GetProcAddress(imageBase, kSecretFunction) ||
GetProcAddress(imageBase, kSecretFunctionWithSuffix) ||
!EXPORT_FUNCTION_EQ(MAKEINTRESOURCE(1), SecretFunction1) ||
!EXPORT_FUNCTION_EQ(MAKEINTRESOURCE(2), SecretFunction2)) {
gChildProcessStatus = kTestFail;
}
}
class ChildProcess final {
nsAutoHandle mChildProcess;
nsAutoHandle mChildMainThread;
public:
static int Main(const nsAutoHandle& aEvent, const wchar_t* aOption) {
if (wcscmp(aOption, kNoModification) == 0) {
;
} else if (wcscmp(aOption, kNoExport) == 0) {
HideExportSection();
} else if (wcscmp(aOption, kModifyTableEntry) == 0) {
ModifyExportAddressTableEntry();
} else if (wcscmp(aOption, kModifyTable) == 0) {
ModifyExportAddressTable();
} else if (wcscmp(aOption, kModifyDirectoryEntry) == 0) {
ModifyExportDirectoryEntry();
} else if (wcscmp(aOption, kExportByOrdinal) == 0) {
ExportByOrdinal();
}
// Letting the parent process know the child process is ready.
::SetEvent(aEvent);
// The child process does not exit itself. It's force terminated by
// the parent process when all tests are done.
for (;;) {
::Sleep(100);
}
}
ChildProcess(const wchar_t* aExecutable, const wchar_t* aOption,
const nsAutoHandle& aEvent, const nsAutoHandle& aJob) {
const wchar_t* childArgv[] = {aExecutable, aOption};
auto cmdLine(mozilla::MakeCommandLine(std::size(childArgv), childArgv));
STARTUPINFOW si = {sizeof(si)};
PROCESS_INFORMATION pi;
BOOL ok = ::CreateProcessW(aExecutable, cmdLine.get(), nullptr, nullptr,
FALSE, 0, nullptr, nullptr, &si, &pi);
if (!ok) {
printf(
"TEST-FAILED | TestPEExportSection | "
"CreateProcessW falied - %08lx.\n",
GetLastError());
return;
}
if (aJob && !::AssignProcessToJobObject(aJob, pi.hProcess)) {
printf(
"TEST-FAILED | TestPEExportSection | "
"AssignProcessToJobObject falied - %08lx.\n",
GetLastError());
::TerminateProcess(pi.hProcess, 1);
return;
}
// Wait until requested modification is done in the child process.
if (::WaitForSingleObject(aEvent, kEventTimeoutinMs) != WAIT_OBJECT_0) {
printf(
"TEST-FAILED | TestPEExportSection | "
"Child process was not ready in time.\n");
return;
}
mChildProcess.own(pi.hProcess);
mChildMainThread.own(pi.hThread);
}
~ChildProcess() { ::TerminateProcess(mChildProcess, 0); }
operator HANDLE() const { return mChildProcess; }
TestResult GetStatus() const {
TestResult status = kTestSuccess;
if (!::ReadProcessMemory(mChildProcess, &gChildProcessStatus, &status,
sizeof(status), nullptr)) {
status = kTestFail;
printf(
"TEST-FAILED | TestPEExportSection | "
"ReadProcessMemory failed - %08lx\n",
GetLastError());
}
return status;
}
};
template <typename MMPolicy>
TestResult BasicTest(const MMPolicy& aMMPolicy) {
const bool isAppHelpLoaded = ::GetModuleHandleW(L"apphelp.dll");
// Use ntdll.dll because it does not have any forwarder RVA.
HMODULE ntdllImageBase = ::GetModuleHandleW(L"ntdll.dll");
auto ntdllExports = PEExportSection<MMPolicy>::Get(ntdllImageBase, aMMPolicy);
auto exportDir = ntdllExports.GetExportDirectory();
auto tableOfNames =
ntdllExports.template RVAToPtr<const PDWORD>(exportDir->AddressOfNames);
for (DWORD i = 0; i < exportDir->NumberOfNames; ++i) {
const auto name =
ntdllExports.template RVAToPtr<const char*>(tableOfNames[i]);
if (isAppHelpLoaded && strcmp(name, "NtdllDefWindowProc_W") == 0) {
// In this case, GetProcAddress will return
// apphelp!DWM8AND16BitHook_DefWindowProcW.
continue;
}
auto funcEntry = ntdllExports.FindExportAddressTableEntry(name);
if (ntdllExports.template RVAToPtr<const void*>(*funcEntry) !=
::GetProcAddress(ntdllImageBase, name)) {
printf(
"TEST-FAILED | TestPEExportSection | "
"FindExportAddressTableEntry did not resolve ntdll!%s.\n",
name);
return kTestFail;
}
}
for (DWORD i = 0; i < 0x10000; i += 0x10) {
if (ntdllExports.GetProcAddress(MAKEINTRESOURCE(i)) !=
::GetProcAddress(ntdllImageBase, MAKEINTRESOURCE(i))) {
printf(
"TEST-FAILED | TestPEExportSection | "
"GetProcAddress did not resolve ntdll!Ordinal#%lu.\n",
i);
return kTestFail;
}
}
// Test a known forwarder RVA.
auto k32Exports = PEExportSection<MMPolicy>::Get(
::GetModuleHandleW(L"kernel32.dll"), aMMPolicy);
if (k32Exports.FindExportAddressTableEntry("HeapAlloc")) {
printf(
"TEST-FAILED | TestPEExportSection | "
"kernel32!HeapAlloc should be forwarded to ntdll!RtlAllocateHeap.\n");
return kTestFail;
}
// Test invalid names.
if (k32Exports.FindExportAddressTableEntry("Invalid name") ||
k32Exports.FindExportAddressTableEntry("")) {
printf(
"TEST-FAILED | TestPEExportSection | "
"FindExportAddressTableEntry should return "
"nullptr for a non-existent name.\n");
return kTestFail;
}
return kTestSuccess;
}
TestResult RunChildProcessTest(
const wchar_t* aExecutable, const wchar_t* aOption,
const nsAutoHandle& aEvent, const nsAutoHandle& aJob,
TestResult (*aTestCallback)(const RemotePEExportSection&)) {
ChildProcess childProcess(aExecutable, aOption, aEvent, aJob);
if (!childProcess) {
return kTestFail;
}
auto result = childProcess.GetStatus();
if (result != kTestSuccess) {
return result;
}
MMPolicyOutOfProcess policy(childProcess);
// One time is enough to run BasicTest in the child process.
static TestResult oneTimeResult = BasicTest<MMPolicyOutOfProcess>(policy);
if (oneTimeResult != kTestSuccess) {
return oneTimeResult;
}
auto exportTableChild =
RemotePEExportSection::Get(::GetModuleHandleW(nullptr), policy);
return aTestCallback(exportTableChild);
}
mozilla::LauncherResult<nsReturnRef<HANDLE>> CreateJobToLimitProcessLifetime() {
uint64_t version;
PEHeaders ntdllHeaders(::GetModuleHandleW(L"ntdll.dll"));
if (!ntdllHeaders.GetVersionInfo(version)) {
printf(
"TEST-FAILED | TestPEExportSection | "
"Unable to obtain version information from ntdll.dll\n");
return LAUNCHER_ERROR_FROM_LAST();
}
constexpr uint64_t kWin8 = 0x60002ull << 32;
nsAutoHandle job;
if (version < kWin8) {
// Since a process can be associated only with a single job in Win7 or
// older and this test program is already assigned with a job by
// infrastructure, we cannot use a job.
return job.out();
}
job.own(::CreateJobObject(nullptr, nullptr));
if (!job) {
printf(
"TEST-FAILED | TestPEExportSection | "
"CreateJobObject falied - %08lx.\n",
GetLastError());
return LAUNCHER_ERROR_FROM_LAST();
}
JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobInfo = {};
jobInfo.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
if (!::SetInformationJobObject(job, JobObjectExtendedLimitInformation,
&jobInfo, sizeof(jobInfo))) {
printf(
"TEST-FAILED | TestPEExportSection | "
"SetInformationJobObject falied - %08lx.\n",
GetLastError());
return LAUNCHER_ERROR_FROM_LAST();
}
return job.out();
}
extern "C" int wmain(int argc, wchar_t* argv[]) {
nsAutoHandle controlEvent(
::CreateEventW(nullptr, FALSE, FALSE, kProcessControlEventName));
if (argc == 2) {
return ChildProcess::Main(controlEvent, argv[1]);
}
if (argc != 1) {
printf(
"TEST-FAILED | TestPEExportSection | "
"Invalid arguments.\n");
return kTestFail;
}
MMPolicyInProcess policy;
if (BasicTest<MMPolicyInProcess>(policy)) {
return kTestFail;
}
auto exportTableSelf =
LocalPEExportSection::Get(::GetModuleHandleW(nullptr), policy);
if (!exportTableSelf) {
printf(
"TEST-FAILED | TestPEExportSection | "
"LocalPEExportSection::Get failed.\n");
return kTestFail;
}
VERIFY_EXPORT_FUNCTION(exportTableSelf, "Export1", Export1,
"Local | Export1 was not exported.\n");
VERIFY_EXPORT_FUNCTION(exportTableSelf, "Export2", Export2,
"Local | Export2 was not exported.\n");
VERIFY_EXPORT_FUNCTION(
exportTableSelf, "Invalid name", 0,
"Local | GetProcAddress should return nullptr for an invalid name.\n");
// We'll add the child process to a job so that, in the event of a failure in
// this parent process, the child process will be automatically terminated.
auto probablyJob = CreateJobToLimitProcessLifetime();
if (probablyJob.isErr()) {
return kTestFail;
}
nsAutoHandle job(probablyJob.unwrap());
auto result = RunChildProcessTest(
argv[0], kNoModification, controlEvent, job,
[](const RemotePEExportSection& aTables) {
VERIFY_EXPORT_FUNCTION(aTables, "Export1", Export1,
"NoModification | Export1 was not exported.\n");
VERIFY_EXPORT_FUNCTION(aTables, "Export2", Export2,
"NoModification | Export2 was not exported.\n");
return kTestSuccess;
});
if (result == kTestFail) {
return result;
}
result = RunChildProcessTest(
argv[0], kNoExport, controlEvent, job,
[](const RemotePEExportSection& aTables) {
VERIFY_EXPORT_FUNCTION(aTables, "Export1", 0,
"NoExport | Export1 was exported.\n");
VERIFY_EXPORT_FUNCTION(aTables, "Export2", 0,
"NoExport | Export2 was exported.\n");
return kTestSuccess;
});
if (result == kTestFail) {
return result;
}
result = RunChildProcessTest(
argv[0], kModifyTableEntry, controlEvent, job,
[](const RemotePEExportSection& aTables) {
VERIFY_EXPORT_FUNCTION(
aTables, "Export1", SecretFunction1,
"ModifyTableEntry | SecretFunction1 was not exported.\n");
VERIFY_EXPORT_FUNCTION(
aTables, "Export2", Export2,
"ModifyTableEntry | Export2 was not exported.\n");
return kTestSuccess;
});
if (result == kTestFail) {
return result;
}
result = RunChildProcessTest(
argv[0], kModifyTable, controlEvent, job,
[](const RemotePEExportSection& aTables) {
VERIFY_EXPORT_FUNCTION(aTables, "Export1", 0,
"ModifyTable | Export1 was exported.\n");
VERIFY_EXPORT_FUNCTION(aTables, "Export2", 0,
"ModifyTable | Export2 was exported.\n");
VERIFY_EXPORT_FUNCTION(
aTables, kSecretFunction, SecretFunction1,
"ModifyTable | SecretFunction1 was not exported.\n");
VERIFY_EXPORT_FUNCTION(
aTables, kSecretFunctionWithSuffix, SecretFunction2,
"ModifyTable | SecretFunction2 was not exported.\n");
VERIFY_EXPORT_FUNCTION(
aTables, kSecretFunctionInvalid, 0,
"ModifyTable | kSecretFunctionInvalid was exported.\n");
return kTestSuccess;
});
if (result == kTestFail) {
return result;
}
result = RunChildProcessTest(
argv[0], kModifyDirectoryEntry, controlEvent, job,
[](const RemotePEExportSection& aTables) {
VERIFY_EXPORT_FUNCTION(
aTables, "Export1", 0,
"ModifyDirectoryEntry | Export1 was exported.\n");
VERIFY_EXPORT_FUNCTION(
aTables, "Export2", 0,
"ModifyDirectoryEntry | Export2 was exported.\n");
VERIFY_EXPORT_FUNCTION(
aTables, kSecretFunction, SecretFunction1,
"ModifyDirectoryEntry | SecretFunction1 was not exported.\n");
VERIFY_EXPORT_FUNCTION(
aTables, kSecretFunctionWithSuffix, SecretFunction2,
"ModifyDirectoryEntry | SecretFunction2 was not exported.\n");
VERIFY_EXPORT_FUNCTION(
aTables, kSecretFunctionInvalid, 0,
"ModifyDirectoryEntry | kSecretFunctionInvalid was exported.\n");
return kTestSuccess;
});
if (result == kTestFail) {
return result;
}
result = RunChildProcessTest(
argv[0], kExportByOrdinal, controlEvent, job,
[](const RemotePEExportSection& aTables) {
VERIFY_EXPORT_FUNCTION(aTables, "Export1", 0,
"ExportByOrdinal | Export1 was exported.\n");
VERIFY_EXPORT_FUNCTION(aTables, "Export2", 0,
"ExportByOrdinal | Export2 was exported.\n");
VERIFY_EXPORT_FUNCTION(
aTables, kSecretFunction, 0,
"ModifyDirectoryEntry | kSecretFunction was exported by name.\n");
VERIFY_EXPORT_FUNCTION(
aTables, kSecretFunctionWithSuffix, 0,
"ModifyDirectoryEntry | "
"kSecretFunctionWithSuffix was exported by name.\n");
VERIFY_EXPORT_FUNCTION(
aTables, MAKEINTRESOURCE(1), SecretFunction1,
"ModifyDirectoryEntry | "
"kSecretFunction was not exported by ordinal.\n");
VERIFY_EXPORT_FUNCTION(
aTables, MAKEINTRESOURCE(2), SecretFunction2,
"ModifyDirectoryEntry | "
"kSecretFunctionWithSuffix was not exported by ordinal.\n");
return kTestSuccess;
});
if (result == kTestFail) {
return result;
}
return kTestSuccess;
}