From b75c80fbad7bee0ce9cc5e33ff7bc4c5f3702022 Mon Sep 17 00:00:00 2001 From: chase-irql <218126529+chase-irql@users.noreply.github.com> Date: Tue, 18 Aug 2026 22:10:28 -0400 Subject: [PATCH 1/3] Add Linux host support --- .github/workflows/tests.yml | 36 +++- CMakeLists.txt | 82 ++++++-- README.md | 49 ++++- StrataDMA/StrataDMA/StrataDMA/DMA.Backend.cpp | 42 +++-- .../StrataDMA/StrataDMA/DMA.Platform.hpp | 176 ++++++++++++++++++ StrataDMA/StrataDMA/StrataDMA/DMA.Types.cpp | 8 +- StrataDMA/StrataDMA/StrataDMA/DMA.Types.hpp | 1 + StrataDMA/StrataDMA/StrataDMA/DMA.cpp | 19 +- .../StrataDMA/tests/LifecycleMemoryTests.cpp | 2 +- 9 files changed, 364 insertions(+), 51 deletions(-) create mode 100644 StrataDMA/StrataDMA/StrataDMA/DMA.Platform.hpp diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 01ddcb4..170893d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,11 +1,11 @@ -name: Windows tests +name: Cross-platform tests on: push: pull_request: jobs: - build-and-test: + windows: runs-on: windows-latest strategy: fail-fast: false @@ -25,3 +25,35 @@ jobs: ctest --test-dir build -C ${{ matrix.configuration }} --output-on-failure + + fedora: + runs-on: ubuntu-latest + container: fedora:44 + env: + STRATA_DMA_RUNTIME_DIR: /tmp/memprocfs-runtime + MEMPROCFS_LINUX_URL: >- + https://github.com/ufrisk/MemProcFS/releases/download/v5.16/MemProcFS_files_and_binaries_v5.16.14-linux_x64-20260211.tar.gz + MEMPROCFS_LINUX_SHA256: bc7ea472af63894b15ff6ebc93d62cdf35d3e802e9db157598fef79a4737af0b + steps: + - name: Install Fedora build dependencies + run: dnf install -y cmake curl gcc-c++ git gzip libusb1 ninja-build tar + - uses: actions/checkout@v5 + - name: Install matching MemProcFS build runtime + shell: bash + run: | + mkdir -p "$STRATA_DMA_RUNTIME_DIR" + curl --fail --location --silent --show-error \ + "$MEMPROCFS_LINUX_URL" --output /tmp/memprocfs-runtime.tar.gz + echo "$MEMPROCFS_LINUX_SHA256 /tmp/memprocfs-runtime.tar.gz" | sha256sum --check + tar -xzf /tmp/memprocfs-runtime.tar.gz -C "$STRATA_DMA_RUNTIME_DIR" + test -f "$STRATA_DMA_RUNTIME_DIR/vmm.so" + test -f "$STRATA_DMA_RUNTIME_DIR/leechcore.so" + - name: Configure + run: >- + cmake -S . -B build -G Ninja + -DSTRATA_DMA_BUILD_EXAMPLE=OFF + -DSTRATA_DMA_BUILD_TESTS=ON + - name: Build + run: cmake --build build --parallel + - name: Test + run: ctest --test-dir build --output-on-failure diff --git a/CMakeLists.txt b/CMakeLists.txt index 81a73a9..4fa7bf1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,12 +1,20 @@ cmake_minimum_required(VERSION 3.20) project(StrataDMA VERSION 2.0.0 LANGUAGES CXX) -if(NOT WIN32) - message(FATAL_ERROR "StrataDMA currently supports Windows only.") +if(NOT WIN32 AND NOT CMAKE_SYSTEM_NAME STREQUAL "Linux") + message(FATAL_ERROR "StrataDMA supports Windows and Linux only.") +endif() +if(NOT CMAKE_SIZEOF_VOID_P EQUAL 8) + message(FATAL_ERROR "StrataDMA requires a 64-bit toolchain.") endif() option(STRATA_DMA_BUILD_EXAMPLE "Build the existing example executable" ON) option(STRATA_DMA_BUILD_TESTS "Build hardware-independent mock tests" ON) +set(STRATA_DMA_RUNTIME_DIR "" CACHE PATH + "Directory containing the MemProcFS runtime libraries") +if(NOT STRATA_DMA_RUNTIME_DIR AND DEFINED ENV{STRATA_DMA_RUNTIME_DIR}) + file(TO_CMAKE_PATH "$ENV{STRATA_DMA_RUNTIME_DIR}" STRATA_DMA_RUNTIME_DIR) +endif() set(STRATA_DMA_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/StrataDMA/StrataDMA") set(STRATA_DMA_SOURCE_DIR "${STRATA_DMA_ROOT}/StrataDMA") @@ -28,22 +36,49 @@ target_compile_features(strata_dma PUBLIC cxx_std_17) target_include_directories(strata_dma PUBLIC "$" "$") -target_compile_definitions(strata_dma PUBLIC NOMINMAX) -target_link_libraries(strata_dma PUBLIC - "${STRATA_DMA_SOURCE_DIR}/libs/vmm.lib" - "${STRATA_DMA_SOURCE_DIR}/libs/leechcore.lib") +target_compile_definitions(strata_dma PUBLIC + NOMINMAX + $<$:LINUX>) +set_target_properties(strata_dma PROPERTIES POSITION_INDEPENDENT_CODE ON) + +if(WIN32) + target_link_libraries(strata_dma PUBLIC + "${STRATA_DMA_SOURCE_DIR}/libs/vmm.lib" + "${STRATA_DMA_SOURCE_DIR}/libs/leechcore.lib") +else() + if(NOT STRATA_DMA_RUNTIME_DIR) + message(FATAL_ERROR + "A Linux build requires STRATA_DMA_RUNTIME_DIR to point to a " + "MemProcFS directory containing vmm.so and leechcore.so.") + endif() + get_filename_component(STRATA_DMA_RUNTIME_DIR + "${STRATA_DMA_RUNTIME_DIR}" ABSOLUTE) + find_file(STRATA_DMA_VMM_LIBRARY NAMES vmm.so + PATHS "${STRATA_DMA_RUNTIME_DIR}" NO_DEFAULT_PATH REQUIRED) + find_file(STRATA_DMA_LEECHCORE_LIBRARY NAMES leechcore.so + PATHS "${STRATA_DMA_RUNTIME_DIR}" NO_DEFAULT_PATH REQUIRED) + target_link_directories(strata_dma PUBLIC "${STRATA_DMA_RUNTIME_DIR}") + target_link_libraries(strata_dma PUBLIC "-l:vmm.so" "-l:leechcore.so") + set_target_properties(strata_dma PROPERTIES + BUILD_RPATH "${STRATA_DMA_RUNTIME_DIR}") +endif() if(MSVC) target_compile_options(strata_dma PRIVATE /W4 /permissive-) +elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + target_compile_options(strata_dma PRIVATE + -Wall -Wextra -Wno-format -Wno-unused-variable) endif() if(STRATA_DMA_BUILD_EXAMPLE) add_executable(strata_dma_example "${STRATA_DMA_ROOT}/main.cpp") target_link_libraries(strata_dma_example PRIVATE StrataDMA::StrataDMA) - add_custom_command(TARGET strata_dma_example POST_BUILD - COMMAND "${CMAKE_COMMAND}" -E copy_if_different - "${STRATA_DMA_SOURCE_DIR}/deps/info.db" - "$/info.db") + if(WIN32) + add_custom_command(TARGET strata_dma_example POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "${STRATA_DMA_SOURCE_DIR}/deps/info.db" + "$/info.db") + endif() endif() if(STRATA_DMA_BUILD_TESTS) @@ -61,11 +96,18 @@ if(STRATA_DMA_BUILD_TESTS) "${STRATA_DMA_SOURCE_DIR}/DMA.Types.cpp") add_library(strata_dma_test_core STATIC ${STRATA_DMA_TEST_CORE_SOURCES}) + set_target_properties(strata_dma_test_core PROPERTIES + POSITION_INDEPENDENT_CODE ON) target_compile_features(strata_dma_test_core PUBLIC cxx_std_17) target_include_directories(strata_dma_test_core PUBLIC "${STRATA_DMA_ROOT}") - target_compile_definitions(strata_dma_test_core PUBLIC NOMINMAX) + target_compile_definitions(strata_dma_test_core PUBLIC + NOMINMAX + $<$:LINUX>) if(MSVC) target_compile_options(strata_dma_test_core PRIVATE /W4 /WX /permissive-) + elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + target_compile_options(strata_dma_test_core PRIVATE + -Wall -Wextra -Wno-format -Wno-unused-variable) endif() function(strata_add_mock_suite target suite_source suite_name suite_label) @@ -78,6 +120,9 @@ if(STRATA_DMA_BUILD_TESTS) STRATA_TEST_SUITE="${suite_name}") if(MSVC) target_compile_options(${target} PRIVATE /W4 /WX /permissive-) + elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + target_compile_options(${target} PRIVATE + -Wall -Wextra -Wno-format -Wno-unused-variable) endif() add_test(NAME ${target} COMMAND ${target}) set_tests_properties(${target} PROPERTIES @@ -105,18 +150,21 @@ install(FILES "${STRATA_DMA_SOURCE_DIR}/DMA.hpp" "${STRATA_DMA_SOURCE_DIR}/DMA.Backend.hpp" "${STRATA_DMA_SOURCE_DIR}/DMA.Context.hpp" + "${STRATA_DMA_SOURCE_DIR}/DMA.Platform.hpp" "${STRATA_DMA_SOURCE_DIR}/DMA.Types.hpp" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/StrataDMA") install(FILES "${STRATA_DMA_SOURCE_DIR}/deps/vmmdll.h" "${STRATA_DMA_SOURCE_DIR}/deps/leechcore.h" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/StrataDMA/deps") -install(FILES - "${STRATA_DMA_SOURCE_DIR}/libs/vmm.lib" - "${STRATA_DMA_SOURCE_DIR}/libs/leechcore.lib" - DESTINATION "${CMAKE_INSTALL_LIBDIR}") -install(FILES "${STRATA_DMA_SOURCE_DIR}/deps/info.db" - DESTINATION "${CMAKE_INSTALL_DATADIR}/StrataDMA") +if(WIN32) + install(FILES + "${STRATA_DMA_SOURCE_DIR}/libs/vmm.lib" + "${STRATA_DMA_SOURCE_DIR}/libs/leechcore.lib" + DESTINATION "${CMAKE_INSTALL_LIBDIR}") + install(FILES "${STRATA_DMA_SOURCE_DIR}/deps/info.db" + DESTINATION "${CMAKE_INSTALL_DATADIR}/StrataDMA") +endif() install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE" "${CMAKE_CURRENT_SOURCE_DIR}/THIRD_PARTY_NOTICES.md" diff --git a/README.md b/README.md index 77ce3b8..c66c8d8 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ # StrataDMA -[![Windows tests](https://github.com/chase-irql/StrataDMA/actions/workflows/tests.yml/badge.svg)](https://github.com/chase-irql/StrataDMA/actions/workflows/tests.yml) +[![Windows and Linux tests](https://github.com/chase-irql/StrataDMA/actions/workflows/tests.yml/badge.svg)](https://github.com/chase-irql/StrataDMA/actions/workflows/tests.yml) [![License: AGPL v3](https://img.shields.io/badge/License-AGPL_v3-blue.svg)](LICENSE) -A C++17 wrapper around MemProcFS/VMMDLL for authorized DMA-backed Windows -memory inspection. The repository vendors the MemProcFS 5.16.5 and LeechCore -headers and x64 import libraries. +A cross-platform C++17 wrapper around MemProcFS/VMMDLL for authorized +DMA-backed Windows memory inspection. Windows and 64-bit Linux hosts are +supported; the inspected target remains Windows. The repository vendors the +MemProcFS 5.16.5 and LeechCore headers plus Windows x64 import libraries. Use it only on systems and processes you are authorized to inspect. @@ -60,6 +61,25 @@ At runtime, place `vmm.dll` and `leechcore.dll` beside the consuming executable. Place `info.db` there as well when InfoDB/symbol functionality is used. The repository contains import libraries, not the two runtime DLLs. +On Fedora, install the compiler and MemProcFS's USB dependency, unpack the +matching official MemProcFS Linux release, and point CMake at that directory: + +```bash +sudo dnf install cmake gcc-c++ libusb1 ninja-build +export STRATA_DMA_RUNTIME_DIR=/opt/memprocfs +cmake -S . -B build -G Ninja \ + -DSTRATA_DMA_BUILD_EXAMPLE=ON \ + -DSTRATA_DMA_BUILD_TESTS=ON +cmake --build build --parallel +ctest --test-dir build --output-on-failure +``` + +The Linux runtime directory must contain `vmm.so` and `leechcore.so`; keep +`info.db` and any device or symbol plugins from the same MemProcFS release in +that directory. CMake links against the two shared libraries without copying +them into the install tree. At runtime, put the directory in the loader search +path or launch with `LD_LIBRARY_PATH="$STRATA_DMA_RUNTIME_DIR"`. + ## Initialization and attachment ```cpp @@ -349,7 +369,8 @@ PE-dump reconstruction, CR3 recovery and rollback, native/WoW64 PEB parsing, physical-map export, RWX cave scanning, registry, VFS, and unsupported backend operations. Failure injection exercises prepare/execute, read/write, plugin, timeout, malformed-data, and I/O paths. MSVC warnings are errors for the mock -test core, and GitHub Actions runs Debug and Release builds. +test core. GitHub Actions runs Windows Debug/Release builds and a Fedora GCC +build. ```powershell cmake -S . -B build -A x64 ` @@ -362,6 +383,12 @@ ctest --test-dir build -C Debug --output-on-failure ctest --test-dir build -C Debug -L scatter --output-on-failure ``` +The equivalent Linux label command omits the multi-config argument: + +```bash +ctest --test-dir build -L scatter --output-on-failure +``` + All native VMMDLL function calls are contained in `DMA.Backend.cpp`. Implement `IVmmBackend` and pass a `shared_ptr` to `DMA` to add deterministic fixtures without hardware. Optional methods return `DMAStatus::Unsupported` @@ -370,13 +397,21 @@ unless the mock overrides them. ## Second-PC hardware smoke test The example includes a finite, target-memory-read-only hardware check. Copy the -compiled example, matching `vmm.dll`, `leechcore.dll`, `info.db`, and the symbol -support DLLs from the same MemProcFS release to the acquisition PC, then run: +compiled example, matching VMMDLL/LeechCore runtime files, `info.db`, and symbol +support files from the same MemProcFS release to the acquisition PC, then run +on Windows: ```powershell .\strata_dma_example.exe --hardware-test explorer.exe ``` +On Linux, use the matching `.so` runtime and run: + +```bash +LD_LIBRARY_PATH="$STRATA_DMA_RUNTIME_DIR" \ + ./strata_dma_example --hardware-test explorer.exe +``` + It validates initialization/version discovery, the physical map, normal attach with bounded CR3-recovery fallback, expanded process information, an MZ read, scatter, module/section and VAD/PTE enumeration, then attempts PEB and VFS diff --git a/StrataDMA/StrataDMA/StrataDMA/DMA.Backend.cpp b/StrataDMA/StrataDMA/StrataDMA/DMA.Backend.cpp index 80fdf6c..8ef35de 100644 --- a/StrataDMA/StrataDMA/StrataDMA/DMA.Backend.cpp +++ b/StrataDMA/StrataDMA/StrataDMA/DMA.Backend.cpp @@ -26,6 +26,12 @@ std::string CopyString(const char* value) return value ? value : ""; } +size_t BoundedStringLength(const char* value, size_t capacity) +{ + return static_cast( + std::find(value, value + capacity, '\0') - value); +} + DMAProcessInfo CopyProcessInfo(const VMMDLL_PROCESS_INFORMATION& native) { DMAProcessInfo process; @@ -192,8 +198,11 @@ DMAOperationResult VmmdllBackend::ConfigGet(ULONG64 option, uint64_t& value) con value = 0; if (!handle_) return NotInitialized(); - return VMMDLL_ConfigGet(handle_, option, &value) - ? DMAOperationResult::Success() : BackendFailure("VMMDLL_ConfigGet"); + ULONG64 nativeValue = 0; + if (!VMMDLL_ConfigGet(handle_, option, &nativeValue)) + return BackendFailure("VMMDLL_ConfigGet"); + value = static_cast(nativeValue); + return DMAOperationResult::Success(); } DMAOperationResult VmmdllBackend::ConfigSet(ULONG64 option, uint64_t value) @@ -258,9 +267,9 @@ DMAOperationResult VmmdllBackend::PrefetchPages(DWORD pid, if (addresses.empty() || addresses.size() > std::numeric_limits::max()) return DMAOperationResult::Failure(DMAStatus::InvalidArgument, "Prefetch requires one or more addresses."); - return VMMDLL_MemPrefetchPages(handle_, pid, - const_cast(addresses.data()), - static_cast(addresses.size())) + std::vector nativeAddresses(addresses.begin(), addresses.end()); + return VMMDLL_MemPrefetchPages(handle_, pid, nativeAddresses.data(), + static_cast(nativeAddresses.size())) ? DMAOperationResult::Success() : BackendFailure("VMMDLL_MemPrefetchPages"); } @@ -270,8 +279,11 @@ DMAOperationResult VmmdllBackend::VirtualToPhysical(DWORD pid, physicalAddress = 0; if (!handle_) return NotInitialized(); - return VMMDLL_MemVirt2Phys(handle_, pid, virtualAddress, &physicalAddress) - ? DMAOperationResult::Success() : BackendFailure("VMMDLL_MemVirt2Phys"); + ULONG64 nativeAddress = 0; + if (!VMMDLL_MemVirt2Phys(handle_, pid, virtualAddress, &nativeAddress)) + return BackendFailure("VMMDLL_MemVirt2Phys"); + physicalAddress = static_cast(nativeAddress); + return DMAOperationResult::Success(); } std::unique_ptr VmmdllBackend::CreateScatter( @@ -574,10 +586,14 @@ DMAOperationResult VmmdllBackend::ResolveSymbol(const std::string& symbolModule, address = 0; if (!handle_) return NotInitialized(); - return VMMDLL_PdbSymbolAddress(handle_, symbolModule.c_str(), symbol.c_str(), - &address) ? DMAOperationResult::Success() - : DMAOperationResult::Failure(DMAStatus::NotFound, + ULONG64 nativeAddress = 0; + if (!VMMDLL_PdbSymbolAddress(handle_, symbolModule.c_str(), symbol.c_str(), + &nativeAddress)) { + return DMAOperationResult::Failure(DMAStatus::NotFound, "The PDB symbol was not found."); + } + address = static_cast(nativeAddress); + return DMAOperationResult::Success(); } DMAOperationResult VmmdllBackend::LookupSymbol(const std::string& symbolModule, @@ -680,8 +696,12 @@ DMAOperationResult VmmdllBackend::EnumerateRegistryKeys(const std::string& path, break; DMARegistryKeyInfo key; key.name = name.data(); +#ifdef _WIN32 key.lastWriteTime = (static_cast(time.dwHighDateTime) << 32) | time.dwLowDateTime; +#else + key.lastWriteTime = static_cast(time); +#endif keys.push_back(std::move(key)); } return DMAOperationResult::Success(); @@ -809,7 +829,7 @@ DMAOperationResult VmmdllBackend::ListVfs(const std::string& path, const char* name = blob->uszMultiText + native.ouszName; const size_t remaining = blob->cbMultiText - static_cast(native.ouszName); - entry.name.assign(name, strnlen_s(name, remaining)); + entry.name.assign(name, BoundedStringLength(name, remaining)); entry.directory = native.cbFileSize == std::numeric_limits::max(); entry.size = entry.directory ? 0 : native.cbFileSize; if (native.ExInfo.dwVersion == VMMDLL_VFS_FILELIST_EXINFO_VERSION) { diff --git a/StrataDMA/StrataDMA/StrataDMA/DMA.Platform.hpp b/StrataDMA/StrataDMA/StrataDMA/DMA.Platform.hpp new file mode 100644 index 0000000..668aeee --- /dev/null +++ b/StrataDMA/StrataDMA/StrataDMA/DMA.Platform.hpp @@ -0,0 +1,176 @@ +#pragma once + +// VMMDLL supplies the basic Win32-compatible integer types and section-header +// layout on Linux. StrataDMA also uses a small set of Windows data definitions +// that are part of the target-memory format rather than the host operating +// system. Keep those definitions here so public code does not depend on the +// Windows SDK when built on Linux. + +#if defined(LINUX) && !defined(_WIN32) + +#include + +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +#define TRUE 1 +#endif + +struct POINT { + std::int32_t x; + std::int32_t y; +}; + +inline constexpr DWORD REG_SZ = 1; +inline constexpr DWORD REG_EXPAND_SZ = 2; +inline constexpr DWORD REG_BINARY = 3; +inline constexpr DWORD REG_DWORD = 4; +inline constexpr DWORD REG_MULTI_SZ = 7; +inline constexpr DWORD REG_QWORD = 11; + +inline constexpr WORD IMAGE_DOS_SIGNATURE = 0x5a4d; +inline constexpr DWORD IMAGE_NT_SIGNATURE = 0x00004550; +inline constexpr WORD IMAGE_FILE_MACHINE_AMD64 = 0x8664; +inline constexpr WORD IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x10b; +inline constexpr WORD IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20b; +inline constexpr DWORD IMAGE_SCN_MEM_EXECUTE = 0x20000000; +inline constexpr DWORD IMAGE_SCN_MEM_READ = 0x40000000; +inline constexpr DWORD IMAGE_SCN_MEM_WRITE = 0x80000000; +inline constexpr DWORD IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16; +inline constexpr DWORD IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT = 11; + +struct IMAGE_DOS_HEADER { + WORD e_magic; + WORD e_cblp; + WORD e_cp; + WORD e_crlc; + WORD e_cparhdr; + WORD e_minalloc; + WORD e_maxalloc; + WORD e_ss; + WORD e_sp; + WORD e_csum; + WORD e_ip; + WORD e_cs; + WORD e_lfarlc; + WORD e_ovno; + WORD e_res[4]; + WORD e_oemid; + WORD e_oeminfo; + WORD e_res2[10]; + std::int32_t e_lfanew; +}; +using PIMAGE_DOS_HEADER = IMAGE_DOS_HEADER*; + +struct IMAGE_FILE_HEADER { + WORD Machine; + WORD NumberOfSections; + DWORD TimeDateStamp; + DWORD PointerToSymbolTable; + DWORD NumberOfSymbols; + WORD SizeOfOptionalHeader; + WORD Characteristics; +}; +using PIMAGE_FILE_HEADER = IMAGE_FILE_HEADER*; + +struct IMAGE_OPTIONAL_HEADER32 { + WORD Magic; + BYTE MajorLinkerVersion; + BYTE MinorLinkerVersion; + DWORD SizeOfCode; + DWORD SizeOfInitializedData; + DWORD SizeOfUninitializedData; + DWORD AddressOfEntryPoint; + DWORD BaseOfCode; + DWORD BaseOfData; + DWORD ImageBase; + DWORD SectionAlignment; + DWORD FileAlignment; + WORD MajorOperatingSystemVersion; + WORD MinorOperatingSystemVersion; + WORD MajorImageVersion; + WORD MinorImageVersion; + WORD MajorSubsystemVersion; + WORD MinorSubsystemVersion; + DWORD Win32VersionValue; + DWORD SizeOfImage; + DWORD SizeOfHeaders; + DWORD CheckSum; + WORD Subsystem; + WORD DllCharacteristics; + DWORD SizeOfStackReserve; + DWORD SizeOfStackCommit; + DWORD SizeOfHeapReserve; + DWORD SizeOfHeapCommit; + DWORD LoaderFlags; + DWORD NumberOfRvaAndSizes; + IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; +}; +using PIMAGE_OPTIONAL_HEADER32 = IMAGE_OPTIONAL_HEADER32*; + +struct IMAGE_OPTIONAL_HEADER64 { + WORD Magic; + BYTE MajorLinkerVersion; + BYTE MinorLinkerVersion; + DWORD SizeOfCode; + DWORD SizeOfInitializedData; + DWORD SizeOfUninitializedData; + DWORD AddressOfEntryPoint; + DWORD BaseOfCode; + QWORD ImageBase; + DWORD SectionAlignment; + DWORD FileAlignment; + WORD MajorOperatingSystemVersion; + WORD MinorOperatingSystemVersion; + WORD MajorImageVersion; + WORD MinorImageVersion; + WORD MajorSubsystemVersion; + WORD MinorSubsystemVersion; + DWORD Win32VersionValue; + DWORD SizeOfImage; + DWORD SizeOfHeaders; + DWORD CheckSum; + WORD Subsystem; + WORD DllCharacteristics; + QWORD SizeOfStackReserve; + QWORD SizeOfStackCommit; + QWORD SizeOfHeapReserve; + QWORD SizeOfHeapCommit; + DWORD LoaderFlags; + DWORD NumberOfRvaAndSizes; + IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; +}; +using PIMAGE_OPTIONAL_HEADER64 = IMAGE_OPTIONAL_HEADER64*; + +struct IMAGE_NT_HEADERS64 { + DWORD Signature; + IMAGE_FILE_HEADER FileHeader; + IMAGE_OPTIONAL_HEADER64 OptionalHeader; +}; + +inline PIMAGE_SECTION_HEADER ImageFirstSection(IMAGE_NT_HEADERS64* ntHeader) +{ + return reinterpret_cast( + reinterpret_cast(&ntHeader->OptionalHeader) + + ntHeader->FileHeader.SizeOfOptionalHeader); +} + +inline const IMAGE_SECTION_HEADER* ImageFirstSection( + const IMAGE_NT_HEADERS64* ntHeader) +{ + return reinterpret_cast( + reinterpret_cast(&ntHeader->OptionalHeader) + + ntHeader->FileHeader.SizeOfOptionalHeader); +} + +#define IMAGE_FIRST_SECTION(nt_header) ImageFirstSection(nt_header) + +static_assert(sizeof(POINT) == 8); +static_assert(sizeof(IMAGE_DOS_HEADER) == 64); +static_assert(sizeof(IMAGE_FILE_HEADER) == 20); +static_assert(sizeof(IMAGE_OPTIONAL_HEADER32) == 224); +static_assert(sizeof(IMAGE_OPTIONAL_HEADER64) == 240); +static_assert(sizeof(IMAGE_NT_HEADERS64) == 264); + +#endif diff --git a/StrataDMA/StrataDMA/StrataDMA/DMA.Types.cpp b/StrataDMA/StrataDMA/StrataDMA/DMA.Types.cpp index 31f530a..ffe5c7d 100644 --- a/StrataDMA/StrataDMA/StrataDMA/DMA.Types.cpp +++ b/StrataDMA/StrataDMA/StrataDMA/DMA.Types.cpp @@ -10,12 +10,12 @@ std::string DMARegistryValue::AsString() const // REG_SZ, REG_EXPAND_SZ and REG_MULTI_SZ are stored as UTF-16LE. if (type == REG_SZ || type == REG_EXPAND_SZ || type == REG_MULTI_SZ) { std::string result; - const size_t characters = data.size() / sizeof(wchar_t); + const size_t characters = data.size() / sizeof(char16_t); result.reserve(characters); for (size_t index = 0; index < characters; ++index) { - wchar_t value = 0; - std::memcpy(&value, data.data() + index * sizeof(wchar_t), sizeof(value)); - if (value == L'\0') + char16_t value = 0; + std::memcpy(&value, data.data() + index * sizeof(value), sizeof(value)); + if (value == u'\0') break; result.push_back(value <= 0x7f ? static_cast(value) : '?'); } diff --git a/StrataDMA/StrataDMA/StrataDMA/DMA.Types.hpp b/StrataDMA/StrataDMA/StrataDMA/DMA.Types.hpp index e6c80ef..0b3712d 100644 --- a/StrataDMA/StrataDMA/StrataDMA/DMA.Types.hpp +++ b/StrataDMA/StrataDMA/StrataDMA/DMA.Types.hpp @@ -12,6 +12,7 @@ #ifdef _MSC_VER #pragma warning(pop) #endif +#include "DMA.Platform.hpp" #include #include diff --git a/StrataDMA/StrataDMA/StrataDMA/DMA.cpp b/StrataDMA/StrataDMA/StrataDMA/DMA.cpp index 476b9f9..77bc549 100644 --- a/StrataDMA/StrataDMA/StrataDMA/DMA.cpp +++ b/StrataDMA/StrataDMA/StrataDMA/DMA.cpp @@ -579,18 +579,19 @@ std::string DMA::ReadString(uint64_t address, size_t maxLength) { /// terminator. std::wstring DMA::ReadWString(uint64_t address, size_t maxLength) { if (address == 0 || maxLength == 0 || - maxLength > std::numeric_limits::max() / sizeof(wchar_t)) + maxLength > std::numeric_limits::max() / sizeof(char16_t)) + return L""; + std::vector encoded(maxLength); + if (!ReadRaw(address, encoded.data(), encoded.size() * sizeof(char16_t))) return L""; std::wstring result; - result.resize(maxLength); - if (ReadRaw(address, result.data(), maxLength * sizeof(wchar_t))) { - size_t nullTerminator = result.find(L'\0'); - if (nullTerminator != std::wstring::npos) { - result.resize(nullTerminator); - } - return result; + result.reserve(maxLength); + for (char16_t value : encoded) { + if (value == u'\0') + break; + result.push_back(static_cast(value)); } - return L""; + return result; } /// diff --git a/StrataDMA/StrataDMA/tests/LifecycleMemoryTests.cpp b/StrataDMA/StrataDMA/tests/LifecycleMemoryTests.cpp index ad701aa..8d196ca 100644 --- a/StrataDMA/StrataDMA/tests/LifecycleMemoryTests.cpp +++ b/StrataDMA/StrataDMA/tests/LifecycleMemoryTests.cpp @@ -170,7 +170,7 @@ STRATA_TEST_CASE(strings_pointer_chains_and_relative_addresses_work) fixture.Attach(); const char text[] = "hello"; fixture.backend->StoreBytes(0x3000, text, sizeof(text)); - const wchar_t wide[] = L"wide"; + const char16_t wide[] = u"wide"; fixture.backend->StoreBytes(0x3100, wide, sizeof(wide)); STRATA_REQUIRE(fixture.dma.ReadString(0x3000, 32) == "hello"); STRATA_REQUIRE(fixture.dma.ReadWString(0x3100, 32) == L"wide"); From 63eb2c0e625621f1ebde7fade848718024dd6328 Mon Sep 17 00:00:00 2001 From: chase-irql <218126529+chase-irql@users.noreply.github.com> Date: Tue, 18 Aug 2026 22:17:47 -0400 Subject: [PATCH 2/3] Support runtime-managed Linux consumers --- CMakeLists.txt | 11 ++++++++++- README.md | 5 ++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4fa7bf1..1022cfa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,8 @@ endif() option(STRATA_DMA_BUILD_EXAMPLE "Build the existing example executable" ON) option(STRATA_DMA_BUILD_TESTS "Build hardware-independent mock tests" ON) +option(STRATA_DMA_LINK_RUNTIME + "Link Linux consumers directly to the MemProcFS shared libraries" ON) set(STRATA_DMA_RUNTIME_DIR "" CACHE PATH "Directory containing the MemProcFS runtime libraries") if(NOT STRATA_DMA_RUNTIME_DIR AND DEFINED ENV{STRATA_DMA_RUNTIME_DIR}) @@ -45,7 +47,7 @@ if(WIN32) target_link_libraries(strata_dma PUBLIC "${STRATA_DMA_SOURCE_DIR}/libs/vmm.lib" "${STRATA_DMA_SOURCE_DIR}/libs/leechcore.lib") -else() +elseif(STRATA_DMA_LINK_RUNTIME) if(NOT STRATA_DMA_RUNTIME_DIR) message(FATAL_ERROR "A Linux build requires STRATA_DMA_RUNTIME_DIR to point to a " @@ -63,6 +65,13 @@ else() BUILD_RPATH "${STRATA_DMA_RUNTIME_DIR}") endif() +if(CMAKE_SYSTEM_NAME STREQUAL "Linux" + AND STRATA_DMA_BUILD_EXAMPLE + AND NOT STRATA_DMA_LINK_RUNTIME) + message(FATAL_ERROR + "STRATA_DMA_BUILD_EXAMPLE requires STRATA_DMA_LINK_RUNTIME=ON.") +endif() + if(MSVC) target_compile_options(strata_dma PRIVATE /W4 /permissive-) elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") diff --git a/README.md b/README.md index c66c8d8..6c7a765 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,10 @@ ctest --test-dir build -C Release --output-on-failure cmake --install build --config Release --prefix package ``` -Options are `STRATA_DMA_BUILD_EXAMPLE` and `STRATA_DMA_BUILD_TESTS`. +Options are `STRATA_DMA_BUILD_EXAMPLE`, `STRATA_DMA_BUILD_TESTS`, and +`STRATA_DMA_LINK_RUNTIME`. The last option is Linux-only in effect and defaults +to `ON`. Dynamic hosts that preload `leechcore.so` and `vmm.so` themselves may +turn it off; the final module must then allow and resolve the VMMDLL symbols. At runtime, place `vmm.dll` and `leechcore.dll` beside the consuming executable. Place `info.db` there as well when InfoDB/symbol functionality is used. The From 0fec68a0973047c2c3d9e0226dd6dcafc2d405c6 Mon Sep 17 00:00:00 2001 From: chase-irql <218126529+chase-irql@users.noreply.github.com> Date: Tue, 18 Aug 2026 23:03:36 -0400 Subject: [PATCH 3/3] Fix VMMDLL initialization on Linux --- StrataDMA/StrataDMA/StrataDMA/DMA.cpp | 2 +- StrataDMA/StrataDMA/tests/LifecycleMemoryTests.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/StrataDMA/StrataDMA/StrataDMA/DMA.cpp b/StrataDMA/StrataDMA/StrataDMA/DMA.cpp index 77bc549..e94ed00 100644 --- a/StrataDMA/StrataDMA/StrataDMA/DMA.cpp +++ b/StrataDMA/StrataDMA/StrataDMA/DMA.cpp @@ -213,7 +213,7 @@ DMAOperationResult DMA::Initialize(const DMAInitializationOptions& options) auto attemptInitialize = [&](bool includeMemoryMap) { std::vector arguments = { - "StrataDMA", "-device", options.device + "", "-device", options.device }; if (options.debug) { arguments.push_back("-v"); diff --git a/StrataDMA/StrataDMA/tests/LifecycleMemoryTests.cpp b/StrataDMA/StrataDMA/tests/LifecycleMemoryTests.cpp index 8d196ca..0cae1f3 100644 --- a/StrataDMA/StrataDMA/tests/LifecycleMemoryTests.cpp +++ b/StrataDMA/StrataDMA/tests/LifecycleMemoryTests.cpp @@ -32,6 +32,8 @@ STRATA_TEST_CASE(initialization_forwards_options_and_plugins) STRATA_REQUIRE(fixture.dma.Initialize(options)); STRATA_REQUIRE(fixture.backend->pluginsInitialized); + STRATA_REQUIRE(!fixture.backend->initializeArguments.empty()); + STRATA_REQUIRE(fixture.backend->initializeArguments.front().empty()); STRATA_REQUIRE(HasArgument(fixture.backend->initializeArguments, "mock://device")); STRATA_REQUIRE(HasArgument(fixture.backend->initializeArguments, "-memmap"));