From f30b012bd476c69a9db3a3f883b41b1271300818 Mon Sep 17 00:00:00 2001 From: Toshihito Kikuchi Date: Tue, 7 Apr 2020 14:39:51 +0000 Subject: [PATCH] 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 --- browser/app/winlauncher/DllBlocklistInit.cpp | 6 + .../freestanding/FunctionTableResolver.cpp | 113 ++++++++++++++++++ .../freestanding/FunctionTableResolver.h | 60 ++++++++++ .../app/winlauncher/freestanding/moz.build | 1 + mozglue/misc/NativeNt.h | 28 +++++ mozglue/tests/TestNativeNt.cpp | 23 ++++ 6 files changed, 231 insertions(+) create mode 100644 browser/app/winlauncher/freestanding/FunctionTableResolver.cpp create mode 100644 browser/app/winlauncher/freestanding/FunctionTableResolver.h diff --git a/browser/app/winlauncher/DllBlocklistInit.cpp b/browser/app/winlauncher/DllBlocklistInit.cpp index 91cc9231a9e5..333f5bede127 100644 --- a/browser/app/winlauncher/DllBlocklistInit.cpp +++ b/browser/app/winlauncher/DllBlocklistInit.cpp @@ -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"); diff --git a/browser/app/winlauncher/freestanding/FunctionTableResolver.cpp b/browser/app/winlauncher/freestanding/FunctionTableResolver.cpp new file mode 100644 index 000000000000..a71d7e4d18b1 --- /dev/null +++ b/browser/app/winlauncher/freestanding/FunctionTableResolver.cpp @@ -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(base + mOffsets.m##name) + +void Kernel32ExportsSolver::Init() { + if (mState == State::Initialized || mState == State::Resolved) { + return; + } + + interceptor::MMPolicyInProcess policy; + auto k32Exports = nt::PEExportSection::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(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(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 diff --git a/browser/app/winlauncher/freestanding/FunctionTableResolver.h b/browser/app/winlauncher/freestanding/FunctionTableResolver.h new file mode 100644 index 000000000000..ec9aa5c651dd --- /dev/null +++ b/browser/app/winlauncher/freestanding/FunctionTableResolver.h @@ -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 diff --git a/browser/app/winlauncher/freestanding/moz.build b/browser/app/winlauncher/freestanding/moz.build index 0daf54889a01..01feb1e3dcce 100644 --- a/browser/app/winlauncher/freestanding/moz.build +++ b/browser/app/winlauncher/freestanding/moz.build @@ -15,6 +15,7 @@ NO_PGO = True UNIFIED_SOURCES += [ 'DllBlocklist.cpp', + 'FunctionTableResolver.cpp', 'LoaderPrivateAPI.cpp', 'ModuleLoadFrame.cpp', ] diff --git a/mozglue/misc/NativeNt.h b/mozglue/misc/NativeNt.h index 8f83f9c4fabc..fdfa62107ff1 100644 --- a/mozglue/misc/NativeNt.h +++ b/mozglue/misc/NativeNt.h @@ -1243,6 +1243,34 @@ inline LauncherResult GetProcessExeModule(HANDLE aProcess) { #if !defined(MOZILLA_INTERNAL_API) +inline LauncherResult GetModuleHandleFromLeafName( + const UNICODE_STRING& aTarget) { + auto maybePeb = nt::GetProcessPebPtr(kCurrentProcess); + if (maybePeb.isErr()) { + return LAUNCHER_ERROR_FROM_RESULT(maybePeb); + } + + const PPEB peb = reinterpret_cast(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, ¤tTableEntry->FullDllName); + + if (::RtlCompareUnicodeString(&leafName, &aTarget, TRUE) == 0) { + return reinterpret_cast(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) {} diff --git a/mozglue/tests/TestNativeNt.cpp b/mozglue/tests/TestNativeNt.cpp index 01ed03da66de..25bad806b73a 100644 --- a/mozglue/tests/TestNativeNt.cpp +++ b/mozglue/tests/TestNativeNt.cpp @@ -64,6 +64,12 @@ bool TestVirtualQuery(HANDLE aProcess, LPCVOID aAddress) { return true; } +LauncherResult 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(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; }