Files
kvc/kvc/OffsetFinder.cpp
2025-09-23 01:39:54 +02:00

219 lines
7.2 KiB
C++

// 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;
}