mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-19 00:05:36 +00:00
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:
parent
9fe11cf59a
commit
f30b012bd4
@ -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");
|
||||
|
||||
|
113
browser/app/winlauncher/freestanding/FunctionTableResolver.cpp
Normal file
113
browser/app/winlauncher/freestanding/FunctionTableResolver.cpp
Normal 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
|
60
browser/app/winlauncher/freestanding/FunctionTableResolver.h
Normal file
60
browser/app/winlauncher/freestanding/FunctionTableResolver.h
Normal 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
|
@ -15,6 +15,7 @@ NO_PGO = True
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'DllBlocklist.cpp',
|
||||
'FunctionTableResolver.cpp',
|
||||
'LoaderPrivateAPI.cpp',
|
||||
'ModuleLoadFrame.cpp',
|
||||
]
|
||||
|
@ -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, ¤tTableEntry->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) {}
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user