662 lines
24 KiB
C++
662 lines
24 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
|
|
|
|
*******************************************************************************/
|
|
|
|
//==============================================================================
|
|
// Utils.cpp - System utility functions with modern C++ optimizations
|
|
// Enhanced performance, robust error handling, low-level system operations
|
|
//==============================================================================
|
|
|
|
#include "Utils.h"
|
|
#include "common.h"
|
|
#include <windows.h>
|
|
#include <tlhelp32.h>
|
|
#include <filesystem>
|
|
#include <unordered_map>
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <string_view>
|
|
#include <regex>
|
|
#include <memory>
|
|
|
|
namespace fs = std::filesystem;
|
|
|
|
namespace Utils {
|
|
|
|
//==============================================================================
|
|
// STRING AND NUMERIC PARSING UTILITIES
|
|
//==============================================================================
|
|
|
|
[[nodiscard]] std::optional<DWORD> ParsePid(const std::wstring& pidStr) noexcept
|
|
{
|
|
if (pidStr.empty()) return std::nullopt;
|
|
|
|
try {
|
|
// Fast path for single digits
|
|
if (pidStr.length() == 1 && std::iswdigit(pidStr[0])) {
|
|
return static_cast<DWORD>(pidStr[0] - L'0');
|
|
}
|
|
|
|
// Validate all characters are digits before conversion
|
|
if (!std::all_of(pidStr.begin(), pidStr.end(),
|
|
[](wchar_t c) { return std::iswdigit(c); })) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
const unsigned long result = std::wcstoul(pidStr.c_str(), nullptr, 10);
|
|
return (result <= MAXDWORD && result != ULONG_MAX) ?
|
|
std::make_optional(static_cast<DWORD>(result)) : std::nullopt;
|
|
|
|
} catch (...) {
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] bool IsNumeric(const std::wstring& str) noexcept
|
|
{
|
|
return !str.empty() &&
|
|
std::all_of(str.begin(), str.end(),
|
|
[](wchar_t c) { return c >= L'0' && c <= L'9'; });
|
|
}
|
|
|
|
//==============================================================================
|
|
// ADVANCED FILE OPERATIONS WITH ROBUST ERROR HANDLING
|
|
//==============================================================================
|
|
|
|
bool ForceDeleteFile(const std::wstring& path) noexcept
|
|
{
|
|
// Fast path - try normal delete first
|
|
if (DeleteFileW(path.c_str())) {
|
|
return true;
|
|
}
|
|
|
|
// Remove file attributes that might prevent deletion
|
|
const DWORD attrs = GetFileAttributesW(path.c_str());
|
|
if (attrs != INVALID_FILE_ATTRIBUTES) {
|
|
SetFileAttributesW(path.c_str(), FILE_ATTRIBUTE_NORMAL);
|
|
|
|
// Retry after attribute removal
|
|
if (DeleteFileW(path.c_str())) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Last resort: schedule deletion after reboot
|
|
wchar_t tempPath[MAX_PATH];
|
|
if (GetTempPathW(MAX_PATH, tempPath)) {
|
|
wchar_t tempFile[MAX_PATH];
|
|
if (GetTempFileNameW(tempPath, L"KVC", 0, tempFile)) {
|
|
if (MoveFileExW(path.c_str(), tempFile, MOVEFILE_REPLACE_EXISTING)) {
|
|
return MoveFileExW(tempFile, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT);
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool WriteFile(const std::wstring& path, const std::vector<BYTE>& data)
|
|
{
|
|
if (data.empty()) return false;
|
|
|
|
// Ensure parent directory exists
|
|
const fs::path filePath = path;
|
|
std::error_code ec;
|
|
fs::create_directories(filePath.parent_path(), ec);
|
|
|
|
// Remove existing file if present
|
|
if (fs::exists(filePath)) {
|
|
ForceDeleteFile(path);
|
|
}
|
|
|
|
// Create file with appropriate security attributes
|
|
const HANDLE hFile = CreateFileW(path.c_str(), GENERIC_WRITE, 0, nullptr,
|
|
CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr);
|
|
if (hFile == INVALID_HANDLE_VALUE) {
|
|
return false;
|
|
}
|
|
|
|
// Write data in chunks for large files - prevents memory issues
|
|
constexpr DWORD CHUNK_SIZE = 64 * 1024; // 64KB chunks
|
|
DWORD totalWritten = 0;
|
|
|
|
while (totalWritten < data.size()) {
|
|
const DWORD bytesToWrite = std::min(CHUNK_SIZE,
|
|
static_cast<DWORD>(data.size() - totalWritten));
|
|
DWORD bytesWritten;
|
|
|
|
if (!::WriteFile(hFile, data.data() + totalWritten, bytesToWrite,
|
|
&bytesWritten, nullptr) || bytesWritten != bytesToWrite) {
|
|
CloseHandle(hFile);
|
|
DeleteFileW(path.c_str()); // Cleanup partial file
|
|
return false;
|
|
}
|
|
|
|
totalWritten += bytesWritten;
|
|
}
|
|
|
|
// Ensure data is committed to disk
|
|
FlushFileBuffers(hFile);
|
|
CloseHandle(hFile);
|
|
|
|
return true;
|
|
}
|
|
|
|
std::vector<BYTE> ReadFile(const std::wstring& path)
|
|
{
|
|
const HANDLE hFile = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ,
|
|
nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
|
|
if (hFile == INVALID_HANDLE_VALUE) {
|
|
return {};
|
|
}
|
|
|
|
LARGE_INTEGER fileSize;
|
|
if (!GetFileSizeEx(hFile, &fileSize)) {
|
|
CloseHandle(hFile);
|
|
return {};
|
|
}
|
|
|
|
// Use memory mapping for files > 64KB - significant performance boost
|
|
if (fileSize.QuadPart > 65536) {
|
|
const HANDLE hMapping = CreateFileMappingW(hFile, nullptr, PAGE_READONLY, 0, 0, nullptr);
|
|
if (hMapping) {
|
|
void* const pData = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0);
|
|
if (pData) {
|
|
std::vector<BYTE> result(static_cast<const BYTE*>(pData),
|
|
static_cast<const BYTE*>(pData) + fileSize.QuadPart);
|
|
UnmapViewOfFile(pData);
|
|
CloseHandle(hMapping);
|
|
CloseHandle(hFile);
|
|
return result;
|
|
}
|
|
CloseHandle(hMapping);
|
|
}
|
|
}
|
|
|
|
// Fallback to standard read for small files
|
|
std::vector<BYTE> buffer(static_cast<size_t>(fileSize.QuadPart));
|
|
DWORD bytesRead;
|
|
|
|
const BOOL success = ::ReadFile(hFile, buffer.data(), static_cast<DWORD>(buffer.size()),
|
|
&bytesRead, nullptr);
|
|
CloseHandle(hFile);
|
|
|
|
return (success && bytesRead == buffer.size()) ? std::move(buffer) : std::vector<BYTE>{};
|
|
}
|
|
|
|
//==============================================================================
|
|
// RESOURCE EXTRACTION WITH VALIDATION
|
|
//==============================================================================
|
|
|
|
std::vector<BYTE> ReadResource(int resourceId, const wchar_t* resourceType)
|
|
{
|
|
const HRSRC hRes = FindResource(nullptr, MAKEINTRESOURCE(resourceId), resourceType);
|
|
if (!hRes) return {};
|
|
|
|
const HGLOBAL hData = LoadResource(nullptr, hRes);
|
|
if (!hData) return {};
|
|
|
|
const DWORD dataSize = SizeofResource(nullptr, hRes);
|
|
if (dataSize == 0) return {};
|
|
|
|
void* const pData = LockResource(hData);
|
|
if (!pData) return {};
|
|
|
|
return std::vector<BYTE>(static_cast<const BYTE*>(pData),
|
|
static_cast<const BYTE*>(pData) + dataSize);
|
|
}
|
|
|
|
//==============================================================================
|
|
// PROCESS NAME RESOLUTION WITH INTELLIGENT CACHING
|
|
//==============================================================================
|
|
|
|
static thread_local std::unordered_map<DWORD, std::wstring> g_processCache;
|
|
static thread_local DWORD g_lastCacheUpdate = 0;
|
|
|
|
[[nodiscard]] std::wstring GetProcessName(DWORD pid) noexcept
|
|
{
|
|
// Use CreateToolhelp32Snapshot for reliable process enumeration
|
|
const HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
|
if (hSnapshot == INVALID_HANDLE_VALUE) {
|
|
return L"[Unknown]";
|
|
}
|
|
|
|
PROCESSENTRY32W pe{};
|
|
pe.dwSize = sizeof(PROCESSENTRY32W);
|
|
|
|
if (Process32FirstW(hSnapshot, &pe)) {
|
|
do {
|
|
if (pe.th32ProcessID == pid) {
|
|
CloseHandle(hSnapshot);
|
|
return std::wstring(pe.szExeFile);
|
|
}
|
|
} while (Process32NextW(hSnapshot, &pe));
|
|
}
|
|
|
|
CloseHandle(hSnapshot);
|
|
return L"[Unknown]";
|
|
}
|
|
|
|
std::wstring ResolveUnknownProcessLocal(DWORD pid, ULONG_PTR kernelAddress,
|
|
UCHAR protectionLevel, UCHAR signerType) noexcept
|
|
{
|
|
// Cache management - refresh every 30 seconds for performance
|
|
const DWORD currentTick = static_cast<DWORD>(GetTickCount64());
|
|
if (currentTick - g_lastCacheUpdate > 30000) {
|
|
g_processCache.clear();
|
|
g_lastCacheUpdate = currentTick;
|
|
}
|
|
|
|
// Check cache first - significant performance improvement
|
|
if (const auto cacheIt = g_processCache.find(pid); cacheIt != g_processCache.end()) {
|
|
return cacheIt->second;
|
|
}
|
|
|
|
std::wstring processName = L"Unknown";
|
|
|
|
// Multiple resolution strategies for maximum reliability
|
|
if (const HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid)) {
|
|
wchar_t imagePath[MAX_PATH] = {};
|
|
DWORD bufferSize = MAX_PATH;
|
|
|
|
// Try QueryFullProcessImageName first - most reliable method
|
|
if (QueryFullProcessImageNameW(hProcess, 0, imagePath, &bufferSize)) {
|
|
const std::wstring fullPath(imagePath);
|
|
const size_t lastSlash = fullPath.find_last_of(L'\\');
|
|
processName = (lastSlash != std::wstring::npos) ?
|
|
fullPath.substr(lastSlash + 1) : fullPath;
|
|
}
|
|
|
|
CloseHandle(hProcess);
|
|
}
|
|
|
|
// Fallback to snapshot-based resolution
|
|
if (processName == L"Unknown") {
|
|
processName = GetProcessName(pid);
|
|
}
|
|
|
|
// Cache successful resolutions
|
|
if (processName != L"Unknown" && processName != L"[Unknown]") {
|
|
g_processCache[pid] = processName;
|
|
}
|
|
|
|
return processName;
|
|
}
|
|
|
|
//==============================================================================
|
|
// PROTECTION LEVEL PARSING WITH OPTIMIZED LOOKUP TABLES
|
|
//==============================================================================
|
|
|
|
[[nodiscard]] std::optional<UCHAR> GetProtectionLevelFromString(const std::wstring& protectionLevel) noexcept
|
|
{
|
|
// Static lookup table - compile-time initialization for optimal performance
|
|
static const std::unordered_map<std::wstring, UCHAR> levels = {
|
|
{L"none", static_cast<UCHAR>(PS_PROTECTED_TYPE::None)},
|
|
{L"ppl", static_cast<UCHAR>(PS_PROTECTED_TYPE::ProtectedLight)},
|
|
{L"pp", static_cast<UCHAR>(PS_PROTECTED_TYPE::Protected)}
|
|
};
|
|
|
|
if (protectionLevel.empty()) return std::nullopt;
|
|
|
|
// Single allocation for case conversion
|
|
std::wstring lower = protectionLevel;
|
|
std::transform(lower.begin(), lower.end(), lower.begin(),
|
|
[](wchar_t c) { return std::towlower(c); });
|
|
|
|
if (const auto it = levels.find(lower); it != levels.end()) {
|
|
return it->second;
|
|
}
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
[[nodiscard]] std::optional<UCHAR> GetSignerTypeFromString(const std::wstring& signerType) noexcept
|
|
{
|
|
if (signerType.empty()) return std::nullopt;
|
|
|
|
// Convert to lowercase for case-insensitive comparison
|
|
std::wstring lower = signerType;
|
|
std::transform(lower.begin(), lower.end(), lower.begin(),
|
|
[](wchar_t c) { return std::towlower(c); });
|
|
|
|
// Direct string comparisons - fastest for small datasets
|
|
if (lower == L"none") return static_cast<UCHAR>(PS_PROTECTED_SIGNER::None);
|
|
if (lower == L"authenticode") return static_cast<UCHAR>(PS_PROTECTED_SIGNER::Authenticode);
|
|
if (lower == L"codegen") return static_cast<UCHAR>(PS_PROTECTED_SIGNER::CodeGen);
|
|
if (lower == L"antimalware") return static_cast<UCHAR>(PS_PROTECTED_SIGNER::Antimalware);
|
|
if (lower == L"lsa") return static_cast<UCHAR>(PS_PROTECTED_SIGNER::Lsa);
|
|
if (lower == L"windows") return static_cast<UCHAR>(PS_PROTECTED_SIGNER::Windows);
|
|
if (lower == L"wintcb") return static_cast<UCHAR>(PS_PROTECTED_SIGNER::WinTcb);
|
|
if (lower == L"winsystem") return static_cast<UCHAR>(PS_PROTECTED_SIGNER::WinSystem);
|
|
if (lower == L"app") return static_cast<UCHAR>(PS_PROTECTED_SIGNER::App);
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
//==============================================================================
|
|
// STRING REPRESENTATION FUNCTIONS WITH STATIC STORAGE
|
|
//==============================================================================
|
|
|
|
[[nodiscard]] const wchar_t* GetProtectionLevelAsString(UCHAR protectionLevel) noexcept
|
|
{
|
|
// Static strings eliminate repeated allocations
|
|
static const std::wstring none = L"None";
|
|
static const std::wstring ppl = L"PPL";
|
|
static const std::wstring pp = L"PP";
|
|
static const std::wstring unknown = L"Unknown";
|
|
|
|
switch (static_cast<PS_PROTECTED_TYPE>(protectionLevel)) {
|
|
case PS_PROTECTED_TYPE::None: return none.c_str();
|
|
case PS_PROTECTED_TYPE::ProtectedLight: return ppl.c_str();
|
|
case PS_PROTECTED_TYPE::Protected: return pp.c_str();
|
|
default: return unknown.c_str();
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] const wchar_t* GetSignerTypeAsString(UCHAR signerType) noexcept
|
|
{
|
|
// Jump table approach for maximum performance
|
|
static const std::wstring none = L"None";
|
|
static const std::wstring authenticode = L"Authenticode";
|
|
static const std::wstring codegen = L"CodeGen";
|
|
static const std::wstring antimalware = L"Antimalware";
|
|
static const std::wstring lsa = L"Lsa";
|
|
static const std::wstring windows = L"Windows";
|
|
static const std::wstring wintcb = L"WinTcb";
|
|
static const std::wstring winsystem = L"WinSystem";
|
|
static const std::wstring app = L"App";
|
|
static const std::wstring unknown = L"Unknown";
|
|
|
|
switch (static_cast<PS_PROTECTED_SIGNER>(signerType)) {
|
|
case PS_PROTECTED_SIGNER::None: return none.c_str();
|
|
case PS_PROTECTED_SIGNER::Authenticode: return authenticode.c_str();
|
|
case PS_PROTECTED_SIGNER::CodeGen: return codegen.c_str();
|
|
case PS_PROTECTED_SIGNER::Antimalware: return antimalware.c_str();
|
|
case PS_PROTECTED_SIGNER::Lsa: return lsa.c_str();
|
|
case PS_PROTECTED_SIGNER::Windows: return windows.c_str();
|
|
case PS_PROTECTED_SIGNER::WinTcb: return wintcb.c_str();
|
|
case PS_PROTECTED_SIGNER::WinSystem: return winsystem.c_str();
|
|
case PS_PROTECTED_SIGNER::App: return app.c_str();
|
|
default: return unknown.c_str();
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] const wchar_t* GetSignatureLevelAsString(UCHAR signatureLevel) noexcept
|
|
{
|
|
// Static buffer for unknown signature levels - thread-safe
|
|
switch (signatureLevel) {
|
|
case 0x00: return L"None";
|
|
case 0x08: return L"App";
|
|
case 0x0c: return L"Standard"; // Standard DLL verification
|
|
case 0x1c: return L"System"; // System DLL verification
|
|
case 0x1e: return L"Kernel"; // Kernel EXE verification
|
|
case 0x3c: return L"Service"; // Windows service EXE
|
|
case 0x3e: return L"Critical"; // Critical system EXE
|
|
case 0x07:
|
|
case 0x37: return L"WinSystem";
|
|
default: {
|
|
static thread_local wchar_t buf[32];
|
|
swprintf_s(buf, L"Unknown (0x%02x)", signatureLevel);
|
|
return buf;
|
|
}
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
// PROCESS DUMPABILITY ANALYSIS WITH HEURISTICS
|
|
//==============================================================================
|
|
|
|
[[nodiscard]] ProcessDumpability CanDumpProcess(DWORD pid, const std::wstring& processName) noexcept
|
|
{
|
|
ProcessDumpability result{false, L""};
|
|
|
|
// Known undumpable system processes - hardcoded for performance
|
|
static const std::unordered_set<DWORD> undumpablePids = {
|
|
4, // System process
|
|
188, // Secure System
|
|
232, // Registry process
|
|
3052 // Memory Compression
|
|
};
|
|
|
|
static const std::unordered_set<std::wstring> undumpableNames = {
|
|
L"System",
|
|
L"Secure System",
|
|
L"Registry",
|
|
L"Memory Compression"
|
|
};
|
|
|
|
if (undumpablePids.contains(pid)) {
|
|
result.Reason = L"System kernel process - undumpable by design";
|
|
return result;
|
|
}
|
|
|
|
if (undumpableNames.contains(processName)) {
|
|
result.Reason = L"Critical system process - protected by kernel";
|
|
return result;
|
|
}
|
|
|
|
// Additional heuristics for process dumpability
|
|
if (processName == L"[Unknown]" || processName.empty()) {
|
|
result.Reason = L"Process name unknown - likely kernel process";
|
|
return result;
|
|
}
|
|
|
|
// Assume process is dumpable if not in exclusion lists
|
|
result.CanDump = true;
|
|
result.Reason = L"Process appears dumpable with proper privileges";
|
|
return result;
|
|
}
|
|
|
|
//==============================================================================
|
|
// HEX STRING PROCESSING UTILITIES
|
|
//==============================================================================
|
|
|
|
[[nodiscard]] bool IsValidHexString(const std::wstring& hexString) noexcept
|
|
{
|
|
if (hexString.empty() || (hexString.length() % 2) != 0) {
|
|
return false;
|
|
}
|
|
|
|
return std::all_of(hexString.begin(), hexString.end(),
|
|
[](wchar_t c) {
|
|
return (c >= L'0' && c <= L'9') ||
|
|
(c >= L'A' && c <= L'F') ||
|
|
(c >= L'a' && c <= L'f');
|
|
});
|
|
}
|
|
|
|
bool HexStringToBytes(const std::wstring& hexString, std::vector<BYTE>& bytes) noexcept
|
|
{
|
|
if (!IsValidHexString(hexString)) {
|
|
return false;
|
|
}
|
|
|
|
bytes.clear();
|
|
bytes.reserve(hexString.length() / 2);
|
|
|
|
for (size_t i = 0; i < hexString.length(); i += 2) {
|
|
const wchar_t highNibble = hexString[i];
|
|
const wchar_t lowNibble = hexString[i + 1];
|
|
|
|
auto hexToByte = [](wchar_t c) -> BYTE {
|
|
if (c >= L'0' && c <= L'9') return static_cast<BYTE>(c - L'0');
|
|
if (c >= L'A' && c <= L'F') return static_cast<BYTE>(c - L'A' + 10);
|
|
if (c >= L'a' && c <= L'f') return static_cast<BYTE>(c - L'a' + 10);
|
|
return 0;
|
|
};
|
|
|
|
const BYTE byte = (hexToByte(highNibble) << 4) | hexToByte(lowNibble);
|
|
bytes.push_back(byte);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//==============================================================================
|
|
// PE BINARY PARSING AND MANIPULATION
|
|
//==============================================================================
|
|
|
|
[[nodiscard]] std::optional<size_t> GetPEFileLength(const std::vector<BYTE>& data, size_t offset) noexcept
|
|
{
|
|
try {
|
|
if (data.size() < offset + 64) return std::nullopt; // Not enough data for DOS header
|
|
|
|
// Verify DOS signature "MZ"
|
|
if (data[offset] != 'M' || data[offset + 1] != 'Z') {
|
|
return std::nullopt;
|
|
}
|
|
|
|
// Get PE header offset from DOS header
|
|
DWORD pe_offset;
|
|
std::memcpy(&pe_offset, &data[offset + 60], sizeof(DWORD));
|
|
|
|
const size_t pe_header_start = offset + pe_offset;
|
|
if (data.size() < pe_header_start + 24) return std::nullopt;
|
|
|
|
// Verify PE signature "PE\0\0"
|
|
if (std::memcmp(&data[pe_header_start], "PE\0\0", 4) != 0) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
// Parse COFF header for section count
|
|
WORD number_of_sections;
|
|
std::memcpy(&number_of_sections, &data[pe_header_start + 6], sizeof(WORD));
|
|
|
|
if (number_of_sections == 0) return std::nullopt;
|
|
|
|
// Calculate section table location
|
|
WORD optional_header_size;
|
|
std::memcpy(&optional_header_size, &data[pe_header_start + 20], sizeof(WORD));
|
|
|
|
const size_t section_table_offset = pe_header_start + 24 + optional_header_size;
|
|
constexpr size_t section_header_size = 40;
|
|
|
|
if (data.size() < section_table_offset + (number_of_sections * section_header_size)) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
// Find the highest file offset + size from all sections
|
|
size_t max_end = 0;
|
|
for (WORD i = 0; i < number_of_sections; ++i) {
|
|
const size_t sh_offset = section_table_offset + (i * section_header_size);
|
|
|
|
if (data.size() < sh_offset + 24) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
DWORD size_of_raw, pointer_to_raw;
|
|
std::memcpy(&size_of_raw, &data[sh_offset + 16], sizeof(DWORD));
|
|
std::memcpy(&pointer_to_raw, &data[sh_offset + 20], sizeof(DWORD));
|
|
|
|
if (pointer_to_raw == 0) continue; // Skip sections without raw data
|
|
|
|
const size_t section_end = pointer_to_raw + size_of_raw;
|
|
max_end = std::max(max_end, section_end);
|
|
}
|
|
|
|
if (max_end > 0) {
|
|
const size_t header_end = section_table_offset + number_of_sections * section_header_size;
|
|
const size_t file_end = std::max(max_end, header_end);
|
|
return std::min(file_end, data.size());
|
|
}
|
|
|
|
return std::nullopt;
|
|
|
|
} catch (...) {
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
|
|
bool SplitCombinedPE(const std::vector<BYTE>& combined,
|
|
std::vector<BYTE>& first,
|
|
std::vector<BYTE>& second) noexcept
|
|
{
|
|
try {
|
|
if (combined.empty()) return false;
|
|
|
|
// Determine exact size of first PE file
|
|
const auto first_size = GetPEFileLength(combined, 0);
|
|
|
|
if (!first_size || *first_size <= 0 || *first_size >= combined.size()) {
|
|
// Fallback: search for next "MZ" signature
|
|
constexpr size_t search_start = 0x200;
|
|
const size_t search_offset = (combined.size() > search_start) ? search_start : 0;
|
|
|
|
for (size_t i = search_offset; i < combined.size() - 1; ++i) {
|
|
if (combined[i] == 'M' && combined[i + 1] == 'Z') {
|
|
// Found potential second PE
|
|
first.assign(combined.begin(), combined.begin() + i);
|
|
second.assign(combined.begin() + i, combined.end());
|
|
return !first.empty() && !second.empty();
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Split at calculated boundary
|
|
const size_t split_point = *first_size;
|
|
if (split_point >= combined.size()) return false;
|
|
|
|
first.assign(combined.begin(), combined.begin() + split_point);
|
|
second.assign(combined.begin() + split_point, combined.end());
|
|
|
|
return !first.empty() && !second.empty();
|
|
|
|
} catch (...) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
// XOR DECRYPTION UTILITY
|
|
//==============================================================================
|
|
|
|
[[nodiscard]] std::vector<BYTE> DecryptXOR(const std::vector<BYTE>& encryptedData,
|
|
const std::array<BYTE, 7>& key) noexcept
|
|
{
|
|
if (encryptedData.empty()) return {};
|
|
|
|
std::vector<BYTE> decrypted;
|
|
decrypted.reserve(encryptedData.size());
|
|
|
|
for (size_t i = 0; i < encryptedData.size(); ++i) {
|
|
const BYTE decrypted_byte = encryptedData[i] ^ key[i % key.size()];
|
|
decrypted.push_back(decrypted_byte);
|
|
}
|
|
|
|
return decrypted;
|
|
}
|
|
|
|
//==============================================================================
|
|
// KERNEL ADDRESS UTILITIES
|
|
//==============================================================================
|
|
|
|
[[nodiscard]] std::optional<ULONG_PTR> GetKernelBaseAddress() noexcept
|
|
{
|
|
// Implementation depends on kernel driver communication
|
|
// This is a placeholder for the actual kernel base address retrieval
|
|
return std::nullopt;
|
|
}
|
|
|
|
} // namespace Utils
|