mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
feat: kernel memory plugin + unified source menu + driver improvements
- KernelMemory plugin: kernel-mode process/physical memory R/W via IOCTL driver - rcxdrv.sys: MmCopyMemory for reads, MDL mapping with correct cache types (MmCached for RAM, MmNonCached for MMIO only — fixes cache corruption BSOD) - Driver reconnect: ensureDriverLoaded tries device handle first, no auto stop+delete cycle. Manual unload closes handle only, service stays running. - Unified source menu: ProviderRegistry::populateSourceMenu() shared by both main window Data Source menu and RcxEditor inline picker (icons + dll names) - IProviderPlugin::populatePluginMenu() for conditional plugin actions (e.g. "Unload Kernel Driver" only when loaded) - Physical memory mode removed from selectTarget (access via context menu only) - requestOpenProviderTab sets base address from provider after template load - Address parser: vtop(), cr3(), physRead() callbacks for kernel paging expressions
This commit is contained in:
63
plugins/KernelMemory/CMakeLists.txt
Normal file
63
plugins/KernelMemory/CMakeLists.txt
Normal file
@@ -0,0 +1,63 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(KernelMemoryPlugin LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# Qt is found by the parent project; QT variable (Qt5 or Qt6) is inherited
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
set(CMAKE_AUTOUIC OFF) # run uic manually to avoid dupbuild with ProcessMemoryPlugin
|
||||
|
||||
# ─── Generate ui_processpicker.h in our own build dir ────────────────
|
||||
set(_UI_SRC "${CMAKE_CURRENT_SOURCE_DIR}/../../src/processpicker.ui")
|
||||
set(_UI_HDR "${CMAKE_CURRENT_BINARY_DIR}/ui_processpicker.h")
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT "${_UI_HDR}"
|
||||
COMMAND ${QT}::uic -o "${_UI_HDR}" "${_UI_SRC}"
|
||||
DEPENDS "${_UI_SRC}"
|
||||
COMMENT "UIC processpicker.ui (KernelMemoryPlugin)"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
# ─── Plugin DLL ──────────────────────────────────────────────────────
|
||||
set(PLUGIN_SOURCES
|
||||
KernelMemoryPlugin.h
|
||||
KernelMemoryPlugin.cpp
|
||||
rcx_drv_protocol.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../src/processpicker.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../src/processpicker.cpp
|
||||
"${_UI_HDR}"
|
||||
)
|
||||
|
||||
add_library(KernelMemoryPlugin SHARED ${PLUGIN_SOURCES})
|
||||
|
||||
target_link_libraries(KernelMemoryPlugin PRIVATE
|
||||
${QT}::Widgets
|
||||
${_QT_WINEXTRAS}
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
target_link_libraries(KernelMemoryPlugin PRIVATE psapi shell32 advapi32)
|
||||
endif()
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
target_compile_options(KernelMemoryPlugin PRIVATE -fvisibility=hidden)
|
||||
endif()
|
||||
|
||||
target_include_directories(KernelMemoryPlugin PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../src
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_BINARY_DIR} # for ui_processpicker.h
|
||||
)
|
||||
|
||||
set_target_properties(KernelMemoryPlugin PROPERTIES
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/Plugins"
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/Plugins"
|
||||
)
|
||||
|
||||
install(TARGETS KernelMemoryPlugin
|
||||
LIBRARY DESTINATION Plugins
|
||||
RUNTIME DESTINATION Plugins
|
||||
)
|
||||
751
plugins/KernelMemory/KernelMemoryPlugin.cpp
Normal file
751
plugins/KernelMemory/KernelMemoryPlugin.cpp
Normal file
@@ -0,0 +1,751 @@
|
||||
#include "KernelMemoryPlugin.h"
|
||||
#include "../../src/processpicker.h"
|
||||
|
||||
#include <QStyle>
|
||||
#include <QApplication>
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QGuiApplication>
|
||||
#include <QLibrary>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <tlhelp32.h>
|
||||
#include <psapi.h>
|
||||
#include <shellapi.h>
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
#include <QtWin>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Helper: DeviceIoControl wrapper
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
static bool ioctlCall(HANDLE h, DWORD code,
|
||||
const void* in, DWORD inLen,
|
||||
void* out, DWORD outLen,
|
||||
DWORD* bytesReturned = nullptr)
|
||||
{
|
||||
DWORD br = 0;
|
||||
BOOL ok = DeviceIoControl(h, code, const_cast<LPVOID>(in), inLen,
|
||||
out, outLen, &br, nullptr);
|
||||
if (bytesReturned) *bytesReturned = br;
|
||||
return ok != FALSE;
|
||||
}
|
||||
|
||||
#endif // _WIN32
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// KernelProcessProvider
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
KernelProcessProvider::KernelProcessProvider(void* driverHandle, uint32_t pid, const QString& processName)
|
||||
: m_driverHandle(driverHandle)
|
||||
, m_pid(pid)
|
||||
, m_processName(processName)
|
||||
{
|
||||
if (m_driverHandle) {
|
||||
queryPeb();
|
||||
cacheModules();
|
||||
}
|
||||
}
|
||||
|
||||
bool KernelProcessProvider::read(uint64_t addr, void* buf, int len) const
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (!m_driverHandle || len <= 0) return false;
|
||||
if (len > RCX_DRV_MAX_VIRTUAL) len = RCX_DRV_MAX_VIRTUAL;
|
||||
|
||||
RcxDrvReadRequest req{};
|
||||
req.pid = m_pid;
|
||||
req.address = addr;
|
||||
req.length = (uint32_t)len;
|
||||
|
||||
DWORD br = 0;
|
||||
BOOL ok = DeviceIoControl((HANDLE)m_driverHandle,
|
||||
IOCTL_RCX_READ_MEMORY,
|
||||
&req, sizeof(req),
|
||||
buf, (DWORD)len, &br, nullptr);
|
||||
// Zero unread portion (partial copy)
|
||||
if ((int)br < len)
|
||||
memset((char*)buf + br, 0, len - br);
|
||||
return ok || br > 0;
|
||||
#else
|
||||
Q_UNUSED(addr); Q_UNUSED(buf); Q_UNUSED(len);
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
int KernelProcessProvider::size() const
|
||||
{
|
||||
return m_driverHandle ? 0x10000 : 0;
|
||||
}
|
||||
|
||||
bool KernelProcessProvider::write(uint64_t addr, const void* buf, int len)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (!m_driverHandle || len <= 0) return false;
|
||||
if (len > RCX_DRV_MAX_VIRTUAL) return false;
|
||||
|
||||
// Build request: header + inline data
|
||||
QByteArray packet(sizeof(RcxDrvWriteRequest) + len, Qt::Uninitialized);
|
||||
auto* req = reinterpret_cast<RcxDrvWriteRequest*>(packet.data());
|
||||
req->pid = m_pid;
|
||||
req->_pad0 = 0;
|
||||
req->address = addr;
|
||||
req->length = (uint32_t)len;
|
||||
req->_pad1 = 0;
|
||||
memcpy(packet.data() + sizeof(RcxDrvWriteRequest), buf, len);
|
||||
|
||||
return ioctlCall((HANDLE)m_driverHandle, IOCTL_RCX_WRITE_MEMORY,
|
||||
packet.constData(), (DWORD)packet.size(),
|
||||
nullptr, 0);
|
||||
#else
|
||||
Q_UNUSED(addr); Q_UNUSED(buf); Q_UNUSED(len);
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
QString KernelProcessProvider::getSymbol(uint64_t addr) const
|
||||
{
|
||||
for (const auto& mod : m_modules) {
|
||||
if (addr >= mod.base && addr < mod.base + mod.size) {
|
||||
uint64_t offset = addr - mod.base;
|
||||
return QStringLiteral("%1+0x%2")
|
||||
.arg(mod.name)
|
||||
.arg(offset, 0, 16, QChar('0'));
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
uint64_t KernelProcessProvider::symbolToAddress(const QString& name) const
|
||||
{
|
||||
for (const auto& mod : m_modules) {
|
||||
if (mod.name.compare(name, Qt::CaseInsensitive) == 0)
|
||||
return mod.base;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
QVector<rcx::MemoryRegion> KernelProcessProvider::enumerateRegions() const
|
||||
{
|
||||
QVector<rcx::MemoryRegion> regions;
|
||||
#ifdef _WIN32
|
||||
if (!m_driverHandle) return regions;
|
||||
|
||||
RcxDrvQueryRegionsRequest req{};
|
||||
req.pid = m_pid;
|
||||
|
||||
// Allocate generous output buffer for region entries
|
||||
constexpr int kMaxEntries = 8192;
|
||||
QByteArray outBuf(kMaxEntries * sizeof(RcxDrvRegionEntry), Qt::Uninitialized);
|
||||
|
||||
DWORD br = 0;
|
||||
if (!ioctlCall((HANDLE)m_driverHandle, IOCTL_RCX_QUERY_REGIONS,
|
||||
&req, sizeof(req),
|
||||
outBuf.data(), (DWORD)outBuf.size(), &br))
|
||||
return regions;
|
||||
|
||||
int count = (int)(br / sizeof(RcxDrvRegionEntry));
|
||||
auto* entries = reinterpret_cast<const RcxDrvRegionEntry*>(outBuf.constData());
|
||||
|
||||
for (int i = 0; i < count; ++i) {
|
||||
const auto& e = entries[i];
|
||||
// Only include committed, accessible regions
|
||||
if (!(e.state & 0x1000)) continue; // MEM_COMMIT = 0x1000
|
||||
uint32_t p = e.protect;
|
||||
if (p & 0x01) continue; // PAGE_NOACCESS
|
||||
if (p & 0x100) continue; // PAGE_GUARD
|
||||
|
||||
rcx::MemoryRegion region;
|
||||
region.base = e.base;
|
||||
region.size = e.size;
|
||||
region.readable = true;
|
||||
region.writable = (p & 0x04) || (p & 0x08) || (p & 0x40) || (p & 0x80);
|
||||
region.executable = (p & 0x10) || (p & 0x20) || (p & 0x40) || (p & 0x80);
|
||||
|
||||
// Match module name
|
||||
for (const auto& mod : m_modules) {
|
||||
if (region.base >= mod.base && region.base < mod.base + mod.size) {
|
||||
region.moduleName = mod.name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
regions.append(region);
|
||||
}
|
||||
#endif
|
||||
return regions;
|
||||
}
|
||||
|
||||
void KernelProcessProvider::queryPeb()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
RcxDrvQueryPebRequest req{};
|
||||
req.pid = m_pid;
|
||||
|
||||
RcxDrvQueryPebResponse resp{};
|
||||
if (ioctlCall((HANDLE)m_driverHandle, IOCTL_RCX_QUERY_PEB,
|
||||
&req, sizeof(req), &resp, sizeof(resp))) {
|
||||
m_peb = resp.pebAddress;
|
||||
if (resp.pointerSize == 4)
|
||||
m_pointerSize = 4;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
QVector<rcx::Provider::ThreadInfo> KernelProcessProvider::tebs() const
|
||||
{
|
||||
QVector<ThreadInfo> result;
|
||||
#ifdef _WIN32
|
||||
if (!m_driverHandle) return result;
|
||||
|
||||
RcxDrvQueryTebsRequest req{};
|
||||
req.pid = m_pid;
|
||||
|
||||
constexpr int kMaxThreads = 4096;
|
||||
QByteArray outBuf(kMaxThreads * sizeof(RcxDrvTebEntry), Qt::Uninitialized);
|
||||
|
||||
DWORD br = 0;
|
||||
if (!ioctlCall((HANDLE)m_driverHandle, IOCTL_RCX_QUERY_TEBS,
|
||||
&req, sizeof(req),
|
||||
outBuf.data(), (DWORD)outBuf.size(), &br))
|
||||
return result;
|
||||
|
||||
int count = (int)(br / sizeof(RcxDrvTebEntry));
|
||||
auto* entries = reinterpret_cast<const RcxDrvTebEntry*>(outBuf.constData());
|
||||
|
||||
for (int i = 0; i < count; ++i)
|
||||
result.append({entries[i].tebAddress, entries[i].threadId});
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
void KernelProcessProvider::cacheModules()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (!m_driverHandle) return;
|
||||
|
||||
RcxDrvQueryModulesRequest req{};
|
||||
req.pid = m_pid;
|
||||
|
||||
constexpr int kMaxModules = 1024;
|
||||
QByteArray outBuf(kMaxModules * sizeof(RcxDrvModuleEntry), Qt::Uninitialized);
|
||||
|
||||
DWORD br = 0;
|
||||
if (!ioctlCall((HANDLE)m_driverHandle, IOCTL_RCX_QUERY_MODULES,
|
||||
&req, sizeof(req),
|
||||
outBuf.data(), (DWORD)outBuf.size(), &br))
|
||||
return;
|
||||
|
||||
int count = (int)(br / sizeof(RcxDrvModuleEntry));
|
||||
auto* entries = reinterpret_cast<const RcxDrvModuleEntry*>(outBuf.constData());
|
||||
|
||||
m_modules.reserve(count);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
QString modName = QString::fromUtf16(reinterpret_cast<const char16_t*>(entries[i].name));
|
||||
if (i == 0)
|
||||
m_base = entries[i].base;
|
||||
|
||||
m_modules.append({modName, entries[i].base, entries[i].size});
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// KernelProcessProvider — paging / address translation
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
uint64_t KernelProcessProvider::getCr3() const
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (m_cr3Cache) return m_cr3Cache;
|
||||
if (!m_driverHandle) return 0;
|
||||
|
||||
RcxDrvReadCr3Request req{};
|
||||
req.pid = m_pid;
|
||||
|
||||
RcxDrvReadCr3Response resp{};
|
||||
if (ioctlCall((HANDLE)m_driverHandle, IOCTL_RCX_READ_CR3,
|
||||
&req, sizeof(req), &resp, sizeof(resp))) {
|
||||
m_cr3Cache = resp.cr3;
|
||||
return m_cr3Cache;
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
rcx::VtopResult KernelProcessProvider::translateAddress(uint64_t va) const
|
||||
{
|
||||
rcx::VtopResult result{};
|
||||
#ifdef _WIN32
|
||||
if (!m_driverHandle) return result;
|
||||
|
||||
RcxDrvVtopRequest req{};
|
||||
req.pid = m_pid;
|
||||
req.virtualAddress = va;
|
||||
|
||||
RcxDrvVtopResponse resp{};
|
||||
if (ioctlCall((HANDLE)m_driverHandle, IOCTL_RCX_VTOP,
|
||||
&req, sizeof(req), &resp, sizeof(resp))) {
|
||||
result.physical = resp.physicalAddress;
|
||||
result.pml4e = resp.pml4e;
|
||||
result.pdpte = resp.pdpte;
|
||||
result.pde = resp.pde;
|
||||
result.pte = resp.pte;
|
||||
result.pageSize = resp.pageSize;
|
||||
result.valid = resp.valid != 0;
|
||||
}
|
||||
#else
|
||||
Q_UNUSED(va);
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
QVector<uint64_t> KernelProcessProvider::readPageTable(uint64_t physAddr, int startIdx, int count) const
|
||||
{
|
||||
QVector<uint64_t> entries;
|
||||
#ifdef _WIN32
|
||||
if (!m_driverHandle) return entries;
|
||||
if (startIdx < 0 || startIdx >= 512) return entries;
|
||||
if (count <= 0) return entries;
|
||||
if (startIdx + count > 512) count = 512 - startIdx;
|
||||
|
||||
// Read the full 4KB page table via physical read
|
||||
int byteOffset = startIdx * 8;
|
||||
int byteLen = count * 8;
|
||||
QByteArray buf(byteLen, 0);
|
||||
|
||||
RcxDrvPhysReadRequest req{};
|
||||
req.physAddress = physAddr + byteOffset;
|
||||
req.length = (uint32_t)byteLen;
|
||||
req.width = 0; // memcpy mode
|
||||
|
||||
DWORD br = 0;
|
||||
if (ioctlCall((HANDLE)m_driverHandle, IOCTL_RCX_READ_PHYS,
|
||||
&req, sizeof(req), buf.data(), (DWORD)byteLen, &br)) {
|
||||
entries.resize(count);
|
||||
memcpy(entries.data(), buf.constData(), byteLen);
|
||||
}
|
||||
#else
|
||||
Q_UNUSED(physAddr); Q_UNUSED(startIdx); Q_UNUSED(count);
|
||||
#endif
|
||||
return entries;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// KernelPhysProvider
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
KernelPhysProvider::KernelPhysProvider(void* driverHandle, uint64_t baseAddr)
|
||||
: m_driverHandle(driverHandle)
|
||||
, m_baseAddr(baseAddr)
|
||||
{
|
||||
}
|
||||
|
||||
bool KernelPhysProvider::read(uint64_t addr, void* buf, int len) const
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (!m_driverHandle || len <= 0) return false;
|
||||
|
||||
// Read in 4KB chunks (driver cap)
|
||||
int offset = 0;
|
||||
while (offset < len) {
|
||||
int chunk = qMin(len - offset, (int)RCX_DRV_MAX_PHYSICAL);
|
||||
|
||||
RcxDrvPhysReadRequest req{};
|
||||
req.physAddress = addr + offset;
|
||||
req.length = (uint32_t)chunk;
|
||||
req.width = 0; // memcpy mode
|
||||
|
||||
DWORD br = 0;
|
||||
BOOL ok = DeviceIoControl((HANDLE)m_driverHandle,
|
||||
IOCTL_RCX_READ_PHYS,
|
||||
&req, sizeof(req),
|
||||
(char*)buf + offset, (DWORD)chunk, &br, nullptr);
|
||||
if (!ok && br == 0) {
|
||||
memset((char*)buf + offset, 0, len - offset);
|
||||
return offset > 0;
|
||||
}
|
||||
if ((int)br < chunk)
|
||||
memset((char*)buf + offset + br, 0, chunk - br);
|
||||
offset += chunk;
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
Q_UNUSED(addr); Q_UNUSED(buf); Q_UNUSED(len);
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool KernelPhysProvider::write(uint64_t addr, const void* buf, int len)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (!m_driverHandle || len <= 0) return false;
|
||||
|
||||
int offset = 0;
|
||||
while (offset < len) {
|
||||
int chunk = qMin(len - offset, (int)RCX_DRV_MAX_PHYSICAL);
|
||||
|
||||
QByteArray packet(sizeof(RcxDrvPhysWriteRequest) + chunk, Qt::Uninitialized);
|
||||
auto* req = reinterpret_cast<RcxDrvPhysWriteRequest*>(packet.data());
|
||||
req->physAddress = addr + offset;
|
||||
req->length = (uint32_t)chunk;
|
||||
req->width = 0;
|
||||
memcpy(packet.data() + sizeof(RcxDrvPhysWriteRequest), (const char*)buf + offset, chunk);
|
||||
|
||||
if (!ioctlCall((HANDLE)m_driverHandle, IOCTL_RCX_WRITE_PHYS,
|
||||
packet.constData(), (DWORD)packet.size(),
|
||||
nullptr, 0))
|
||||
return false;
|
||||
offset += chunk;
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
Q_UNUSED(addr); Q_UNUSED(buf); Q_UNUSED(len);
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// KernelMemoryPlugin
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
KernelMemoryPlugin::KernelMemoryPlugin()
|
||||
{
|
||||
}
|
||||
|
||||
KernelMemoryPlugin::~KernelMemoryPlugin()
|
||||
{
|
||||
stopDriver();
|
||||
}
|
||||
|
||||
QIcon KernelMemoryPlugin::Icon() const
|
||||
{
|
||||
return qApp->style()->standardIcon(QStyle::SP_DriveHDIcon);
|
||||
}
|
||||
|
||||
bool KernelMemoryPlugin::canHandle(const QString& target) const
|
||||
{
|
||||
return target.startsWith(QStringLiteral("km:"))
|
||||
|| target.startsWith(QStringLiteral("phys:"));
|
||||
}
|
||||
|
||||
std::unique_ptr<rcx::Provider> KernelMemoryPlugin::createProvider(const QString& target, QString* errorMsg)
|
||||
{
|
||||
if (!ensureDriverLoaded(errorMsg))
|
||||
return nullptr;
|
||||
|
||||
#ifdef _WIN32
|
||||
if (target.startsWith(QStringLiteral("km:"))) {
|
||||
// km:{pid}:{name}
|
||||
QStringList parts = target.mid(3).split(':');
|
||||
bool ok = false;
|
||||
uint32_t pid = parts[0].toUInt(&ok);
|
||||
if (!ok || pid == 0) {
|
||||
if (errorMsg) *errorMsg = QStringLiteral("Invalid PID in target: ") + target;
|
||||
return nullptr;
|
||||
}
|
||||
QString name = parts.size() > 1 ? parts[1] : QStringLiteral("PID %1").arg(pid);
|
||||
auto prov = std::make_unique<KernelProcessProvider>((void*)m_driverHandle, pid, name);
|
||||
if (!prov->isValid()) {
|
||||
if (errorMsg)
|
||||
*errorMsg = QStringLiteral("Failed to read process %1 (PID: %2) via kernel driver.")
|
||||
.arg(name).arg(pid);
|
||||
return nullptr;
|
||||
}
|
||||
return prov;
|
||||
}
|
||||
|
||||
if (target.startsWith(QStringLiteral("phys:"))) {
|
||||
// phys:{baseAddr}
|
||||
bool ok = false;
|
||||
uint64_t baseAddr = target.mid(5).toULongLong(&ok, 16);
|
||||
if (!ok) baseAddr = 0;
|
||||
return std::make_unique<KernelPhysProvider>((void*)m_driverHandle, baseAddr);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
if (errorMsg) *errorMsg = QStringLiteral("Unknown target format: ") + target;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint64_t KernelMemoryPlugin::getInitialBaseAddress(const QString& target) const
|
||||
{
|
||||
if (target.startsWith(QStringLiteral("phys:"))) {
|
||||
bool ok = false;
|
||||
uint64_t addr = target.mid(5).toULongLong(&ok, 16);
|
||||
return ok ? addr : 0;
|
||||
}
|
||||
// For process mode, the provider discovers base via modules
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool KernelMemoryPlugin::selectTarget(QWidget* parent, QString* target)
|
||||
{
|
||||
// Show process picker directly (physical memory is accessed via
|
||||
// context menu "Browse Page Tables" / "Follow Physical Frame" on an
|
||||
// attached kernel process).
|
||||
QVector<PluginProcessInfo> pluginProcesses = enumerateProcesses();
|
||||
QList<ProcessInfo> processes;
|
||||
for (const auto& pinfo : pluginProcesses) {
|
||||
ProcessInfo info;
|
||||
info.pid = pinfo.pid;
|
||||
info.name = pinfo.name;
|
||||
info.path = pinfo.path;
|
||||
info.icon = pinfo.icon;
|
||||
info.is32Bit = pinfo.is32Bit;
|
||||
processes.append(info);
|
||||
}
|
||||
|
||||
ProcessPicker picker(processes, parent);
|
||||
if (picker.exec() == QDialog::Accepted) {
|
||||
uint32_t pid = picker.selectedProcessId();
|
||||
QString name = picker.selectedProcessName();
|
||||
*target = QStringLiteral("km:%1:%2").arg(pid).arg(name);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QVector<PluginProcessInfo> KernelMemoryPlugin::enumerateProcesses()
|
||||
{
|
||||
QVector<PluginProcessInfo> processes;
|
||||
|
||||
#ifdef _WIN32
|
||||
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
if (snapshot == INVALID_HANDLE_VALUE) return processes;
|
||||
|
||||
PROCESSENTRY32W entry;
|
||||
entry.dwSize = sizeof(entry);
|
||||
|
||||
if (Process32FirstW(snapshot, &entry)) {
|
||||
do {
|
||||
PluginProcessInfo info;
|
||||
info.pid = entry.th32ProcessID;
|
||||
info.name = QString::fromWCharArray(entry.szExeFile);
|
||||
|
||||
HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, entry.th32ProcessID);
|
||||
if (hProcess) {
|
||||
wchar_t path[MAX_PATH * 2];
|
||||
DWORD pathLen = sizeof(path) / sizeof(wchar_t);
|
||||
|
||||
if (QueryFullProcessImageNameW(hProcess, 0, path, &pathLen)) {
|
||||
info.path = QString::fromWCharArray(path);
|
||||
|
||||
SHFILEINFOW sfi = {};
|
||||
if (SHGetFileInfoW(path, 0, &sfi, sizeof(sfi), SHGFI_ICON | SHGFI_SMALLICON)) {
|
||||
if (sfi.hIcon) {
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
QPixmap pixmap = QPixmap::fromImage(QImage::fromHICON(sfi.hIcon));
|
||||
#else
|
||||
QPixmap pixmap = QtWin::fromHICON(sfi.hIcon);
|
||||
#endif
|
||||
info.icon = QIcon(pixmap);
|
||||
DestroyIcon(sfi.hIcon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BOOL isWow64 = FALSE;
|
||||
if (IsWow64Process(hProcess, &isWow64) && isWow64)
|
||||
info.is32Bit = true;
|
||||
|
||||
CloseHandle(hProcess);
|
||||
}
|
||||
|
||||
processes.append(info);
|
||||
} while (Process32NextW(snapshot, &entry));
|
||||
}
|
||||
|
||||
CloseHandle(snapshot);
|
||||
#endif
|
||||
|
||||
return processes;
|
||||
}
|
||||
|
||||
void KernelMemoryPlugin::populatePluginMenu(QMenu* menu)
|
||||
{
|
||||
if (!m_driverLoaded) return;
|
||||
menu->addAction(QStringLiteral("Unload Kernel Driver"), [this]() { unloadDriver(); });
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Driver service management
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
QString KernelMemoryPlugin::driverPath() const
|
||||
{
|
||||
// Resolve rcxdrv.sys next to the plugin DLL
|
||||
QString pluginDir = QCoreApplication::applicationDirPath() + QStringLiteral("/Plugins");
|
||||
return pluginDir + QStringLiteral("/rcxdrv.sys");
|
||||
}
|
||||
|
||||
bool KernelMemoryPlugin::ensureDriverLoaded(QString* errorMsg)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
// Already connected?
|
||||
if (m_driverLoaded && m_driverHandle != INVALID_HANDLE_VALUE) {
|
||||
RcxDrvPingResponse ping{};
|
||||
if (ioctlCall(m_driverHandle, IOCTL_RCX_PING, nullptr, 0, &ping, sizeof(ping)))
|
||||
return true;
|
||||
// Handle went stale — close it and try to reconnect
|
||||
CloseHandle(m_driverHandle);
|
||||
m_driverHandle = INVALID_HANDLE_VALUE;
|
||||
m_driverLoaded = false;
|
||||
}
|
||||
|
||||
// Show wait cursor (SCM + StartService can take seconds on first load)
|
||||
struct WaitCursorGuard {
|
||||
WaitCursorGuard() { QGuiApplication::setOverrideCursor(Qt::WaitCursor); }
|
||||
~WaitCursorGuard() { QGuiApplication::restoreOverrideCursor(); }
|
||||
} waitCursor;
|
||||
|
||||
// Fast path: driver may already be running (previous session, or after disconnect).
|
||||
// Just try to open the device handle directly.
|
||||
m_driverHandle = CreateFileA(RCX_DRV_USERMODE_PATH,
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
0, nullptr, OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (m_driverHandle != INVALID_HANDLE_VALUE) {
|
||||
RcxDrvPingResponse ping{};
|
||||
if (ioctlCall(m_driverHandle, IOCTL_RCX_PING, nullptr, 0, &ping, sizeof(ping))) {
|
||||
m_driverLoaded = true;
|
||||
return true;
|
||||
}
|
||||
CloseHandle(m_driverHandle);
|
||||
m_driverHandle = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
// Slow path: need to install/start the service.
|
||||
QString sysPath = driverPath();
|
||||
if (!QFileInfo::exists(sysPath)) {
|
||||
if (errorMsg)
|
||||
*errorMsg = QStringLiteral("Driver not found: %1\n\n"
|
||||
"Place rcxdrv.sys in the Plugins folder next to the plugin DLL.").arg(sysPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
SC_HANDLE scm = OpenSCManagerW(nullptr, nullptr, SC_MANAGER_ALL_ACCESS);
|
||||
if (!scm) {
|
||||
if (errorMsg)
|
||||
*errorMsg = QStringLiteral("Failed to open Service Control Manager.\n"
|
||||
"Run Reclass as Administrator to load the kernel driver.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to open existing service first
|
||||
SC_HANDLE svc = OpenServiceW(scm, L"RcxDrv", SERVICE_ALL_ACCESS);
|
||||
if (!svc) {
|
||||
// Service doesn't exist — create it
|
||||
std::wstring wPath = sysPath.toStdWString();
|
||||
svc = CreateServiceW(scm, L"RcxDrv", L"RcxDrv",
|
||||
SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER,
|
||||
SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
|
||||
wPath.c_str(),
|
||||
nullptr, nullptr, nullptr, nullptr, nullptr);
|
||||
if (!svc) {
|
||||
DWORD err = GetLastError();
|
||||
if (errorMsg)
|
||||
*errorMsg = QStringLiteral("Failed to create driver service (error %1).\n"
|
||||
"Ensure test signing is enabled: bcdedit /set testsigning on").arg(err);
|
||||
CloseServiceHandle(scm);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Start service (ERROR_SERVICE_ALREADY_RUNNING is fine — means it's already up)
|
||||
if (!StartServiceW(svc, 0, nullptr)) {
|
||||
DWORD err = GetLastError();
|
||||
if (err != ERROR_SERVICE_ALREADY_RUNNING) {
|
||||
if (errorMsg)
|
||||
*errorMsg = QStringLiteral("Failed to start driver (error %1).\n"
|
||||
"Ensure test signing is enabled and the driver is properly signed.").arg(err);
|
||||
CloseServiceHandle(svc);
|
||||
CloseServiceHandle(scm);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Done with SCM — don't hold handles open
|
||||
CloseServiceHandle(svc);
|
||||
CloseServiceHandle(scm);
|
||||
|
||||
// Open device handle
|
||||
m_driverHandle = CreateFileA(RCX_DRV_USERMODE_PATH,
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
0, nullptr, OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (m_driverHandle == INVALID_HANDLE_VALUE) {
|
||||
if (errorMsg)
|
||||
*errorMsg = QStringLiteral("Driver started but could not open device handle.\n"
|
||||
"Device path: %1").arg(QString::fromLatin1(RCX_DRV_USERMODE_PATH));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify with ping
|
||||
RcxDrvPingResponse ping{};
|
||||
if (!ioctlCall(m_driverHandle, IOCTL_RCX_PING, nullptr, 0, &ping, sizeof(ping))) {
|
||||
if (errorMsg)
|
||||
*errorMsg = QStringLiteral("Driver opened but ping failed.");
|
||||
CloseHandle(m_driverHandle);
|
||||
m_driverHandle = INVALID_HANDLE_VALUE;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_driverLoaded = true;
|
||||
return true;
|
||||
#else
|
||||
if (errorMsg)
|
||||
*errorMsg = QStringLiteral("Kernel driver is only supported on Windows.");
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void KernelMemoryPlugin::unloadDriver()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
// Close device handle only — service stays running so we can reconnect
|
||||
if (m_driverHandle != INVALID_HANDLE_VALUE) {
|
||||
CloseHandle(m_driverHandle);
|
||||
m_driverHandle = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
m_driverLoaded = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void KernelMemoryPlugin::stopDriver()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
unloadDriver();
|
||||
|
||||
// Full cleanup: stop + delete the service
|
||||
SC_HANDLE scm = OpenSCManagerW(nullptr, nullptr, SC_MANAGER_ALL_ACCESS);
|
||||
if (scm) {
|
||||
SC_HANDLE svc = OpenServiceW(scm, L"RcxDrv", SERVICE_ALL_ACCESS);
|
||||
if (svc) {
|
||||
SERVICE_STATUS ss;
|
||||
ControlService(svc, SERVICE_CONTROL_STOP, &ss);
|
||||
DeleteService(svc);
|
||||
CloseServiceHandle(svc);
|
||||
}
|
||||
CloseServiceHandle(scm);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Plugin factory
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
extern "C" RCX_PLUGIN_EXPORT IPlugin* CreatePlugin()
|
||||
{
|
||||
return new KernelMemoryPlugin();
|
||||
}
|
||||
142
plugins/KernelMemory/KernelMemoryPlugin.h
Normal file
142
plugins/KernelMemory/KernelMemoryPlugin.h
Normal file
@@ -0,0 +1,142 @@
|
||||
#pragma once
|
||||
#include "../../src/iplugin.h"
|
||||
#include "../../src/core.h"
|
||||
#include "rcx_drv_protocol.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Provider variants
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Kernel-mode process memory provider.
|
||||
* Reads/writes target process virtual memory via IOCTL_RCX_READ/WRITE_MEMORY.
|
||||
*/
|
||||
class KernelProcessProvider : public rcx::Provider
|
||||
{
|
||||
public:
|
||||
KernelProcessProvider(void* driverHandle, uint32_t pid, const QString& processName);
|
||||
~KernelProcessProvider() override = default;
|
||||
|
||||
bool read(uint64_t addr, void* buf, int len) const override;
|
||||
int size() const override;
|
||||
|
||||
bool write(uint64_t addr, const void* buf, int len) override;
|
||||
bool isWritable() const override { return true; }
|
||||
QString name() const override { return m_processName; }
|
||||
QString kind() const override { return QStringLiteral("KernelProcess"); }
|
||||
QString getSymbol(uint64_t addr) const override;
|
||||
uint64_t symbolToAddress(const QString& name) const override;
|
||||
|
||||
bool isLive() const override { return true; }
|
||||
uint64_t base() const override { return m_base; }
|
||||
int pointerSize() const override { return m_pointerSize; }
|
||||
QVector<rcx::MemoryRegion> enumerateRegions() const override;
|
||||
bool isReadable(uint64_t, int len) const override { return m_driverHandle && len >= 0; }
|
||||
|
||||
uint32_t pid() const { return m_pid; }
|
||||
uint64_t peb() const override { return m_peb; }
|
||||
QVector<ThreadInfo> tebs() const override;
|
||||
|
||||
// ── Paging / address translation ──
|
||||
bool hasKernelPaging() const override { return true; }
|
||||
uint64_t getCr3() const override;
|
||||
rcx::VtopResult translateAddress(uint64_t va) const override;
|
||||
QVector<uint64_t> readPageTable(uint64_t physAddr, int startIdx = 0, int count = 512) const override;
|
||||
void* driverHandle() const { return m_driverHandle; }
|
||||
|
||||
private:
|
||||
void queryPeb();
|
||||
void cacheModules();
|
||||
|
||||
void* m_driverHandle;
|
||||
uint32_t m_pid;
|
||||
QString m_processName;
|
||||
uint64_t m_base = 0;
|
||||
int m_pointerSize = 8;
|
||||
uint64_t m_peb = 0;
|
||||
mutable uint64_t m_cr3Cache = 0;
|
||||
|
||||
struct ModuleInfo {
|
||||
QString name;
|
||||
uint64_t base;
|
||||
uint64_t size;
|
||||
};
|
||||
QVector<ModuleInfo> m_modules;
|
||||
};
|
||||
|
||||
/**
|
||||
* Kernel-mode physical memory provider.
|
||||
* Reads/writes raw physical addresses via IOCTL_RCX_READ/WRITE_PHYS.
|
||||
*/
|
||||
class KernelPhysProvider : public rcx::Provider
|
||||
{
|
||||
public:
|
||||
KernelPhysProvider(void* driverHandle, uint64_t baseAddr);
|
||||
~KernelPhysProvider() override = default;
|
||||
|
||||
bool read(uint64_t addr, void* buf, int len) const override;
|
||||
int size() const override { return m_driverHandle ? 0x10000 : 0; }
|
||||
|
||||
bool write(uint64_t addr, const void* buf, int len) override;
|
||||
bool isWritable() const override { return true; }
|
||||
QString name() const override { return QStringLiteral("Physical Memory"); }
|
||||
QString kind() const override { return QStringLiteral("Physical"); }
|
||||
|
||||
bool isLive() const override { return true; }
|
||||
uint64_t base() const override { return m_baseAddr; }
|
||||
bool isReadable(uint64_t, int len) const override { return m_driverHandle && len >= 0; }
|
||||
|
||||
void setBaseAddr(uint64_t addr) { m_baseAddr = addr; }
|
||||
void* driverHandle() const { return m_driverHandle; }
|
||||
|
||||
private:
|
||||
void* m_driverHandle;
|
||||
uint64_t m_baseAddr;
|
||||
};
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Plugin
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
class KernelMemoryPlugin : public IProviderPlugin
|
||||
{
|
||||
public:
|
||||
KernelMemoryPlugin();
|
||||
~KernelMemoryPlugin() override;
|
||||
|
||||
std::string Name() const override { return "Kernel Memory"; }
|
||||
std::string Version() const override { return "1.0.0"; }
|
||||
std::string Author() const override { return "Reclass"; }
|
||||
std::string Description() const override { return "Read and write memory via kernel driver (IOCTL)"; }
|
||||
k_ELoadType LoadType() const override { return k_ELoadTypeManual; }
|
||||
QIcon Icon() const override;
|
||||
|
||||
bool canHandle(const QString& target) const override;
|
||||
std::unique_ptr<rcx::Provider> createProvider(const QString& target, QString* errorMsg) override;
|
||||
uint64_t getInitialBaseAddress(const QString& target) const override;
|
||||
bool selectTarget(QWidget* parent, QString* target) override;
|
||||
|
||||
bool providesProcessList() const override { return true; }
|
||||
QVector<PluginProcessInfo> enumerateProcesses() override;
|
||||
void populatePluginMenu(QMenu* menu) override;
|
||||
|
||||
private:
|
||||
bool ensureDriverLoaded(QString* errorMsg = nullptr);
|
||||
void unloadDriver(); // close handle only — service stays running
|
||||
void stopDriver(); // full cleanup: close handle + stop + delete service
|
||||
QString driverPath() const;
|
||||
|
||||
#ifdef _WIN32
|
||||
HANDLE m_driverHandle = INVALID_HANDLE_VALUE;
|
||||
#endif
|
||||
bool m_driverLoaded = false;
|
||||
};
|
||||
|
||||
// Plugin export
|
||||
extern "C" RCX_PLUGIN_EXPORT IPlugin* CreatePlugin();
|
||||
56
plugins/KernelMemory/driver/build_driver.bat
Normal file
56
plugins/KernelMemory/driver/build_driver.bat
Normal file
@@ -0,0 +1,56 @@
|
||||
@echo off
|
||||
setlocal
|
||||
|
||||
set MSVC=C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC\14.39.33519
|
||||
set WDK=C:\Program Files (x86)\Windows Kits\10
|
||||
set WDKVER=10.0.22621.0
|
||||
|
||||
set CL_EXE=%MSVC%\bin\Hostx64\x64\cl.exe
|
||||
set LINK_EXE=%MSVC%\bin\Hostx64\x64\link.exe
|
||||
|
||||
set SRCDIR=%~dp0
|
||||
set OUTDIR=%SRCDIR%build
|
||||
|
||||
if not exist "%OUTDIR%" mkdir "%OUTDIR%"
|
||||
|
||||
echo === Compiling rcxdrv.c ===
|
||||
"%CL_EXE%" /nologo /c /Zi /W4 /WX- /O2 /GS- ^
|
||||
/D "NDEBUG" /D "_AMD64_" /D "AMD64" /D "_WIN64" /D "KERNEL" ^
|
||||
/D "NTDDI_VERSION=0x0A000000" ^
|
||||
/I "%WDK%\Include\%WDKVER%\km" ^
|
||||
/I "%WDK%\Include\%WDKVER%\km\crt" ^
|
||||
/I "%WDK%\Include\%WDKVER%\shared" ^
|
||||
/kernel ^
|
||||
/Fo"%OUTDIR%\rcxdrv.obj" ^
|
||||
"%SRCDIR%rcxdrv.c"
|
||||
if errorlevel 1 goto :fail
|
||||
|
||||
echo === Linking rcxdrv.sys ===
|
||||
"%LINK_EXE%" /nologo ^
|
||||
/OUT:"%OUTDIR%\rcxdrv.sys" ^
|
||||
/DRIVER:WDM ^
|
||||
/SUBSYSTEM:NATIVE ^
|
||||
/ENTRY:DriverEntry ^
|
||||
/MACHINE:X64 ^
|
||||
/NODEFAULTLIB ^
|
||||
/RELEASE ^
|
||||
/MERGE:.rdata=.text ^
|
||||
/INTEGRITYCHECK ^
|
||||
/PDBALTPATH:rcxdrv.pdb ^
|
||||
/PDB:"%OUTDIR%\rcxdrv.pdb" ^
|
||||
"%OUTDIR%\rcxdrv.obj" ^
|
||||
"%WDK%\Lib\%WDKVER%\km\x64\ntoskrnl.lib" ^
|
||||
"%WDK%\Lib\%WDKVER%\km\x64\hal.lib" ^
|
||||
"%WDK%\Lib\%WDKVER%\km\x64\BufferOverflowK.lib" ^
|
||||
"%MSVC%\lib\x64\libcmt.lib"
|
||||
if errorlevel 1 goto :fail
|
||||
|
||||
echo.
|
||||
echo === SUCCESS ===
|
||||
echo Output: %OUTDIR%\rcxdrv.sys
|
||||
goto :eof
|
||||
|
||||
:fail
|
||||
echo.
|
||||
echo === BUILD FAILED ===
|
||||
exit /b 1
|
||||
808
plugins/KernelMemory/driver/rcxdrv.c
Normal file
808
plugins/KernelMemory/driver/rcxdrv.c
Normal file
@@ -0,0 +1,808 @@
|
||||
/*
|
||||
* rcxdrv.c -- Minimal kernel-mode memory driver for Reclass.
|
||||
*
|
||||
* Provides: virtual memory R/W (per-process), physical memory R/W,
|
||||
* region/PEB/module/TEB query, CR3 read, virtual-to-physical translation.
|
||||
*
|
||||
* Safety: all inputs validated, SEH around privileged instructions,
|
||||
* MmCopyVirtualMemory for cross-process reads (no attach deadlock),
|
||||
* METHOD_BUFFERED (no raw user pointers).
|
||||
*/
|
||||
#include <ntifs.h>
|
||||
#include "../rcx_drv_protocol.h"
|
||||
|
||||
/* ── Undocumented but stable kernel exports (Vista+) ────────────── */
|
||||
|
||||
NTSTATUS NTAPI MmCopyVirtualMemory(
|
||||
PEPROCESS SourceProcess, PVOID SourceAddress,
|
||||
PEPROCESS TargetProcess, PVOID TargetAddress,
|
||||
SIZE_T BufferSize, KPROCESSOR_MODE PreviousMode,
|
||||
PSIZE_T ReturnSize);
|
||||
|
||||
PPEB NTAPI PsGetProcessPeb(PEPROCESS Process);
|
||||
PVOID NTAPI PsGetProcessWow64Process(PEPROCESS Process);
|
||||
PVOID NTAPI PsGetThreadTeb(PETHREAD Thread);
|
||||
|
||||
/*
|
||||
* PsGetNextProcessThread is undocumented (not in any .lib).
|
||||
* We resolve it dynamically via MmGetSystemRoutineAddress.
|
||||
*/
|
||||
typedef PETHREAD (NTAPI *PsGetNextProcessThread_t)(PEPROCESS Process, PETHREAD Thread);
|
||||
static PsGetNextProcessThread_t g_PsGetNextProcessThread = NULL;
|
||||
|
||||
/* ── Manual structure definitions (kernel-mode) ─────────────────── */
|
||||
/* These are partially opaque in WDK headers; define just the offsets we need. */
|
||||
|
||||
typedef struct _MEMORY_BASIC_INFORMATION_KM {
|
||||
PVOID BaseAddress;
|
||||
PVOID AllocationBase;
|
||||
ULONG AllocationProtect;
|
||||
SIZE_T RegionSize;
|
||||
ULONG State;
|
||||
ULONG Protect;
|
||||
ULONG Type;
|
||||
} MEMORY_BASIC_INFORMATION_KM;
|
||||
|
||||
#define MEM_COMMIT_KM 0x1000
|
||||
|
||||
/* PEB.Ldr minimal definition for module enumeration */
|
||||
typedef struct _PEB_LDR_DATA_KM {
|
||||
UCHAR Reserved1[8];
|
||||
PVOID Reserved2[3];
|
||||
LIST_ENTRY InLoadOrderModuleList;
|
||||
} PEB_LDR_DATA_KM;
|
||||
|
||||
/* PEB minimal: only need Ldr at offset 0x18 (x64) */
|
||||
typedef struct _PEB_KM {
|
||||
UCHAR Reserved1[2];
|
||||
UCHAR BeingDebugged;
|
||||
UCHAR Reserved2[0x15];
|
||||
PEB_LDR_DATA_KM* Ldr; /* offset 0x18 on x64 */
|
||||
} PEB_KM;
|
||||
|
||||
/* LDR_DATA_TABLE_ENTRY minimal for walking InLoadOrderModuleList */
|
||||
typedef struct _LDR_DATA_TABLE_ENTRY_KM {
|
||||
LIST_ENTRY InLoadOrderLinks; /* offset 0x00 */
|
||||
LIST_ENTRY InMemoryOrderLinks; /* offset 0x10 */
|
||||
LIST_ENTRY InInitializationOrderLinks; /* offset 0x20 */
|
||||
PVOID DllBase; /* offset 0x30 */
|
||||
PVOID EntryPoint; /* offset 0x38 */
|
||||
ULONG SizeOfImage; /* offset 0x40 */
|
||||
ULONG _pad;
|
||||
UNICODE_STRING FullDllName; /* offset 0x48 */
|
||||
UNICODE_STRING BaseDllName; /* offset 0x58 */
|
||||
} LDR_DATA_TABLE_ENTRY_KM;
|
||||
|
||||
/* ── Forward declarations ────────────────────────────────────────── */
|
||||
|
||||
static NTSTATUS DispatchCreateClose(PDEVICE_OBJECT dev, PIRP irp);
|
||||
static NTSTATUS DispatchIoctl(PDEVICE_OBJECT dev, PIRP irp);
|
||||
DRIVER_UNLOAD DriverUnload;
|
||||
|
||||
/* ZwCurrentProcess() macro for ZwQueryVirtualMemory */
|
||||
#ifndef ZwCurrentProcess
|
||||
#define ZwCurrentProcess() ((HANDLE)(LONG_PTR)-1)
|
||||
#endif
|
||||
|
||||
/* ── Helpers ─────────────────────────────────────────────────────── */
|
||||
|
||||
#define VALIDATE_INPUT(irp, stk, T) \
|
||||
do { \
|
||||
if ((stk)->Parameters.DeviceIoControl.InputBufferLength < sizeof(T)) { \
|
||||
(irp)->IoStatus.Status = STATUS_BUFFER_TOO_SMALL; \
|
||||
(irp)->IoStatus.Information = 0; \
|
||||
IoCompleteRequest((irp), IO_NO_INCREMENT); \
|
||||
return STATUS_BUFFER_TOO_SMALL; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define VALIDATE_OUTPUT(irp, stk, minSize) \
|
||||
do { \
|
||||
if ((stk)->Parameters.DeviceIoControl.OutputBufferLength < (ULONG)(minSize)) { \
|
||||
(irp)->IoStatus.Status = STATUS_BUFFER_TOO_SMALL; \
|
||||
(irp)->IoStatus.Information = 0; \
|
||||
IoCompleteRequest((irp), IO_NO_INCREMENT); \
|
||||
return STATUS_BUFFER_TOO_SMALL; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static NTSTATUS LookupProcess(ULONG pid, PEPROCESS* proc)
|
||||
{
|
||||
return PsLookupProcessByProcessId((HANDLE)(ULONG_PTR)pid, proc);
|
||||
}
|
||||
|
||||
/* ── Safe physical mapping (MDL-based, avoids MmMapIoSpace BSOD) ── */
|
||||
/*
|
||||
* MmMapIoSpace/MmUnmapIoSpace BSODs (bugcheck 0x50 in
|
||||
* MiClearMappingAndDereferenceIoSpace) when used on RAM-backed physical
|
||||
* addresses. MDL-based mapping is safe for both RAM and MMIO.
|
||||
*
|
||||
* CRITICAL: cacheType must match the existing kernel mapping of the page.
|
||||
* Use MmCached for RAM pages (already mapped cached by the kernel).
|
||||
* Use MmNonCached ONLY for MMIO/device registers.
|
||||
* Mismatched cache attributes (e.g. MmNonCached on RAM) cause silent
|
||||
* kernel memory corruption via CPU cache coherency conflicts.
|
||||
*/
|
||||
|
||||
typedef struct { PMDL mdl; PVOID base; } PHYS_MAP_CTX;
|
||||
|
||||
static PVOID MapPhysical(uint64_t physAddr, SIZE_T size,
|
||||
MEMORY_CACHING_TYPE cacheType, PHYS_MAP_CTX* ctx)
|
||||
{
|
||||
ctx->mdl = NULL;
|
||||
ctx->base = NULL;
|
||||
|
||||
ULONG_PTR pageOff = (ULONG_PTR)(physAddr & (PAGE_SIZE - 1));
|
||||
SIZE_T totalSize = pageOff + size;
|
||||
ULONG pages = (ULONG)((totalSize + PAGE_SIZE - 1) / PAGE_SIZE);
|
||||
|
||||
PMDL mdl = IoAllocateMdl(NULL, (ULONG)totalSize, FALSE, FALSE, NULL);
|
||||
if (!mdl) return NULL;
|
||||
|
||||
PPFN_NUMBER pfn = MmGetMdlPfnArray(mdl);
|
||||
PFN_NUMBER startPfn = (PFN_NUMBER)(physAddr / PAGE_SIZE);
|
||||
for (ULONG i = 0; i < pages; i++)
|
||||
pfn[i] = startPfn + i;
|
||||
mdl->MdlFlags |= MDL_PAGES_LOCKED;
|
||||
|
||||
__try {
|
||||
ctx->base = MmMapLockedPagesSpecifyCache(
|
||||
mdl, KernelMode, cacheType, NULL, FALSE, NormalPagePriority);
|
||||
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
IoFreeMdl(mdl);
|
||||
return NULL;
|
||||
}
|
||||
if (!ctx->base) { IoFreeMdl(mdl); return NULL; }
|
||||
|
||||
ctx->mdl = mdl;
|
||||
return (PUCHAR)ctx->base + pageOff;
|
||||
}
|
||||
|
||||
static void UnmapPhysical(PHYS_MAP_CTX* ctx)
|
||||
{
|
||||
if (ctx->base) MmUnmapLockedPages(ctx->base, ctx->mdl);
|
||||
if (ctx->mdl) IoFreeMdl(ctx->mdl);
|
||||
ctx->base = NULL;
|
||||
ctx->mdl = NULL;
|
||||
}
|
||||
|
||||
/* ── Virtual memory read ─────────────────────────────────────────── */
|
||||
|
||||
static NTSTATUS HandleReadMemory(PIRP irp, PIO_STACK_LOCATION stk)
|
||||
{
|
||||
VALIDATE_INPUT(irp, stk, struct RcxDrvReadRequest);
|
||||
|
||||
struct RcxDrvReadRequest* req = (struct RcxDrvReadRequest*)irp->AssociatedIrp.SystemBuffer;
|
||||
if (req->length == 0 || req->length > RCX_DRV_MAX_VIRTUAL)
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
|
||||
VALIDATE_OUTPUT(irp, stk, req->length);
|
||||
|
||||
/* Save request fields before MmCopyVirtualMemory overwrites SystemBuffer.
|
||||
* METHOD_BUFFERED aliases input and output to the same buffer, so the
|
||||
* copy destination (SystemBuffer) clobbers req->* fields. */
|
||||
ULONG pid = req->pid;
|
||||
uint64_t address = req->address;
|
||||
ULONG length = req->length;
|
||||
|
||||
PEPROCESS proc = NULL;
|
||||
NTSTATUS st = LookupProcess(pid, &proc);
|
||||
if (!NT_SUCCESS(st)) return st;
|
||||
|
||||
SIZE_T bytesRead = 0;
|
||||
st = MmCopyVirtualMemory(
|
||||
proc, (PVOID)address,
|
||||
PsGetCurrentProcess(), irp->AssociatedIrp.SystemBuffer,
|
||||
(SIZE_T)length, KernelMode, &bytesRead);
|
||||
|
||||
ObDereferenceObject(proc);
|
||||
|
||||
/* Partial reads: zero remainder, report success */
|
||||
if (st == STATUS_PARTIAL_COPY) {
|
||||
RtlZeroMemory((PUCHAR)irp->AssociatedIrp.SystemBuffer + bytesRead,
|
||||
length - bytesRead);
|
||||
irp->IoStatus.Information = length;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
irp->IoStatus.Information = NT_SUCCESS(st) ? length : 0;
|
||||
return st;
|
||||
}
|
||||
|
||||
/* ── Virtual memory write ────────────────────────────────────────── */
|
||||
|
||||
static NTSTATUS HandleWriteMemory(PIRP irp, PIO_STACK_LOCATION stk)
|
||||
{
|
||||
ULONG inputLen = stk->Parameters.DeviceIoControl.InputBufferLength;
|
||||
if (inputLen < sizeof(struct RcxDrvWriteRequest))
|
||||
return STATUS_BUFFER_TOO_SMALL;
|
||||
|
||||
struct RcxDrvWriteRequest* req = (struct RcxDrvWriteRequest*)irp->AssociatedIrp.SystemBuffer;
|
||||
if (req->length == 0 || req->length > RCX_DRV_MAX_VIRTUAL)
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
if (inputLen < sizeof(struct RcxDrvWriteRequest) + req->length)
|
||||
return STATUS_BUFFER_TOO_SMALL;
|
||||
|
||||
PEPROCESS proc = NULL;
|
||||
NTSTATUS st = LookupProcess(req->pid, &proc);
|
||||
if (!NT_SUCCESS(st)) return st;
|
||||
|
||||
PUCHAR data = (PUCHAR)req + sizeof(struct RcxDrvWriteRequest);
|
||||
SIZE_T bytesWritten = 0;
|
||||
st = MmCopyVirtualMemory(
|
||||
PsGetCurrentProcess(), data,
|
||||
proc, (PVOID)req->address,
|
||||
(SIZE_T)req->length, KernelMode, &bytesWritten);
|
||||
|
||||
ObDereferenceObject(proc);
|
||||
irp->IoStatus.Information = 0;
|
||||
return st;
|
||||
}
|
||||
|
||||
/* ── Physical memory read ────────────────────────────────────────── */
|
||||
|
||||
static NTSTATUS HandleReadPhys(PIRP irp, PIO_STACK_LOCATION stk)
|
||||
{
|
||||
VALIDATE_INPUT(irp, stk, struct RcxDrvPhysReadRequest);
|
||||
|
||||
struct RcxDrvPhysReadRequest* req = (struct RcxDrvPhysReadRequest*)irp->AssociatedIrp.SystemBuffer;
|
||||
if (req->length == 0 || req->length > RCX_DRV_MAX_PHYSICAL)
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
if (req->width != 0 && req->width != 1 && req->width != 2 && req->width != 4)
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
|
||||
VALIDATE_OUTPUT(irp, stk, req->length);
|
||||
|
||||
/* Save request fields before SystemBuffer is overwritten (METHOD_BUFFERED
|
||||
* aliases input and output to the same buffer). */
|
||||
uint64_t physAddress = req->physAddress;
|
||||
ULONG length = req->length;
|
||||
ULONG width = req->width;
|
||||
|
||||
PUCHAR dst = (PUCHAR)irp->AssociatedIrp.SystemBuffer;
|
||||
|
||||
if (width == 0) {
|
||||
/* Byte copy -- use MmCopyMemory (safe for both RAM and MMIO) */
|
||||
MM_COPY_ADDRESS srcAddr;
|
||||
srcAddr.PhysicalAddress.QuadPart = (LONGLONG)physAddress;
|
||||
SIZE_T bytesCopied = 0;
|
||||
NTSTATUS st = MmCopyMemory(dst, srcAddr, (SIZE_T)length,
|
||||
MM_COPY_MEMORY_PHYSICAL, &bytesCopied);
|
||||
if (!NT_SUCCESS(st)) return st;
|
||||
if (bytesCopied < length)
|
||||
RtlZeroMemory(dst + bytesCopied, length - bytesCopied);
|
||||
irp->IoStatus.Information = length;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/* Width-aware MMIO reads -- map via MDL (safe for all physical addresses).
|
||||
* Use MmNonCached: width>0 implies MMIO register access where uncached
|
||||
* semantics are required for correct device interaction. */
|
||||
PHYS_MAP_CTX mapCtx;
|
||||
PUCHAR src = (PUCHAR)MapPhysical(physAddress, (SIZE_T)length, MmNonCached, &mapCtx);
|
||||
if (!src) return STATUS_UNSUCCESSFUL;
|
||||
|
||||
__try {
|
||||
ULONG off = 0;
|
||||
while (off + width <= length) {
|
||||
if (width == 1)
|
||||
dst[off] = READ_REGISTER_UCHAR(&src[off]);
|
||||
else if (width == 2)
|
||||
*(USHORT*)(dst + off) = READ_REGISTER_USHORT((PUSHORT)(src + off));
|
||||
else
|
||||
*(ULONG*)(dst + off) = READ_REGISTER_ULONG((PULONG)(src + off));
|
||||
off += width;
|
||||
}
|
||||
if (off < length)
|
||||
RtlZeroMemory(dst + off, length - off);
|
||||
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
UnmapPhysical(&mapCtx);
|
||||
return STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
UnmapPhysical(&mapCtx);
|
||||
irp->IoStatus.Information = length;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/* ── Physical memory write ───────────────────────────────────────── */
|
||||
|
||||
static NTSTATUS HandleWritePhys(PIRP irp, PIO_STACK_LOCATION stk)
|
||||
{
|
||||
ULONG inputLen = stk->Parameters.DeviceIoControl.InputBufferLength;
|
||||
if (inputLen < sizeof(struct RcxDrvPhysWriteRequest))
|
||||
return STATUS_BUFFER_TOO_SMALL;
|
||||
|
||||
struct RcxDrvPhysWriteRequest* req = (struct RcxDrvPhysWriteRequest*)irp->AssociatedIrp.SystemBuffer;
|
||||
if (req->length == 0 || req->length > RCX_DRV_MAX_PHYSICAL)
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
if (req->width != 0 && req->width != 1 && req->width != 2 && req->width != 4)
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
if (inputLen < sizeof(struct RcxDrvPhysWriteRequest) + req->length)
|
||||
return STATUS_BUFFER_TOO_SMALL;
|
||||
|
||||
PUCHAR src = (PUCHAR)req + sizeof(struct RcxDrvPhysWriteRequest);
|
||||
|
||||
/* Map via MDL (safe for both RAM and MMIO).
|
||||
* width==0 → RAM byte write (MmCached to avoid cache attribute conflict).
|
||||
* width>0 → MMIO register write (MmNonCached for correct device semantics). */
|
||||
MEMORY_CACHING_TYPE ct = (req->width == 0) ? MmCached : MmNonCached;
|
||||
PHYS_MAP_CTX mapCtx;
|
||||
PUCHAR dst = (PUCHAR)MapPhysical(req->physAddress, (SIZE_T)req->length, ct, &mapCtx);
|
||||
if (!dst) return STATUS_UNSUCCESSFUL;
|
||||
|
||||
__try {
|
||||
if (req->width == 0) {
|
||||
RtlCopyMemory(dst, src, req->length);
|
||||
} else {
|
||||
ULONG off = 0;
|
||||
while (off + req->width <= req->length) {
|
||||
if (req->width == 1)
|
||||
WRITE_REGISTER_UCHAR(&dst[off], src[off]);
|
||||
else if (req->width == 2)
|
||||
WRITE_REGISTER_USHORT((PUSHORT)(dst + off), *(USHORT*)(src + off));
|
||||
else
|
||||
WRITE_REGISTER_ULONG((PULONG)(dst + off), *(ULONG*)(src + off));
|
||||
off += req->width;
|
||||
}
|
||||
}
|
||||
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
UnmapPhysical(&mapCtx);
|
||||
return STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
UnmapPhysical(&mapCtx);
|
||||
irp->IoStatus.Information = 0;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/* ── Ping ────────────────────────────────────────────────────────── */
|
||||
|
||||
static NTSTATUS HandlePing(PIRP irp, PIO_STACK_LOCATION stk)
|
||||
{
|
||||
VALIDATE_OUTPUT(irp, stk, sizeof(struct RcxDrvPingResponse));
|
||||
|
||||
struct RcxDrvPingResponse* rsp = (struct RcxDrvPingResponse*)irp->AssociatedIrp.SystemBuffer;
|
||||
rsp->version = RCX_DRV_VERSION;
|
||||
rsp->driverBuild = __LINE__;
|
||||
irp->IoStatus.Information = sizeof(struct RcxDrvPingResponse);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/* ── Query PEB ───────────────────────────────────────────────────── */
|
||||
|
||||
static NTSTATUS HandleQueryPeb(PIRP irp, PIO_STACK_LOCATION stk)
|
||||
{
|
||||
VALIDATE_INPUT(irp, stk, struct RcxDrvQueryPebRequest);
|
||||
VALIDATE_OUTPUT(irp, stk, sizeof(struct RcxDrvQueryPebResponse));
|
||||
|
||||
struct RcxDrvQueryPebRequest* req = (struct RcxDrvQueryPebRequest*)irp->AssociatedIrp.SystemBuffer;
|
||||
struct RcxDrvQueryPebResponse* rsp = (struct RcxDrvQueryPebResponse*)irp->AssociatedIrp.SystemBuffer;
|
||||
|
||||
PEPROCESS proc = NULL;
|
||||
NTSTATUS st = LookupProcess(req->pid, &proc);
|
||||
if (!NT_SUCCESS(st)) return st;
|
||||
|
||||
rsp->pebAddress = (uint64_t)(ULONG_PTR)PsGetProcessPeb(proc);
|
||||
rsp->pointerSize = 8;
|
||||
rsp->_pad = 0;
|
||||
|
||||
/* Detect WoW64 (32-bit process on 64-bit OS) */
|
||||
PVOID wow64 = PsGetProcessWow64Process(proc);
|
||||
if (wow64) {
|
||||
rsp->pebAddress = (uint64_t)(ULONG_PTR)wow64;
|
||||
rsp->pointerSize = 4;
|
||||
}
|
||||
|
||||
ObDereferenceObject(proc);
|
||||
irp->IoStatus.Information = sizeof(struct RcxDrvQueryPebResponse);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/* ── Query Regions ───────────────────────────────────────────────── */
|
||||
|
||||
static NTSTATUS HandleQueryRegions(PIRP irp, PIO_STACK_LOCATION stk)
|
||||
{
|
||||
VALIDATE_INPUT(irp, stk, struct RcxDrvQueryRegionsRequest);
|
||||
|
||||
struct RcxDrvQueryRegionsRequest* req = (struct RcxDrvQueryRegionsRequest*)irp->AssociatedIrp.SystemBuffer;
|
||||
ULONG outputLen = stk->Parameters.DeviceIoControl.OutputBufferLength;
|
||||
ULONG maxEntries = outputLen / sizeof(struct RcxDrvRegionEntry);
|
||||
if (maxEntries == 0) return STATUS_BUFFER_TOO_SMALL;
|
||||
|
||||
PEPROCESS proc = NULL;
|
||||
NTSTATUS st = LookupProcess(req->pid, &proc);
|
||||
if (!NT_SUCCESS(st)) return st;
|
||||
|
||||
/* Attach to target process to query its address space.
|
||||
* IOCTLs arrive at PASSIVE_LEVEL; KeStackAttachProcess requires <= APC_LEVEL.
|
||||
* ZwQueryVirtualMemory with ZwCurrentProcess() while attached queries the
|
||||
* attached process's address space (correct). */
|
||||
KAPC_STATE apcState;
|
||||
KeStackAttachProcess(proc, &apcState);
|
||||
|
||||
struct RcxDrvRegionEntry* entries = (struct RcxDrvRegionEntry*)irp->AssociatedIrp.SystemBuffer;
|
||||
ULONG count = 0;
|
||||
PVOID addr = NULL;
|
||||
MEMORY_BASIC_INFORMATION_KM mbi;
|
||||
|
||||
while (count < maxEntries) {
|
||||
SIZE_T retLen = 0;
|
||||
st = ZwQueryVirtualMemory(ZwCurrentProcess(), addr, 0 /*MemoryBasicInformation*/,
|
||||
&mbi, sizeof(mbi), &retLen);
|
||||
if (!NT_SUCCESS(st)) break;
|
||||
|
||||
if (mbi.State == MEM_COMMIT_KM) {
|
||||
entries[count].base = (uint64_t)(ULONG_PTR)mbi.BaseAddress;
|
||||
entries[count].size = (uint64_t)mbi.RegionSize;
|
||||
entries[count].protect = mbi.Protect;
|
||||
entries[count].state = mbi.State;
|
||||
count++;
|
||||
}
|
||||
|
||||
ULONG_PTR next = (ULONG_PTR)mbi.BaseAddress + mbi.RegionSize;
|
||||
if (next <= (ULONG_PTR)addr) break; /* overflow */
|
||||
addr = (PVOID)next;
|
||||
}
|
||||
|
||||
KeUnstackDetachProcess(&apcState);
|
||||
ObDereferenceObject(proc);
|
||||
|
||||
irp->IoStatus.Information = count * sizeof(struct RcxDrvRegionEntry);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/* ── Query Modules ───────────────────────────────────────────────── */
|
||||
|
||||
static NTSTATUS HandleQueryModules(PIRP irp, PIO_STACK_LOCATION stk)
|
||||
{
|
||||
VALIDATE_INPUT(irp, stk, struct RcxDrvQueryModulesRequest);
|
||||
|
||||
struct RcxDrvQueryModulesRequest* req = (struct RcxDrvQueryModulesRequest*)irp->AssociatedIrp.SystemBuffer;
|
||||
ULONG outputLen = stk->Parameters.DeviceIoControl.OutputBufferLength;
|
||||
ULONG maxEntries = outputLen / sizeof(struct RcxDrvModuleEntry);
|
||||
if (maxEntries == 0) return STATUS_BUFFER_TOO_SMALL;
|
||||
|
||||
PEPROCESS proc = NULL;
|
||||
NTSTATUS st = LookupProcess(req->pid, &proc);
|
||||
if (!NT_SUCCESS(st)) return st;
|
||||
|
||||
/* Attach to target process to read PEB->Ldr */
|
||||
KAPC_STATE apcState;
|
||||
KeStackAttachProcess(proc, &apcState);
|
||||
|
||||
struct RcxDrvModuleEntry* entries = (struct RcxDrvModuleEntry*)irp->AssociatedIrp.SystemBuffer;
|
||||
ULONG count = 0;
|
||||
|
||||
__try {
|
||||
/* Read PEB address */
|
||||
PEB_KM* peb = (PEB_KM*)PsGetProcessPeb(proc);
|
||||
if (!peb) goto done;
|
||||
ProbeForRead(peb, sizeof(PEB_KM), 1);
|
||||
|
||||
/* PEB->Ldr at offset 0x18 (x64) */
|
||||
PEB_LDR_DATA_KM* ldr = peb->Ldr;
|
||||
if (!ldr) goto done;
|
||||
ProbeForRead(ldr, sizeof(PEB_LDR_DATA_KM), 1);
|
||||
|
||||
/* Walk InLoadOrderModuleList */
|
||||
LIST_ENTRY* head = &ldr->InLoadOrderModuleList;
|
||||
LIST_ENTRY* cur = head->Flink;
|
||||
|
||||
while (cur != head && count < maxEntries) {
|
||||
LDR_DATA_TABLE_ENTRY_KM* entry = CONTAINING_RECORD(cur, LDR_DATA_TABLE_ENTRY_KM, InLoadOrderLinks);
|
||||
|
||||
entries[count].base = (uint64_t)(ULONG_PTR)entry->DllBase;
|
||||
entries[count].size = (uint64_t)entry->SizeOfImage;
|
||||
|
||||
/* Copy wide-char name (truncate to 259 chars + null) */
|
||||
USHORT nameLen = entry->BaseDllName.Length / sizeof(WCHAR);
|
||||
if (nameLen > 259) nameLen = 259;
|
||||
if (entry->BaseDllName.Buffer) {
|
||||
RtlCopyMemory(entries[count].name, entry->BaseDllName.Buffer,
|
||||
nameLen * sizeof(uint16_t));
|
||||
}
|
||||
entries[count].name[nameLen] = 0;
|
||||
|
||||
count++;
|
||||
cur = cur->Flink;
|
||||
}
|
||||
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
/* Partial results are fine */
|
||||
}
|
||||
|
||||
done:
|
||||
KeUnstackDetachProcess(&apcState);
|
||||
ObDereferenceObject(proc);
|
||||
|
||||
irp->IoStatus.Information = count * sizeof(struct RcxDrvModuleEntry);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/* ── Query TEBs ──────────────────────────────────────────────────── */
|
||||
|
||||
/*
|
||||
* Walk the target process's thread list to collect TEB addresses.
|
||||
* Uses PsGetNextProcessThread (undocumented but stable since Vista).
|
||||
*/
|
||||
static NTSTATUS HandleQueryTebs(PIRP irp, PIO_STACK_LOCATION stk)
|
||||
{
|
||||
VALIDATE_INPUT(irp, stk, struct RcxDrvQueryTebsRequest);
|
||||
|
||||
struct RcxDrvQueryTebsRequest* req = (struct RcxDrvQueryTebsRequest*)irp->AssociatedIrp.SystemBuffer;
|
||||
ULONG outputLen = stk->Parameters.DeviceIoControl.OutputBufferLength;
|
||||
ULONG maxEntries = outputLen / sizeof(struct RcxDrvTebEntry);
|
||||
if (maxEntries == 0) return STATUS_BUFFER_TOO_SMALL;
|
||||
|
||||
PEPROCESS proc = NULL;
|
||||
NTSTATUS st = LookupProcess(req->pid, &proc);
|
||||
if (!NT_SUCCESS(st)) return st;
|
||||
|
||||
struct RcxDrvTebEntry* entries = (struct RcxDrvTebEntry*)irp->AssociatedIrp.SystemBuffer;
|
||||
ULONG count = 0;
|
||||
|
||||
if (!g_PsGetNextProcessThread) {
|
||||
ObDereferenceObject(proc);
|
||||
return STATUS_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
/* PsGetNextProcessThread increments the ref on the returned PETHREAD and
|
||||
* dereferences the previous one. We must release the last thread if we
|
||||
* exit the loop early (exception or maxEntries hit). */
|
||||
{
|
||||
PETHREAD thread = NULL;
|
||||
__try {
|
||||
while ((thread = g_PsGetNextProcessThread(proc, thread)) != NULL) {
|
||||
if (count >= maxEntries) {
|
||||
/* Hit limit — release the thread PsGetNextProcessThread just returned */
|
||||
ObDereferenceObject(thread);
|
||||
break;
|
||||
}
|
||||
PVOID teb = PsGetThreadTeb(thread);
|
||||
if (teb) {
|
||||
entries[count].tebAddress = (uint64_t)(ULONG_PTR)teb;
|
||||
entries[count].threadId = (uint32_t)(ULONG_PTR)PsGetThreadId(thread);
|
||||
entries[count]._pad = 0;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
/* Exception mid-iteration: thread holds a referenced PETHREAD — release it */
|
||||
if (thread)
|
||||
ObDereferenceObject(thread);
|
||||
}
|
||||
}
|
||||
|
||||
ObDereferenceObject(proc);
|
||||
|
||||
irp->IoStatus.Information = count * sizeof(struct RcxDrvTebEntry);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/* ── Read CR3 (DirectoryTableBase) ────────────────────────────────── */
|
||||
|
||||
/*
|
||||
* EPROCESS.DirectoryTableBase offset. Stable across Win10/11 x64.
|
||||
* Verified: 0x028 on 1507-22H2+ (KPROCESS is at offset 0 of EPROCESS).
|
||||
*/
|
||||
#define KPROCESS_DIRECTORY_TABLE_BASE 0x028
|
||||
|
||||
static NTSTATUS HandleReadCr3(PIRP irp, PIO_STACK_LOCATION stk)
|
||||
{
|
||||
VALIDATE_INPUT(irp, stk, struct RcxDrvReadCr3Request);
|
||||
VALIDATE_OUTPUT(irp, stk, sizeof(struct RcxDrvReadCr3Response));
|
||||
|
||||
struct RcxDrvReadCr3Request* req = (struct RcxDrvReadCr3Request*)irp->AssociatedIrp.SystemBuffer;
|
||||
struct RcxDrvReadCr3Response* rsp = (struct RcxDrvReadCr3Response*)irp->AssociatedIrp.SystemBuffer;
|
||||
|
||||
PEPROCESS proc = NULL;
|
||||
NTSTATUS st = LookupProcess(req->pid, &proc);
|
||||
if (!NT_SUCCESS(st)) return st;
|
||||
|
||||
__try {
|
||||
rsp->cr3 = *(uint64_t*)((PUCHAR)proc + KPROCESS_DIRECTORY_TABLE_BASE);
|
||||
/* Mask off PCID bits (bits 0-11) to get the PML4 physical address */
|
||||
rsp->cr3 &= ~0xFFFULL;
|
||||
rsp->kernelCr3 = rsp->cr3; /* same on non-KPTI; KPTI shadow is not easily accessible */
|
||||
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
ObDereferenceObject(proc);
|
||||
return STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
ObDereferenceObject(proc);
|
||||
irp->IoStatus.Information = sizeof(struct RcxDrvReadCr3Response);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/* ── Virtual-to-Physical address translation ─────────────────────── */
|
||||
|
||||
/* NOTE: This walks the page table non-atomically via 4 sequential physical reads.
|
||||
* The page table can be modified between reads (e.g., page-out, remap). This is
|
||||
* an inherent limitation shared by WinDbg's !vtop and similar tools. For a
|
||||
* debugging/reversing tool this tradeoff is acceptable. */
|
||||
|
||||
/* Extract physical frame address from a page table entry (bits 51:12) */
|
||||
#define PTE_FRAME(pte) ((pte) & 0x000FFFFFFFFFF000ULL)
|
||||
/* Check Present bit (bit 0) */
|
||||
#define PTE_PRESENT(pte) ((pte) & 1ULL)
|
||||
/* Check Page Size bit (bit 7) -- indicates large/huge page */
|
||||
#define PTE_PS(pte) ((pte) & (1ULL << 7))
|
||||
|
||||
static NTSTATUS HandleVtop(PIRP irp, PIO_STACK_LOCATION stk)
|
||||
{
|
||||
VALIDATE_INPUT(irp, stk, struct RcxDrvVtopRequest);
|
||||
VALIDATE_OUTPUT(irp, stk, sizeof(struct RcxDrvVtopResponse));
|
||||
|
||||
struct RcxDrvVtopRequest* req = (struct RcxDrvVtopRequest*)irp->AssociatedIrp.SystemBuffer;
|
||||
struct RcxDrvVtopResponse* rsp = (struct RcxDrvVtopResponse*)irp->AssociatedIrp.SystemBuffer;
|
||||
|
||||
PEPROCESS proc = NULL;
|
||||
NTSTATUS st = LookupProcess(req->pid, &proc);
|
||||
if (!NT_SUCCESS(st)) return st;
|
||||
|
||||
/* Read CR3 */
|
||||
uint64_t cr3;
|
||||
__try {
|
||||
cr3 = *(uint64_t*)((PUCHAR)proc + KPROCESS_DIRECTORY_TABLE_BASE);
|
||||
cr3 &= ~0xFFFULL;
|
||||
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
ObDereferenceObject(proc);
|
||||
return STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
ObDereferenceObject(proc);
|
||||
|
||||
uint64_t va = req->virtualAddress;
|
||||
RtlZeroMemory(rsp, sizeof(*rsp));
|
||||
|
||||
/* Extract indices from virtual address:
|
||||
* [47:39] = PML4 index, [38:30] = PDPT index,
|
||||
* [29:21] = PD index, [20:12] = PT index,
|
||||
* [11:0] = page offset */
|
||||
ULONG pml4Idx = (ULONG)((va >> 39) & 0x1FF);
|
||||
ULONG pdptIdx = (ULONG)((va >> 30) & 0x1FF);
|
||||
ULONG pdIdx = (ULONG)((va >> 21) & 0x1FF);
|
||||
ULONG ptIdx = (ULONG)((va >> 12) & 0x1FF);
|
||||
|
||||
MM_COPY_ADDRESS ca;
|
||||
SIZE_T copied;
|
||||
uint64_t entry;
|
||||
|
||||
/* Level 4: PML4 -- use MmCopyMemory (safe for RAM, unlike MmMapIoSpace) */
|
||||
ca.PhysicalAddress.QuadPart = (LONGLONG)(cr3 + pml4Idx * 8);
|
||||
st = MmCopyMemory(&entry, ca, 8, MM_COPY_MEMORY_PHYSICAL, &copied);
|
||||
if (!NT_SUCCESS(st) || copied < 8) return STATUS_UNSUCCESSFUL;
|
||||
rsp->pml4e = entry;
|
||||
if (!PTE_PRESENT(entry)) { rsp->valid = 0; goto done; }
|
||||
|
||||
/* Level 3: PDPT */
|
||||
ca.PhysicalAddress.QuadPart = (LONGLONG)(PTE_FRAME(entry) + pdptIdx * 8);
|
||||
st = MmCopyMemory(&entry, ca, 8, MM_COPY_MEMORY_PHYSICAL, &copied);
|
||||
if (!NT_SUCCESS(st) || copied < 8) return STATUS_UNSUCCESSFUL;
|
||||
rsp->pdpte = entry;
|
||||
if (!PTE_PRESENT(entry)) { rsp->valid = 0; goto done; }
|
||||
if (PTE_PS(entry)) {
|
||||
/* 1GB huge page: physical = frame[51:30] | va[29:0] */
|
||||
rsp->physicalAddress = (entry & 0x000FFFFFC0000000ULL) | (va & 0x3FFFFFFFULL);
|
||||
rsp->pageSize = 2;
|
||||
rsp->valid = 1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Level 2: PD */
|
||||
ca.PhysicalAddress.QuadPart = (LONGLONG)(PTE_FRAME(entry) + pdIdx * 8);
|
||||
st = MmCopyMemory(&entry, ca, 8, MM_COPY_MEMORY_PHYSICAL, &copied);
|
||||
if (!NT_SUCCESS(st) || copied < 8) return STATUS_UNSUCCESSFUL;
|
||||
rsp->pde = entry;
|
||||
if (!PTE_PRESENT(entry)) { rsp->valid = 0; goto done; }
|
||||
if (PTE_PS(entry)) {
|
||||
/* 2MB large page: physical = frame[51:21] | va[20:0] */
|
||||
rsp->physicalAddress = (entry & 0x000FFFFFFFE00000ULL) | (va & 0x1FFFFFULL);
|
||||
rsp->pageSize = 1;
|
||||
rsp->valid = 1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Level 1: PT */
|
||||
ca.PhysicalAddress.QuadPart = (LONGLONG)(PTE_FRAME(entry) + ptIdx * 8);
|
||||
st = MmCopyMemory(&entry, ca, 8, MM_COPY_MEMORY_PHYSICAL, &copied);
|
||||
if (!NT_SUCCESS(st) || copied < 8) return STATUS_UNSUCCESSFUL;
|
||||
rsp->pte = entry;
|
||||
if (!PTE_PRESENT(entry)) { rsp->valid = 0; goto done; }
|
||||
|
||||
/* 4KB page: physical = frame[51:12] | va[11:0] */
|
||||
rsp->physicalAddress = PTE_FRAME(entry) | (va & 0xFFFULL);
|
||||
rsp->pageSize = 0;
|
||||
rsp->valid = 1;
|
||||
|
||||
done:
|
||||
irp->IoStatus.Information = sizeof(struct RcxDrvVtopResponse);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/* ── IOCTL dispatch ──────────────────────────────────────────────── */
|
||||
|
||||
static NTSTATUS DispatchIoctl(PDEVICE_OBJECT dev, PIRP irp)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(dev);
|
||||
|
||||
PIO_STACK_LOCATION stk = IoGetCurrentIrpStackLocation(irp);
|
||||
NTSTATUS st;
|
||||
|
||||
switch (stk->Parameters.DeviceIoControl.IoControlCode) {
|
||||
case IOCTL_RCX_READ_MEMORY: st = HandleReadMemory(irp, stk); break;
|
||||
case IOCTL_RCX_WRITE_MEMORY: st = HandleWriteMemory(irp, stk); break;
|
||||
case IOCTL_RCX_QUERY_REGIONS: st = HandleQueryRegions(irp, stk); break;
|
||||
case IOCTL_RCX_QUERY_PEB: st = HandleQueryPeb(irp, stk); break;
|
||||
case IOCTL_RCX_QUERY_MODULES: st = HandleQueryModules(irp, stk); break;
|
||||
case IOCTL_RCX_QUERY_TEBS: st = HandleQueryTebs(irp, stk); break;
|
||||
case IOCTL_RCX_PING: st = HandlePing(irp, stk); break;
|
||||
case IOCTL_RCX_READ_PHYS: st = HandleReadPhys(irp, stk); break;
|
||||
case IOCTL_RCX_WRITE_PHYS: st = HandleWritePhys(irp, stk); break;
|
||||
case IOCTL_RCX_READ_CR3: st = HandleReadCr3(irp, stk); break;
|
||||
case IOCTL_RCX_VTOP: st = HandleVtop(irp, stk); break;
|
||||
default:
|
||||
st = STATUS_INVALID_DEVICE_REQUEST;
|
||||
irp->IoStatus.Information = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
irp->IoStatus.Status = st;
|
||||
IoCompleteRequest(irp, IO_NO_INCREMENT);
|
||||
return st;
|
||||
}
|
||||
|
||||
/* ── Create / Close (permit open/close) ──────────────────────────── */
|
||||
|
||||
static NTSTATUS DispatchCreateClose(PDEVICE_OBJECT dev, PIRP irp)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(dev);
|
||||
irp->IoStatus.Status = STATUS_SUCCESS;
|
||||
irp->IoStatus.Information = 0;
|
||||
IoCompleteRequest(irp, IO_NO_INCREMENT);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/* ── Unload ──────────────────────────────────────────────────────── */
|
||||
|
||||
void DriverUnload(PDRIVER_OBJECT drv)
|
||||
{
|
||||
UNICODE_STRING symlink = RTL_CONSTANT_STRING(L"\\DosDevices\\RcxDrv");
|
||||
IoDeleteSymbolicLink(&symlink);
|
||||
if (drv->DeviceObject)
|
||||
IoDeleteDevice(drv->DeviceObject);
|
||||
}
|
||||
|
||||
/* ── Entry point ─────────────────────────────────────────────────── */
|
||||
|
||||
NTSTATUS DriverEntry(PDRIVER_OBJECT drv, PUNICODE_STRING regPath)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(regPath);
|
||||
|
||||
/* Resolve undocumented APIs */
|
||||
UNICODE_STRING fnName = RTL_CONSTANT_STRING(L"PsGetNextProcessThread");
|
||||
g_PsGetNextProcessThread = (PsGetNextProcessThread_t)MmGetSystemRoutineAddress(&fnName);
|
||||
|
||||
UNICODE_STRING devName = RTL_CONSTANT_STRING(L"\\Device\\RcxDrv");
|
||||
UNICODE_STRING symlink = RTL_CONSTANT_STRING(L"\\DosDevices\\RcxDrv");
|
||||
|
||||
PDEVICE_OBJECT devObj = NULL;
|
||||
NTSTATUS st = IoCreateDevice(drv, 0, &devName, FILE_DEVICE_UNKNOWN,
|
||||
FILE_DEVICE_SECURE_OPEN, FALSE, &devObj);
|
||||
if (!NT_SUCCESS(st)) return st;
|
||||
|
||||
st = IoCreateSymbolicLink(&symlink, &devName);
|
||||
if (!NT_SUCCESS(st)) {
|
||||
IoDeleteDevice(devObj);
|
||||
return st;
|
||||
}
|
||||
|
||||
drv->MajorFunction[IRP_MJ_CREATE] = DispatchCreateClose;
|
||||
drv->MajorFunction[IRP_MJ_CLOSE] = DispatchCreateClose;
|
||||
drv->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchIoctl;
|
||||
drv->DriverUnload = DriverUnload;
|
||||
|
||||
devObj->Flags |= DO_BUFFERED_IO;
|
||||
devObj->Flags &= ~DO_DEVICE_INITIALIZING;
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
17
plugins/KernelMemory/linux/Makefile
Normal file
17
plugins/KernelMemory/linux/Makefile
Normal file
@@ -0,0 +1,17 @@
|
||||
obj-m += rcxkm.o
|
||||
|
||||
KDIR ?= /lib/modules/$(shell uname -r)/build
|
||||
|
||||
all:
|
||||
$(MAKE) -C $(KDIR) M=$(PWD) modules
|
||||
|
||||
clean:
|
||||
$(MAKE) -C $(KDIR) M=$(PWD) clean
|
||||
|
||||
install:
|
||||
insmod rcxkm.ko
|
||||
|
||||
uninstall:
|
||||
rmmod rcxkm
|
||||
|
||||
.PHONY: all clean install uninstall
|
||||
132
plugins/KernelMemory/linux/rcxkm.c
Normal file
132
plugins/KernelMemory/linux/rcxkm.c
Normal file
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* rcxkm.c -- Linux kernel module stub for Reclass kernel memory provider.
|
||||
*
|
||||
* Provides /dev/rcxkm char device with ioctl() dispatch using the same
|
||||
* protocol structs as the Windows driver (rcx_drv_protocol.h).
|
||||
*
|
||||
* Build: make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
|
||||
*
|
||||
* TODO: implement handlers (currently returns -ENOSYS for all IOCTLs).
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/pid.h>
|
||||
#include <linux/mm.h>
|
||||
|
||||
#include "../rcx_drv_protocol.h"
|
||||
|
||||
#define DEVICE_NAME "rcxkm"
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Reclass");
|
||||
MODULE_DESCRIPTION("Reclass kernel memory provider (stub)");
|
||||
|
||||
/* ── IOCTL dispatch ─────────────────────────────────────────────────── */
|
||||
|
||||
static long rcxkm_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
(void)filp;
|
||||
(void)arg;
|
||||
|
||||
switch (cmd) {
|
||||
case IOCTL_RCX_READ_MEMORY:
|
||||
/* TODO: find_get_pid(pid) -> get_task_struct -> access_process_vm() */
|
||||
return -ENOSYS;
|
||||
|
||||
case IOCTL_RCX_WRITE_MEMORY:
|
||||
/* TODO: access_process_vm() with FOLL_WRITE */
|
||||
return -ENOSYS;
|
||||
|
||||
case IOCTL_RCX_QUERY_REGIONS:
|
||||
/* TODO: walk target mm->mmap via VMA iteration */
|
||||
return -ENOSYS;
|
||||
|
||||
case IOCTL_RCX_QUERY_PEB:
|
||||
/* N/A on Linux (no PEB); could return mm->start_brk or similar */
|
||||
return -ENOSYS;
|
||||
|
||||
case IOCTL_RCX_QUERY_MODULES:
|
||||
/* TODO: walk target /proc/pid/maps or mm VMAs */
|
||||
return -ENOSYS;
|
||||
|
||||
case IOCTL_RCX_QUERY_TEBS:
|
||||
/* N/A on Linux (no TEB) */
|
||||
return -ENOSYS;
|
||||
|
||||
case IOCTL_RCX_PING: {
|
||||
struct RcxDrvPingResponse resp = {
|
||||
.version = RCX_DRV_VERSION,
|
||||
.driverBuild = 1,
|
||||
};
|
||||
if (copy_to_user((void __user *)arg, &resp, sizeof(resp)))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
case IOCTL_RCX_READ_PHYS:
|
||||
/* TODO: ioremap() + memcpy_fromio() */
|
||||
return -ENOSYS;
|
||||
|
||||
case IOCTL_RCX_WRITE_PHYS:
|
||||
/* TODO: ioremap() + memcpy_toio() */
|
||||
return -ENOSYS;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── File operations ────────────────────────────────────────────────── */
|
||||
|
||||
static int rcxkm_open(struct inode *inode, struct file *filp)
|
||||
{
|
||||
(void)inode; (void)filp;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rcxkm_release(struct inode *inode, struct file *filp)
|
||||
{
|
||||
(void)inode; (void)filp;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct file_operations rcxkm_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.unlocked_ioctl = rcxkm_ioctl,
|
||||
.open = rcxkm_open,
|
||||
.release = rcxkm_release,
|
||||
};
|
||||
|
||||
static struct miscdevice rcxkm_device = {
|
||||
.minor = MISC_DYNAMIC_MINOR,
|
||||
.name = DEVICE_NAME,
|
||||
.fops = &rcxkm_fops,
|
||||
};
|
||||
|
||||
/* ── Module init/exit ───────────────────────────────────────────────── */
|
||||
|
||||
static int __init rcxkm_init(void)
|
||||
{
|
||||
int ret = misc_register(&rcxkm_device);
|
||||
if (ret) {
|
||||
pr_err("rcxkm: failed to register misc device (err=%d)\n", ret);
|
||||
return ret;
|
||||
}
|
||||
pr_info("rcxkm: loaded, device /dev/%s\n", DEVICE_NAME);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit rcxkm_exit(void)
|
||||
{
|
||||
misc_deregister(&rcxkm_device);
|
||||
pr_info("rcxkm: unloaded\n");
|
||||
}
|
||||
|
||||
module_init(rcxkm_init);
|
||||
module_exit(rcxkm_exit);
|
||||
189
plugins/KernelMemory/rcx_drv_protocol.h
Normal file
189
plugins/KernelMemory/rcx_drv_protocol.h
Normal file
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* RCX Driver Protocol -- shared between kernel driver and usermode plugin.
|
||||
* No dependencies beyond standard C headers. Pure C, no Windows types.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#ifdef KERNEL
|
||||
/* Kernel mode build: avoid stdint.h (not in WDK km/crt) */
|
||||
typedef unsigned char uint8_t;
|
||||
typedef unsigned short uint16_t;
|
||||
typedef unsigned int uint32_t;
|
||||
typedef unsigned __int64 uint64_t;
|
||||
typedef signed __int64 int64_t;
|
||||
#else
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
|
||||
/* ── Device / service names ───────────────────────────────────────── */
|
||||
#define RCX_DRV_DEVICE_NAME L"\\Device\\RcxDrv"
|
||||
#define RCX_DRV_SYMLINK_NAME L"\\DosDevices\\RcxDrv"
|
||||
#define RCX_DRV_USERMODE_PATH "\\\\.\\RcxDrv"
|
||||
#define RCX_DRV_SERVICE_NAME "RcxDrv"
|
||||
|
||||
/* ── Protocol version ─────────────────────────────────────────────── */
|
||||
#define RCX_DRV_VERSION 1
|
||||
|
||||
/* ── Size limits ──────────────────────────────────────────────────── */
|
||||
#define RCX_DRV_MAX_VIRTUAL (1024 * 1024) /* 1 MB per virtual read/write */
|
||||
#define RCX_DRV_MAX_PHYSICAL 4096 /* 4 KB per physical read/write */
|
||||
|
||||
/* ── IOCTL codes ──────────────────────────────────────────────────── */
|
||||
/* CTL_CODE(FILE_DEVICE_UNKNOWN=0x22, function, METHOD_BUFFERED=0, FILE_ANY_ACCESS=0) */
|
||||
|
||||
/* Virtual memory (per-process) */
|
||||
#define IOCTL_RCX_READ_MEMORY 0x222000 /* function 0x800 */
|
||||
#define IOCTL_RCX_WRITE_MEMORY 0x222004 /* function 0x801 */
|
||||
#define IOCTL_RCX_QUERY_REGIONS 0x222008 /* function 0x802 */
|
||||
#define IOCTL_RCX_QUERY_PEB 0x22200C /* function 0x803 */
|
||||
#define IOCTL_RCX_QUERY_MODULES 0x222010 /* function 0x804 */
|
||||
#define IOCTL_RCX_QUERY_TEBS 0x222014 /* function 0x805 */
|
||||
#define IOCTL_RCX_PING 0x222018 /* function 0x806 */
|
||||
|
||||
/* Physical memory (MMIO) */
|
||||
#define IOCTL_RCX_READ_PHYS 0x22201C /* function 0x807 */
|
||||
#define IOCTL_RCX_WRITE_PHYS 0x222020 /* function 0x808 */
|
||||
|
||||
/* Paging / address translation */
|
||||
#define IOCTL_RCX_READ_CR3 0x222044 /* function 0x811 */
|
||||
#define IOCTL_RCX_VTOP 0x222048 /* function 0x812 */
|
||||
|
||||
/* ── Request / Response structures ────────────────────────────────── */
|
||||
/* All structs are naturally aligned. Padding fields are explicit. */
|
||||
|
||||
/* -- Virtual memory -- */
|
||||
|
||||
struct RcxDrvReadRequest {
|
||||
uint32_t pid;
|
||||
uint32_t _pad0;
|
||||
uint64_t address;
|
||||
uint32_t length; /* max RCX_DRV_MAX_VIRTUAL */
|
||||
uint32_t _pad1;
|
||||
};
|
||||
|
||||
/* Write: input = header + inline data bytes */
|
||||
struct RcxDrvWriteRequest {
|
||||
uint32_t pid;
|
||||
uint32_t _pad0;
|
||||
uint64_t address;
|
||||
uint32_t length; /* max RCX_DRV_MAX_VIRTUAL */
|
||||
uint32_t _pad1;
|
||||
/* uint8_t data[length] follows */
|
||||
};
|
||||
|
||||
/* -- Region enumeration -- */
|
||||
|
||||
struct RcxDrvQueryRegionsRequest {
|
||||
uint32_t pid;
|
||||
uint32_t _pad;
|
||||
};
|
||||
|
||||
struct RcxDrvRegionEntry {
|
||||
uint64_t base;
|
||||
uint64_t size;
|
||||
uint32_t protect; /* raw PAGE_* flags */
|
||||
uint32_t state; /* MEM_COMMIT etc. */
|
||||
};
|
||||
|
||||
/* -- PEB -- */
|
||||
|
||||
struct RcxDrvQueryPebRequest {
|
||||
uint32_t pid;
|
||||
uint32_t _pad;
|
||||
};
|
||||
|
||||
struct RcxDrvQueryPebResponse {
|
||||
uint64_t pebAddress;
|
||||
uint32_t pointerSize; /* 4 or 8 */
|
||||
uint32_t _pad;
|
||||
};
|
||||
|
||||
/* -- Modules -- */
|
||||
|
||||
struct RcxDrvQueryModulesRequest {
|
||||
uint32_t pid;
|
||||
uint32_t _pad;
|
||||
};
|
||||
|
||||
struct RcxDrvModuleEntry {
|
||||
uint64_t base;
|
||||
uint64_t size;
|
||||
uint16_t name[260]; /* wide-char, null-terminated */
|
||||
};
|
||||
|
||||
/* -- TEBs -- */
|
||||
|
||||
struct RcxDrvQueryTebsRequest {
|
||||
uint32_t pid;
|
||||
uint32_t _pad;
|
||||
};
|
||||
|
||||
struct RcxDrvTebEntry {
|
||||
uint64_t tebAddress;
|
||||
uint32_t threadId;
|
||||
uint32_t _pad;
|
||||
};
|
||||
|
||||
/* -- Ping -- */
|
||||
|
||||
struct RcxDrvPingResponse {
|
||||
uint32_t version;
|
||||
uint32_t driverBuild;
|
||||
};
|
||||
|
||||
/* -- Physical memory -- */
|
||||
|
||||
struct RcxDrvPhysReadRequest {
|
||||
uint64_t physAddress;
|
||||
uint32_t length; /* max RCX_DRV_MAX_PHYSICAL */
|
||||
uint32_t width; /* access width: 1, 2, or 4 (0 = memcpy) */
|
||||
};
|
||||
|
||||
struct RcxDrvPhysWriteRequest {
|
||||
uint64_t physAddress;
|
||||
uint32_t length; /* max RCX_DRV_MAX_PHYSICAL */
|
||||
uint32_t width; /* access width: 1, 2, or 4 (0 = memcpy) */
|
||||
/* uint8_t data[length] follows */
|
||||
};
|
||||
|
||||
/* -- Paging / address translation -- */
|
||||
|
||||
struct RcxDrvReadCr3Request {
|
||||
uint32_t pid;
|
||||
uint32_t _pad;
|
||||
};
|
||||
|
||||
struct RcxDrvReadCr3Response {
|
||||
uint64_t cr3; /* DirectoryTableBase (PML4 physical address) */
|
||||
uint64_t kernelCr3; /* KernelDirectoryTableBase (KPTI shadow) */
|
||||
};
|
||||
|
||||
struct RcxDrvVtopRequest {
|
||||
uint32_t pid;
|
||||
uint32_t _pad;
|
||||
uint64_t virtualAddress;
|
||||
};
|
||||
|
||||
struct RcxDrvVtopResponse {
|
||||
uint64_t physicalAddress; /* final translated physical address (with page offset) */
|
||||
uint64_t pml4e; /* raw PML4 entry value */
|
||||
uint64_t pdpte; /* raw PDPT entry value */
|
||||
uint64_t pde; /* raw PD entry value */
|
||||
uint64_t pte; /* raw PT entry value (0 if large/huge page) */
|
||||
uint8_t pageSize; /* 0=4KB, 1=2MB, 2=1GB */
|
||||
uint8_t valid; /* 1 if translation succeeded, 0 if not present */
|
||||
uint8_t _pad2[6];
|
||||
};
|
||||
|
||||
/* ── Compile-time validation ──────────────────────────────────────── */
|
||||
#ifdef __cplusplus
|
||||
static_assert(sizeof(RcxDrvReadRequest) == 24, "ReadRequest layout");
|
||||
static_assert(sizeof(RcxDrvWriteRequest) == 24, "WriteRequest layout");
|
||||
static_assert(sizeof(RcxDrvRegionEntry) == 24, "RegionEntry layout");
|
||||
static_assert(sizeof(RcxDrvModuleEntry) == 536, "ModuleEntry layout");
|
||||
static_assert(sizeof(RcxDrvTebEntry) == 16, "TebEntry layout");
|
||||
static_assert(sizeof(RcxDrvPingResponse) == 8, "PingResponse layout");
|
||||
static_assert(sizeof(RcxDrvReadCr3Response) == 16, "ReadCr3Response layout");
|
||||
static_assert(sizeof(RcxDrvVtopRequest) == 16, "VtopRequest layout");
|
||||
static_assert(sizeof(RcxDrvVtopResponse) == 48, "VtopResponse layout");
|
||||
#endif
|
||||
Reference in New Issue
Block a user