diff --git a/plugins/inmemoryscanner/Readme.md b/plugins/inmemoryscanner/Readme.md index 4d181a7f..15b0416f 100644 --- a/plugins/inmemoryscanner/Readme.md +++ b/plugins/inmemoryscanner/Readme.md @@ -71,8 +71,6 @@ For example the following diagram shows the memory padding of one VAD region con Shared memory regions that are not the base image of the process are skipped by default in order to reduce scanning time. This behavior can be controlled via the `scan_all_regions` config option. -To further optimize scan duration, memory regions >50MB will be ignored as well. -If desired, it is possible to increase or reduce the threshold via the `maximum_scan_size` config option. ### In Depth Example @@ -89,7 +87,7 @@ Consider the following VAD entry from the vad tree of a process `winlogon.exe` w | `EndAddress` | 1f43b599000 | This region has a size of `0x1f43b599000` - `0x1f43b593000` = `0x6000`. -However the pages from `0x1f43b596000` to `0x1f43b598000` (size `0x2000`) are not mapped into memory. +However, the pages from `0x1f43b596000` to `0x1f43b598000` (size `0x2000`) are not mapped into memory. Therefore, the resulting files will have the size of `0x5000` (mapped size + one zero-page). Note that the start and end address however are the original ones. ```console @@ -130,7 +128,6 @@ For this, add the following parts to the _VMICore_ config and tweak them to your | `directory` | Path to the folder where the compiled _VMICore_ plugins are located. | | `dump_memory` | Boolean. If set to `true` will result in scanned memory being dumped to files. Regions will be dumped to an `inmemorydumps` subfolder in the output directory. | | `ignored_processes` | List with processes that will not be scanned (or dumped) during the final scan. | -| `maximum_scan_size` | Number of bytes for the size of the largest contiguous memory region that will still be scanned. Defaults to `52428800` (50MB). | | `output_path` | Optional output path. If this is a relative path it is interpreted relatively to the _VMICore_ results directory. | | `plugins` | Add your plugin here by the exact name of your shared library (e.g. `libinmemoryscanner.so`). All plugin specific config keys should be added as sub-keys under this name. | | `scan_all_regions` | Optional boolean (defaults to `false`). Indicates whether to eagerly scan all memory regions as opposed to ignoring shared memory. | @@ -147,7 +144,6 @@ plugin_system: signature_file: /usr/local/share/inmemsigs/sigs.sig dump_memory: false scan_all_regions: false - maximum_scan_size: 52428800 output_path: "" ignored_processes: - SearchUI.exe diff --git a/plugins/inmemoryscanner/src/lib/CMakeLists.txt b/plugins/inmemoryscanner/src/lib/CMakeLists.txt index 6767c9e3..390d21c1 100644 --- a/plugins/inmemoryscanner/src/lib/CMakeLists.txt +++ b/plugins/inmemoryscanner/src/lib/CMakeLists.txt @@ -7,7 +7,7 @@ add_library(inmemoryscanner-obj OBJECT InMemory.cpp OutputXML.cpp Scanner.cpp - Yara.cpp) + YaraInterface.cpp) target_compile_features(inmemoryscanner-obj PUBLIC cxx_std_20) set_target_properties(inmemoryscanner-obj PROPERTIES POSITION_INDEPENDENT_CODE TRUE) target_include_directories(inmemoryscanner-obj INTERFACE $) @@ -16,6 +16,11 @@ include(FindPkgConfig) pkg_check_modules(YARA REQUIRED yara>=4) target_link_libraries(inmemoryscanner-obj PUBLIC ${YARA_LINK_LIBRARIES}) + +if (${YARA_VERSION} VERSION_GREATER_EQUAL 4.1) + target_compile_definitions(inmemoryscanner-obj PRIVATE LIBYARA_4_1) +endif () + pkg_check_modules(TCLAP REQUIRED tclap>=1.2) include(FetchContent) diff --git a/plugins/inmemoryscanner/src/lib/Common.h b/plugins/inmemoryscanner/src/lib/Common.h index 76f58c31..240f87a4 100644 --- a/plugins/inmemoryscanner/src/lib/Common.h +++ b/plugins/inmemoryscanner/src/lib/Common.h @@ -5,6 +5,7 @@ #include #include #include +#include #define INMEMORY_LOGGER_NAME std::string("InMemory_").append(FILENAME_STEM) @@ -14,6 +15,8 @@ namespace InMemoryScanner { std::string matchName; int64_t position; + + bool operator==(const Match& rhs) const = default; }; struct Rule @@ -21,5 +24,12 @@ namespace InMemoryScanner std::string ruleName; std::string ruleNamespace; std::vector matches; + + bool operator==(const Rule& rhs) const = default; }; + + inline std::size_t bytesToNumberOfPages(std::size_t size) + { + return (size + VmiCore::PagingDefinitions::pageSizeInBytes - 1) / VmiCore::PagingDefinitions::pageSizeInBytes; + } } diff --git a/plugins/inmemoryscanner/src/lib/Config.cpp b/plugins/inmemoryscanner/src/lib/Config.cpp index 860bcafd..a236566b 100644 --- a/plugins/inmemoryscanner/src/lib/Config.cpp +++ b/plugins/inmemoryscanner/src/lib/Config.cpp @@ -8,8 +8,6 @@ using VmiCore::Plugin::PluginInterface; namespace InMemoryScanner { - constexpr uint64_t defaultMaxScanSize = 52428800; // 50MB - Config::Config(const PluginInterface* pluginInterface) : logger(pluginInterface->newNamedLogger(INMEMORY_LOGGER_NAME)) { @@ -23,7 +21,6 @@ namespace InMemoryScanner outputPath = rootNode["output_path"].as(); dumpMemory = rootNode["dump_memory"].as(false); scanAllRegions = rootNode["scan_all_regions"].as(false); - maximumScanSize = rootNode["maximum_scan_size"].as(defaultMaxScanSize); auto ignoredProcessesVec = rootNode["ignored_processes"].as>(std::vector()); @@ -61,11 +58,6 @@ namespace InMemoryScanner return dumpMemory; } - uint64_t Config::getMaximumScanSize() const - { - return maximumScanSize; - } - void Config::overrideDumpMemoryFlag(bool value) { dumpMemory = value; diff --git a/plugins/inmemoryscanner/src/lib/Config.h b/plugins/inmemoryscanner/src/lib/Config.h index 6cfc63f7..2c4c79c1 100644 --- a/plugins/inmemoryscanner/src/lib/Config.h +++ b/plugins/inmemoryscanner/src/lib/Config.h @@ -32,8 +32,6 @@ namespace InMemoryScanner [[nodiscard]] virtual bool isDumpingMemoryActivated() const = 0; - [[nodiscard]] virtual uint64_t getMaximumScanSize() const = 0; - virtual void overrideDumpMemoryFlag(bool value) = 0; protected: @@ -59,8 +57,6 @@ namespace InMemoryScanner [[nodiscard]] bool isDumpingMemoryActivated() const override; - [[nodiscard]] uint64_t getMaximumScanSize() const override; - void overrideDumpMemoryFlag(bool value) override; private: @@ -70,6 +66,5 @@ namespace InMemoryScanner std::set ignoredProcesses; bool dumpMemory{}; bool scanAllRegions{}; - uint64_t maximumScanSize{}; }; } diff --git a/plugins/inmemoryscanner/src/lib/IYaraInterface.h b/plugins/inmemoryscanner/src/lib/IYaraInterface.h new file mode 100644 index 00000000..c3815e37 --- /dev/null +++ b/plugins/inmemoryscanner/src/lib/IYaraInterface.h @@ -0,0 +1,30 @@ +#ifndef INMEMORYSCANNER_IYARAINTERFACE_H +#define INMEMORYSCANNER_IYARAINTERFACE_H + +#include "Common.h" +#include +#include +#include +#include + +namespace InMemoryScanner +{ + class YaraException : public std::runtime_error + { + using std::runtime_error::runtime_error; + }; + + class IYaraInterface + { + public: + virtual ~IYaraInterface() = default; + + virtual std::vector scanMemory(VmiCore::addr_t regionBase, + std::span mappedRegions) = 0; + + protected: + IYaraInterface() = default; + }; +} + +#endif // INMEMORYSCANNER_IYARAINTERFACE_H diff --git a/plugins/inmemoryscanner/src/lib/InMemory.cpp b/plugins/inmemoryscanner/src/lib/InMemory.cpp index 0ccb9088..2587904f 100644 --- a/plugins/inmemoryscanner/src/lib/InMemory.cpp +++ b/plugins/inmemoryscanner/src/lib/InMemory.cpp @@ -11,7 +11,7 @@ #include "Config.h" #include "Dumping.h" #include "Filenames.h" -#include "Yara.h" +#include "YaraInterface.h" #include #include #include @@ -44,7 +44,7 @@ namespace InMemoryScanner { configuration->overrideDumpMemoryFlag(dumpMemoryArgument.getValue()); } - auto yara = std::make_unique(configuration->getSignatureFile()); + auto yara = std::make_unique(configuration->getSignatureFile()); auto dumping = std::make_unique(pluginInterface, configuration); scanner = std::make_unique(pluginInterface, configuration, std::move(yara), std::move(dumping)); } diff --git a/plugins/inmemoryscanner/src/lib/Scanner.cpp b/plugins/inmemoryscanner/src/lib/Scanner.cpp index 5984069e..9c11a7a1 100644 --- a/plugins/inmemoryscanner/src/lib/Scanner.cpp +++ b/plugins/inmemoryscanner/src/lib/Scanner.cpp @@ -1,26 +1,29 @@ #include "Scanner.h" #include "Common.h" #include "Filenames.h" +#include #include #include -#include #include +#include using VmiCore::ActiveProcessInformation; using VmiCore::addr_t; +using VmiCore::MappedRegion; using VmiCore::MemoryRegion; using VmiCore::pid_t; +using VmiCore::PagingDefinitions::pageSizeInBytes; using VmiCore::Plugin::PluginInterface; namespace InMemoryScanner { Scanner::Scanner(PluginInterface* pluginInterface, std::shared_ptr configuration, - std::unique_ptr yaraEngine, + std::unique_ptr yaraInterface, std::unique_ptr dumping) : pluginInterface(pluginInterface), configuration(std::move(configuration)), - yaraEngine(std::move(yaraEngine)), + yaraInterface(std::move(yaraInterface)), dumping(std::move(dumping)), logger(pluginInterface->newNamedLogger(INMEMORY_LOGGER_NAME)), inMemResultsLogger(pluginInterface->newNamedLogger(INMEMORY_LOGGER_NAME)) @@ -45,76 +48,104 @@ 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; } - void - Scanner::scanMemoryRegion(pid_t pid, const std::string& processName, const MemoryRegion& memoryRegionDescriptor) + std::vector Scanner::constructPaddedMemoryRegion(std::span regions) + { + std::vector result; + + if (regions.empty()) + { + return result; + } + + std::size_t regionSize = 0; + for (const auto& region : regions) + { + regionSize += region.asSpan().size(); + regionSize += pageSizeInBytes; + } + // last region should not have succeeding padding page + regionSize -= pageSizeInBytes; + + result.reserve(regionSize); + // copy first region + auto frontRegionSpan = regions.front().asSpan(); + std::ranges::copy(frontRegionSpan.begin(), frontRegionSpan.end(), std::back_inserter(result)); + + // copy the rest of the regions with a padding page in between each chunk + for (std::size_t i = 1; i < regions.size(); i++) + { + result.insert(result.end(), pageSizeInBytes, 0); + auto regionSpan = regions[i].asSpan(); + std::ranges::copy(regionSpan.begin(), regionSpan.end(), std::back_inserter(result)); + } + + return result; + } + + void Scanner::scanMemoryRegion(pid_t pid, + addr_t dtb, + const std::string& processName, + const MemoryRegion& memoryRegionDescriptor) { logger->info("Scanning Memory region", {{"VA", fmt::format("{:x}", memoryRegionDescriptor.base)}, {"Size", memoryRegionDescriptor.size}, {"Module", memoryRegionDescriptor.moduleName}}); - if (shouldRegionBeScanned(memoryRegionDescriptor)) + if (!shouldRegionBeScanned(memoryRegionDescriptor)) { - auto scanSize = memoryRegionDescriptor.size; - auto maximumScanSize = configuration->getMaximumScanSize(); - if (scanSize > maximumScanSize) - { - logger->info("Memory region is too big, reducing to maximum scan size", - {{"Size", scanSize}, {"MaximumScanSize", maximumScanSize}}); - scanSize = maximumScanSize; - } + return; + } - logger->debug("Start getProcessMemoryRegion", {{"Size", scanSize}}); + auto memoryMapping = pluginInterface->mapProcessMemoryRegion( + memoryRegionDescriptor.base, dtb, bytesToNumberOfPages(memoryRegionDescriptor.size)); + auto mappedRegions = memoryMapping->getMappedRegions(); - auto memoryRegion = pluginInterface->readProcessMemoryRegion(pid, memoryRegionDescriptor.base, scanSize); + if (mappedRegions.empty()) + { + logger->debug("Extracted memory region has size 0, skipping"); + return; + } - logger->debug("End getProcessMemoryRegion", {{"Size", scanSize}}); - if (memoryRegion->empty()) - { - logger->debug("Extracted memory region has size 0, skipping"); - } - else - { - if (configuration->isDumpingMemoryActivated()) - { - logger->debug("Start dumpVadRegionToFile", {{"Size", memoryRegion->size()}}); - dumping->dumpMemoryRegion(processName, pid, memoryRegionDescriptor, *memoryRegion); - logger->debug("End dumpVadRegionToFile"); - } + if (configuration->isDumpingMemoryActivated()) + { + logger->debug("Start dumpVadRegionToFile", {{"Size", memoryRegionDescriptor.size}}); - logger->debug("Start scanMemory", {{"Size", memoryRegion->size()}}); + 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 = yaraEngine->scanMemory(*memoryRegion); - 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); } } @@ -141,8 +172,10 @@ namespace InMemoryScanner { try { - scanMemoryRegion( - processInformation->pid, *processInformation->fullName, memoryRegionDescriptor); + scanMemoryRegion(processInformation->pid, + processInformation->processUserDtb, + *processInformation->fullName, + memoryRegionDescriptor); } catch (const std::exception& exc) { diff --git a/plugins/inmemoryscanner/src/lib/Scanner.h b/plugins/inmemoryscanner/src/lib/Scanner.h index 37db1ac8..57a6af70 100644 --- a/plugins/inmemoryscanner/src/lib/Scanner.h +++ b/plugins/inmemoryscanner/src/lib/Scanner.h @@ -2,11 +2,12 @@ #include "Config.h" #include "Dumping.h" +#include "IYaraInterface.h" #include "OutputXML.h" #include "Semaphore.h" -#include "YaraInterface.h" #include #include +#include #include #include // NOLINT(modernize-deprecated-headers) @@ -17,7 +18,7 @@ namespace InMemoryScanner public: Scanner(VmiCore::Plugin::PluginInterface* pluginInterface, std::shared_ptr configuration, - std::unique_ptr yaraEngine, + std::unique_ptr yaraInterface, std::unique_ptr dumping); [[nodiscard]] static std::unique_ptr getFilenameFromPath(const std::string& path); @@ -31,7 +32,7 @@ namespace InMemoryScanner private: VmiCore::Plugin::PluginInterface* pluginInterface; std::shared_ptr configuration; - std::unique_ptr yaraEngine; + std::unique_ptr yaraInterface; OutputXML outputXml{}; std::unique_ptr dumping; std::unique_ptr logger; @@ -41,7 +42,10 @@ namespace InMemoryScanner [[nodiscard]] bool shouldRegionBeScanned(const VmiCore::MemoryRegion& memoryRegionDescriptor); + static std::vector constructPaddedMemoryRegion(std::span 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 deleted file mode 100644 index 86c49307..00000000 --- a/plugins/inmemoryscanner/src/lib/Yara.cpp +++ /dev/null @@ -1,89 +0,0 @@ -#include "Yara.h" - -namespace InMemoryScanner -{ - Yara::Yara(const std::string& rulesFile) - { - int err = 0; - - err = yr_initialize(); - if (err != ERROR_SUCCESS) - { - throw YaraException("Cannot initialize Yara. Error code: " + std::to_string(err)); - } - - err = yr_rules_load(rulesFile.c_str(), &rules); - if (err != ERROR_SUCCESS) - { - throw YaraException("Cannot load rules. Error code: " + std::to_string(err)); - } - } - - Yara::~Yara() - { - yr_rules_destroy(rules); - yr_finalize(); - } - - std::unique_ptr> Yara::scanMemory(std::vector& buffer) - { - 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) - { - throw YaraException("Error scanning memory. Error code: " + std::to_string(err)); - } - - return results; - } - - int Yara::yaraCallback(YR_SCAN_CONTEXT* context, int message, void* message_data, void* user_data) - { - int ret = 0; - switch (message) - { - case CALLBACK_MSG_RULE_MATCHING: - ret = handleRuleMatch( - context, static_cast(message_data), static_cast*>(user_data)); - break; - case CALLBACK_MSG_RULE_NOT_MATCHING: - [[fallthrough]]; - case CALLBACK_MSG_SCAN_FINISHED: - ret = CALLBACK_CONTINUE; - break; - default: - ret = CALLBACK_ERROR; - break; - } - - return ret; - } - - int Yara::handleRuleMatch(YR_SCAN_CONTEXT* context, YR_RULE* rule, std::vector* results) - { - YR_STRING* string = nullptr; - YR_MATCH* match = nullptr; - - Rule tmpRule; - tmpRule.ruleName = rule->identifier; - tmpRule.ruleNamespace = rule->ns->name; - - yr_rule_strings_foreach(rule, string) - { - yr_string_matches_foreach(context, string, match) // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) - { - Match tmpMatch; - tmpMatch.matchName = string->identifier; - tmpMatch.position = match->offset; - - tmpRule.matches.push_back(tmpMatch); - } - } - - results->push_back(tmpRule); - - return CALLBACK_CONTINUE; - } -} diff --git a/plugins/inmemoryscanner/src/lib/Yara.h b/plugins/inmemoryscanner/src/lib/Yara.h deleted file mode 100644 index 2df566ce..00000000 --- a/plugins/inmemoryscanner/src/lib/Yara.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#include "YaraInterface.h" -#include - -namespace InMemoryScanner -{ - class Yara : public YaraInterface - { - public: - explicit Yara(const std::string& rulesFile); - - ~Yara() override; - - std::unique_ptr> scanMemory(std::vector& buffer) override; - - private: - YR_RULES* rules = nullptr; - - [[nodiscard]] static int - yaraCallback(YR_SCAN_CONTEXT* context, int message, void* message_data, void* user_data); - - [[nodiscard]] static int handleRuleMatch(YR_SCAN_CONTEXT* context, YR_RULE* rule, std::vector* results); - }; -} diff --git a/plugins/inmemoryscanner/src/lib/YaraInterface.cpp b/plugins/inmemoryscanner/src/lib/YaraInterface.cpp new file mode 100644 index 00000000..1c849b37 --- /dev/null +++ b/plugins/inmemoryscanner/src/lib/YaraInterface.cpp @@ -0,0 +1,146 @@ +#include "YaraInterface.h" +#include + +using VmiCore::addr_t; +using VmiCore::MappedRegion; +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 +{ + YaraInterface::YaraInterface(const std::string& rulesFile) + { + auto err = yr_initialize(); + if (err != ERROR_SUCCESS) + { + throw YaraException(fmt::format("Cannot initialize Yara. Error code: {}", err)); + } + + err = yr_rules_load(rulesFile.c_str(), &rules); + if (err != ERROR_SUCCESS) + { + throw YaraException(fmt::format("Cannot load rules. Error code: {}", err)); + } + } + + YaraInterface::~YaraInterface() + { + if (rules) + { + yr_rules_destroy(rules); + yr_finalize(); + } + } + + std::vector YaraInterface::scanMemory(addr_t regionBase, std::span mappedRegions) + { + std::vector results; + + YaraIteratorContext iteratorContext{}; + iteratorContext.blocks.reserve(mappedRegions.size()); + for (const auto& mappedRegion : mappedRegions) + { + iteratorContext.blocks.emplace_back(mappedRegion.num_pages * pageSizeInBytes, + mappedRegion.guestBaseVA - regionBase, + mappedRegion.mappingBase, + &fetch_block_data); + } +#ifdef LIBYARA_4_1 + 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 = &iteratorContext, .first = &get_first_block, .next = &get_next_block}; +#endif + + if (auto err = yr_rules_scan_mem_blocks(rules, &iterator, 0, yaraCallback, &results, 0); err != ERROR_SUCCESS) + { + throw YaraException(fmt::format("Error scanning memory. Error code: {}", err)); + } + + return results; + } + + int YaraInterface::yaraCallback(YR_SCAN_CONTEXT* context, int message, void* message_data, void* user_data) + { + int ret = 0; + switch (message) + { + case CALLBACK_MSG_RULE_MATCHING: + ret = handleRuleMatch( + context, static_cast(message_data), static_cast*>(user_data)); + break; + case CALLBACK_MSG_RULE_NOT_MATCHING: + [[fallthrough]]; + case CALLBACK_MSG_SCAN_FINISHED: + ret = CALLBACK_CONTINUE; + break; + default: + ret = CALLBACK_ERROR; + break; + } + + return ret; + } + + int YaraInterface::handleRuleMatch(YR_SCAN_CONTEXT* context, YR_RULE* rule, std::vector* results) + { + YR_STRING* string = nullptr; + YR_MATCH* match = nullptr; + + Rule tmpRule; + tmpRule.ruleName = rule->identifier; + tmpRule.ruleNamespace = rule->ns->name; + + yr_rule_strings_foreach(rule, string) + { + yr_string_matches_foreach(context, string, match) // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + { + Match tmpMatch; + tmpMatch.matchName = string->identifier; + tmpMatch.position = match->base + match->offset; + + tmpRule.matches.push_back(tmpMatch); + } + } + + results->push_back(tmpRule); + + return CALLBACK_CONTINUE; + } +} diff --git a/plugins/inmemoryscanner/src/lib/YaraInterface.h b/plugins/inmemoryscanner/src/lib/YaraInterface.h index ad56c641..923d7fb3 100644 --- a/plugins/inmemoryscanner/src/lib/YaraInterface.h +++ b/plugins/inmemoryscanner/src/lib/YaraInterface.h @@ -1,24 +1,49 @@ #pragma once #include "Common.h" -#include +#include "IYaraInterface.h" +#include +#include namespace InMemoryScanner { - class YaraException : public std::runtime_error + class YaraInterface : public IYaraInterface { public: - explicit YaraException(const std::string& Message) : std::runtime_error(Message.c_str()){}; - }; + explicit YaraInterface(const std::string& rulesFile); - class YaraInterface - { - public: - virtual ~YaraInterface() = default; + 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::vector scanMemory(VmiCore::addr_t regionBase, + std::span mappedRegions) override; + + private: + YR_RULES* rules = nullptr; - [[nodiscard]] virtual std::unique_ptr> scanMemory(std::vector& buffer) = 0; + static int yaraCallback(YR_SCAN_CONTEXT* context, int message, void* message_data, void* user_data); - protected: - YaraInterface() = default; + static int handleRuleMatch(YR_SCAN_CONTEXT* context, YR_RULE* rule, std::vector* results); }; } diff --git a/plugins/inmemoryscanner/test/CMakeLists.txt b/plugins/inmemoryscanner/test/CMakeLists.txt index 86e67b5f..9ab81ba0 100644 --- a/plugins/inmemoryscanner/test/CMakeLists.txt +++ b/plugins/inmemoryscanner/test/CMakeLists.txt @@ -1,6 +1,7 @@ add_executable(inmemoryscanner-test - FakeYara.cpp - Scanner_unittest.cpp) + FakeYaraInterface.cpp + Scanner_unittest.cpp + YaraInterface_unittest.cpp) target_link_libraries(inmemoryscanner-test inmemoryscanner-obj pthread) # Setup bundled google test framework diff --git a/plugins/inmemoryscanner/test/FakeYara.h b/plugins/inmemoryscanner/test/FakeYara.h deleted file mode 100644 index 7996caa6..00000000 --- a/plugins/inmemoryscanner/test/FakeYara.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include - -namespace InMemoryScanner -{ - class FakeYara : public YaraInterface - { - public: - std::unique_ptr> scanMemory(std::vector& buffer) override; - - bool max_threads_exceeded = false; - - private: - int concurrentThreads = 0; - }; -} diff --git a/plugins/inmemoryscanner/test/FakeYara.cpp b/plugins/inmemoryscanner/test/FakeYaraInterface.cpp similarity index 58% rename from plugins/inmemoryscanner/test/FakeYara.cpp rename to plugins/inmemoryscanner/test/FakeYaraInterface.cpp index badee8b1..1b0606e3 100644 --- a/plugins/inmemoryscanner/test/FakeYara.cpp +++ b/plugins/inmemoryscanner/test/FakeYaraInterface.cpp @@ -1,10 +1,12 @@ -#include "FakeYara.h" +#include "FakeYaraInterface.h" #include #include // NOLINT(modernize-deprecated-headers) namespace InMemoryScanner { - std::unique_ptr> FakeYara::scanMemory([[maybe_unused]] std::vector& buffer) + std::vector + FakeYaraInterface::scanMemory([[maybe_unused]] VmiCore::addr_t regionBase, + [[maybe_unused]] std::span mappedRegions) { concurrentThreads++; if (concurrentThreads > YR_MAX_THREADS) @@ -13,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 new file mode 100644 index 00000000..62d69bc9 --- /dev/null +++ b/plugins/inmemoryscanner/test/FakeYaraInterface.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +namespace InMemoryScanner +{ + class FakeYaraInterface : public IYaraInterface + { + public: + std::vector scanMemory(VmiCore::addr_t regionBase, + std::span mappedRegions) override; + + bool max_threads_exceeded = false; + + private: + int concurrentThreads = 0; + }; +} diff --git a/plugins/inmemoryscanner/test/Scanner_unittest.cpp b/plugins/inmemoryscanner/test/Scanner_unittest.cpp index 5d7e78c5..4c63d56b 100644 --- a/plugins/inmemoryscanner/test/Scanner_unittest.cpp +++ b/plugins/inmemoryscanner/test/Scanner_unittest.cpp @@ -1,15 +1,17 @@ -#include "FakeYara.h" +#include "FakeYaraInterface.h" #include "mock_Config.h" #include "mock_Dumping.h" -#include "mock_Yara.h" +#include "mock_YaraInterface.h" #include #include #include #include +#include #include #include #include #include +#include using testing::_; using testing::An; @@ -20,10 +22,14 @@ using testing::NiceMock; 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; using VmiCore::MockPageProtection; +using VmiCore::pid_t; +using VmiCore::PagingDefinitions::pageSizeInBytes; using VmiCore::Plugin::MockPluginInterface; namespace InMemoryScanner @@ -31,9 +37,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(); @@ -46,8 +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::vector regionMappings = std::vector(1, MappedRegion(startAddress, testPageContent)); std::unique_ptr>> runningProcesses; @@ -55,23 +64,16 @@ namespace InMemoryScanner { ON_CALL(*pluginInterface, newNamedLogger(_)) .WillByDefault([]() { return std::make_unique>(); }); - ON_CALL(*pluginInterface, getResultsDir()).WillByDefault([]() { return std::make_unique(); }); - ON_CALL(*configuration, getMaximumScanSize()).WillByDefault(Return(maxScanSize)); - // make sure that we return a non-empty memory region or else we might skip important parts - ON_CALL(*pluginInterface, readProcessMemoryRegion(_, _, _)) - .WillByDefault([]() { return std::make_unique>(6, 9); }); - ON_CALL(*configuration, getOutputPath()) .WillByDefault([inMemoryDumpsPath = inMemoryDumpsPath]() { return inMemoryDumpsPath; }); - ON_CALL(*configuration, getMaximumScanSize()).WillByDefault(Return(maxScanSize)); runningProcesses = std::make_unique>>(); auto m1 = std::make_unique(); systemMemoryRegionExtractorRaw = m1.get(); runningProcesses->push_back(std::make_shared( ActiveProcessInformation{0, - 0, - 0, + testDtb, + testDtb, testPid, 0, "System.exe", @@ -83,8 +85,8 @@ namespace InMemoryScanner sharedBaseImageMemoryRegionExtractorRaw = m2.get(); runningProcesses->push_back(std::make_shared( ActiveProcessInformation{0, - 0, - 0, + dtbWithSharedBaseImageRegion, + dtbWithSharedBaseImageRegion, processIdWithSharedBaseImageRegion, 0, "SomeProcess.exe", @@ -92,7 +94,10 @@ namespace InMemoryScanner std::make_unique(""), std::move(m2), false})); - }; + + createMemoryMapping(testDtb, startAddress, bytesToNumberOfPages(size), regionMappings); + createMemoryMapping(dtbWithSharedBaseImageRegion, startAddress, bytesToNumberOfPages(size), regionMappings); + } std::shared_ptr getProcessInfoFromRunningProcesses(pid_t pid) { @@ -100,7 +105,20 @@ namespace InMemoryScanner runningProcesses->cend(), [pid = pid](const std::shared_ptr& a) { return a->pid == pid; }); - }; + } + + void + createMemoryMapping(addr_t dtb, addr_t baseVA, std::size_t numberOfPages, std::span mappedRegions) + { + ON_CALL(*pluginInterface, mapProcessMemoryRegion(baseVA, dtb, numberOfPages)) + .WillByDefault( + [mappedRegions = mappedRegions]() + { + auto mapping = std::make_unique(); + ON_CALL(*mapping, getMappedRegions()).WillByDefault(Return(mappedRegions)); + return mapping; + }); + } }; class ScannerTestFixtureDumpingDisabled : public ScannerTestBaseFixture @@ -115,7 +133,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::vector{})); + scanner.emplace(pluginInterface.get(), configuration, std::move(yara), std::move(dumping)); }; }; @@ -152,8 +172,8 @@ namespace InMemoryScanner return memoryRegions; }); auto dumping = std::make_unique(pluginInterface.get(), configuration); - auto yara = std::make_unique>(); - ON_CALL(*yara, scanMemory(_)).WillByDefault([]() { return std::make_unique>(); }); + auto yara = std::make_unique>(); + ON_CALL(*yara, scanMemory(_, _)).WillByDefault(Return(std::vector{})); scanner.emplace(pluginInterface.get(), configuration, std::move(yara), std::move(dumping)); }; @@ -169,21 +189,17 @@ namespace InMemoryScanner } }; - TEST_F(ScannerTestFixtureDumpingDisabled, scanProcess_largeMemoryRegion_trimToMaxScanSize) + std::vector constructPaddedRegion(const std::initializer_list>& list) { - ON_CALL(*systemMemoryRegionExtractorRaw, extractAllMemoryRegions()) - .WillByDefault( - [startAddress = startAddress, maxScanSize = maxScanSize]() - { - auto memoryRegions = std::make_unique>(); - memoryRegions->emplace_back( - startAddress, maxScanSize + 1, "", std::make_unique(), false, false, false); - return memoryRegions; - }); + std::vector paddedRegion{}; + paddedRegion.reserve(list.size() * pageSizeInBytes); - EXPECT_CALL(*pluginInterface, readProcessMemoryRegion(testPid, startAddress, maxScanSize)) - .WillOnce(Return(ByMove(std::make_unique>()))); - EXPECT_NO_THROW(scanner->scanProcess(getProcessInfoFromRunningProcesses(testPid))); + for (const auto& el : list) + { + std::copy(el.begin(), el.end(), std::back_inserter(paddedRegion)); + } + + return paddedRegion; } TEST_F(ScannerTestFixtureDumpingDisabled, scanProcess_smallMemoryRegion_originalReadMemoryRegionSize) @@ -197,10 +213,18 @@ namespace InMemoryScanner startAddress, size, "", std::make_unique(), false, false, false); return memoryRegions; }); + auto process = getProcessInfoFromRunningProcesses(testPid); - EXPECT_CALL(*pluginInterface, readProcessMemoryRegion(testPid, startAddress, size)) - .WillOnce(Return(ByMove(std::make_unique>()))); - EXPECT_NO_THROW(scanner->scanProcess(getProcessInfoFromRunningProcesses(testPid))); + EXPECT_CALL(*pluginInterface, + mapProcessMemoryRegion(startAddress, process->processUserDtb, bytesToNumberOfPages(size))) + .WillOnce( + [regionMappings = regionMappings]() + { + auto mapping = std::make_unique(); + EXPECT_CALL(*mapping, getMappedRegions()).WillOnce(Return(regionMappings)); + return mapping; + }); + EXPECT_NO_THROW(scanner->scanProcess(process)); } TEST_F(ScannerTestFixtureDumpingDisabled, scanProcess_disabledDumping_dumpingNotCalled) @@ -214,10 +238,8 @@ namespace InMemoryScanner startAddress, size, "", std::make_unique(), false, false, false); return memoryRegions; }); - EXPECT_CALL(*pluginInterface, readProcessMemoryRegion(testPid, startAddress, size)) - .WillOnce(Return(ByMove(std::make_unique>()))); - EXPECT_CALL(*dumpingRawPointer, dumpMemoryRegion(_, _, _, _)).Times(0); + EXPECT_CALL(*dumpingRawPointer, dumpMemoryRegion(_, _, _, _)).Times(0); EXPECT_NO_THROW(scanner->scanProcess(getProcessInfoFromRunningProcesses(testPid))); } @@ -225,13 +247,14 @@ namespace InMemoryScanner { std::string fullProcessName = "abcdefghijklmnopqrstuvwxyz!1!"; std::string trimmedProcessName = "abcdefghijklmn"; - auto pid = 123; + pid_t pid = 123; + addr_t dtb = 0x4444; auto memoryRegionExtractor = std::make_unique(); auto* memoryRegionExtractorRaw = memoryRegionExtractor.get(); auto processWithLongName = std::make_shared( ActiveProcessInformation{0, - 0, - 0, + dtb, + dtb, pid, 0, trimmedProcessName, @@ -256,6 +279,7 @@ namespace InMemoryScanner startAddress + size, uidRegEx); auto expectedFileNameWithPathRegEx = "^" + (dumpedRegionsPath / expectedFileNameRegEx).string() + "$"; + createMemoryMapping(dtb, startAddress, bytesToNumberOfPages(size), regionMappings); EXPECT_CALL(*pluginInterface, writeToFile(ContainsRegex(expectedFileNameWithPathRegEx), An&>())); @@ -283,7 +307,7 @@ namespace InMemoryScanner TEST_F(ScannerTestFixtureDumpingDisabled, scanAllProcesses_MoreScanningThreadThanAllowedByYara_ThreadLimitNotExceeded) { - auto yaraFake = std::make_unique(); + auto yaraFake = std::make_unique(); auto* yaraFakeRaw = yaraFake.get(); scanner.emplace( pluginInterface.get(), configuration, std::move(yaraFake), std::make_unique>()); @@ -291,8 +315,8 @@ namespace InMemoryScanner auto* memoryRegionExtractorRaw = memoryRegionExtractor.get(); auto processInfo = std::make_shared( ActiveProcessInformation{0, - 0, - 0, + testDtb, + testDtb, testPid, 0, "System.exe", @@ -327,13 +351,14 @@ namespace InMemoryScanner { std::string fullProcessName = "abcdefghijklmnop"; std::string trimmedProcessName = "abcdefghijklmn"; - auto pid = 123; + pid_t pid = 312; + addr_t dtb = 0x5555; auto memoryRegionExtractor = std::make_unique(); auto* memoryRegionExtractorRaw = memoryRegionExtractor.get(); auto processInfo = std::make_shared( ActiveProcessInformation{0, - 0, - 0, + dtb, + dtb, pid, 0, "", @@ -369,6 +394,7 @@ namespace InMemoryScanner return memoryRegions; }); + createMemoryMapping(dtb, startAddress, bytesToNumberOfPages(size), regionMappings); EXPECT_CALL(*pluginInterface, writeToFile(_, An())).Times(AnyNumber()); EXPECT_CALL(*pluginInterface, writeToFile(expectedFileName.string(), expectedFileContent + "\n")).Times(1); @@ -376,4 +402,45 @@ namespace InMemoryScanner ASSERT_NO_THROW(scanner->scanAllProcesses()); ASSERT_NO_THROW(scanner->saveOutput()); } + + TEST_F(ScannerTestFixtureDumpingEnabled, scanProcess_complexMemoryRegion_regionWithCorrectPaddingDumped) + { + pid_t pid = 333; + addr_t dtb = 0x4554; + auto memoryRegionExtractor = std::make_unique(); + auto* memoryRegionExtractorRaw = memoryRegionExtractor.get(); + auto processInfo = + std::make_shared(ActiveProcessInformation{0, + dtb, + dtb, + pid, + 0, + "", + std::make_unique(""), + std::make_unique(""), + std::move(memoryRegionExtractor), + false}); + // Layout of complex region: 1 page, followed by 2 unmapped pages, followed by 2 pages + std::size_t complexRegionSize = 5 * pageSizeInBytes; + auto complexRegionDescriptor = MemoryRegion( + startAddress, complexRegionSize, "", std::make_unique(), false, false, false); + ON_CALL(*memoryRegionExtractorRaw, extractAllMemoryRegions()) + .WillByDefault( + [&memoryRegionDescriptor = complexRegionDescriptor]() + { + auto memoryRegions = std::make_unique>(); + memoryRegions->push_back(std::move(memoryRegionDescriptor)); + + return memoryRegions; + }); + auto twoPageRegionContent = std::vector(2 * pageSizeInBytes, 0xCA); + 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}); + + EXPECT_CALL(*pluginInterface, writeToFile(_, expectedPaddedRegion)).Times(1); + ASSERT_NO_THROW(scanner->scanProcess(processInfo)); + } } diff --git a/plugins/inmemoryscanner/test/YaraInterface_unittest.cpp b/plugins/inmemoryscanner/test/YaraInterface_unittest.cpp new file mode 100644 index 00000000..f9539eb1 --- /dev/null +++ b/plugins/inmemoryscanner/test/YaraInterface_unittest.cpp @@ -0,0 +1,186 @@ +#include +#include +#include +#include +#include + +using testing::UnorderedElementsAre; +using VmiCore::PagingDefinitions::pageSizeInBytes; + +namespace InMemoryScanner +{ + std::vector constructPageWithContent(const std::string& string, bool insertAtBack = false) + { + std::vector result(pageSizeInBytes, 0); + auto insertPosition = insertAtBack ? result.end() - string.size() : result.begin(); + std::copy(string.begin(), string.end(), insertPosition); + return result; + } + + std::string compileYaraRules(std::string_view rules) + { + auto fileName = fmt::format("{}.sigs", testing::UnitTest::GetInstance()->current_test_info()->name()); + + if (yr_initialize() != ERROR_SUCCESS) + { + throw std::runtime_error("Unable to initialize libyara"); + } + YR_COMPILER* compiler = nullptr; + if (yr_compiler_create(&compiler) != ERROR_SUCCESS) + { + throw std::runtime_error("Unable to create yara compiler"); + } + if (auto syntax_errors = yr_compiler_add_string(compiler, rules.data(), nullptr) > 0) + { + throw std::runtime_error( + fmt::format("Compiled rules are faulty. Number of syntax errors: {}", syntax_errors)); + } + YR_RULES* compiled_rules = nullptr; + if (yr_compiler_get_rules(compiler, &compiled_rules)) + { + throw std::runtime_error("Unable to obtain rules from compiler"); + } + if (yr_rules_save(compiled_rules, fileName.c_str()) != ERROR_SUCCESS) + { + throw std::runtime_error("Unable to save rules to file"); + } + yr_compiler_destroy(compiler); + yr_finalize(); + + return fileName; + } + + TEST(YaraTest, scanMemory_MissingMemoryRegion_NoMatch) + { + auto* rules = R"( + rule testRule + { + strings: + $test = "ABCD" + $test2 = "DCBA" + + condition: + all of them + } + )"; + auto yaraInterface = YaraInterface(compileYaraRules(rules)); + auto subRegion1 = constructPageWithContent("ABCD"); + std::vector memoryRegions{{0x0, subRegion1}}; + + auto matches = yaraInterface.scanMemory(memoryRegions.front().guestBaseVA, memoryRegions); + + EXPECT_EQ(matches.size(), 0); + } + + TEST(YaraTest, scanMemory_StringSplitInHalfThroughSubRegionBoundary_NoMatch) + { + auto* rules = R"( + rule testRule + { + strings: + $test = "CDDC" + + condition: + all of them + } + )"; + auto yaraInterface = YaraInterface(compileYaraRules(rules)); + auto subRegion1 = constructPageWithContent("ABCD", true); + auto subRegion2 = constructPageWithContent("DCBA", false); + std::vector memoryRegions{{0x0, subRegion1}, {pageSizeInBytes, subRegion2}}; + + auto matches = yaraInterface.scanMemory(memoryRegions.front().guestBaseVA, memoryRegions); + + EXPECT_EQ(matches.size(), 0); + } + + TEST(YaraTest, scanMemory_AllOfConditionStringsInDifferentRegions_NoMatch) + { + auto* rules = R"( + rule testRule + { + strings: + $test = "ABCD" + $test2 = "DCBA" + + condition: + all of them + } + )"; + auto yaraInterface = YaraInterface(compileYaraRules(rules)); + auto subRegion1 = constructPageWithContent("ABCD"); + auto subRegion2 = constructPageWithContent("DCBA"); + std::vector memoryRegion1{{0x0, subRegion1}}; + std::vector memoryRegion2{{4 * pageSizeInBytes, subRegion2}}; + + 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); + } + + TEST(YaraTest, scanMemory_AllOfConditionStringsInDifferentSubRegions_Matches) + { + auto* rules = R"( + rule testRule + { + strings: + $test = "ABCD" + $test2 = "DCBA" + + condition: + all of them + } + )"; + auto yaraInterface = YaraInterface(compileYaraRules(rules)); + auto subRegion1 = constructPageWithContent("ABCD"); + auto subRegion2 = constructPageWithContent("DCBA"); + std::vector memoryRegions{{0x0, subRegion1}, {4 * pageSizeInBytes, subRegion2}}; + Rule expectedMatch{"testRule", "default", {{"$test", 0x0}, {"$test2", 4 * pageSizeInBytes}}}; + + auto matches = yaraInterface.scanMemory(memoryRegions.front().guestBaseVA, memoryRegions); + + ASSERT_EQ(matches.size(), 1); + EXPECT_THAT(matches, UnorderedElementsAre(expectedMatch)); + } + + TEST(YaraTest, scanMemory_DifferentRulesInRegions_BothMatch) + { + auto* rules = R"( + rule testRule + { + strings: + $test = "ABCD" + $test2 = "DCBA" + + condition: + all of them + } + + rule testRule2 + { + strings: + $test = "E" + $test2 = "F" + + condition: + all of them + } + )"; + auto yaraInterface = YaraInterface(compileYaraRules(rules)); + auto subRegion1 = constructPageWithContent("ABCD"); + auto subRegion2 = constructPageWithContent("DCBA"); + auto subRegion3 = constructPageWithContent("EFGH"); + std::vector memoryRegions{ + {0x0, subRegion1}, {4 * pageSizeInBytes, subRegion2}, {8 * pageSizeInBytes, subRegion3}}; + Rule expectedMatch1{"testRule", "default", {{"$test", 0x0}, {"$test2", 4 * pageSizeInBytes}}}; + Rule expectedMatch2{ + "testRule2", "default", {{"$test", 8 * pageSizeInBytes}, {"$test2", 8 * pageSizeInBytes + 1}}}; + + auto matches = yaraInterface.scanMemory(memoryRegions.front().guestBaseVA, memoryRegions); + + ASSERT_EQ(matches.size(), 2); + EXPECT_THAT(matches, UnorderedElementsAre(expectedMatch1, expectedMatch2)); + } +} 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 deleted file mode 100644 index 1dea4dc1..00000000 --- a/plugins/inmemoryscanner/test/mock_Yara.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include -#include - -namespace InMemoryScanner -{ - class MockYara : public YaraInterface - { - public: - MOCK_METHOD(std::unique_ptr>, scanMemory, (std::vector & buffer), (override)); - }; -} diff --git a/plugins/inmemoryscanner/test/mock_YaraInterface.h b/plugins/inmemoryscanner/test/mock_YaraInterface.h new file mode 100644 index 00000000..d9efa3c4 --- /dev/null +++ b/plugins/inmemoryscanner/test/mock_YaraInterface.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +namespace InMemoryScanner +{ + class MockYaraInterface : public IYaraInterface + { + public: + MOCK_METHOD(std::vector, + scanMemory, + (VmiCore::addr_t, std::span), + (override)); + }; +}