Bug 1603974 - Part 6: Introduce Kernel32ExportsSolver. r=mhowell

This patch introduces `Kernel32ExportsSolver` which calculates RVAs of
kernel32's functions and transfers them to a target process, where the
transferred RVAs are resolved into function addresses.

Depends on D68346

Differential Revision: https://phabricator.services.mozilla.com/D68347

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Toshihito Kikuchi 2020-04-07 14:39:51 +00:00
parent 9fe11cf59a
commit f30b012bd4
6 changed files with 231 additions and 0 deletions

View File

@ -16,6 +16,7 @@
#include "DllBlocklistInit.h"
#include "freestanding/DllBlocklist.h"
#include "freestanding/FunctionTableResolver.h"
#if defined(_MSC_VER)
extern "C" IMAGE_DOS_HEADER __ImageBase;
@ -42,6 +43,11 @@ LauncherVoidResultWithLineInfo InitializeDllBlocklistOOPFromLauncher(
static LauncherVoidResultWithLineInfo InitializeDllBlocklistOOPInternal(
const wchar_t* aFullImagePath, HANDLE aChildProcess) {
freestanding::gK32.Init();
if (freestanding::gK32.IsInitialized()) {
freestanding::gK32.Transfer(aChildProcess, &freestanding::gK32);
}
CrossProcessDllInterceptor intcpt(aChildProcess);
intcpt.Init(L"ntdll.dll");

View File

@ -0,0 +1,113 @@
/* -*- 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
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
#include "FunctionTableResolver.h"
namespace mozilla {
namespace freestanding {
Kernel32ExportsSolver gK32;
bool Kernel32ExportsSolver::IsInitialized() const {
return mState == State::Initialized || IsResolved();
}
bool Kernel32ExportsSolver::IsResolved() const {
return mState == State::Resolved;
}
// Why don't we use ::GetProcAddress?
// If the export table of kernel32.dll is tampered in the current process,
// we cannot transfer an RVA because the function pointed by the RVA may not
// exist in a target process.
// We can use ::GetProcAddress with additional check to detect tampering, but
// FindExportAddressTableEntry fits perfectly here because it returns nullptr
// if the target entry is outside the image, which means it's tampered or
// forwarded to another DLL.
#define INIT_FUNCTION(exports, name) \
do { \
auto rvaToFunction = exports.FindExportAddressTableEntry(#name); \
if (!rvaToFunction) { \
return; \
} \
mOffsets.m##name = *rvaToFunction; \
} while (0)
#define RESOLVE_FUNCTION(base, name) \
m##name = reinterpret_cast<decltype(m##name)>(base + mOffsets.m##name)
void Kernel32ExportsSolver::Init() {
if (mState == State::Initialized || mState == State::Resolved) {
return;
}
interceptor::MMPolicyInProcess policy;
auto k32Exports = nt::PEExportSection<interceptor::MMPolicyInProcess>::Get(
::GetModuleHandleW(L"kernel32.dll"), policy);
if (!k32Exports) {
return;
}
// Please make sure these functions are not forwarded to another DLL.
INIT_FUNCTION(k32Exports, FlushInstructionCache);
INIT_FUNCTION(k32Exports, GetSystemInfo);
INIT_FUNCTION(k32Exports, VirtualProtect);
mState = State::Initialized;
}
void Kernel32ExportsSolver::ResolveInternal() {
if (mState == State::Resolved) {
return;
}
MOZ_RELEASE_ASSERT(mState == State::Initialized);
UNICODE_STRING k32Name;
::RtlInitUnicodeString(&k32Name, L"kernel32.dll");
// We cannot use GetModuleHandleW because this code can be called
// before IAT is resolved.
auto k32Module = nt::GetModuleHandleFromLeafName(k32Name);
MOZ_RELEASE_ASSERT(k32Module.isOk());
uintptr_t k32Base =
nt::PEHeaders::HModuleToBaseAddr<uintptr_t>(k32Module.unwrap());
RESOLVE_FUNCTION(k32Base, FlushInstructionCache);
RESOLVE_FUNCTION(k32Base, GetSystemInfo);
RESOLVE_FUNCTION(k32Base, VirtualProtect);
mState = State::Resolved;
}
/* static */
ULONG NTAPI Kernel32ExportsSolver::ResolveOnce(PRTL_RUN_ONCE aRunOnce,
PVOID aParameter, PVOID*) {
reinterpret_cast<Kernel32ExportsSolver*>(aParameter)->ResolveInternal();
return TRUE;
}
void Kernel32ExportsSolver::Resolve(RTL_RUN_ONCE& aRunOnce) {
::RtlRunOnceExecuteOnce(&aRunOnce, &ResolveOnce, this, nullptr);
}
void Kernel32ExportsSolver::Transfer(
HANDLE aTargetProcess, Kernel32ExportsSolver* aTargetAddress) const {
SIZE_T bytesWritten = 0;
BOOL ok = ::WriteProcessMemory(aTargetProcess, &aTargetAddress->mOffsets,
&mOffsets, sizeof(mOffsets), &bytesWritten);
if (!ok) {
return;
}
State stateInChild = State::Initialized;
::WriteProcessMemory(aTargetProcess, &aTargetAddress->mState, &stateInChild,
sizeof(stateInChild), &bytesWritten);
}
} // namespace freestanding
} // namespace mozilla

View File

@ -0,0 +1,60 @@
/* -*- 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
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
#ifndef mozilla_freestanding_FunctionTableResolver_h
#define mozilla_freestanding_FunctionTableResolver_h
#include "mozilla/NativeNt.h"
#include "mozilla/interceptor/MMPolicies.h"
namespace mozilla {
namespace freestanding {
// This class calculates RVAs of kernel32's functions and transfers them
// to a target process, where the transferred RVAs are resolved into
// function addresses so that the target process can use them after
// kernel32.dll is loaded and before IAT is resolved.
class MOZ_TRIVIAL_CTOR_DTOR Kernel32ExportsSolver final
: public interceptor::MMPolicyInProcessEarlyStage::Kernel32Exports {
enum class State {
Uninitialized,
Initialized,
Resolved,
} mState;
struct FunctionOffsets {
uint32_t mFlushInstructionCache;
uint32_t mGetSystemInfo;
uint32_t mVirtualProtect;
} mOffsets;
static ULONG NTAPI ResolveOnce(PRTL_RUN_ONCE aRunOnce, PVOID aParameter,
PVOID*);
void ResolveInternal();
public:
Kernel32ExportsSolver() = default;
Kernel32ExportsSolver(const Kernel32ExportsSolver&) = delete;
Kernel32ExportsSolver(Kernel32ExportsSolver&&) = delete;
Kernel32ExportsSolver& operator=(const Kernel32ExportsSolver&) = delete;
Kernel32ExportsSolver& operator=(Kernel32ExportsSolver&&) = delete;
bool IsInitialized() const;
bool IsResolved() const;
void Init();
void Resolve(RTL_RUN_ONCE& aRunOnce);
void Transfer(HANDLE aTargetProcess,
Kernel32ExportsSolver* aTargetAddress) const;
};
extern Kernel32ExportsSolver gK32;
} // namespace freestanding
} // namespace mozilla
#endif // mozilla_freestanding_FunctionTableResolver_h

View File

@ -15,6 +15,7 @@ NO_PGO = True
UNIFIED_SOURCES += [
'DllBlocklist.cpp',
'FunctionTableResolver.cpp',
'LoaderPrivateAPI.cpp',
'ModuleLoadFrame.cpp',
]

View File

@ -1243,6 +1243,34 @@ inline LauncherResult<HMODULE> GetProcessExeModule(HANDLE aProcess) {
#if !defined(MOZILLA_INTERNAL_API)
inline LauncherResult<HMODULE> GetModuleHandleFromLeafName(
const UNICODE_STRING& aTarget) {
auto maybePeb = nt::GetProcessPebPtr(kCurrentProcess);
if (maybePeb.isErr()) {
return LAUNCHER_ERROR_FROM_RESULT(maybePeb);
}
const PPEB peb = reinterpret_cast<PPEB>(maybePeb.unwrap());
if (!peb->Ldr) {
return LAUNCHER_ERROR_FROM_WIN32(ERROR_BAD_EXE_FORMAT);
}
auto firstItem = &peb->Ldr->InMemoryOrderModuleList;
for (auto p = firstItem->Flink; p != firstItem; p = p->Flink) {
const auto currentTableEntry =
CONTAINING_RECORD(p, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
UNICODE_STRING leafName;
nt::GetLeafName(&leafName, &currentTableEntry->FullDllName);
if (::RtlCompareUnicodeString(&leafName, &aTarget, TRUE) == 0) {
return reinterpret_cast<HMODULE>(currentTableEntry->DllBase);
}
}
return LAUNCHER_ERROR_FROM_WIN32(ERROR_MOD_NOT_FOUND);
}
class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS SRWLock final {
public:
constexpr SRWLock() : mLock(SRWLOCK_INIT) {}

View File

@ -64,6 +64,12 @@ bool TestVirtualQuery(HANDLE aProcess, LPCVOID aAddress) {
return true;
}
LauncherResult<HMODULE> GetModuleHandleFromLeafName(const wchar_t* aName) {
UNICODE_STRING name;
::RtlInitUnicodeString(&name, aName);
return nt::GetModuleHandleFromLeafName(name);
}
// Need a non-inline function to bypass compiler optimization that the thread
// local storage pointer is cached in a register before accessing a thread-local
// variable.
@ -266,6 +272,23 @@ int wmain(int argc, wchar_t* argv[]) {
return 1;
}
auto moduleResult = GetModuleHandleFromLeafName(kKernel32);
if (moduleResult.isErr() ||
moduleResult.inspect() != k32headers.template RVAToPtr<HMODULE>(0)) {
printf(
"TEST-FAILED | NativeNt | "
"GetModuleHandleFromLeafName returns a wrong value.\n");
return 1;
}
moduleResult = GetModuleHandleFromLeafName(L"invalid");
if (moduleResult.isOk()) {
printf(
"TEST-FAILED | NativeNt | "
"GetModuleHandleFromLeafName unexpectedly returns a value.\n");
return 1;
}
printf("TEST-PASS | NativeNt | All tests ran successfully\n");
return 0;
}