Files
kvc/kvc/OffsetFinder.cpp
2025-09-30 15:31:30 +02:00

244 lines
8.5 KiB
C++

/*******************************************************************************
_ ____ ______
| |/ /\ \ / / ___|
| ' / \ \ / / |
| . \ \ V /| |___
|_|\_\ \_/ \____|
The **Kernel Vulnerability Capabilities (KVC)** framework represents a paradigm shift in Windows security research,
offering unprecedented access to modern Windows internals through sophisticated ring-0 operations. Originally conceived
as "Kernel Process Control," the framework has evolved to emphasize not just control, but the complete **exploitation
of kernel-level primitives** for legitimate security research and penetration testing.
KVC addresses the critical gap left by traditional forensic tools that have become obsolete in the face of modern Windows
security hardening. Where tools like ProcDump and Process Explorer fail against Protected Process Light (PPL) and Antimalware
Protected Interface (AMSI) boundaries, KVC succeeds by operating at the kernel level, manipulating the very structures
that define these protections.
-----------------------------------------------------------------------------
Author : Marek Wesołowski
Email : marek@wesolowski.eu.org
Phone : +48 607 440 283 (Tel/WhatsApp)
Date : 04-09-2025
*******************************************************************************/
// OffsetFinder.cpp
#include "OffsetFinder.h"
#include "Utils.h"
#include "common.h"
#include <cstring>
namespace {
// Safe offset extraction with validation to prevent crashes
std::optional<WORD> SafeExtractWord(const void* base, size_t byteOffset) noexcept
{
if (!base) return std::nullopt;
WORD value = 0;
__try {
std::memcpy(&value, reinterpret_cast<const BYTE*>(base) + byteOffset, sizeof(value));
}
__except (EXCEPTION_EXECUTE_HANDLER) {
return std::nullopt;
}
// Sanity check - offsets should be reasonable for EPROCESS structure
if (value == 0 || value > 0x3000) {
return std::nullopt;
}
return value;
}
}
// Initialize offset finder with kernel image analysis
OffsetFinder::OffsetFinder()
{
HMODULE rawModule = LoadLibraryW(L"ntoskrnl.exe");
m_kernelModule = ModuleHandle(rawModule);
if (!m_kernelModule) {
ERROR(L"OffsetFinder: Failed to load kernel image (error: %d) - verify administrator privileges", GetLastError());
}
}
OffsetFinder::~OffsetFinder() = default;
std::optional<DWORD> OffsetFinder::GetOffset(Offset name) const noexcept
{
if (auto it = m_offsetMap.find(name); it != m_offsetMap.end())
return it->second;
return std::nullopt;
}
// Master offset discovery in dependency order
bool OffsetFinder::FindAllOffsets() noexcept
{
return FindKernelPsInitialSystemProcessOffset() &&
FindProcessUniqueProcessIdOffset() &&
FindProcessProtectionOffset() &&
FindProcessActiveProcessLinksOffset() &&
FindProcessSignatureLevelOffset() &&
FindProcessSectionSignatureLevelOffset();
}
// PsInitialSystemProcess export location discovery
bool OffsetFinder::FindKernelPsInitialSystemProcessOffset() noexcept
{
if (m_offsetMap.contains(Offset::KernelPsInitialSystemProcess))
return true;
if (!m_kernelModule) {
ERROR(L"Cannot find PsInitialSystemProcess - kernel image not loaded");
return false;
}
auto pPsInitialSystemProcess = reinterpret_cast<ULONG_PTR>(
GetProcAddress(m_kernelModule.get(), "PsInitialSystemProcess"));
if (!pPsInitialSystemProcess) {
ERROR(L"PsInitialSystemProcess export not found (error: %d)", GetLastError());
// Test if other exports are accessible
if (GetProcAddress(m_kernelModule.get(), "PsGetProcessId")) {
ERROR(L"Other kernel exports accessible - partial export table issue");
} else {
ERROR(L"No kernel exports accessible - incompatible kernel image");
}
return false;
}
DWORD offset = static_cast<DWORD>(pPsInitialSystemProcess - reinterpret_cast<ULONG_PTR>(m_kernelModule.get()));
// Sanity check for reasonable offset range
if (offset < 0x1000 || offset > 0x2000000) {
ERROR(L"PsInitialSystemProcess offset 0x%x outside reasonable range", offset);
return false;
}
m_offsetMap[Offset::KernelPsInitialSystemProcess] = offset;
SUCCESS(L"Found PsInitialSystemProcess offset: 0x%x", offset);
return true;
}
// ActiveProcessLinks follows UniqueProcessId in EPROCESS structure
bool OffsetFinder::FindProcessActiveProcessLinksOffset() noexcept
{
if (m_offsetMap.contains(Offset::ProcessActiveProcessLinks))
return true;
if (!m_offsetMap.contains(Offset::ProcessUniqueProcessId))
return false;
// ActiveProcessLinks is always sizeof(HANDLE) bytes after UniqueProcessId
WORD offset = static_cast<WORD>(m_offsetMap[Offset::ProcessUniqueProcessId] + sizeof(HANDLE));
m_offsetMap[Offset::ProcessActiveProcessLinks] = offset;
return true;
}
// UniqueProcessId offset extraction from PsGetProcessId function
bool OffsetFinder::FindProcessUniqueProcessIdOffset() noexcept
{
if (m_offsetMap.contains(Offset::ProcessUniqueProcessId))
return true;
if (!m_kernelModule)
return false;
FARPROC pPsGetProcessId = GetProcAddress(m_kernelModule.get(), "PsGetProcessId");
if (!pPsGetProcessId) {
ERROR(L"PsGetProcessId export not found (error: %d)", GetLastError());
return false;
}
// Extract offset from function disassembly
std::optional<WORD> offset;
#ifdef _WIN64
// mov rax, [rcx+offset] - offset at bytes 3-4
offset = SafeExtractWord(pPsGetProcessId, 3);
#else
// mov eax, [ecx+offset] - offset at bytes 2-3
offset = SafeExtractWord(pPsGetProcessId, 2);
#endif
if (!offset) {
ERROR(L"Failed to extract UniqueProcessId offset from PsGetProcessId function");
return false;
}
// Sanity check for EPROCESS structure size
if (offset.value() > 0x1500) {
ERROR(L"UniqueProcessId offset 0x%x appears too large for EPROCESS", offset.value());
return false;
}
m_offsetMap[Offset::ProcessUniqueProcessId] = offset.value();
SUCCESS(L"Found UniqueProcessId offset: 0x%x", offset.value());
return true;
}
// Process protection offset validation using dual function analysis
bool OffsetFinder::FindProcessProtectionOffset() noexcept
{
if (m_offsetMap.contains(Offset::ProcessProtection))
return true;
if (!m_kernelModule)
return false;
FARPROC pPsIsProtectedProcess = GetProcAddress(m_kernelModule.get(), "PsIsProtectedProcess");
FARPROC pPsIsProtectedProcessLight = GetProcAddress(m_kernelModule.get(), "PsIsProtectedProcessLight");
if (!pPsIsProtectedProcess || !pPsIsProtectedProcessLight) {
ERROR(L"Protection function exports not found in kernel image");
return false;
}
// Both functions should reference the same offset
auto offsetA = SafeExtractWord(pPsIsProtectedProcess, 2);
auto offsetB = SafeExtractWord(pPsIsProtectedProcessLight, 2);
if (!offsetA || !offsetB) {
ERROR(L"Failed to extract offsets from protection validation functions");
return false;
}
// Cross-validation - both functions must agree
if (offsetA.value() != offsetB.value() || offsetA.value() > 0x1500) {
ERROR(L"Protection offset validation failed: A=0x%x, B=0x%x", offsetA.value(), offsetB.value());
return false;
}
m_offsetMap[Offset::ProcessProtection] = offsetA.value();
SUCCESS(L"Found ProcessProtection offset: 0x%x", offsetA.value());
return true;
}
// SignatureLevel precedes Protection field by 2 bytes
bool OffsetFinder::FindProcessSignatureLevelOffset() noexcept
{
if (m_offsetMap.contains(Offset::ProcessSignatureLevel))
return true;
if (!m_offsetMap.contains(Offset::ProcessProtection))
return false;
WORD offset = static_cast<WORD>(m_offsetMap[Offset::ProcessProtection] - (2 * sizeof(UCHAR)));
m_offsetMap[Offset::ProcessSignatureLevel] = offset;
return true;
}
// SectionSignatureLevel precedes Protection field by 1 byte
bool OffsetFinder::FindProcessSectionSignatureLevelOffset() noexcept
{
if (m_offsetMap.contains(Offset::ProcessSectionSignatureLevel))
return true;
if (!m_offsetMap.contains(Offset::ProcessProtection))
return false;
WORD offset = static_cast<WORD>(m_offsetMap[Offset::ProcessProtection] - sizeof(UCHAR));
m_offsetMap[Offset::ProcessSectionSignatureLevel] = offset;
return true;
}