From 2298e290768a6c532383be46d9b30fe8ae34afff Mon Sep 17 00:00:00 2001 From: Dorian Eikenberg Date: Fri, 24 Mar 2023 13:54:27 +0100 Subject: [PATCH] WIP: vmi_mmap_guest_2 --- .../inmemoryscanner/src/lib/IYaraInterface.h | 11 +-- plugins/inmemoryscanner/src/lib/Scanner.cpp | 83 ++++++++--------- plugins/inmemoryscanner/src/lib/Scanner.h | 3 +- .../inmemoryscanner/src/lib/YaraInterface.cpp | 92 +++++++++++-------- .../inmemoryscanner/src/lib/YaraInterface.h | 27 +++++- plugins/inmemoryscanner/test/CMakeLists.txt | 2 +- .../test/FakeYaraInterface.cpp | 7 +- .../inmemoryscanner/test/FakeYaraInterface.h | 3 +- .../inmemoryscanner/test/Scanner_unittest.cpp | 35 +++---- ...itttest.cpp => YaraInterface_unittest.cpp} | 55 +++++------ .../inmemoryscanner/test/mock_YaraInterface.h | 4 +- .../include/vmicore/plugins/PluginInterface.h | 11 ++- .../src/include/vmicore/vmi/IMemoryMapping.h | 35 ++++--- .../src/include/vmicore/vmi/MappedRegion.h | 40 ++++++-- vmicore/src/lib/plugins/PluginSystem.cpp | 7 +- vmicore/src/lib/vmi/LibvmiInterface.cpp | 19 ++-- vmicore/src/lib/vmi/LibvmiInterface.h | 8 +- vmicore/src/lib/vmi/MemoryMapping.cpp | 86 +++++------------ vmicore/src/lib/vmi/MemoryMapping.h | 19 ++-- vmicore/test/CMakeLists.txt | 1 + .../vmicore_test/vmi/mock_MemoryMapping.h | 4 +- .../test/lib/vmi/MappedRegion_UnitTest.cpp | 14 +++ .../test/lib/vmi/MemoryMapping_UnitTest.cpp | 3 +- vmicore/test/lib/vmi/ProcessesMemoryState.h | 1 - vmicore/test/lib/vmi/mock_LibvmiInterface.h | 4 +- 25 files changed, 312 insertions(+), 262 deletions(-) rename plugins/inmemoryscanner/test/{YaraInterface_unitttest.cpp => YaraInterface_unittest.cpp} (73%) create mode 100644 vmicore/test/lib/vmi/MappedRegion_UnitTest.cpp diff --git a/plugins/inmemoryscanner/src/lib/IYaraInterface.h b/plugins/inmemoryscanner/src/lib/IYaraInterface.h index 309e9b73..c3815e37 100644 --- a/plugins/inmemoryscanner/src/lib/IYaraInterface.h +++ b/plugins/inmemoryscanner/src/lib/IYaraInterface.h @@ -2,8 +2,8 @@ #define INMEMORYSCANNER_IYARAINTERFACE_H #include "Common.h" -#include -#include +#include +#include #include #include @@ -11,8 +11,7 @@ namespace InMemoryScanner { class YaraException : public std::runtime_error { - public: - explicit YaraException(const std::string& Message) : std::runtime_error(Message.c_str()){}; + using std::runtime_error::runtime_error; }; class IYaraInterface @@ -20,8 +19,8 @@ namespace InMemoryScanner public: virtual ~IYaraInterface() = default; - virtual std::unique_ptr> - scanMemory(const std::vector& mappedRegions) = 0; + virtual std::vector scanMemory(VmiCore::addr_t regionBase, + std::span mappedRegions) = 0; protected: IYaraInterface() = default; diff --git a/plugins/inmemoryscanner/src/lib/Scanner.cpp b/plugins/inmemoryscanner/src/lib/Scanner.cpp index d3ff21f2..22e4b9bf 100644 --- a/plugins/inmemoryscanner/src/lib/Scanner.cpp +++ b/plugins/inmemoryscanner/src/lib/Scanner.cpp @@ -1,10 +1,9 @@ #include "Scanner.h" #include "Common.h" #include "Filenames.h" +#include #include #include -#include -#include #include #include @@ -49,20 +48,19 @@ namespace InMemoryScanner bool Scanner::shouldRegionBeScanned(const MemoryRegion& memoryRegionDescriptor) { - bool verdict = true; if (configuration->isScanAllRegionsActivated()) { return true; } if (memoryRegionDescriptor.isSharedMemory && !memoryRegionDescriptor.isProcessBaseImage) { - verdict = false; logger->info("Skipping: Is shared memory and not the process base image."); + return false; } - return verdict; + return true; } - std::vector Scanner::constructPaddedMemoryRegion(const std::vector& regions) + std::vector Scanner::constructPaddedMemoryRegion(std::span regions) { std::vector result; @@ -74,7 +72,7 @@ namespace InMemoryScanner std::size_t regionSize = 0; for (const auto& region : regions) { - regionSize += region.mapping.size(); + regionSize += region.asSpan().size(); regionSize += pageSizeInBytes; } // last region should not have succeeding padding page @@ -82,14 +80,16 @@ namespace InMemoryScanner result.reserve(regionSize); // copy first region - std::copy(regions.front().mapping.begin(), regions.front().mapping.end(), std::back_inserter(result)); + auto frontRegionSpan = regions.front().asSpan(); + std::ranges::copy(frontRegionSpan.begin(), frontRegionSpan.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)); + auto regionSpan = region.asSpan(); + std::ranges::copy(regionSpan.begin(), regionSpan.end(), std::back_inserter(result)); } return result; @@ -105,47 +105,48 @@ namespace InMemoryScanner {"Size", memoryRegionDescriptor.size}, {"Module", memoryRegionDescriptor.moduleName}}); - if (shouldRegionBeScanned(memoryRegionDescriptor)) + if (!shouldRegionBeScanned(memoryRegionDescriptor)) { - auto memoryMapping = pluginInterface->mapProcessMemoryRegion( - memoryRegionDescriptor.base, dtb, bytesToNumberOfPages(memoryRegionDescriptor.size)); - auto mappedRegions = memoryMapping->getMappedRegions().lock(); + return; + } - if (mappedRegions->empty()) - { - logger->debug("Extracted memory region has size 0, skipping"); - } - else - { - if (configuration->isDumpingMemoryActivated()) - { - logger->debug("Start dumpVadRegionToFile", {{"Size", memoryMapping->getSizeInGuest()}}); + auto memoryMapping = pluginInterface->mapProcessMemoryRegion( + memoryRegionDescriptor.base, dtb, bytesToNumberOfPages(memoryRegionDescriptor.size)); + auto mappedRegions = memoryMapping->getMappedRegions(); - auto paddedRegion = constructPaddedMemoryRegion(*mappedRegions); + if (mappedRegions.empty()) + { + logger->debug("Extracted memory region has size 0, skipping"); + return; + } - dumping->dumpMemoryRegion(processName, pid, memoryRegionDescriptor, paddedRegion); - } + if (configuration->isDumpingMemoryActivated()) + { + logger->debug("Start dumpVadRegionToFile", {{"Size", memoryRegionDescriptor.size}}); - logger->debug("Start scanMemory", {{"Size", memoryMapping->getSizeInGuest()}}); + auto paddedRegion = constructPaddedMemoryRegion(mappedRegions); - // The semaphore protects the yara rules from being accessed more than YR_MAX_THREADS (32 atm.) times in - // parallel. - semaphore.wait(); - auto results = yaraInterface->scanMemory(*mappedRegions); - semaphore.notify(); + dumping->dumpMemoryRegion(processName, pid, memoryRegionDescriptor, paddedRegion); + } - logger->debug("End scanMemory"); + logger->debug("Start scanMemory", {{"Size", memoryRegionDescriptor.size}}); - if (!results->empty()) - { - for (const auto& result : *results) - { - pluginInterface->sendInMemDetectionEvent(result.ruleName); - } - outputXml.addResult(processName, pid, memoryRegionDescriptor.base, *results); - logInMemoryResultToTextFile(processName, pid, memoryRegionDescriptor.base, *results); - } + // The semaphore protects the yara rules from being accessed more than YR_MAX_THREADS (32 atm.) times in + // parallel. + semaphore.wait(); + auto results = yaraInterface->scanMemory(memoryRegionDescriptor.base, mappedRegions); + semaphore.notify(); + + logger->debug("End scanMemory"); + + if (!results.empty()) + { + for (const auto& result : results) + { + pluginInterface->sendInMemDetectionEvent(result.ruleName); } + outputXml.addResult(processName, pid, memoryRegionDescriptor.base, results); + logInMemoryResultToTextFile(processName, pid, memoryRegionDescriptor.base, results); } } diff --git a/plugins/inmemoryscanner/src/lib/Scanner.h b/plugins/inmemoryscanner/src/lib/Scanner.h index f31b00d6..57a6af70 100644 --- a/plugins/inmemoryscanner/src/lib/Scanner.h +++ b/plugins/inmemoryscanner/src/lib/Scanner.h @@ -7,6 +7,7 @@ #include "Semaphore.h" #include #include +#include #include #include // NOLINT(modernize-deprecated-headers) @@ -41,7 +42,7 @@ namespace InMemoryScanner [[nodiscard]] bool shouldRegionBeScanned(const VmiCore::MemoryRegion& memoryRegionDescriptor); - static std::vector constructPaddedMemoryRegion(const std::vector& regions); + static std::vector constructPaddedMemoryRegion(std::span regions); void scanMemoryRegion(pid_t pid, uint64_t dtb, diff --git a/plugins/inmemoryscanner/src/lib/YaraInterface.cpp b/plugins/inmemoryscanner/src/lib/YaraInterface.cpp index 76bd1a2e..1c849b37 100644 --- a/plugins/inmemoryscanner/src/lib/YaraInterface.cpp +++ b/plugins/inmemoryscanner/src/lib/YaraInterface.cpp @@ -1,8 +1,42 @@ #include "YaraInterface.h" #include +using VmiCore::addr_t; using VmiCore::MappedRegion; -using BlockIteratorPair = std::pair::iterator, std::vector::iterator>; +using VmiCore::PagingDefinitions::pageSizeInBytes; + +namespace +{ + struct YaraIteratorContext + { + std::vector blocks; + std::size_t index; + }; + + YR_MEMORY_BLOCK* get_next_block(YR_MEMORY_BLOCK_ITERATOR* iterator) + { + if (auto* iteratorContext = static_cast(iterator->context); + ++iteratorContext->index < iteratorContext->blocks.size()) + { + return &iteratorContext->blocks[iteratorContext->index]; + } + + return nullptr; + } + + YR_MEMORY_BLOCK* get_first_block(YR_MEMORY_BLOCK_ITERATOR* iterator) + { + auto* iteratorContext = static_cast(iterator->context); + iteratorContext->index = 0; + + return &iteratorContext->blocks[iteratorContext->index]; + } + + const uint8_t* fetch_block_data(YR_MEMORY_BLOCK* block) + { + return static_cast(block->context); + } +} namespace InMemoryScanner { @@ -11,74 +45,52 @@ namespace InMemoryScanner auto err = yr_initialize(); if (err != ERROR_SUCCESS) { - throw YaraException("Cannot initialize Yara. Error code: " + std::to_string(err)); + throw YaraException(fmt::format("Cannot initialize Yara. Error code: {}", err)); } err = yr_rules_load(rulesFile.c_str(), &rules); if (err != ERROR_SUCCESS) { - throw YaraException("Cannot load rules. Error code: " + std::to_string(err)); + throw YaraException(fmt::format("Cannot load rules. Error code: {}", err)); } } YaraInterface::~YaraInterface() { - yr_rules_destroy(rules); - yr_finalize(); - } - - YR_MEMORY_BLOCK* get_next_block(YR_MEMORY_BLOCK_ITERATOR* iterator) - { - auto* blockVectorIterators = reinterpret_cast(iterator->context); - blockVectorIterators->first++; - - if (blockVectorIterators->first == blockVectorIterators->second) + if (rules) { - return nullptr; + yr_rules_destroy(rules); + yr_finalize(); } - - return &*blockVectorIterators->first; - } - - YR_MEMORY_BLOCK* get_first_block(YR_MEMORY_BLOCK_ITERATOR* iterator) - { - return &*reinterpret_cast(iterator->context)->first; } - const uint8_t* fetch_block_data(YR_MEMORY_BLOCK* block) + std::vector YaraInterface::scanMemory(addr_t regionBase, std::span mappedRegions) { - return reinterpret_cast(block->context); - } + std::vector results; - std::unique_ptr> YaraInterface::scanMemory(const std::vector& mappedRegions) - { - auto results = std::make_unique>(); - - std::vector blocks; - blocks.reserve(mappedRegions.size()); + YaraIteratorContext iteratorContext{}; + iteratorContext.blocks.reserve(mappedRegions.size()); for (const auto& mappedRegion : mappedRegions) { - blocks.emplace_back(mappedRegion.mapping.size(), - mappedRegion.guestBaseVA - mappedRegions.front().guestBaseVA, - reinterpret_cast(mappedRegion.mapping.data()), - &fetch_block_data); + iteratorContext.blocks.emplace_back(mappedRegion.num_pages * pageSizeInBytes, + mappedRegion.guestBaseVA - regionBase, + mappedRegion.mappingBase, + &fetch_block_data); } - auto blockIterators = std::make_pair(blocks.begin(), blocks.end()); #ifdef LIBYARA_4_1 - YR_MEMORY_BLOCK_ITERATOR iterator{.context = &blockIterators, + YR_MEMORY_BLOCK_ITERATOR iterator{.context = &iteratorContext, .first = &get_first_block, .next = &get_next_block, .file_size = nullptr, .last_error = ERROR_SUCCESS}; #else YR_MEMORY_BLOCK_ITERATOR iterator{ - .context = &blockIterators, .first = &get_first_block, .next = &get_next_block}; + .context = &iteratorContext, .first = &get_first_block, .next = &get_next_block}; #endif - auto err = yr_rules_scan_mem_blocks(rules, &iterator, 0, yaraCallback, results.get(), 0); - if (err != ERROR_SUCCESS) + if (auto err = yr_rules_scan_mem_blocks(rules, &iterator, 0, yaraCallback, &results, 0); err != ERROR_SUCCESS) { - throw YaraException("Error scanning memory. Error code: " + std::to_string(err)); + throw YaraException(fmt::format("Error scanning memory. Error code: {}", err)); } return results; diff --git a/plugins/inmemoryscanner/src/lib/YaraInterface.h b/plugins/inmemoryscanner/src/lib/YaraInterface.h index bff1708a..923d7fb3 100644 --- a/plugins/inmemoryscanner/src/lib/YaraInterface.h +++ b/plugins/inmemoryscanner/src/lib/YaraInterface.h @@ -2,9 +2,7 @@ #include "Common.h" #include "IYaraInterface.h" -#include #include -#include #include namespace InMemoryScanner @@ -14,9 +12,32 @@ namespace InMemoryScanner public: explicit YaraInterface(const std::string& rulesFile); + YaraInterface(const YaraInterface& other) = delete; + + YaraInterface(YaraInterface&& other) noexcept : rules(other.rules) + { + other.rules = nullptr; + } + + YaraInterface& operator=(const YaraInterface& other) = delete; + + YaraInterface& operator=(YaraInterface&& other) noexcept + { + if (this == &other) + { + return *this; + } + + rules = other.rules; + other.rules = nullptr; + + return *this; + } + ~YaraInterface() override; - std::unique_ptr> scanMemory(const std::vector& mappedRegions) override; + std::vector scanMemory(VmiCore::addr_t regionBase, + std::span mappedRegions) override; private: YR_RULES* rules = nullptr; diff --git a/plugins/inmemoryscanner/test/CMakeLists.txt b/plugins/inmemoryscanner/test/CMakeLists.txt index 07637324..9ab81ba0 100644 --- a/plugins/inmemoryscanner/test/CMakeLists.txt +++ b/plugins/inmemoryscanner/test/CMakeLists.txt @@ -1,7 +1,7 @@ add_executable(inmemoryscanner-test FakeYaraInterface.cpp Scanner_unittest.cpp - YaraInterface_unitttest.cpp) + YaraInterface_unittest.cpp) target_link_libraries(inmemoryscanner-test inmemoryscanner-obj pthread) # Setup bundled google test framework diff --git a/plugins/inmemoryscanner/test/FakeYaraInterface.cpp b/plugins/inmemoryscanner/test/FakeYaraInterface.cpp index 9be8133b..1b0606e3 100644 --- a/plugins/inmemoryscanner/test/FakeYaraInterface.cpp +++ b/plugins/inmemoryscanner/test/FakeYaraInterface.cpp @@ -4,8 +4,9 @@ namespace InMemoryScanner { - std::unique_ptr> - FakeYaraInterface::scanMemory([[maybe_unused]] const std::vector& mappedRegions) + std::vector + FakeYaraInterface::scanMemory([[maybe_unused]] VmiCore::addr_t regionBase, + [[maybe_unused]] std::span mappedRegions) { concurrentThreads++; if (concurrentThreads > YR_MAX_THREADS) @@ -14,6 +15,6 @@ namespace InMemoryScanner } std::this_thread::sleep_for(std::chrono::seconds(1)); concurrentThreads--; - return std::make_unique>(); + return {}; } } diff --git a/plugins/inmemoryscanner/test/FakeYaraInterface.h b/plugins/inmemoryscanner/test/FakeYaraInterface.h index eff36427..62d69bc9 100644 --- a/plugins/inmemoryscanner/test/FakeYaraInterface.h +++ b/plugins/inmemoryscanner/test/FakeYaraInterface.h @@ -7,7 +7,8 @@ namespace InMemoryScanner class FakeYaraInterface : public IYaraInterface { public: - std::unique_ptr> scanMemory(const std::vector& mappedRegions) override; + std::vector scanMemory(VmiCore::addr_t regionBase, + std::span mappedRegions) override; bool max_threads_exceeded = false; diff --git a/plugins/inmemoryscanner/test/Scanner_unittest.cpp b/plugins/inmemoryscanner/test/Scanner_unittest.cpp index 1ebcfa69..4c63d56b 100644 --- a/plugins/inmemoryscanner/test/Scanner_unittest.cpp +++ b/plugins/inmemoryscanner/test/Scanner_unittest.cpp @@ -23,6 +23,7 @@ using testing::Return; using testing::Unused; using VmiCore::ActiveProcessInformation; using VmiCore::addr_t; +using VmiCore::MappedRegion; using VmiCore::MemoryRegion; using VmiCore::MockLogger; using VmiCore::MockMemoryRegionExtractor; @@ -52,12 +53,10 @@ namespace InMemoryScanner std::filesystem::path inMemoryDumpsPath = "inMemDumps"; std::filesystem::path dumpedRegionsPath = inMemoryDumpsPath / "dumpedRegions"; - VmiCore::addr_t startAddress = 0x1234000; - size_t size = 0x666; + addr_t startAddress = 0x1234000; + size_t size = pageSizeInBytes; std::vector testPageContent = std::vector(size, 1); - std::shared_ptr> regionMappings = - std::make_shared>(1, - VmiCore::MappedRegion(startAddress, testPageContent)); + std::vector regionMappings = std::vector(1, MappedRegion(startAddress, testPageContent)); std::unique_ptr>> runningProcesses; @@ -98,7 +97,7 @@ namespace InMemoryScanner createMemoryMapping(testDtb, startAddress, bytesToNumberOfPages(size), regionMappings); createMemoryMapping(dtbWithSharedBaseImageRegion, startAddress, bytesToNumberOfPages(size), regionMappings); - }; + } std::shared_ptr getProcessInfoFromRunningProcesses(pid_t pid) { @@ -106,20 +105,17 @@ namespace InMemoryScanner runningProcesses->cend(), [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) + void + createMemoryMapping(addr_t dtb, addr_t baseVA, std::size_t numberOfPages, std::span mappedRegions) { ON_CALL(*pluginInterface, mapProcessMemoryRegion(baseVA, dtb, numberOfPages)) .WillByDefault( - [mappedRegions = std::move(mappedRegions)]() + [mappedRegions = mappedRegions]() { auto mapping = std::make_unique(); - ON_CALL(*mapping, getMappedRegions()) - .WillByDefault([mappedRegions = mappedRegions]() { return mappedRegions; }); + ON_CALL(*mapping, getMappedRegions()).WillByDefault(Return(mappedRegions)); return mapping; }); } @@ -138,7 +134,7 @@ namespace InMemoryScanner auto dumping = std::make_unique>(); dumpingRawPointer = dumping.get(); auto yara = std::make_unique>(); - ON_CALL(*yara, scanMemory(_)).WillByDefault([]() { return std::make_unique>(); }); + ON_CALL(*yara, scanMemory(_, _)).WillByDefault(Return(std::vector{})); scanner.emplace(pluginInterface.get(), configuration, std::move(yara), std::move(dumping)); }; }; @@ -177,7 +173,7 @@ namespace InMemoryScanner }); auto dumping = std::make_unique(pluginInterface.get(), configuration); auto yara = std::make_unique>(); - ON_CALL(*yara, scanMemory(_)).WillByDefault([]() { return std::make_unique>(); }); + ON_CALL(*yara, scanMemory(_, _)).WillByDefault(Return(std::vector{})); scanner.emplace(pluginInterface.get(), configuration, std::move(yara), std::move(dumping)); }; @@ -225,8 +221,7 @@ namespace InMemoryScanner [regionMappings = regionMappings]() { auto mapping = std::make_unique(); - EXPECT_CALL(*mapping, getMappedRegions()) - .WillOnce([regionMappings = regionMappings]() { return regionMappings; }); + EXPECT_CALL(*mapping, getMappedRegions()).WillOnce(Return(regionMappings)); return mapping; }); EXPECT_NO_THROW(scanner->scanProcess(process)); @@ -439,8 +434,8 @@ namespace InMemoryScanner return memoryRegions; }); auto twoPageRegionContent = std::vector(2 * pageSizeInBytes, 0xCA); - auto complexMappings = std::make_shared>(std::vector{ - {startAddress, testPageContent}, {startAddress + 3 * pageSizeInBytes, twoPageRegionContent}}); + std::vector complexMappings{{startAddress, testPageContent}, + {startAddress + 3 * pageSizeInBytes, twoPageRegionContent}}; createMemoryMapping(dtb, startAddress, bytesToNumberOfPages(complexRegionSize), complexMappings); auto paddingPage = std::vector(pageSizeInBytes, 0); auto expectedPaddedRegion = constructPaddedRegion({testPageContent, paddingPage, twoPageRegionContent}); diff --git a/plugins/inmemoryscanner/test/YaraInterface_unitttest.cpp b/plugins/inmemoryscanner/test/YaraInterface_unittest.cpp similarity index 73% rename from plugins/inmemoryscanner/test/YaraInterface_unitttest.cpp rename to plugins/inmemoryscanner/test/YaraInterface_unittest.cpp index e857ab2c..ce2c3802 100644 --- a/plugins/inmemoryscanner/test/YaraInterface_unitttest.cpp +++ b/plugins/inmemoryscanner/test/YaraInterface_unittest.cpp @@ -5,12 +5,15 @@ #include using testing::UnorderedElementsAre; +using VmiCore::PagingDefinitions::pageSizeInBytes; namespace InMemoryScanner { - std::vector toBytes(const std::string& string) + std::vector constructPageWithContent(const std::string& string) { - return {string.begin(), string.end()}; + std::vector result(pageSizeInBytes, 0); + std::copy(string.begin(), string.end(), result.begin()); + return result; } std::string compileYaraRules(std::string_view rules) @@ -60,12 +63,12 @@ namespace InMemoryScanner } )"; auto yaraInterface = YaraInterface(compileYaraRules(rules)); - auto subRegion1 = toBytes("ABCD"); + auto subRegion1 = constructPageWithContent("ABCD"); std::vector memoryRegions{{0x0, subRegion1}}; - auto matches = yaraInterface.scanMemory(memoryRegions); + auto matches = yaraInterface.scanMemory(memoryRegions.front().guestBaseVA, memoryRegions); - EXPECT_EQ(matches->size(), 0); + EXPECT_EQ(matches.size(), 0); } TEST(YaraTest, scanMemory_StringSplitInHalfThroughSubRegionBoundary_NoMatch) @@ -81,13 +84,13 @@ namespace InMemoryScanner } )"; auto yaraInterface = YaraInterface(compileYaraRules(rules)); - auto subRegion1 = toBytes("ABCD"); - auto subRegion2 = toBytes("DCBA"); + auto subRegion1 = constructPageWithContent("ABCD"); + auto subRegion2 = constructPageWithContent("DCBA"); std::vector memoryRegions{{0x0, subRegion1}, {0x40, subRegion2}}; - auto matches = yaraInterface.scanMemory(memoryRegions); + auto matches = yaraInterface.scanMemory(memoryRegions.front().guestBaseVA, memoryRegions); - EXPECT_EQ(matches->size(), 0); + EXPECT_EQ(matches.size(), 0); } TEST(YaraTest, scanMemory_AllOfConditionStringsInDifferentRegions_NoMatch) @@ -104,16 +107,16 @@ namespace InMemoryScanner } )"; auto yaraInterface = YaraInterface(compileYaraRules(rules)); - auto subRegion1 = toBytes("ABCD"); - auto subRegion2 = toBytes("DCBA"); + auto subRegion1 = constructPageWithContent("ABCD"); + auto subRegion2 = constructPageWithContent("DCBA"); std::vector memoryRegion1{{0x0, subRegion1}}; std::vector memoryRegion2{{0x40, subRegion2}}; - auto matches1 = yaraInterface.scanMemory(memoryRegion1); - auto matches2 = yaraInterface.scanMemory(memoryRegion2); + auto matches1 = yaraInterface.scanMemory(memoryRegion1.front().guestBaseVA, memoryRegion1); + auto matches2 = yaraInterface.scanMemory(memoryRegion2.front().guestBaseVA, memoryRegion2); - EXPECT_EQ(matches1->size(), 0); - EXPECT_EQ(matches2->size(), 0); + EXPECT_EQ(matches1.size(), 0); + EXPECT_EQ(matches2.size(), 0); } TEST(YaraTest, scanMemory_AllOfConditionStringsInDifferentSubRegions_Matches) @@ -130,15 +133,15 @@ namespace InMemoryScanner } )"; auto yaraInterface = YaraInterface(compileYaraRules(rules)); - auto subRegion1 = toBytes("ABCD"); - auto subRegion2 = toBytes("DCBA"); + auto subRegion1 = constructPageWithContent("ABCD"); + auto subRegion2 = constructPageWithContent("DCBA"); std::vector memoryRegions{{0x0, subRegion1}, {0x40, subRegion2}}; Rule expectedMatch{"testRule", "default", {{"$test", 0x0}, {"$test2", 0x40}}}; - auto matches = yaraInterface.scanMemory(memoryRegions); + auto matches = yaraInterface.scanMemory(memoryRegions.front().guestBaseVA, memoryRegions); - ASSERT_EQ(matches->size(), 1); - EXPECT_THAT(*matches, UnorderedElementsAre(expectedMatch)); + ASSERT_EQ(matches.size(), 1); + EXPECT_THAT(matches, UnorderedElementsAre(expectedMatch)); } TEST(YaraTest, scanMemory_DifferentRulesInRegions_BothMatch) @@ -165,16 +168,16 @@ namespace InMemoryScanner } )"; auto yaraInterface = YaraInterface(compileYaraRules(rules)); - auto subRegion1 = toBytes("ABCD"); - auto subRegion2 = toBytes("DCBA"); - auto subRegion3 = toBytes("EFGH"); + auto subRegion1 = constructPageWithContent("ABCD"); + auto subRegion2 = constructPageWithContent("DCBA"); + auto subRegion3 = constructPageWithContent("EFGH"); std::vector memoryRegions{{0x0, subRegion1}, {0x40, subRegion2}, {0x80, subRegion3}}; Rule expectedMatch1{"testRule", "default", {{"$test", 0x0}, {"$test2", 0x40}}}; Rule expectedMatch2{"testRule2", "default", {{"$test", 0x80}, {"$test2", 0x81}}}; - auto matches = yaraInterface.scanMemory(memoryRegions); + auto matches = yaraInterface.scanMemory(memoryRegions.front().guestBaseVA, memoryRegions); - ASSERT_EQ(matches->size(), 2); - EXPECT_THAT(*matches, UnorderedElementsAre(expectedMatch1, expectedMatch2)); + ASSERT_EQ(matches.size(), 2); + EXPECT_THAT(matches, UnorderedElementsAre(expectedMatch1, expectedMatch2)); } } diff --git a/plugins/inmemoryscanner/test/mock_YaraInterface.h b/plugins/inmemoryscanner/test/mock_YaraInterface.h index cbeccc01..d9efa3c4 100644 --- a/plugins/inmemoryscanner/test/mock_YaraInterface.h +++ b/plugins/inmemoryscanner/test/mock_YaraInterface.h @@ -8,9 +8,9 @@ namespace InMemoryScanner class MockYaraInterface : public IYaraInterface { public: - MOCK_METHOD(std::unique_ptr>, + MOCK_METHOD(std::vector, scanMemory, - (const std::vector& mappedRegions), + (VmiCore::addr_t, std::span), (override)); }; } diff --git a/vmicore/src/include/vmicore/plugins/PluginInterface.h b/vmicore/src/include/vmicore/plugins/PluginInterface.h index 9100286c..3b7bc148 100644 --- a/vmicore/src/include/vmicore/plugins/PluginInterface.h +++ b/vmicore/src/include/vmicore/plugins/PluginInterface.h @@ -23,10 +23,19 @@ namespace VmiCore::Plugin class PluginInterface { public: - constexpr static uint8_t API_VERSION = 15; + constexpr static uint8_t API_VERSION = 16; virtual ~PluginInterface() = default; + /** + * Map a guest memory region into the address space of the introspection application. See IMemoryMapping.h for + * more details. + * + * @param baseVA Start of the virtual address range inside the guest. + * @param dtb Process dtb + * @param numberOfPages Size of the region in 4kb pages. + * @return An instance of IMemoryMapping. See IMemoryMapping.h for details. + */ [[nodiscard]] virtual std::unique_ptr mapProcessMemoryRegion(addr_t baseVA, addr_t dtb, std::size_t numberOfPages) const = 0; diff --git a/vmicore/src/include/vmicore/vmi/IMemoryMapping.h b/vmicore/src/include/vmicore/vmi/IMemoryMapping.h index 40bd00c3..b6c7054d 100644 --- a/vmicore/src/include/vmicore/vmi/IMemoryMapping.h +++ b/vmicore/src/include/vmicore/vmi/IMemoryMapping.h @@ -2,28 +2,35 @@ #define VMICORE_IMEMORYMAPPING_H #include "MappedRegion.h" -#include -#include +#include namespace VmiCore { + class MemoryMappingError : public std::runtime_error + { + using std::runtime_error::runtime_error; + }; + + /** + * Class representing chunks of memory that were able to be mapped within in a given virtual address range. + */ 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; - + /** + * Retrieves a set memory mapping descriptors. See MappedRegion.h for details. Elements are ordered from lowest + * to highest guest VA. + * + * @throws MemoryMappingError Will occur if unmap has already been called. + */ + [[nodiscard]] virtual std::span getMappedRegions() const = 0; + + /** + * Will unmap all mappings. This function will also be called as soon as an instance of this class goes out of + * scope. + */ virtual void unmap() = 0; protected: diff --git a/vmicore/src/include/vmicore/vmi/MappedRegion.h b/vmicore/src/include/vmicore/vmi/MappedRegion.h index 534441fc..92103828 100644 --- a/vmicore/src/include/vmicore/vmi/MappedRegion.h +++ b/vmicore/src/include/vmicore/vmi/MappedRegion.h @@ -1,29 +1,53 @@ #ifndef VMICORE_MAPPEDREGION_H #define VMICORE_MAPPEDREGION_H +#include "../os/PagingDefinitions.h" #include "../types.h" -#include +#include #include +#include namespace VmiCore { + /** + * Represents a chunk of contiguous memory that has been mapped into the address space of the introspection + * application. + */ struct MappedRegion { + /// A virtual address representing the start of the memory region inside the guest. addr_t guestBaseVA; - std::span mapping; + /// Size of the memory region in pages. Will always be the finest granularity, even if the guest uses large + /// pages. + std::size_t num_pages; + /// Base address of the mapped memory region inside the introspection application's address space. + void* mappingBase; - MappedRegion(addr_t guestBaseVA, std::span region) : guestBaseVA(guestBaseVA), mapping(region){}; + MappedRegion(addr_t guestBaseVA, std::size_t num_pages, void* mappingBase) + : guestBaseVA(guestBaseVA), num_pages(num_pages), mappingBase(mappingBase) + { + } - bool operator==(const MappedRegion& rhs) const + MappedRegion(addr_t guestBaseVA, std::span mapping) + : guestBaseVA(guestBaseVA), + num_pages(mapping.size() / PagingDefinitions::pageSizeInBytes), + mappingBase(static_cast(mapping.data())) { - return guestBaseVA == rhs.guestBaseVA && mapping.data() == rhs.mapping.data() && - mapping.size() == rhs.mapping.size(); + if (mapping.size() % PagingDefinitions::pageSizeInBytes != 0) + { + throw std::invalid_argument("Mapping has to be page aligned"); + } } - bool operator!=(const MappedRegion& rhs) const + /** + * Convenience method for safe access to the mapped memory. + */ + [[nodiscard]] std::span asSpan() const { - return !(rhs == *this); + return {static_cast(mappingBase), num_pages * PagingDefinitions::pageSizeInBytes}; } + + bool operator==(const MappedRegion&) const = default; }; } diff --git a/vmicore/src/lib/plugins/PluginSystem.cpp b/vmicore/src/lib/plugins/PluginSystem.cpp index f63c4bb9..27eaa9b9 100644 --- a/vmicore/src/lib/plugins/PluginSystem.cpp +++ b/vmicore/src/lib/plugins/PluginSystem.cpp @@ -1,19 +1,17 @@ #include "PluginSystem.h" -#include #include "../vmi/MemoryMapping.h" +#include #include #include #include #include #include -#include namespace VmiCore { namespace { bool isInstanciated = false; - constexpr char const* paddingLogFile = "memoryExtractionPaddingLog.txt"; } PluginSystem::PluginSystem(std::shared_ptr configInterface, @@ -47,7 +45,8 @@ namespace VmiCore std::unique_ptr PluginSystem::mapProcessMemoryRegion(addr_t baseVA, addr_t dtb, std::size_t numberOfPages) const { - return std::make_unique(baseVA, vmiInterface->mmapGuest(baseVA, dtb, numberOfPages), loggingLib); + return std::make_unique( + loggingLib, vmiInterface, vmiInterface->mmapGuest(baseVA, dtb, numberOfPages)); } void PluginSystem::registerProcessStartEvent( diff --git a/vmicore/src/lib/vmi/LibvmiInterface.cpp b/vmicore/src/lib/vmi/LibvmiInterface.cpp index df180773..d272f0e2 100644 --- a/vmicore/src/lib/vmi/LibvmiInterface.cpp +++ b/vmicore/src/lib/vmi/LibvmiInterface.cpp @@ -3,9 +3,9 @@ #include "VmiException.h" #include "VmiInitData.h" #include "VmiInitError.h" +#include #include #include -#include namespace VmiCore { @@ -183,19 +183,24 @@ namespace VmiCore return true; } - std::vector LibvmiInterface::mmapGuest(addr_t baseVA, addr_t dtb, std::size_t numberOfPages) + mapped_regions_t LibvmiInterface::mmapGuest(addr_t baseVA, addr_t dtb, std::size_t numberOfPages) { - auto accessPointers = std::vector(numberOfPages); + mapped_regions_t regions{}; auto accessContext = createVirtualAddressAccessContext(baseVA, dtb); - std::lock_guard lock(libvmiLock); - if (vmi_mmap_guest(vmiInstance, &accessContext, numberOfPages, PROT_READ, accessPointers.data()) != VMI_SUCCESS) + std::lock_guard lock(libvmiLock); + if (vmi_mmap_guest_2(vmiInstance, &accessContext, numberOfPages, PROT_READ, ®ions) != VMI_SUCCESS) { throw VmiException(fmt::format("{}: Unable to create memory mapping for VA {:#x} with number of pages {}", - __func__, + std::source_location::current().function_name(), baseVA, numberOfPages)); } - return accessPointers; + return regions; + } + + void LibvmiInterface::freeMappedRegions(const mapped_regions_t& mappedRegions) + { + vmi_free_mapped_regions(vmiInstance, &mappedRegions); } void LibvmiInterface::write8PA(addr_t physicalAddress, uint8_t value) diff --git a/vmicore/src/lib/vmi/LibvmiInterface.h b/vmicore/src/lib/vmi/LibvmiInterface.h index f709f586..820ab8d9 100644 --- a/vmicore/src/lib/vmi/LibvmiInterface.h +++ b/vmicore/src/lib/vmi/LibvmiInterface.h @@ -33,7 +33,9 @@ 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 mapped_regions_t mmapGuest(addr_t baseVA, addr_t dtb, std::size_t numberOfPages) = 0; + + virtual void freeMappedRegions(const mapped_regions_t& mappedRegions) = 0; virtual void write8PA(addr_t physicalAddress, uint8_t value) = 0; @@ -81,7 +83,9 @@ 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; + mapped_regions_t mmapGuest(addr_t baseVA, addr_t dtb, std::size_t numberOfPages) override; + + void freeMappedRegions(const mapped_regions_t& mappedRegions) override; void write8PA(addr_t physicalAddress, uint8_t value) override; diff --git a/vmicore/src/lib/vmi/MemoryMapping.cpp b/vmicore/src/lib/vmi/MemoryMapping.cpp index cf581d9c..992ad3ea 100644 --- a/vmicore/src/lib/vmi/MemoryMapping.cpp +++ b/vmicore/src/lib/vmi/MemoryMapping.cpp @@ -1,57 +1,23 @@ #include "MemoryMapping.h" -#include -#include -#include #include -#include + +// Currently implemented in GNU libstdc++ but missing in LLVM libc++. +#ifdef __cpp_lib_is_layout_compatible +#include + +static_assert(std::is_layout_compatible_v, + "Layout of libvmi mapped_region not compatible with VmiCore MappedRegion"); +#endif 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>()) + MemoryMapping::MemoryMapping(const std::shared_ptr& logging, + std::shared_ptr vmiInterface, + mapped_regions_t mappedRegions) + : logger(logging->newNamedLogger(FILENAME_STEM)), + vmiInterface(std::move(vmiInterface)), + libvmiMappings(mappedRegions) { - // 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() @@ -62,29 +28,21 @@ namespace VmiCore } } - std::weak_ptr> MemoryMapping::getMappedRegions() + std::span MemoryMapping::getMappedRegions() const { - return mappings; - } + if (!isMapped) + { + throw MemoryMappingError("Cannot retrieve mappings for regions that have already been unmapped"); + } - size_t MemoryMapping::getSizeInGuest() - { - return sizeInGuest; + return {std::bit_cast(libvmiMappings.regions), libvmiMappings.size}; } void MemoryMapping::unmap() { - if (!mappings->empty()) + if (isMapped) { - 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) - } - } + vmiInterface->freeMappedRegions(libvmiMappings); isMapped = false; } diff --git a/vmicore/src/lib/vmi/MemoryMapping.h b/vmicore/src/lib/vmi/MemoryMapping.h index 6f4f94ad..68aabd7e 100644 --- a/vmicore/src/lib/vmi/MemoryMapping.h +++ b/vmicore/src/lib/vmi/MemoryMapping.h @@ -2,9 +2,7 @@ #define VMICORE_MEMORYMAPPING_H #include "../io/ILogging.h" -#include -#include -#include +#include "LibvmiInterface.h" #include namespace VmiCore @@ -12,9 +10,9 @@ namespace VmiCore class MemoryMapping final : public IMemoryMapping { public: - MemoryMapping(addr_t guestBaseVA, - const std::vector& accessPointers, - const std::shared_ptr& logging); + MemoryMapping(const std::shared_ptr& logging, + std::shared_ptr vmiInterface, + mapped_regions_t mappedRegions); ~MemoryMapping() override; @@ -26,17 +24,14 @@ namespace VmiCore MemoryMapping& operator=(const MemoryMapping&&) = delete; - std::weak_ptr> getMappedRegions() override; - - size_t getSizeInGuest() override; + [[nodiscard]] std::span getMappedRegions() const override; void unmap() override; private: std::unique_ptr logger; - std::shared_ptr> mappings; - std::size_t sizeInGuest = 0; - std::size_t mappingSize = 0; + std::shared_ptr vmiInterface; + mapped_regions_t libvmiMappings; bool isMapped = true; }; } // VmiCore diff --git a/vmicore/test/CMakeLists.txt b/vmicore/test/CMakeLists.txt index e8563902..5ab81977 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/MappedRegion_UnitTest.cpp lib/vmi/MemoryMapping_UnitTest.cpp lib/vmi/SingleStepSupervisor_UnitTest.cpp) target_compile_options(vmicore-test PRIVATE -Wno-missing-field-initializers) diff --git a/vmicore/test/include/vmicore_test/vmi/mock_MemoryMapping.h b/vmicore/test/include/vmicore_test/vmi/mock_MemoryMapping.h index 829215c7..12c49403 100644 --- a/vmicore/test/include/vmicore_test/vmi/mock_MemoryMapping.h +++ b/vmicore/test/include/vmicore_test/vmi/mock_MemoryMapping.h @@ -9,9 +9,7 @@ namespace VmiCore class MockMemoryMapping : public IMemoryMapping { public: - MOCK_METHOD(std::weak_ptr>, getMappedRegions, (), (override)); - - MOCK_METHOD(std::size_t, getSizeInGuest, (), (override)); + MOCK_METHOD(std::span, getMappedRegions, (), (const override)); MOCK_METHOD(void, unmap, (), (override)); }; diff --git a/vmicore/test/lib/vmi/MappedRegion_UnitTest.cpp b/vmicore/test/lib/vmi/MappedRegion_UnitTest.cpp new file mode 100644 index 00000000..d5dde27b --- /dev/null +++ b/vmicore/test/lib/vmi/MappedRegion_UnitTest.cpp @@ -0,0 +1,14 @@ +#include +#include +#include +#include + +using VmiCore::MappedRegion; + +TEST(MappedRegionTests, structFieldOrdering) +{ + mapped_region_t libvmiRegion{.start_va = 1, .num_pages = 2, .access_ptr = reinterpret_cast(3)}; + MappedRegion vmicoreRegion{1, 2, reinterpret_cast(3)}; + + EXPECT_EQ(std::bit_cast(libvmiRegion), vmicoreRegion); +} diff --git a/vmicore/test/lib/vmi/MemoryMapping_UnitTest.cpp b/vmicore/test/lib/vmi/MemoryMapping_UnitTest.cpp index adaa3e5b..58f58102 100644 --- a/vmicore/test/lib/vmi/MemoryMapping_UnitTest.cpp +++ b/vmicore/test/lib/vmi/MemoryMapping_UnitTest.cpp @@ -6,7 +6,7 @@ using testing::NiceMock; using VmiCore::PagingDefinitions::numberOfPageIndexBits; using VmiCore::PagingDefinitions::pageSizeInBytes; - +/* namespace VmiCore { constexpr uint64_t testBaseVA = 0x123 << numberOfPageIndexBits; @@ -121,3 +121,4 @@ namespace VmiCore EXPECT_EQ(*memoryMapping.getMappedRegions().lock(), expectedMappedRegions); } } +*/ diff --git a/vmicore/test/lib/vmi/ProcessesMemoryState.h b/vmicore/test/lib/vmi/ProcessesMemoryState.h index c3211518..5ee7efe7 100644 --- a/vmicore/test/lib/vmi/ProcessesMemoryState.h +++ b/vmicore/test/lib/vmi/ProcessesMemoryState.h @@ -17,7 +17,6 @@ #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 a794392e..319316db 100644 --- a/vmicore/test/lib/vmi/mock_LibvmiInterface.h +++ b/vmicore/test/lib/vmi/mock_LibvmiInterface.h @@ -27,7 +27,9 @@ 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(mapped_regions_t, mmapGuest, (addr_t, addr_t, std::size_t), (override)); + + MOCK_METHOD(void, freeMappedRegions, (const mapped_regions_t&), (override)); MOCK_METHOD(void, write8PA, (uint64_t, uint8_t), (override));