From 71d959ac72209b75cddf0b008e4c8b140aa34f1c Mon Sep 17 00:00:00 2001 From: Dorian Eikenberg Date: Tue, 15 Nov 2022 16:16:21 +0100 Subject: [PATCH] Introduce memory mapping API --- plugins/inmemoryscanner/Readme.md | 6 +- plugins/inmemoryscanner/src/lib/Common.h | 6 + plugins/inmemoryscanner/src/lib/Config.cpp | 8 - plugins/inmemoryscanner/src/lib/Config.h | 5 - plugins/inmemoryscanner/src/lib/Scanner.cpp | 79 ++++--- plugins/inmemoryscanner/src/lib/Scanner.h | 3 + plugins/inmemoryscanner/src/lib/Yara.cpp | 15 +- plugins/inmemoryscanner/src/lib/Yara.h | 2 +- .../inmemoryscanner/src/lib/YaraInterface.h | 4 +- plugins/inmemoryscanner/test/FakeYara.cpp | 3 +- plugins/inmemoryscanner/test/FakeYara.h | 2 +- .../inmemoryscanner/test/Scanner_unittest.cpp | 152 ++++++++++---- plugins/inmemoryscanner/test/mock_Config.h | 1 - plugins/inmemoryscanner/test/mock_Yara.h | 5 +- vmicore/src/include/CMakeLists.txt | 2 + .../include/vmicore/plugins/PluginInterface.h | 12 +- .../src/include/vmicore/vmi/IMemoryMapping.h | 34 +++ .../src/include/vmicore/vmi/MappedRegion.h | 30 +++ vmicore/src/lib/CMakeLists.txt | 1 + .../os/windows/ActiveProcessesSupervisor.cpp | 1 + vmicore/src/lib/plugins/PluginSystem.cpp | 60 +----- vmicore/src/lib/plugins/PluginSystem.h | 7 +- vmicore/src/lib/vmi/LibvmiInterface.cpp | 16 ++ vmicore/src/lib/vmi/LibvmiInterface.h | 4 + vmicore/src/lib/vmi/MemoryMapping.cpp | 92 +++++++++ vmicore/src/lib/vmi/MemoryMapping.h | 44 ++++ vmicore/test/CMakeLists.txt | 1 + vmicore/test/include/CMakeLists.txt | 3 +- .../plugins/mock_PluginInterface.h | 6 +- .../vmicore_test/vmi/mock_MemoryMapping.h | 20 ++ .../lib/plugins/PluginSystem_UnitTest.cpp | 193 ------------------ vmicore/test/lib/plugins/mock_PluginSystem.h | 5 + .../test/lib/vmi/MemoryMapping_UnitTest.cpp | 123 +++++++++++ vmicore/test/lib/vmi/ProcessesMemoryState.h | 1 + vmicore/test/lib/vmi/mock_LibvmiInterface.h | 2 + 35 files changed, 590 insertions(+), 358 deletions(-) create mode 100644 vmicore/src/include/vmicore/vmi/IMemoryMapping.h create mode 100644 vmicore/src/include/vmicore/vmi/MappedRegion.h create mode 100644 vmicore/src/lib/vmi/MemoryMapping.cpp create mode 100644 vmicore/src/lib/vmi/MemoryMapping.h create mode 100644 vmicore/test/include/vmicore_test/vmi/mock_MemoryMapping.h create mode 100644 vmicore/test/lib/vmi/MemoryMapping_UnitTest.cpp diff --git a/plugins/inmemoryscanner/Readme.md b/plugins/inmemoryscanner/Readme.md index 4d181a7f..15b0416f 100644 --- a/plugins/inmemoryscanner/Readme.md +++ b/plugins/inmemoryscanner/Readme.md @@ -71,8 +71,6 @@ For example the following diagram shows the memory padding of one VAD region con Shared memory regions that are not the base image of the process are skipped by default in order to reduce scanning time. This behavior can be controlled via the `scan_all_regions` config option. -To further optimize scan duration, memory regions >50MB will be ignored as well. -If desired, it is possible to increase or reduce the threshold via the `maximum_scan_size` config option. ### In Depth Example @@ -89,7 +87,7 @@ Consider the following VAD entry from the vad tree of a process `winlogon.exe` w | `EndAddress` | 1f43b599000 | This region has a size of `0x1f43b599000` - `0x1f43b593000` = `0x6000`. -However the pages from `0x1f43b596000` to `0x1f43b598000` (size `0x2000`) are not mapped into memory. +However, the pages from `0x1f43b596000` to `0x1f43b598000` (size `0x2000`) are not mapped into memory. Therefore, the resulting files will have the size of `0x5000` (mapped size + one zero-page). Note that the start and end address however are the original ones. ```console @@ -130,7 +128,6 @@ For this, add the following parts to the _VMICore_ config and tweak them to your | `directory` | Path to the folder where the compiled _VMICore_ plugins are located. | | `dump_memory` | Boolean. If set to `true` will result in scanned memory being dumped to files. Regions will be dumped to an `inmemorydumps` subfolder in the output directory. | | `ignored_processes` | List with processes that will not be scanned (or dumped) during the final scan. | -| `maximum_scan_size` | Number of bytes for the size of the largest contiguous memory region that will still be scanned. Defaults to `52428800` (50MB). | | `output_path` | Optional output path. If this is a relative path it is interpreted relatively to the _VMICore_ results directory. | | `plugins` | Add your plugin here by the exact name of your shared library (e.g. `libinmemoryscanner.so`). All plugin specific config keys should be added as sub-keys under this name. | | `scan_all_regions` | Optional boolean (defaults to `false`). Indicates whether to eagerly scan all memory regions as opposed to ignoring shared memory. | @@ -147,7 +144,6 @@ plugin_system: signature_file: /usr/local/share/inmemsigs/sigs.sig dump_memory: false scan_all_regions: false - maximum_scan_size: 52428800 output_path: "" ignored_processes: - SearchUI.exe diff --git a/plugins/inmemoryscanner/src/lib/Common.h b/plugins/inmemoryscanner/src/lib/Common.h index 76f58c31..3c070181 100644 --- a/plugins/inmemoryscanner/src/lib/Common.h +++ b/plugins/inmemoryscanner/src/lib/Common.h @@ -5,6 +5,7 @@ #include #include #include +#include #define INMEMORY_LOGGER_NAME std::string("InMemory_").append(FILENAME_STEM) @@ -22,4 +23,9 @@ namespace InMemoryScanner std::string ruleNamespace; std::vector matches; }; + + inline std::size_t bytesToNumberOfPages(std::size_t size) + { + return (size + VmiCore::PagingDefinitions::pageSizeInBytes - 1) / VmiCore::PagingDefinitions::pageSizeInBytes; + } } diff --git a/plugins/inmemoryscanner/src/lib/Config.cpp b/plugins/inmemoryscanner/src/lib/Config.cpp index 860bcafd..a236566b 100644 --- a/plugins/inmemoryscanner/src/lib/Config.cpp +++ b/plugins/inmemoryscanner/src/lib/Config.cpp @@ -8,8 +8,6 @@ using VmiCore::Plugin::PluginInterface; namespace InMemoryScanner { - constexpr uint64_t defaultMaxScanSize = 52428800; // 50MB - Config::Config(const PluginInterface* pluginInterface) : logger(pluginInterface->newNamedLogger(INMEMORY_LOGGER_NAME)) { @@ -23,7 +21,6 @@ namespace InMemoryScanner outputPath = rootNode["output_path"].as(); dumpMemory = rootNode["dump_memory"].as(false); scanAllRegions = rootNode["scan_all_regions"].as(false); - maximumScanSize = rootNode["maximum_scan_size"].as(defaultMaxScanSize); auto ignoredProcessesVec = rootNode["ignored_processes"].as>(std::vector()); @@ -61,11 +58,6 @@ namespace InMemoryScanner return dumpMemory; } - uint64_t Config::getMaximumScanSize() const - { - return maximumScanSize; - } - void Config::overrideDumpMemoryFlag(bool value) { dumpMemory = value; diff --git a/plugins/inmemoryscanner/src/lib/Config.h b/plugins/inmemoryscanner/src/lib/Config.h index 6cfc63f7..2c4c79c1 100644 --- a/plugins/inmemoryscanner/src/lib/Config.h +++ b/plugins/inmemoryscanner/src/lib/Config.h @@ -32,8 +32,6 @@ namespace InMemoryScanner [[nodiscard]] virtual bool isDumpingMemoryActivated() const = 0; - [[nodiscard]] virtual uint64_t getMaximumScanSize() const = 0; - virtual void overrideDumpMemoryFlag(bool value) = 0; protected: @@ -59,8 +57,6 @@ namespace InMemoryScanner [[nodiscard]] bool isDumpingMemoryActivated() const override; - [[nodiscard]] uint64_t getMaximumScanSize() const override; - void overrideDumpMemoryFlag(bool value) override; private: @@ -70,6 +66,5 @@ namespace InMemoryScanner std::set ignoredProcesses; bool dumpMemory{}; bool scanAllRegions{}; - uint64_t maximumScanSize{}; }; } diff --git a/plugins/inmemoryscanner/src/lib/Scanner.cpp b/plugins/inmemoryscanner/src/lib/Scanner.cpp index 5984069e..d2a5551b 100644 --- a/plugins/inmemoryscanner/src/lib/Scanner.cpp +++ b/plugins/inmemoryscanner/src/lib/Scanner.cpp @@ -4,12 +4,16 @@ #include #include #include +#include #include +#include using VmiCore::ActiveProcessInformation; using VmiCore::addr_t; +using VmiCore::MappedRegion; using VmiCore::MemoryRegion; using VmiCore::pid_t; +using VmiCore::PagingDefinitions::pageSizeInBytes; using VmiCore::Plugin::PluginInterface; namespace InMemoryScanner @@ -58,8 +62,43 @@ namespace InMemoryScanner return verdict; } - void - Scanner::scanMemoryRegion(pid_t pid, const std::string& processName, const MemoryRegion& memoryRegionDescriptor) + std::vector Scanner::constructPaddedMemoryRegion(const std::vector& regions) + { + std::vector result; + + if (regions.empty()) + { + return result; + } + + std::size_t regionSize = 0; + for (const auto& region : regions) + { + regionSize += region.mapping.size(); + regionSize += pageSizeInBytes; + } + // last region should not have succeeding padding page + regionSize -= pageSizeInBytes; + + result.reserve(regionSize); + // copy first region + std::copy(regions.front().mapping.begin(), regions.front().mapping.end(), std::back_inserter(result)); + + for (std::size_t i = 1; i < regions.size(); i++) + { + const auto& region = regions[i]; + // padding page + result.insert(result.end(), pageSizeInBytes, 0); + std::copy(region.mapping.begin(), region.mapping.end(), std::back_inserter(result)); + } + + return result; + } + + void Scanner::scanMemoryRegion(pid_t pid, + addr_t dtb, + const std::string& processName, + const MemoryRegion& memoryRegionDescriptor) { logger->info("Scanning Memory region", {{"VA", fmt::format("{:x}", memoryRegionDescriptor.base)}, @@ -68,21 +107,11 @@ namespace InMemoryScanner if (shouldRegionBeScanned(memoryRegionDescriptor)) { - auto scanSize = memoryRegionDescriptor.size; - auto maximumScanSize = configuration->getMaximumScanSize(); - if (scanSize > maximumScanSize) - { - logger->info("Memory region is too big, reducing to maximum scan size", - {{"Size", scanSize}, {"MaximumScanSize", maximumScanSize}}); - scanSize = maximumScanSize; - } - - logger->debug("Start getProcessMemoryRegion", {{"Size", scanSize}}); - - auto memoryRegion = pluginInterface->readProcessMemoryRegion(pid, memoryRegionDescriptor.base, scanSize); + auto memoryMapping = pluginInterface->mapProcessMemoryRegion( + memoryRegionDescriptor.base, dtb, bytesToNumberOfPages(memoryRegionDescriptor.size)); + auto mappedRegions = memoryMapping->getMappedRegions().lock(); - logger->debug("End getProcessMemoryRegion", {{"Size", scanSize}}); - if (memoryRegion->empty()) + if (mappedRegions->empty()) { logger->debug("Extracted memory region has size 0, skipping"); } @@ -90,17 +119,19 @@ namespace InMemoryScanner { if (configuration->isDumpingMemoryActivated()) { - logger->debug("Start dumpVadRegionToFile", {{"Size", memoryRegion->size()}}); - dumping->dumpMemoryRegion(processName, pid, memoryRegionDescriptor, *memoryRegion); - logger->debug("End dumpVadRegionToFile"); + logger->debug("Start dumpVadRegionToFile", {{"Size", memoryMapping->getSizeInGuest()}}); + + auto paddedRegion = constructPaddedMemoryRegion(*mappedRegions); + + dumping->dumpMemoryRegion(processName, pid, memoryRegionDescriptor, paddedRegion); } - logger->debug("Start scanMemory", {{"Size", memoryRegion->size()}}); + logger->debug("Start scanMemory", {{"Size", memoryMapping->getSizeInGuest()}}); // The semaphore protects the yara rules from being accessed more than YR_MAX_THREADS (32 atm.) times in // parallel. semaphore.wait(); - auto results = yaraEngine->scanMemory(*memoryRegion); + auto results = yaraEngine->scanMemory(*mappedRegions); semaphore.notify(); logger->debug("End scanMemory"); @@ -141,8 +172,10 @@ namespace InMemoryScanner { try { - scanMemoryRegion( - processInformation->pid, *processInformation->fullName, memoryRegionDescriptor); + scanMemoryRegion(processInformation->pid, + processInformation->processUserDtb, + *processInformation->fullName, + memoryRegionDescriptor); } catch (const std::exception& exc) { diff --git a/plugins/inmemoryscanner/src/lib/Scanner.h b/plugins/inmemoryscanner/src/lib/Scanner.h index 37db1ac8..643085d5 100644 --- a/plugins/inmemoryscanner/src/lib/Scanner.h +++ b/plugins/inmemoryscanner/src/lib/Scanner.h @@ -41,7 +41,10 @@ namespace InMemoryScanner [[nodiscard]] bool shouldRegionBeScanned(const VmiCore::MemoryRegion& memoryRegionDescriptor); + static std::vector constructPaddedMemoryRegion(const std::vector& regions); + void scanMemoryRegion(pid_t pid, + uint64_t dtb, const std::string& processName, const VmiCore::MemoryRegion& memoryRegionDescriptor); diff --git a/plugins/inmemoryscanner/src/lib/Yara.cpp b/plugins/inmemoryscanner/src/lib/Yara.cpp index 86c49307..7ef5ad90 100644 --- a/plugins/inmemoryscanner/src/lib/Yara.cpp +++ b/plugins/inmemoryscanner/src/lib/Yara.cpp @@ -1,5 +1,7 @@ #include "Yara.h" +using VmiCore::MappedRegion; + namespace InMemoryScanner { Yara::Yara(const std::string& rulesFile) @@ -25,15 +27,18 @@ namespace InMemoryScanner yr_finalize(); } - std::unique_ptr> Yara::scanMemory(std::vector& buffer) + std::unique_ptr> Yara::scanMemory(const std::vector& mappedRegions) { auto results = std::make_unique>(); - int err = 0; - err = yr_rules_scan_mem(rules, buffer.data(), buffer.size(), 0, yaraCallback, results.get(), 0); - if (err != ERROR_SUCCESS) + for (const auto& mappedRegion : mappedRegions) { - throw YaraException("Error scanning memory. Error code: " + std::to_string(err)); + auto err = yr_rules_scan_mem( + rules, mappedRegion.mapping.data(), mappedRegion.mapping.size(), 0, yaraCallback, results.get(), 0); + if (err != ERROR_SUCCESS) + { + throw YaraException("Error scanning memory. Error code: " + std::to_string(err)); + } } return results; diff --git a/plugins/inmemoryscanner/src/lib/Yara.h b/plugins/inmemoryscanner/src/lib/Yara.h index 2df566ce..b41b63cd 100644 --- a/plugins/inmemoryscanner/src/lib/Yara.h +++ b/plugins/inmemoryscanner/src/lib/Yara.h @@ -12,7 +12,7 @@ namespace InMemoryScanner ~Yara() override; - std::unique_ptr> scanMemory(std::vector& buffer) override; + std::unique_ptr> scanMemory(const std::vector& mappedRegions) override; private: YR_RULES* rules = nullptr; diff --git a/plugins/inmemoryscanner/src/lib/YaraInterface.h b/plugins/inmemoryscanner/src/lib/YaraInterface.h index ad56c641..833f4b8b 100644 --- a/plugins/inmemoryscanner/src/lib/YaraInterface.h +++ b/plugins/inmemoryscanner/src/lib/YaraInterface.h @@ -2,6 +2,7 @@ #include "Common.h" #include +#include namespace InMemoryScanner { @@ -16,7 +17,8 @@ namespace InMemoryScanner public: virtual ~YaraInterface() = default; - [[nodiscard]] virtual std::unique_ptr> scanMemory(std::vector& buffer) = 0; + virtual std::unique_ptr> + scanMemory(const std::vector& mappedRegions) = 0; protected: YaraInterface() = default; diff --git a/plugins/inmemoryscanner/test/FakeYara.cpp b/plugins/inmemoryscanner/test/FakeYara.cpp index badee8b1..60a135b4 100644 --- a/plugins/inmemoryscanner/test/FakeYara.cpp +++ b/plugins/inmemoryscanner/test/FakeYara.cpp @@ -4,7 +4,8 @@ namespace InMemoryScanner { - std::unique_ptr> FakeYara::scanMemory([[maybe_unused]] std::vector& buffer) + std::unique_ptr> + FakeYara::scanMemory([[maybe_unused]] const std::vector& mappedRegions) { concurrentThreads++; if (concurrentThreads > YR_MAX_THREADS) diff --git a/plugins/inmemoryscanner/test/FakeYara.h b/plugins/inmemoryscanner/test/FakeYara.h index 7996caa6..3860f85f 100644 --- a/plugins/inmemoryscanner/test/FakeYara.h +++ b/plugins/inmemoryscanner/test/FakeYara.h @@ -7,7 +7,7 @@ namespace InMemoryScanner class FakeYara : public YaraInterface { public: - std::unique_ptr> scanMemory(std::vector& buffer) override; + std::unique_ptr> scanMemory(const std::vector& mappedRegions) override; bool max_threads_exceeded = false; diff --git a/plugins/inmemoryscanner/test/Scanner_unittest.cpp b/plugins/inmemoryscanner/test/Scanner_unittest.cpp index 5d7e78c5..aca17d16 100644 --- a/plugins/inmemoryscanner/test/Scanner_unittest.cpp +++ b/plugins/inmemoryscanner/test/Scanner_unittest.cpp @@ -6,10 +6,12 @@ #include #include #include +#include #include #include #include #include +#include using testing::_; using testing::An; @@ -20,10 +22,13 @@ using testing::NiceMock; using testing::Return; using testing::Unused; using VmiCore::ActiveProcessInformation; +using VmiCore::addr_t; using VmiCore::MemoryRegion; using VmiCore::MockLogger; using VmiCore::MockMemoryRegionExtractor; using VmiCore::MockPageProtection; +using VmiCore::pid_t; +using VmiCore::PagingDefinitions::pageSizeInBytes; using VmiCore::Plugin::MockPluginInterface; namespace InMemoryScanner @@ -31,9 +36,10 @@ namespace InMemoryScanner class ScannerTestBaseFixture : public testing::Test { protected: - const size_t maxScanSize = 0x3200000; const pid_t testPid = 4; + const addr_t testDtb = 0x9876; const pid_t processIdWithSharedBaseImageRegion = 5; + const addr_t dtbWithSharedBaseImageRegion = 0x8765; std::unique_ptr pluginInterface = std::make_unique(); std::shared_ptr configuration = std::make_shared(); @@ -48,6 +54,10 @@ namespace InMemoryScanner std::filesystem::path dumpedRegionsPath = inMemoryDumpsPath / "dumpedRegions"; VmiCore::addr_t startAddress = 0x1234000; size_t size = 0x666; + std::vector testPageContent = std::vector(size, 1); + std::shared_ptr> regionMappings = + std::make_shared>(1, + VmiCore::MappedRegion(startAddress, testPageContent)); std::unique_ptr>> runningProcesses; @@ -55,23 +65,16 @@ namespace InMemoryScanner { ON_CALL(*pluginInterface, newNamedLogger(_)) .WillByDefault([]() { return std::make_unique>(); }); - ON_CALL(*pluginInterface, getResultsDir()).WillByDefault([]() { return std::make_unique(); }); - ON_CALL(*configuration, getMaximumScanSize()).WillByDefault(Return(maxScanSize)); - // make sure that we return a non-empty memory region or else we might skip important parts - ON_CALL(*pluginInterface, readProcessMemoryRegion(_, _, _)) - .WillByDefault([]() { return std::make_unique>(6, 9); }); - ON_CALL(*configuration, getOutputPath()) .WillByDefault([inMemoryDumpsPath = inMemoryDumpsPath]() { return inMemoryDumpsPath; }); - ON_CALL(*configuration, getMaximumScanSize()).WillByDefault(Return(maxScanSize)); runningProcesses = std::make_unique>>(); auto m1 = std::make_unique(); systemMemoryRegionExtractorRaw = m1.get(); runningProcesses->push_back(std::make_shared( ActiveProcessInformation{0, - 0, - 0, + testDtb, + testDtb, testPid, 0, "System.exe", @@ -83,8 +86,8 @@ namespace InMemoryScanner sharedBaseImageMemoryRegionExtractorRaw = m2.get(); runningProcesses->push_back(std::make_shared( ActiveProcessInformation{0, - 0, - 0, + dtbWithSharedBaseImageRegion, + dtbWithSharedBaseImageRegion, processIdWithSharedBaseImageRegion, 0, "SomeProcess.exe", @@ -92,6 +95,9 @@ namespace InMemoryScanner std::make_unique(""), std::move(m2), false})); + + createMemoryMapping(testDtb, startAddress, bytesToNumberOfPages(size), regionMappings); + createMemoryMapping(dtbWithSharedBaseImageRegion, startAddress, bytesToNumberOfPages(size), regionMappings); }; std::shared_ptr getProcessInfoFromRunningProcesses(pid_t pid) @@ -101,6 +107,22 @@ namespace InMemoryScanner [pid = pid](const std::shared_ptr& a) { return a->pid == pid; }); }; + + void createMemoryMapping(addr_t dtb, + VmiCore::addr_t baseVA, + std::size_t numberOfPages, + std::shared_ptr> mappedRegions) + { + ON_CALL(*pluginInterface, mapProcessMemoryRegion(baseVA, dtb, numberOfPages)) + .WillByDefault( + [mappedRegions = std::move(mappedRegions)]() + { + auto mapping = std::make_unique(); + ON_CALL(*mapping, getMappedRegions()) + .WillByDefault([mappedRegions = mappedRegions]() { return mappedRegions; }); + return mapping; + }); + } }; class ScannerTestFixtureDumpingDisabled : public ScannerTestBaseFixture @@ -115,7 +137,9 @@ namespace InMemoryScanner ON_CALL(*configuration, isDumpingMemoryActivated()).WillByDefault(Return(false)); auto dumping = std::make_unique>(); dumpingRawPointer = dumping.get(); - scanner.emplace(pluginInterface.get(), configuration, std::unique_ptr{}, std::move(dumping)); + auto yara = std::make_unique>(); + ON_CALL(*yara, scanMemory(_)).WillByDefault([]() { return std::make_unique>(); }); + scanner.emplace(pluginInterface.get(), configuration, std::move(yara), std::move(dumping)); }; }; @@ -169,21 +193,17 @@ namespace InMemoryScanner } }; - TEST_F(ScannerTestFixtureDumpingDisabled, scanProcess_largeMemoryRegion_trimToMaxScanSize) + std::vector constructPaddedRegion(const std::initializer_list>& list) { - ON_CALL(*systemMemoryRegionExtractorRaw, extractAllMemoryRegions()) - .WillByDefault( - [startAddress = startAddress, maxScanSize = maxScanSize]() - { - auto memoryRegions = std::make_unique>(); - memoryRegions->emplace_back( - startAddress, maxScanSize + 1, "", std::make_unique(), false, false, false); - return memoryRegions; - }); + std::vector paddedRegion{}; + paddedRegion.reserve(list.size() * pageSizeInBytes); - EXPECT_CALL(*pluginInterface, readProcessMemoryRegion(testPid, startAddress, maxScanSize)) - .WillOnce(Return(ByMove(std::make_unique>()))); - EXPECT_NO_THROW(scanner->scanProcess(getProcessInfoFromRunningProcesses(testPid))); + for (const auto& el : list) + { + std::copy(el.begin(), el.end(), std::back_inserter(paddedRegion)); + } + + return paddedRegion; } TEST_F(ScannerTestFixtureDumpingDisabled, scanProcess_smallMemoryRegion_originalReadMemoryRegionSize) @@ -197,10 +217,19 @@ namespace InMemoryScanner startAddress, size, "", std::make_unique(), false, false, false); return memoryRegions; }); + auto process = getProcessInfoFromRunningProcesses(testPid); - EXPECT_CALL(*pluginInterface, readProcessMemoryRegion(testPid, startAddress, size)) - .WillOnce(Return(ByMove(std::make_unique>()))); - EXPECT_NO_THROW(scanner->scanProcess(getProcessInfoFromRunningProcesses(testPid))); + EXPECT_CALL(*pluginInterface, + mapProcessMemoryRegion(startAddress, process->processUserDtb, bytesToNumberOfPages(size))) + .WillOnce( + [regionMappings = regionMappings]() + { + auto mapping = std::make_unique(); + EXPECT_CALL(*mapping, getMappedRegions()) + .WillOnce([regionMappings = regionMappings]() { return regionMappings; }); + return mapping; + }); + EXPECT_NO_THROW(scanner->scanProcess(process)); } TEST_F(ScannerTestFixtureDumpingDisabled, scanProcess_disabledDumping_dumpingNotCalled) @@ -214,10 +243,8 @@ namespace InMemoryScanner startAddress, size, "", std::make_unique(), false, false, false); return memoryRegions; }); - EXPECT_CALL(*pluginInterface, readProcessMemoryRegion(testPid, startAddress, size)) - .WillOnce(Return(ByMove(std::make_unique>()))); - EXPECT_CALL(*dumpingRawPointer, dumpMemoryRegion(_, _, _, _)).Times(0); + EXPECT_CALL(*dumpingRawPointer, dumpMemoryRegion(_, _, _, _)).Times(0); EXPECT_NO_THROW(scanner->scanProcess(getProcessInfoFromRunningProcesses(testPid))); } @@ -225,13 +252,14 @@ namespace InMemoryScanner { std::string fullProcessName = "abcdefghijklmnopqrstuvwxyz!1!"; std::string trimmedProcessName = "abcdefghijklmn"; - auto pid = 123; + pid_t pid = 123; + addr_t dtb = 0x4444; auto memoryRegionExtractor = std::make_unique(); auto* memoryRegionExtractorRaw = memoryRegionExtractor.get(); auto processWithLongName = std::make_shared( ActiveProcessInformation{0, - 0, - 0, + dtb, + dtb, pid, 0, trimmedProcessName, @@ -256,6 +284,7 @@ namespace InMemoryScanner startAddress + size, uidRegEx); auto expectedFileNameWithPathRegEx = "^" + (dumpedRegionsPath / expectedFileNameRegEx).string() + "$"; + createMemoryMapping(dtb, startAddress, bytesToNumberOfPages(size), regionMappings); EXPECT_CALL(*pluginInterface, writeToFile(ContainsRegex(expectedFileNameWithPathRegEx), An&>())); @@ -291,8 +320,8 @@ namespace InMemoryScanner auto* memoryRegionExtractorRaw = memoryRegionExtractor.get(); auto processInfo = std::make_shared( ActiveProcessInformation{0, - 0, - 0, + testDtb, + testDtb, testPid, 0, "System.exe", @@ -327,13 +356,14 @@ namespace InMemoryScanner { std::string fullProcessName = "abcdefghijklmnop"; std::string trimmedProcessName = "abcdefghijklmn"; - auto pid = 123; + pid_t pid = 312; + addr_t dtb = 0x5555; auto memoryRegionExtractor = std::make_unique(); auto* memoryRegionExtractorRaw = memoryRegionExtractor.get(); auto processInfo = std::make_shared( ActiveProcessInformation{0, - 0, - 0, + dtb, + dtb, pid, 0, "", @@ -369,6 +399,7 @@ namespace InMemoryScanner return memoryRegions; }); + createMemoryMapping(dtb, startAddress, bytesToNumberOfPages(size), regionMappings); EXPECT_CALL(*pluginInterface, writeToFile(_, An())).Times(AnyNumber()); EXPECT_CALL(*pluginInterface, writeToFile(expectedFileName.string(), expectedFileContent + "\n")).Times(1); @@ -376,4 +407,45 @@ namespace InMemoryScanner ASSERT_NO_THROW(scanner->scanAllProcesses()); ASSERT_NO_THROW(scanner->saveOutput()); } + + TEST_F(ScannerTestFixtureDumpingEnabled, scanProcess_complexMemoryRegion_regionWithCorrectPaddingDumped) + { + pid_t pid = 333; + addr_t dtb = 0x4554; + auto memoryRegionExtractor = std::make_unique(); + auto* memoryRegionExtractorRaw = memoryRegionExtractor.get(); + auto processInfo = + std::make_shared(ActiveProcessInformation{0, + dtb, + dtb, + pid, + 0, + "", + std::make_unique(""), + std::make_unique(""), + std::move(memoryRegionExtractor), + false}); + // Layout of complex region: 1 page, followed by 2 unmapped pages, followed by 2 pages + std::size_t complexRegionSize = 5 * pageSizeInBytes; + auto complexRegionDescriptor = MemoryRegion( + startAddress, complexRegionSize, "", std::make_unique(), false, false, false); + ON_CALL(*memoryRegionExtractorRaw, extractAllMemoryRegions()) + .WillByDefault( + [&memoryRegionDescriptor = complexRegionDescriptor]() + { + auto memoryRegions = std::make_unique>(); + memoryRegions->push_back(std::move(memoryRegionDescriptor)); + + return memoryRegions; + }); + auto twoPageRegionContent = std::vector(2 * pageSizeInBytes, 0xCA); + auto complexMappings = std::make_shared>(std::vector{ + {startAddress, testPageContent}, {startAddress + 3 * pageSizeInBytes, twoPageRegionContent}}); + createMemoryMapping(dtb, startAddress, bytesToNumberOfPages(complexRegionSize), complexMappings); + auto paddingPage = std::vector(pageSizeInBytes, 0); + auto expectedPaddedRegion = constructPaddedRegion({testPageContent, paddingPage, twoPageRegionContent}); + + EXPECT_CALL(*pluginInterface, writeToFile(_, expectedPaddedRegion)).Times(1); + ASSERT_NO_THROW(scanner->scanProcess(processInfo)); + } } diff --git a/plugins/inmemoryscanner/test/mock_Config.h b/plugins/inmemoryscanner/test/mock_Config.h index d643dae7..108089fe 100644 --- a/plugins/inmemoryscanner/test/mock_Config.h +++ b/plugins/inmemoryscanner/test/mock_Config.h @@ -14,7 +14,6 @@ namespace InMemoryScanner MOCK_METHOD(bool, isProcessIgnored, (const std::string& processName), (const, override)); MOCK_METHOD(bool, isScanAllRegionsActivated, (), (const, override)); MOCK_METHOD(bool, isDumpingMemoryActivated, (), (const, override)); - MOCK_METHOD(uint64_t, getMaximumScanSize, (), (const, override)); MOCK_METHOD(void, overrideDumpMemoryFlag, (bool value), (override)); }; } diff --git a/plugins/inmemoryscanner/test/mock_Yara.h b/plugins/inmemoryscanner/test/mock_Yara.h index 1dea4dc1..a4b43526 100644 --- a/plugins/inmemoryscanner/test/mock_Yara.h +++ b/plugins/inmemoryscanner/test/mock_Yara.h @@ -8,6 +8,9 @@ namespace InMemoryScanner class MockYara : public YaraInterface { public: - MOCK_METHOD(std::unique_ptr>, scanMemory, (std::vector & buffer), (override)); + MOCK_METHOD(std::unique_ptr>, + scanMemory, + (const std::vector& mappedRegions), + (override)); }; } diff --git a/vmicore/src/include/CMakeLists.txt b/vmicore/src/include/CMakeLists.txt index 7dd44541..47f773a1 100644 --- a/vmicore/src/include/CMakeLists.txt +++ b/vmicore/src/include/CMakeLists.txt @@ -17,6 +17,8 @@ target_sources(vmicore-public-headers INTERFACE vmicore/callback.h vmicore/vmi/IBreakpoint.h vmicore/vmi/IIntrospectionAPI.h + vmicore/vmi/IMemoryMapping.h + vmicore/vmi/MappedRegion.h vmicore/vmi/events/IInterruptEvent.h vmicore/vmi/events/IRegisterReadable.h vmicore/filename.h diff --git a/vmicore/src/include/vmicore/plugins/PluginInterface.h b/vmicore/src/include/vmicore/plugins/PluginInterface.h index 94b548a9..9100286c 100644 --- a/vmicore/src/include/vmicore/plugins/PluginInterface.h +++ b/vmicore/src/include/vmicore/plugins/PluginInterface.h @@ -7,6 +7,7 @@ #include "../vmi/BpResponse.h" #include "../vmi/IBreakpoint.h" #include "../vmi/IIntrospectionAPI.h" +#include "../vmi/IMemoryMapping.h" #include "../vmi/events/IInterruptEvent.h" #include #include @@ -26,15 +27,8 @@ namespace VmiCore::Plugin virtual ~PluginInterface() = default; - /** - * Reads a region of contiguous virtual memory from a process. The starting offset as well as the size must be - * 4kb page aligned. - * - * @return A unique pointer to a byte vector containing the memory content. Subregions that could not be - * extracted (e.g. because they are paged out) will be replaced by a single all zero padding page. - */ - [[nodiscard]] virtual std::unique_ptr> - readProcessMemoryRegion(pid_t pid, addr_t address, size_t numberOfBytes) const = 0; + [[nodiscard]] virtual std::unique_ptr + mapProcessMemoryRegion(addr_t baseVA, addr_t dtb, std::size_t numberOfPages) const = 0; /** * Obtain a vector containing an OS-agnostic representation of all currently running processes. diff --git a/vmicore/src/include/vmicore/vmi/IMemoryMapping.h b/vmicore/src/include/vmicore/vmi/IMemoryMapping.h new file mode 100644 index 00000000..40bd00c3 --- /dev/null +++ b/vmicore/src/include/vmicore/vmi/IMemoryMapping.h @@ -0,0 +1,34 @@ +#ifndef VMICORE_IMEMORYMAPPING_H +#define VMICORE_IMEMORYMAPPING_H + +#include "MappedRegion.h" +#include +#include + +namespace VmiCore +{ + class IMemoryMapping + { + public: + virtual ~IMemoryMapping() = default; + + IMemoryMapping(const IMemoryMapping&) = delete; + + IMemoryMapping(const IMemoryMapping&&) = delete; + + IMemoryMapping& operator=(const IMemoryMapping&) = delete; + + IMemoryMapping& operator=(const IMemoryMapping&&) = delete; + + virtual std::weak_ptr> getMappedRegions() = 0; + + virtual std::size_t getSizeInGuest() = 0; + + virtual void unmap() = 0; + + protected: + IMemoryMapping() = default; + }; +} + +#endif // VMICORE_IMEMORYMAPPING_H diff --git a/vmicore/src/include/vmicore/vmi/MappedRegion.h b/vmicore/src/include/vmicore/vmi/MappedRegion.h new file mode 100644 index 00000000..534441fc --- /dev/null +++ b/vmicore/src/include/vmicore/vmi/MappedRegion.h @@ -0,0 +1,30 @@ +#ifndef VMICORE_MAPPEDREGION_H +#define VMICORE_MAPPEDREGION_H + +#include "../types.h" +#include +#include + +namespace VmiCore +{ + struct MappedRegion + { + addr_t guestBaseVA; + std::span mapping; + + MappedRegion(addr_t guestBaseVA, std::span region) : guestBaseVA(guestBaseVA), mapping(region){}; + + bool operator==(const MappedRegion& rhs) const + { + return guestBaseVA == rhs.guestBaseVA && mapping.data() == rhs.mapping.data() && + mapping.size() == rhs.mapping.size(); + } + + bool operator!=(const MappedRegion& rhs) const + { + return !(rhs == *this); + } + }; +} + +#endif // VMICORE_MAPPEDREGION_H diff --git a/vmicore/src/lib/CMakeLists.txt b/vmicore/src/lib/CMakeLists.txt index 9c2e81e3..d5ee3dc1 100644 --- a/vmicore/src/lib/CMakeLists.txt +++ b/vmicore/src/lib/CMakeLists.txt @@ -28,6 +28,7 @@ add_library(vmicore-lib OBJECT vmi/InterruptEventSupervisor.cpp vmi/InterruptGuard.cpp vmi/LibvmiInterface.cpp + vmi/MemoryMapping.cpp vmi/SingleStepSupervisor.cpp vmi/VmiInitData.cpp vmi/VmiInitError.cpp) diff --git a/vmicore/src/lib/os/windows/ActiveProcessesSupervisor.cpp b/vmicore/src/lib/os/windows/ActiveProcessesSupervisor.cpp index e637ca1b..c4fb1661 100644 --- a/vmicore/src/lib/os/windows/ActiveProcessesSupervisor.cpp +++ b/vmicore/src/lib/os/windows/ActiveProcessesSupervisor.cpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace VmiCore::Windows { diff --git a/vmicore/src/lib/plugins/PluginSystem.cpp b/vmicore/src/lib/plugins/PluginSystem.cpp index 251def93..f63c4bb9 100644 --- a/vmicore/src/lib/plugins/PluginSystem.cpp +++ b/vmicore/src/lib/plugins/PluginSystem.cpp @@ -1,5 +1,7 @@ #include "PluginSystem.h" #include +#include "../vmi/MemoryMapping.h" +#include #include #include #include @@ -42,62 +44,10 @@ namespace VmiCore isInstanciated = false; } - std::unique_ptr> - PluginSystem::readPagesWithUnmappedRegionPadding(uint64_t pageAlignedVA, uint64_t cr3, uint64_t numberOfPages) const + std::unique_ptr + PluginSystem::mapProcessMemoryRegion(addr_t baseVA, addr_t dtb, std::size_t numberOfPages) const { - if (pageAlignedVA % PagingDefinitions::pageSizeInBytes != 0) - { - throw std::invalid_argument( - fmt::format("{}: Starting address {:#x} is not aligned to page boundary", __func__, pageAlignedVA)); - } - auto vadIdentifier(fmt::format("CR3 {:#x} VAD @ {:#x}-{:#x}", - cr3, - pageAlignedVA, - (pageAlignedVA + numberOfPages * PagingDefinitions::pageSizeInBytes))); - auto memoryRegion = std::make_unique>(); - auto needsPadding = true; - for (uint64_t currentPageIndex = 0; currentPageIndex < numberOfPages; currentPageIndex++) - { - auto memoryPage = std::vector(PagingDefinitions::pageSizeInBytes); - if (vmiInterface->readXVA(pageAlignedVA, cr3, memoryPage, memoryPage.size())) - { - if (!needsPadding) - { - needsPadding = true; - logger->info("First successful page extraction after padding", - {{WRITE_TO_FILE_TAG, paddingLogFile}, - {"vadIdentifier", vadIdentifier}, - {"pageAlignedVA", fmt::format("{:#x}", pageAlignedVA)}}); - } - memoryRegion->insert(memoryRegion->cend(), memoryPage.cbegin(), memoryPage.cend()); - } - else - { - if (needsPadding) - { - memoryRegion->insert(memoryRegion->cend(), PagingDefinitions::pageSizeInBytes, 0x0); - needsPadding = false; - logger->info("Start of padding", - {{WRITE_TO_FILE_TAG, paddingLogFile}, - {"vadIdentifier", vadIdentifier}, - {"pageAlignedVA", fmt::format("{:#x}", pageAlignedVA)}}); - } - } - pageAlignedVA += PagingDefinitions::pageSizeInBytes; - } - return memoryRegion; - } - - std::unique_ptr> - PluginSystem::readProcessMemoryRegion(pid_t pid, addr_t address, size_t count) const - { - if (count % PagingDefinitions::pageSizeInBytes != 0) - { - throw std::invalid_argument("Size of memory region must be page size aligned."); - } - auto numberOfPages = count >> PagingDefinitions::numberOfPageIndexBits; - auto process = activeProcessesSupervisor->getProcessInformationByPid(pid); - return readPagesWithUnmappedRegionPadding(address, process->processDtb, numberOfPages); + return std::make_unique(baseVA, vmiInterface->mmapGuest(baseVA, dtb, numberOfPages), loggingLib); } void PluginSystem::registerProcessStartEvent( diff --git a/vmicore/src/lib/plugins/PluginSystem.h b/vmicore/src/lib/plugins/PluginSystem.h index b516ffe1..93417f70 100644 --- a/vmicore/src/lib/plugins/PluginSystem.h +++ b/vmicore/src/lib/plugins/PluginSystem.h @@ -78,11 +78,8 @@ namespace VmiCore [[nodiscard]] std::unique_ptr getResultsDir() const override; - [[nodiscard]] std::unique_ptr> - readPagesWithUnmappedRegionPadding(uint64_t pageAlignedVA, uint64_t cr3, uint64_t numberOfPages) const; - - [[nodiscard]] std::unique_ptr> - readProcessMemoryRegion(pid_t pid, addr_t address, size_t numberOfBytes) const override; + [[nodiscard]] std::unique_ptr + mapProcessMemoryRegion(addr_t baseVA, addr_t dtb, std::size_t numberOfPages) const override; [[nodiscard]] std::unique_ptr>> getRunningProcesses() const override; diff --git a/vmicore/src/lib/vmi/LibvmiInterface.cpp b/vmicore/src/lib/vmi/LibvmiInterface.cpp index f3144bf2..df180773 100644 --- a/vmicore/src/lib/vmi/LibvmiInterface.cpp +++ b/vmicore/src/lib/vmi/LibvmiInterface.cpp @@ -5,6 +5,7 @@ #include "VmiInitError.h" #include #include +#include namespace VmiCore { @@ -182,6 +183,21 @@ namespace VmiCore return true; } + std::vector LibvmiInterface::mmapGuest(addr_t baseVA, addr_t dtb, std::size_t numberOfPages) + { + auto accessPointers = std::vector(numberOfPages); + auto accessContext = createVirtualAddressAccessContext(baseVA, dtb); + std::lock_guard lock(libvmiLock); + if (vmi_mmap_guest(vmiInstance, &accessContext, numberOfPages, PROT_READ, accessPointers.data()) != VMI_SUCCESS) + { + throw VmiException(fmt::format("{}: Unable to create memory mapping for VA {:#x} with number of pages {}", + __func__, + baseVA, + numberOfPages)); + } + return accessPointers; + } + void LibvmiInterface::write8PA(addr_t physicalAddress, uint8_t value) { auto accessContext = createPhysicalAddressAccessContext(physicalAddress); diff --git a/vmicore/src/lib/vmi/LibvmiInterface.h b/vmicore/src/lib/vmi/LibvmiInterface.h index 91c08727..f709f586 100644 --- a/vmicore/src/lib/vmi/LibvmiInterface.h +++ b/vmicore/src/lib/vmi/LibvmiInterface.h @@ -33,6 +33,8 @@ namespace VmiCore virtual void clearEvent(vmi_event_t& event, bool deallocate) = 0; + virtual std::vector mmapGuest(addr_t baseVA, addr_t dtb, std::size_t numberOfPages) = 0; + virtual void write8PA(addr_t physicalAddress, uint8_t value) = 0; virtual void eventsListen(uint32_t timeout) = 0; @@ -79,6 +81,8 @@ namespace VmiCore [[nodiscard]] bool readXVA(addr_t virtualAddress, addr_t cr3, std::vector& content, std::size_t size) override; + std::vector mmapGuest(addr_t baseVA, addr_t dtb, std::size_t numberOfPages) override; + void write8PA(addr_t physicalAddress, uint8_t value) override; void eventsListen(uint32_t timeout) override; diff --git a/vmicore/src/lib/vmi/MemoryMapping.cpp b/vmicore/src/lib/vmi/MemoryMapping.cpp new file mode 100644 index 00000000..cf581d9c --- /dev/null +++ b/vmicore/src/lib/vmi/MemoryMapping.cpp @@ -0,0 +1,92 @@ +#include "MemoryMapping.h" +#include +#include +#include +#include +#include + +namespace VmiCore +{ + MemoryMapping::MemoryMapping(addr_t guestBaseVA, + const std::vector& accessPointers, + const std::shared_ptr& logging) + : logger(logging->newNamedLogger(FILENAME_STEM)), mappings(std::make_shared>()) + { + // find coherent regions that are not interrupted by NULL access pointers + std::size_t numPagesInRegion = 0; + void* currentBase = nullptr; + + for (std::size_t i = 0; i < accessPointers.size(); i++) + { + auto* accessPointer = accessPointers[i]; + + if (accessPointer != nullptr) + { + mappingSize += PagingDefinitions::pageSizeInBytes; + + // new region starts + if (currentBase == nullptr) + { + currentBase = accessPointer; + } + numPagesInRegion++; + } + // current region ends + else if (currentBase != nullptr) + { + mappings->emplace_back(guestBaseVA + (i - numPagesInRegion) * PagingDefinitions::pageSizeInBytes, + std::span(reinterpret_cast(currentBase), + numPagesInRegion * PagingDefinitions::pageSizeInBytes)); + numPagesInRegion = 0; + currentBase = nullptr; + } + } + + // current region is mapped until the end of the array + if (currentBase != nullptr) + { + mappings->emplace_back(guestBaseVA + + (accessPointers.size() - numPagesInRegion) * PagingDefinitions::pageSizeInBytes, + std::span(reinterpret_cast(currentBase), + numPagesInRegion * PagingDefinitions::pageSizeInBytes)); + } + + sizeInGuest = accessPointers.size() * PagingDefinitions::pageSizeInBytes; + } + + MemoryMapping::~MemoryMapping() + { + if (isMapped) + { + unmap(); + } + } + + std::weak_ptr> MemoryMapping::getMappedRegions() + { + return mappings; + } + + size_t MemoryMapping::getSizeInGuest() + { + return sizeInGuest; + } + + void MemoryMapping::unmap() + { + if (!mappings->empty()) + { + for (auto region : *mappings) + { + if (munmap(region.mapping.data(), region.mapping.size()) != 0) + { + logger->warning("Failed to unmap guest memory", + {{"Pointer", reinterpret_cast(region.mapping.data())}, + {"Error", std::strerror(errno)}}); // NOLINT(concurrency-mt-unsafe) + } + } + + isMapped = false; + } + } +} diff --git a/vmicore/src/lib/vmi/MemoryMapping.h b/vmicore/src/lib/vmi/MemoryMapping.h new file mode 100644 index 00000000..6f4f94ad --- /dev/null +++ b/vmicore/src/lib/vmi/MemoryMapping.h @@ -0,0 +1,44 @@ +#ifndef VMICORE_MEMORYMAPPING_H +#define VMICORE_MEMORYMAPPING_H + +#include "../io/ILogging.h" +#include +#include +#include +#include + +namespace VmiCore +{ + class MemoryMapping final : public IMemoryMapping + { + public: + MemoryMapping(addr_t guestBaseVA, + const std::vector& accessPointers, + const std::shared_ptr& logging); + + ~MemoryMapping() override; + + MemoryMapping(const MemoryMapping&) = delete; + + MemoryMapping(const MemoryMapping&&) = delete; + + MemoryMapping& operator=(const MemoryMapping&) = delete; + + MemoryMapping& operator=(const MemoryMapping&&) = delete; + + std::weak_ptr> getMappedRegions() override; + + size_t getSizeInGuest() override; + + void unmap() override; + + private: + std::unique_ptr logger; + std::shared_ptr> mappings; + std::size_t sizeInGuest = 0; + std::size_t mappingSize = 0; + bool isMapped = true; + }; +} // VmiCore + +#endif // VMICORE_MEMORYMAPPING_H diff --git a/vmicore/test/CMakeLists.txt b/vmicore/test/CMakeLists.txt index 3740f820..e8563902 100644 --- a/vmicore/test/CMakeLists.txt +++ b/vmicore/test/CMakeLists.txt @@ -6,6 +6,7 @@ add_executable(vmicore-test lib/vmi/ContextSwitchHandler_UnitTest.cpp lib/vmi/InterruptEventSupervisor_UnitTest.cpp lib/vmi/LibvmiInterface_UnitTest.cpp + lib/vmi/MemoryMapping_UnitTest.cpp lib/vmi/SingleStepSupervisor_UnitTest.cpp) target_compile_options(vmicore-test PRIVATE -Wno-missing-field-initializers) target_link_libraries(vmicore-test vmicore-lib pthread) diff --git a/vmicore/test/include/CMakeLists.txt b/vmicore/test/include/CMakeLists.txt index cda0fd87..cab41c75 100644 --- a/vmicore/test/include/CMakeLists.txt +++ b/vmicore/test/include/CMakeLists.txt @@ -18,7 +18,8 @@ target_sources(vmicore-public-test-headers INTERFACE vmicore_test/plugins/mock_PluginInterface.h vmicore_test/vmi/mock_Breakpoint.h vmicore_test/vmi/mock_InterruptEvent.h - vmicore_test/vmi/mock_IntrospectionAPI.h) + vmicore_test/vmi/mock_IntrospectionAPI.h + vmicore_test/vmi/mock_MemoryMapping.h) target_include_directories(vmicore-public-test-headers INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}") target_compile_features(vmicore-public-test-headers INTERFACE cxx_std_20) target_link_libraries(vmicore-public-test-headers INTERFACE vmicore-public-headers gmock) diff --git a/vmicore/test/include/vmicore_test/plugins/mock_PluginInterface.h b/vmicore/test/include/vmicore_test/plugins/mock_PluginInterface.h index 15e32a8e..4f272122 100644 --- a/vmicore/test/include/vmicore_test/plugins/mock_PluginInterface.h +++ b/vmicore/test/include/vmicore_test/plugins/mock_PluginInterface.h @@ -9,9 +9,9 @@ namespace VmiCore::Plugin class MockPluginInterface : public PluginInterface { public: - MOCK_METHOD(std::unique_ptr>, - readProcessMemoryRegion, - (pid_t, addr_t, size_t), + MOCK_METHOD(std::unique_ptr, + mapProcessMemoryRegion, + (addr_t, addr_t, std::size_t), (const, override)); MOCK_METHOD(std::unique_ptr>>, diff --git a/vmicore/test/include/vmicore_test/vmi/mock_MemoryMapping.h b/vmicore/test/include/vmicore_test/vmi/mock_MemoryMapping.h new file mode 100644 index 00000000..829215c7 --- /dev/null +++ b/vmicore/test/include/vmicore_test/vmi/mock_MemoryMapping.h @@ -0,0 +1,20 @@ +#ifndef VMICORE_MOCK_MEMORYMAPPING_H +#define VMICORE_MOCK_MEMORYMAPPING_H + +#include +#include + +namespace VmiCore +{ + class MockMemoryMapping : public IMemoryMapping + { + public: + MOCK_METHOD(std::weak_ptr>, getMappedRegions, (), (override)); + + MOCK_METHOD(std::size_t, getSizeInGuest, (), (override)); + + MOCK_METHOD(void, unmap, (), (override)); + }; +} + +#endif // VMICORE_MOCK_MEMORYMAPPING_H diff --git a/vmicore/test/lib/plugins/PluginSystem_UnitTest.cpp b/vmicore/test/lib/plugins/PluginSystem_UnitTest.cpp index 6f7bcdd4..41c42bf4 100644 --- a/vmicore/test/lib/plugins/PluginSystem_UnitTest.cpp +++ b/vmicore/test/lib/plugins/PluginSystem_UnitTest.cpp @@ -214,197 +214,4 @@ namespace VmiCore std::advance(regionIterator, 2); EXPECT_EQ(regionIterator->size, vadRootNodeLeftChildMemoryRegionSize); } - - struct memoryRegionTestInformation - { - uint64_t virtualAddress; - uint64_t cr3; - size_t contentSize; - std::vector memoryPageContent; - }; - - class ReadProcessMemoryRegionFixture : public PluginSystemFixture - { - protected: - uint64_t unalignedVA = 1234; - uint64_t singlePageRegionBaseVA = 1234 * PagingDefinitions::pageSizeInBytes; - uint64_t threePagesRegionBaseVA = 2345 * PagingDefinitions::pageSizeInBytes; - uint64_t sevenPagesRegionBaseVA = 6666 * PagingDefinitions::pageSizeInBytes; - - memoryRegionTestInformation singlePageMemoryRegion{ - singlePageRegionBaseVA, - systemCR3, - PagingDefinitions::pageSizeInBytes, - std::vector(PagingDefinitions::pageSizeInBytes, 0xCD)}; - std::unique_ptr> threePagesMemoryRegion; - std::unique_ptr> sevenPagesMemoryRegionInfo; - - std::unique_ptr> - createMultipageRegionInformation(uint64_t baseVA, uint64_t cr3, size_t numberOfBytes) - { - auto resultVector = std::make_unique>(); - if (numberOfBytes > 0) - { - uint64_t numberOfSubsequentPages = - ((baseVA + numberOfBytes - 1) >> PagingDefinitions::numberOfPageIndexBits) - - (baseVA >> PagingDefinitions::numberOfPageIndexBits); - for (uint64_t i = 0; i <= numberOfSubsequentPages; ++i) - { - uint64_t currentPageContentSize = - i == numberOfSubsequentPages - ? numberOfBytes - (numberOfSubsequentPages * PagingDefinitions::pageSizeInBytes) - : PagingDefinitions::pageSizeInBytes; - resultVector->push_back({baseVA + (i * PagingDefinitions::pageSizeInBytes), - cr3, - currentPageContentSize, - std::vector(currentPageContentSize, static_cast(i))}); - } - } - return resultVector; - } - - void setupThreePagesRegionReturns() - { - threePagesMemoryRegion = createMultipageRegionInformation( - threePagesRegionBaseVA, systemCR3, 3 * PagingDefinitions::pageSizeInBytes); - threePagesMemoryRegion->at(1).memoryPageContent.clear(); // simulate non mapped page - for (const auto& element : *threePagesMemoryRegion) - { - setupMemoryRegionReturns(element); - } - } - - void setupMemoryRegionReturns(const memoryRegionTestInformation& memoryRegionInfo) - { - if (!memoryRegionInfo.memoryPageContent.empty()) - { - ON_CALL(*mockVmiInterface, readXVA(memoryRegionInfo.virtualAddress, memoryRegionInfo.cr3, _, _)) - .WillByDefault( - [memoryPageContent = - memoryRegionInfo.memoryPageContent]([[maybe_unused]] uint64_t virtualAddress, - [[maybe_unused]] uint64_t cr3, - std::vector& buffer, - [[maybe_unused]] std::size_t size) - { - buffer = memoryPageContent; - return true; - }); - } - else - { - ON_CALL(*mockVmiInterface, readXVA(memoryRegionInfo.virtualAddress, memoryRegionInfo.cr3, _, _)) - .WillByDefault(Return(false)); - } - } - - void setupSevenPagesRegionReturns() - { - sevenPagesMemoryRegionInfo = createMultipageRegionInformation(6666 * PagingDefinitions::pageSizeInBytes, - process4.directoryTableBase, - 7 * PagingDefinitions::pageSizeInBytes); - sevenPagesMemoryRegionInfo->at(0).memoryPageContent.clear(); - sevenPagesMemoryRegionInfo->at(1).memoryPageContent.clear(); - sevenPagesMemoryRegionInfo->at(3).memoryPageContent.clear(); - sevenPagesMemoryRegionInfo->at(4).memoryPageContent.clear(); - sevenPagesMemoryRegionInfo->at(6).memoryPageContent.clear(); - for (const auto& element : *sevenPagesMemoryRegionInfo) - { - setupMemoryRegionReturns(element); - } - } - - void SetUp() override - { - PluginSystemFixture::SetUp(); - - setupMemoryRegionReturns(singlePageMemoryRegion); - setupThreePagesRegionReturns(); - setupSevenPagesRegionReturns(); - } - }; - - TEST_F(ReadProcessMemoryRegionFixture, - readProcessMemoryRegion_virtualAddressNotPageAligned_invalidArgumentException) - { - size_t numberOfBytes = 4; - std::unique_ptr> data; - - EXPECT_THROW(data = pluginInterface->readProcessMemoryRegion(process4.processId, unalignedVA, numberOfBytes), - std::invalid_argument); - } - - TEST_F(ReadProcessMemoryRegionFixture, readProcessMemoryRegion_NumberOfBytesIsZero_emptyVector) - { - size_t numberOfBytes = 0; - std::unique_ptr> data; - - ASSERT_NO_THROW( - data = pluginInterface->readProcessMemoryRegion(process4.processId, singlePageRegionBaseVA, numberOfBytes)); - - EXPECT_TRUE(data->empty()); - } - - TEST_F(ReadProcessMemoryRegionFixture, readProcessMemoryRegion_unknownPid_invalidArgumentException) - { - size_t numberOfBytes = singlePageMemoryRegion.contentSize; - - EXPECT_THROW(auto _unused = - pluginInterface->readProcessMemoryRegion(unusedPid, singlePageRegionBaseVA, numberOfBytes), - std::invalid_argument); - } - - TEST_F(ReadProcessMemoryRegionFixture, readProcessMemoryRegion_smallMemoryRegion_validMemoryRegion) - { - size_t numberOfBytes = singlePageMemoryRegion.contentSize; - std::unique_ptr> data; - - ASSERT_NO_THROW( - data = pluginInterface->readProcessMemoryRegion(process4.processId, singlePageRegionBaseVA, numberOfBytes)); - - EXPECT_EQ(singlePageMemoryRegion.memoryPageContent, *data); - } - - TEST_F(ReadProcessMemoryRegionFixture, readProcessMemoryRegion_memoryRegionWithNonMappedPage_validMemoryRegion) - { - size_t numberOfBytes = 3 * PagingDefinitions::pageSizeInBytes; - std::unique_ptr> data; - std::vector resultMemoryRegion; - - // First page - resultMemoryRegion.insert(resultMemoryRegion.end(), - threePagesMemoryRegion->at(0).memoryPageContent.cbegin(), - threePagesMemoryRegion->at(0).memoryPageContent.cend()); - // Padding - resultMemoryRegion.insert(resultMemoryRegion.end(), PagingDefinitions::pageSizeInBytes, 0x0); - // Third page - resultMemoryRegion.insert(resultMemoryRegion.end(), - threePagesMemoryRegion->at(2).memoryPageContent.cbegin(), - threePagesMemoryRegion->at(2).memoryPageContent.cend()); - - ASSERT_NO_THROW( - data = pluginInterface->readProcessMemoryRegion(process4.processId, threePagesRegionBaseVA, numberOfBytes)); - - EXPECT_EQ(resultMemoryRegion, *data); - } - - TEST_F(ReadProcessMemoryRegionFixture, readProcessMemoryRegion_memoryRegionWithManyUnmappedPages_validMemoryRegion) - { - size_t sevenPagesSizeInBytes = 7 * PagingDefinitions::pageSizeInBytes; - std::vector expectedMemoryRegion; - expectedMemoryRegion.insert(expectedMemoryRegion.end(), PagingDefinitions::pageSizeInBytes, 0x0); - expectedMemoryRegion.insert(expectedMemoryRegion.end(), - sevenPagesMemoryRegionInfo->at(2).memoryPageContent.cbegin(), - sevenPagesMemoryRegionInfo->at(2).memoryPageContent.cend()); - expectedMemoryRegion.insert(expectedMemoryRegion.end(), PagingDefinitions::pageSizeInBytes, 0x0); - expectedMemoryRegion.insert(expectedMemoryRegion.end(), - sevenPagesMemoryRegionInfo->at(5).memoryPageContent.cbegin(), - sevenPagesMemoryRegionInfo->at(5).memoryPageContent.cend()); - expectedMemoryRegion.insert(expectedMemoryRegion.end(), PagingDefinitions::pageSizeInBytes, 0x0); - std::unique_ptr> data; - - ASSERT_NO_THROW(data = pluginInterface->readProcessMemoryRegion( - process4.processId, sevenPagesRegionBaseVA, sevenPagesSizeInBytes)); - - EXPECT_EQ(expectedMemoryRegion, *data); - } } diff --git a/vmicore/test/lib/plugins/mock_PluginSystem.h b/vmicore/test/lib/plugins/mock_PluginSystem.h index 11f89697..f4e489c9 100644 --- a/vmicore/test/lib/plugins/mock_PluginSystem.h +++ b/vmicore/test/lib/plugins/mock_PluginSystem.h @@ -6,6 +6,11 @@ namespace VmiCore class MockPluginSystem : public IPluginSystem { public: + MOCK_METHOD(std::unique_ptr, + mapProcessMemoryRegion, + (addr_t, addr_t, std::size_t), + (const override)); + MOCK_METHOD(std::unique_ptr>, readProcessMemoryRegion, (pid_t, addr_t, size_t), diff --git a/vmicore/test/lib/vmi/MemoryMapping_UnitTest.cpp b/vmicore/test/lib/vmi/MemoryMapping_UnitTest.cpp new file mode 100644 index 00000000..adaa3e5b --- /dev/null +++ b/vmicore/test/lib/vmi/MemoryMapping_UnitTest.cpp @@ -0,0 +1,123 @@ +#include "../io/mock_Logging.h" +#include +#include +#include + +using testing::NiceMock; +using VmiCore::PagingDefinitions::numberOfPageIndexBits; +using VmiCore::PagingDefinitions::pageSizeInBytes; + +namespace VmiCore +{ + constexpr uint64_t testBaseVA = 0x123 << numberOfPageIndexBits; + + TEST(MemoryMappingTest, constructor_emptyAccessPointers_emptyMappings) + { + auto accessPointers = std::vector{}; + + auto memoryMapping = MemoryMapping(testBaseVA, accessPointers, std::make_shared>()); + + EXPECT_EQ(memoryMapping.getSizeInGuest(), 0); + EXPECT_TRUE(memoryMapping.getMappedRegions().lock()->empty()); + } + + TEST(MemoryMappingTest, constructor_AccessPointersWithNullPointersOnly_emptyMappings) + { + auto accessPointers = std::vector{nullptr, nullptr}; + + auto memoryMapping = MemoryMapping(testBaseVA, accessPointers, std::make_shared>()); + + EXPECT_EQ(memoryMapping.getSizeInGuest(), 2 * pageSizeInBytes); + EXPECT_TRUE(memoryMapping.getMappedRegions().lock()->empty()); + } + + TEST(MemoryMappingTest, constructor_continguousRegion_correctMapping) + { + auto accessPointers = std::vector{reinterpret_cast(0x1 << numberOfPageIndexBits), + reinterpret_cast(0x2 << numberOfPageIndexBits), + reinterpret_cast(0x3 << numberOfPageIndexBits)}; + auto expectedMappedRegions = std::vector{MappedRegion{ + testBaseVA, std::span(reinterpret_cast(0x1 << numberOfPageIndexBits), 3 * pageSizeInBytes)}}; + + auto memoryMapping = MemoryMapping(testBaseVA, accessPointers, std::make_shared>()); + + EXPECT_EQ(memoryMapping.getSizeInGuest(), accessPointers.size() * pageSizeInBytes); + EXPECT_EQ(*memoryMapping.getMappedRegions().lock(), expectedMappedRegions); + } + + TEST(MemoryMappingTest, constructor_twoRegions_correctMapping) + { + auto accessPointers = std::vector{reinterpret_cast(0x1 << numberOfPageIndexBits), + nullptr, + reinterpret_cast(0x2 << numberOfPageIndexBits), + reinterpret_cast(0x3 << numberOfPageIndexBits)}; + auto expectedMappedRegions = std::vector{ + {testBaseVA, {reinterpret_cast(0x1 << numberOfPageIndexBits), pageSizeInBytes}}, + {testBaseVA + 2 * pageSizeInBytes, + {reinterpret_cast(0x2 << numberOfPageIndexBits), 2 * pageSizeInBytes}}}; + + auto memoryMapping = MemoryMapping(testBaseVA, accessPointers, std::make_shared>()); + + EXPECT_EQ(memoryMapping.getSizeInGuest(), accessPointers.size() * pageSizeInBytes); + EXPECT_EQ(*memoryMapping.getMappedRegions().lock(), expectedMappedRegions); + } + + TEST(MemoryMappingTest, constructor_twoRegionsWithlastPageUnmapped_correctMapping) + { + auto accessPointers = std::vector{reinterpret_cast(0x1 << numberOfPageIndexBits), + nullptr, + nullptr, + reinterpret_cast(0x2 << numberOfPageIndexBits), + reinterpret_cast(0x3 << numberOfPageIndexBits), + nullptr}; + auto expectedMappedRegions = std::vector{ + {testBaseVA, {reinterpret_cast(0x1 << numberOfPageIndexBits), pageSizeInBytes}}, + {testBaseVA + 3 * pageSizeInBytes, + {reinterpret_cast(0x2 << numberOfPageIndexBits), 2 * pageSizeInBytes}}}; + + auto memoryMapping = MemoryMapping(testBaseVA, accessPointers, std::make_shared>()); + + EXPECT_EQ(memoryMapping.getSizeInGuest(), accessPointers.size() * pageSizeInBytes); + EXPECT_EQ(*memoryMapping.getMappedRegions().lock(), expectedMappedRegions); + } + + TEST(MemoryMappingTest, constructor_threeRegions_correctMapping) + { + auto accessPointers = std::vector{reinterpret_cast(0x1 << numberOfPageIndexBits), + nullptr, + nullptr, + reinterpret_cast(0x2 << numberOfPageIndexBits), + reinterpret_cast(0x3 << numberOfPageIndexBits), + nullptr, + reinterpret_cast(0x4 << numberOfPageIndexBits), + nullptr}; + auto expectedMappedRegions = std::vector{ + {testBaseVA, {reinterpret_cast(0x1 << numberOfPageIndexBits), pageSizeInBytes}}, + {testBaseVA + 3 * pageSizeInBytes, + {reinterpret_cast(0x2 << numberOfPageIndexBits), 2 * pageSizeInBytes}}, + {testBaseVA + 6 * pageSizeInBytes, + {reinterpret_cast(0x4 << numberOfPageIndexBits), pageSizeInBytes}}}; + + auto memoryMapping = MemoryMapping(testBaseVA, accessPointers, std::make_shared>()); + + EXPECT_EQ(memoryMapping.getSizeInGuest(), accessPointers.size() * pageSizeInBytes); + EXPECT_EQ(*memoryMapping.getMappedRegions().lock(), expectedMappedRegions); + } + + TEST(MemoryMappingTest, constructor_firstTwoPagesUnmapped_correctMapping) + { + auto accessPointers = std::vector{nullptr, + nullptr, + reinterpret_cast(0x1 << numberOfPageIndexBits), + reinterpret_cast(0x2 << numberOfPageIndexBits), + reinterpret_cast(0x3 << numberOfPageIndexBits)}; + auto expectedMappedRegions = std::vector{ + {testBaseVA + 2 * pageSizeInBytes, + {reinterpret_cast(0x1 << numberOfPageIndexBits), 3 * pageSizeInBytes}}}; + + auto memoryMapping = MemoryMapping(testBaseVA, accessPointers, std::make_shared>()); + + EXPECT_EQ(memoryMapping.getSizeInGuest(), accessPointers.size() * pageSizeInBytes); + EXPECT_EQ(*memoryMapping.getMappedRegions().lock(), expectedMappedRegions); + } +} diff --git a/vmicore/test/lib/vmi/ProcessesMemoryState.h b/vmicore/test/lib/vmi/ProcessesMemoryState.h index 5ee7efe7..c3211518 100644 --- a/vmicore/test/lib/vmi/ProcessesMemoryState.h +++ b/vmicore/test/lib/vmi/ProcessesMemoryState.h @@ -17,6 +17,7 @@ #include #include #include +#include namespace VmiCore { diff --git a/vmicore/test/lib/vmi/mock_LibvmiInterface.h b/vmicore/test/lib/vmi/mock_LibvmiInterface.h index 68da8e06..a794392e 100644 --- a/vmicore/test/lib/vmi/mock_LibvmiInterface.h +++ b/vmicore/test/lib/vmi/mock_LibvmiInterface.h @@ -27,6 +27,8 @@ namespace VmiCore MOCK_METHOD(bool, readXVA, (uint64_t, uint64_t, std::vector&, std::size_t), (override)); + MOCK_METHOD(std::vector, mmapGuest, (addr_t, addr_t, std::size_t), (override)); + MOCK_METHOD(void, write8PA, (uint64_t, uint8_t), (override)); MOCK_METHOD(void, eventsListen, (uint32_t), (override));