diff --git a/plugins/inmemoryscanner/Readme.md b/plugins/inmemoryscanner/Readme.md index f37e131a..205a720f 100644 --- a/plugins/inmemoryscanner/Readme.md +++ b/plugins/inmemoryscanner/Readme.md @@ -68,8 +68,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 @@ -86,7 +84,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 @@ -127,7 +125,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. | @@ -144,7 +141,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 1c2b46df..41684c9b 100644 --- a/plugins/inmemoryscanner/src/lib/Common.h +++ b/plugins/inmemoryscanner/src/lib/Common.h @@ -4,6 +4,7 @@ #include #include #include +#include namespace InMemoryScanner { @@ -26,4 +27,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 0c66b1f9..b7a7c9c8 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) : pluginInterface(pluginInterface) {} void Config::parseConfiguration(const IPluginConfig& config) @@ -19,7 +17,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()); @@ -58,11 +55,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 d7e37f26..f1266c29 100644 --- a/plugins/inmemoryscanner/src/lib/Config.h +++ b/plugins/inmemoryscanner/src/lib/Config.h @@ -31,8 +31,6 @@ namespace InMemoryScanner [[nodiscard]] virtual bool isDumpingMemoryActivated() const = 0; - [[nodiscard]] virtual uint64_t getMaximumScanSize() const = 0; - virtual void overrideDumpMemoryFlag(bool value) = 0; protected: @@ -58,8 +56,6 @@ namespace InMemoryScanner [[nodiscard]] bool isDumpingMemoryActivated() const override; - [[nodiscard]] uint64_t getMaximumScanSize() const override; - void overrideDumpMemoryFlag(bool value) override; private: @@ -69,6 +65,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 85542ac5..a0990a60 100644 --- a/plugins/inmemoryscanner/src/lib/Scanner.cpp +++ b/plugins/inmemoryscanner/src/lib/Scanner.cpp @@ -1,12 +1,17 @@ #include "Scanner.h" +#include "Common.h" #include "Filenames.h" #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::LogLevel; using VmiCore::Plugin::PluginInterface; @@ -51,8 +56,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) { pluginInterface->logMessage(LogLevel::info, LOG_FILENAME, @@ -62,24 +102,11 @@ namespace InMemoryScanner if (shouldRegionBeScanned(memoryRegionDescriptor)) { - auto scanSize = memoryRegionDescriptor.size; - auto maximumScanSize = configuration->getMaximumScanSize(); - if (scanSize > maximumScanSize) - { - pluginInterface->logMessage( - LogLevel::info, LOG_FILENAME, "Memory region is too big, reduce to " + intToHex(maximumScanSize)); - scanSize = maximumScanSize; - } - - pluginInterface->logMessage( - LogLevel::debug, LOG_FILENAME, "Start getProcessMemoryRegion with size: " + intToHex(scanSize)); - - auto memoryRegion = pluginInterface->readProcessMemoryRegion(pid, memoryRegionDescriptor.base, scanSize); + auto memoryMapping = pluginInterface->mapProcessMemoryRegion( + memoryRegionDescriptor.base, dtb, bytesToNumberOfPages(memoryRegionDescriptor.size)); + auto mappedRegions = memoryMapping->getMappedRegions().lock(); - pluginInterface->logMessage(LogLevel::debug, - LOG_FILENAME, - "End getProcessMemoryRegion with size: " + intToHex(memoryRegion->size())); - if (memoryRegion->empty()) + if (mappedRegions->empty()) { pluginInterface->logMessage( LogLevel::debug, LOG_FILENAME, "Extracted memory region has size 0, skipping"); @@ -91,18 +118,22 @@ namespace InMemoryScanner pluginInterface->logMessage(LogLevel::debug, LOG_FILENAME, "Start dumpVadRegionToFile with size: " + - intToHex(memoryRegion->size())); - dumping->dumpMemoryRegion(processName, pid, memoryRegionDescriptor, *memoryRegion); + intToHex(memoryMapping->getSizeInGuest())); + + auto paddedRegion = constructPaddedMemoryRegion(*mappedRegions); + + dumping->dumpMemoryRegion(processName, pid, memoryRegionDescriptor, paddedRegion); pluginInterface->logMessage(LogLevel::debug, LOG_FILENAME, "End dumpVadRegionToFile"); } - pluginInterface->logMessage( - LogLevel::debug, LOG_FILENAME, "Start scanMemory with size: " + intToHex(memoryRegion->size())); + pluginInterface->logMessage(LogLevel::debug, + LOG_FILENAME, + "Start scanMemory with size: " + intToHex(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(); pluginInterface->logMessage(LogLevel::debug, LOG_FILENAME, "End scanMemory"); @@ -147,8 +178,10 @@ namespace InMemoryScanner { try { - scanMemoryRegion( - processInformation->pid, *processInformation->fullName, memoryRegionDescriptor); + scanMemoryRegion(processInformation->pid, + processInformation->processCR3, + *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 8877dfef..6ce7597b 100644 --- a/plugins/inmemoryscanner/src/lib/Scanner.h +++ b/plugins/inmemoryscanner/src/lib/Scanner.h @@ -40,7 +40,10 @@ namespace InMemoryScanner 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 fa45b0d3..c6c48912 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 5fda2c9f..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; - 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 90d75f6b..c4710c93 100644 --- a/plugins/inmemoryscanner/test/Scanner_unittest.cpp +++ b/plugins/inmemoryscanner/test/Scanner_unittest.cpp @@ -5,9 +5,11 @@ #include #include #include +#include #include #include #include +#include using testing::_; using testing::An; @@ -18,9 +20,12 @@ using testing::NiceMock; using testing::Return; using testing::Unused; using VmiCore::ActiveProcessInformation; +using VmiCore::addr_t; using VmiCore::MemoryRegion; using VmiCore::MockMemoryRegionExtractor; using VmiCore::MockPageProtection; +using VmiCore::pid_t; +using VmiCore::PagingDefinitions::pageSizeInBytes; using VmiCore::Plugin::MockPluginInterface; namespace InMemoryScanner @@ -28,9 +33,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(); @@ -45,44 +51,46 @@ 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; void SetUp() override { - 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, + testDtb, testPid, 0, "System.exe", std::make_unique("System.exe"), std::make_unique(""), - std::move(m1)})); + std::move(m1), + false})); auto m2 = std::make_unique(); sharedBaseImageMemoryRegionExtractorRaw = m2.get(); runningProcesses->push_back(std::make_shared( ActiveProcessInformation{0, - 0, + dtbWithSharedBaseImageRegion, processIdWithSharedBaseImageRegion, 0, "SomeProcess.exe", std::make_unique("SomeProcess.exe"), std::make_unique(""), - std::move(m2)})); + std::move(m2), + false})); + + createMemoryMapping(testDtb, startAddress, bytesToNumberOfPages(size), regionMappings); + createMemoryMapping(dtbWithSharedBaseImageRegion, startAddress, bytesToNumberOfPages(size), regionMappings); }; std::shared_ptr getProcessInfoFromRunningProcesses(pid_t pid) @@ -92,6 +100,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 @@ -106,7 +130,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)); }; }; @@ -155,21 +181,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) @@ -183,10 +205,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->processCR3, 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) @@ -200,10 +231,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))); } @@ -211,18 +240,20 @@ 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, + dtb, pid, 0, trimmedProcessName, std::make_unique(fullProcessName), std::make_unique(), - std::move(memoryRegionExtractor)}); + std::move(memoryRegionExtractor), + false}); // Redefine default mock return because a new MemoryRegionExtractor mock has been created ON_CALL(*memoryRegionExtractorRaw, extractAllMemoryRegions()) .WillByDefault( @@ -235,6 +266,7 @@ namespace InMemoryScanner auto expectedFileNameRegEx = trimmedProcessName + "-" + std::to_string(pid) + "-" + protectionAsString + "-" + intToHex(startAddress) + "-" + intToHex(startAddress + size) + "-" + uidRegEx; auto expectedFileNameWithPathRegEx = "^" + (dumpedRegionsPath / expectedFileNameRegEx).string() + "$"; + createMemoryMapping(dtb, startAddress, bytesToNumberOfPages(size), regionMappings); EXPECT_CALL(*pluginInterface, writeToFile(ContainsRegex(expectedFileNameWithPathRegEx), An&>())); @@ -266,13 +298,14 @@ namespace InMemoryScanner auto* memoryRegionExtractorRaw = memoryRegionExtractor.get(); auto processInfo = std::make_shared( ActiveProcessInformation{0, - 0, + testDtb, testPid, 0, "System.exe", std::make_unique("System.exe"), std::make_unique(""), - std::move(memoryRegionExtractor)}); + std::move(memoryRegionExtractor), + false}); ON_CALL(*pluginInterface, getRunningProcesses()) .WillByDefault( [&processInfo]() @@ -300,18 +333,20 @@ 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, + dtb, pid, 0, "", std::make_unique(fullProcessName), std::make_unique(""), - std::move(memoryRegionExtractor)}); + std::move(memoryRegionExtractor), + false}); auto expectedFileName = inMemoryDumpsPath / "MemoryRegionInformation.json"; std::string jsonStart = "{"; std::string expectedFileContent = @@ -339,6 +374,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); @@ -346,4 +382,44 @@ 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, + 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 7332551c..37daa594 100644 --- a/vmicore/src/include/CMakeLists.txt +++ b/vmicore/src/include/CMakeLists.txt @@ -8,12 +8,15 @@ target_sources(vmicore-public-headers INTERFACE vmicore/os/MemoryRegion.h vmicore/os/IPageProtection.h vmicore/os/OperatingSystem.h + vmicore/os/PagingDefinitions.h vmicore/plugins/IPluginConfig.h vmicore/plugins/PluginInit.h vmicore/plugins/PluginInterface.h vmicore/vmi/BpResponse.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/lib/os/PagingDefinitions.h b/vmicore/src/include/vmicore/os/PagingDefinitions.h similarity index 100% rename from vmicore/src/lib/os/PagingDefinitions.h rename to vmicore/src/include/vmicore/os/PagingDefinitions.h diff --git a/vmicore/src/include/vmicore/plugins/PluginInterface.h b/vmicore/src/include/vmicore/plugins/PluginInterface.h index 650562b0..a77d8558 100644 --- a/vmicore/src/include/vmicore/plugins/PluginInterface.h +++ b/vmicore/src/include/vmicore/plugins/PluginInterface.h @@ -5,6 +5,7 @@ #include "../types.h" #include "../vmi/IBreakpoint.h" #include "../vmi/IIntrospectionAPI.h" +#include "../vmi/IMemoryMapping.h" #include "IPluginConfig.h" #include #include @@ -31,12 +32,12 @@ namespace VmiCore::Plugin class PluginInterface { public: - constexpr static uint8_t API_VERSION = 13; + constexpr static uint8_t API_VERSION = 14; virtual ~PluginInterface() = default; - [[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; [[nodiscard]] virtual std::unique_ptr>> getRunningProcesses() const = 0; 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 b2ddcacf..5f62e39c 100644 --- a/vmicore/src/lib/CMakeLists.txt +++ b/vmicore/src/lib/CMakeLists.txt @@ -27,6 +27,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 73dd5143..42649f1a 100644 --- a/vmicore/src/lib/os/windows/ActiveProcessesSupervisor.cpp +++ b/vmicore/src/lib/os/windows/ActiveProcessesSupervisor.cpp @@ -1,9 +1,9 @@ #include "ActiveProcessesSupervisor.h" #include "../../vmi/VmiException.h" -#include "../PagingDefinitions.h" #include #include #include +#include namespace VmiCore::Windows { diff --git a/vmicore/src/lib/os/windows/KernelAccess.cpp b/vmicore/src/lib/os/windows/KernelAccess.cpp index 2c022787..10082622 100644 --- a/vmicore/src/lib/os/windows/KernelAccess.cpp +++ b/vmicore/src/lib/os/windows/KernelAccess.cpp @@ -1,8 +1,8 @@ #include "KernelAccess.h" #include "../../vmi/VmiException.h" -#include "../PagingDefinitions.h" #include "Constants.h" #include +#include namespace { diff --git a/vmicore/src/lib/os/windows/VadTreeWin10.cpp b/vmicore/src/lib/os/windows/VadTreeWin10.cpp index 925ea109..1d3583fa 100644 --- a/vmicore/src/lib/os/windows/VadTreeWin10.cpp +++ b/vmicore/src/lib/os/windows/VadTreeWin10.cpp @@ -1,10 +1,10 @@ #include "VadTreeWin10.h" #include "../../vmi/VmiException.h" #include "../PageProtection.h" -#include "../PagingDefinitions.h" #include #include #include +#include namespace VmiCore::Windows { diff --git a/vmicore/src/lib/plugins/PluginSystem.cpp b/vmicore/src/lib/plugins/PluginSystem.cpp index 87a36349..34337f47 100644 --- a/vmicore/src/lib/plugins/PluginSystem.cpp +++ b/vmicore/src/lib/plugins/PluginSystem.cpp @@ -1,11 +1,12 @@ #include "PluginSystem.h" -#include "../os/PagingDefinitions.h" +#include "../vmi/MemoryMapping.h" #include #include #include #include #include #include +#include namespace VmiCore { @@ -48,62 +49,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)) - { - if (!needsPadding) - { - needsPadding = true; - logger->info("First successful page extraction after padding", - {logfield::create(WRITE_TO_FILE_TAG, paddingLogFile), - logfield::create("vadIdentifier", vadIdentifier), - logfield::create("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", - {logfield::create(WRITE_TO_FILE_TAG, paddingLogFile), - logfield::create("vadIdentifier", vadIdentifier), - logfield::create("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->processCR3, numberOfPages); + return std::make_unique(baseVA, vmiInterface->mmapGuest(baseVA, dtb, numberOfPages), loggingLib); } void PluginSystem::registerProcessStartEvent(Plugin::processStartCallback_f startCallback) diff --git a/vmicore/src/lib/plugins/PluginSystem.h b/vmicore/src/lib/plugins/PluginSystem.h index f31dcbe6..0341ad50 100644 --- a/vmicore/src/lib/plugins/PluginSystem.h +++ b/vmicore/src/lib/plugins/PluginSystem.h @@ -52,34 +52,10 @@ namespace VmiCore std::shared_ptr config, const std::vector& args) override; - void passProcessStartEventToRegisteredPlugins( - std::shared_ptr processInformation) override; - - void passProcessTerminationEventToRegisteredPlugins( - std::shared_ptr processInformation) override; - - void passShutdownEventToRegisteredPlugins() override; - - private: - std::shared_ptr configInterface; - std::shared_ptr vmiInterface; - std::shared_ptr activeProcessesSupervisor; - std::shared_ptr interruptEventSupervisor; - std::shared_ptr legacyLogging; - std::vector registeredProcessStartCallbacks; - std::vector registeredProcessTerminationCallbacks; - std::vector registeredShutdownCallbacks; - std::shared_ptr loggingLib; - std::unique_ptr logger; - std::shared_ptr eventStream; - [[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; @@ -90,7 +66,7 @@ namespace VmiCore void registerShutdownEvent(Plugin::shutdownCallback_f shutdownCallback) override; - std::shared_ptr + [[nodiscard]] std::shared_ptr createBreakpoint(uint64_t targetVA, uint64_t processDtb, const std::function& callbackFunction) override; @@ -107,6 +83,27 @@ namespace VmiCore void sendInMemDetectionEvent(std::string_view message) const override; [[nodiscard]] std::shared_ptr getIntrospectionAPI() const override; + + void passProcessStartEventToRegisteredPlugins( + std::shared_ptr processInformation) override; + + void passProcessTerminationEventToRegisteredPlugins( + std::shared_ptr processInformation) override; + + void passShutdownEventToRegisteredPlugins() override; + + private: + std::shared_ptr configInterface; + std::shared_ptr vmiInterface; + std::shared_ptr activeProcessesSupervisor; + std::shared_ptr interruptEventSupervisor; + std::shared_ptr legacyLogging; + std::vector registeredProcessStartCallbacks; + std::vector registeredProcessTerminationCallbacks; + std::vector registeredShutdownCallbacks; + std::shared_ptr loggingLib; + std::unique_ptr logger; + std::shared_ptr eventStream; }; } diff --git a/vmicore/src/lib/vmi/Breakpoint.cpp b/vmicore/src/lib/vmi/Breakpoint.cpp index c1a5d039..06aae622 100644 --- a/vmicore/src/lib/vmi/Breakpoint.cpp +++ b/vmicore/src/lib/vmi/Breakpoint.cpp @@ -1,8 +1,8 @@ #include "Breakpoint.h" -#include "../os/PagingDefinitions.h" #include "VmiException.h" #include #include +#include namespace VmiCore { diff --git a/vmicore/src/lib/vmi/InterruptEventSupervisor.cpp b/vmicore/src/lib/vmi/InterruptEventSupervisor.cpp index 399ad660..f6f1c346 100644 --- a/vmicore/src/lib/vmi/InterruptEventSupervisor.cpp +++ b/vmicore/src/lib/vmi/InterruptEventSupervisor.cpp @@ -1,10 +1,10 @@ #include "InterruptEventSupervisor.h" -#include "../os/PagingDefinitions.h" #include "Event.h" #include "InterruptGuard.h" #include "VmiException.h" #include #include +#include namespace VmiCore { diff --git a/vmicore/src/lib/vmi/InterruptGuard.cpp b/vmicore/src/lib/vmi/InterruptGuard.cpp index 40e92310..8e4776da 100644 --- a/vmicore/src/lib/vmi/InterruptGuard.cpp +++ b/vmicore/src/lib/vmi/InterruptGuard.cpp @@ -1,11 +1,11 @@ #include "InterruptGuard.h" #include "../GlobalControl.h" -#include "../os/PagingDefinitions.h" #include "VmiException.h" #include #include #include #include +#include namespace VmiCore { diff --git a/vmicore/src/lib/vmi/LibvmiInterface.cpp b/vmicore/src/lib/vmi/LibvmiInterface.cpp index 2a51b430..36d45d9c 100644 --- a/vmicore/src/lib/vmi/LibvmiInterface.cpp +++ b/vmicore/src/lib/vmi/LibvmiInterface.cpp @@ -1,11 +1,11 @@ #include "LibvmiInterface.h" #include "../GlobalControl.h" -#include "../os/PagingDefinitions.h" #include "VmiException.h" #include "VmiInitData.h" #include "VmiInitError.h" #include #include +#include namespace VmiCore { @@ -156,6 +156,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, 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(const 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 d2a3d673..6034e346 100644 --- a/vmicore/src/lib/vmi/LibvmiInterface.h +++ b/vmicore/src/lib/vmi/LibvmiInterface.h @@ -32,6 +32,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; @@ -75,6 +77,8 @@ namespace VmiCore bool readXVA(addr_t virtualAddress, addr_t cr3, std::vector& content) 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..b70833b0 --- /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", + {logfield::create("pointer", reinterpret_cast(region.mapping.data())), + logfield::create("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 f5f143a9..5af7d2c1 100644 --- a/vmicore/test/CMakeLists.txt +++ b/vmicore/test/CMakeLists.txt @@ -5,6 +5,7 @@ add_executable(vmicore-test lib/plugins/PluginSystem_UnitTest.cpp lib/vmi/InterruptEventSupervisor_UnitTest.cpp lib/vmi/LibvmiInterface_UnitTest.cpp + lib/vmi/MemoryMapping_UnitTest.cpp lib/vmi/SingleStepSupervisor_UnitTest.cpp) target_link_libraries(vmicore-test vmicore-lib pthread) diff --git a/vmicore/test/include/CMakeLists.txt b/vmicore/test/include/CMakeLists.txt index 401aef6e..5d581619 100644 --- a/vmicore/test/include/CMakeLists.txt +++ b/vmicore/test/include/CMakeLists.txt @@ -16,7 +16,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 ad36765e..dadbfd6e 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/os/windows/KernelAccess_UnitTest.cpp b/vmicore/test/lib/os/windows/KernelAccess_UnitTest.cpp index 979c23fa..8f454d71 100644 --- a/vmicore/test/lib/os/windows/KernelAccess_UnitTest.cpp +++ b/vmicore/test/lib/os/windows/KernelAccess_UnitTest.cpp @@ -1,7 +1,7 @@ #include "../../vmi/ProcessesMemoryState.h" #include #include -#include +#include using testing::Contains; using testing::Not; diff --git a/vmicore/test/lib/plugins/PluginSystem_UnitTest.cpp b/vmicore/test/lib/plugins/PluginSystem_UnitTest.cpp index 1e7b5ecb..41c42bf4 100644 --- a/vmicore/test/lib/plugins/PluginSystem_UnitTest.cpp +++ b/vmicore/test/lib/plugins/PluginSystem_UnitTest.cpp @@ -214,196 +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) - { - 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 83496ac3..fa71498e 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/InterruptEventSupervisor_UnitTest.cpp b/vmicore/test/lib/vmi/InterruptEventSupervisor_UnitTest.cpp index 440645af..6981cb12 100644 --- a/vmicore/test/lib/vmi/InterruptEventSupervisor_UnitTest.cpp +++ b/vmicore/test/lib/vmi/InterruptEventSupervisor_UnitTest.cpp @@ -6,9 +6,9 @@ #include "mock_SingleStepSupervisor.h" #include #include -#include #include #include +#include #include using testing::_; 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 5c77c8dd..31640657 100644 --- a/vmicore/test/lib/vmi/ProcessesMemoryState.h +++ b/vmicore/test/lib/vmi/ProcessesMemoryState.h @@ -12,11 +12,11 @@ #include #include #include -#include #include #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 c90bd6a1..f7e14ece 100644 --- a/vmicore/test/lib/vmi/mock_LibvmiInterface.h +++ b/vmicore/test/lib/vmi/mock_LibvmiInterface.h @@ -25,6 +25,8 @@ namespace VmiCore MOCK_METHOD(bool, readXVA, (const uint64_t, const uint64_t, std::vector&), (override)); + MOCK_METHOD(std::vector, mmapGuest, (addr_t, addr_t, std::size_t), (override)); + MOCK_METHOD(void, write8PA, (const uint64_t, const uint8_t), (override)); MOCK_METHOD(void, eventsListen, (uint32_t), (override));