Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

InMemory: Mitigate slow scanning of certain mapped regions #144

Merged
merged 3 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions plugins/inmemoryscanner/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ For this, add the following parts to the _VMICore_ config and tweak them to your
| `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. |
| `signature_file` | Path to the compiled signatures with which to scan the memory regions. |
| `scan_timeout` | Timeout in seconds that determines when libyara will cancel the scan process for a single memory region. |

Example configuration:

Expand All @@ -145,6 +146,7 @@ plugin_system:
dump_memory: false
scan_all_regions: false
output_path: ""
scan_timeout: 10
ignored_processes:
- SearchUI.exe
- system
Expand Down
6 changes: 1 addition & 5 deletions plugins/inmemoryscanner/src/lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,9 @@ target_include_directories(inmemoryscanner-obj INTERFACE $<BUILD_INTERFACE:${CMA

include(FindPkgConfig)

pkg_check_modules(YARA REQUIRED yara>=4)
pkg_check_modules(YARA REQUIRED yara>=4.2)
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)
Expand Down
6 changes: 6 additions & 0 deletions plugins/inmemoryscanner/src/lib/Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ namespace InMemoryScanner
outputPath = rootNode["output_path"].as<std::string>();
dumpMemory = rootNode["dump_memory"].as<bool>(false);
scanAllRegions = rootNode["scan_all_regions"].as<bool>(false);
scanTimeout = rootNode["scan_timeout"].as<int>(10);

auto ignoredProcessesVec =
rootNode["ignored_processes"].as<std::vector<std::string>>(std::vector<std::string>());
Expand All @@ -43,6 +44,11 @@ namespace InMemoryScanner
return outputPath;
}

int Config::getScanTimeout() const
{
return scanTimeout;
}

bool Config::isProcessIgnored(const std::string& processName) const
{
return ignoredProcesses.find(processName) != ignoredProcesses.end();
Expand Down
5 changes: 5 additions & 0 deletions plugins/inmemoryscanner/src/lib/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ namespace InMemoryScanner

[[nodiscard]] virtual std::filesystem::path getOutputPath() const = 0;

[[nodiscard]] virtual int getScanTimeout() const = 0;

[[nodiscard]] virtual bool isProcessIgnored(const std::string& processName) const = 0;

[[nodiscard]] virtual bool isScanAllRegionsActivated() const = 0;
Expand All @@ -51,6 +53,8 @@ namespace InMemoryScanner

[[nodiscard]] std::filesystem::path getOutputPath() const override;

[[nodiscard]] int getScanTimeout() const override;

[[nodiscard]] bool isProcessIgnored(const std::string& processName) const override;

[[nodiscard]] bool isScanAllRegionsActivated() const override;
Expand All @@ -66,5 +70,6 @@ namespace InMemoryScanner
std::set<std::string> ignoredProcesses;
bool dumpMemory{};
bool scanAllRegions{};
int scanTimeout;
};
}
8 changes: 6 additions & 2 deletions plugins/inmemoryscanner/src/lib/IYaraInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@ namespace InMemoryScanner
using std::runtime_error::runtime_error;
};

class YaraTimeoutException final : public YaraException
{
using YaraException::YaraException;
};

class IYaraInterface
{
public:
virtual ~IYaraInterface() = default;

virtual std::vector<Rule> scanMemory(VmiCore::addr_t regionBase,
std::span<const VmiCore::MappedRegion> mappedRegions) = 0;
virtual std::vector<Rule> scanMemory(std::span<const VmiCore::MappedRegion> mappedRegions) = 0;

protected:
IYaraInterface() = default;
Expand Down
2 changes: 1 addition & 1 deletion plugins/inmemoryscanner/src/lib/InMemory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ namespace InMemoryScanner
{
configuration->overrideDumpMemoryFlag(dumpMemoryArgument.getValue());
}
auto yara = std::make_unique<YaraInterface>(configuration->getSignatureFile());
auto yara = std::make_unique<YaraInterface>(configuration->getSignatureFile(), configuration->getScanTimeout());
auto dumping = std::make_unique<Dumping>(pluginInterface, configuration);
scanner = std::make_unique<Scanner>(pluginInterface, configuration, std::move(yara), std::move(dumping));
}
Expand Down
9 changes: 8 additions & 1 deletion plugins/inmemoryscanner/src/lib/Scanner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ namespace InMemoryScanner
// The semaphore protects the yara rules from being accessed more than YR_MAX_THREADS (32 atm.) times in
// parallel.
semaphore.acquire();
auto results = yaraInterface->scanMemory(memoryRegionDescriptor.base, mappedRegions);
auto results = yaraInterface->scanMemory(mappedRegions);
semaphore.release();

logger->debug("End scanMemory");
Expand Down Expand Up @@ -177,6 +177,13 @@ namespace InMemoryScanner
*processInformation->fullName,
memoryRegionDescriptor);
}
catch (const YaraTimeoutException&)
{
logger->warning("Scan timeout reached",
{{"Process", *processInformation->fullName},
{"BaseVA", memoryRegionDescriptor.base},
{"Size", memoryRegionDescriptor.size}});
}
catch (const std::exception& exc)
{
logger->error("Error scanning memory region of process",
Expand Down
25 changes: 16 additions & 9 deletions plugins/inmemoryscanner/src/lib/YaraInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ namespace

namespace InMemoryScanner
{
YaraInterface::YaraInterface(const std::string& rulesFile)
YaraInterface::YaraInterface(const std::string& rulesFile, int scanTimeout) : scanTimeout(scanTimeout)
{
auto err = yr_initialize();
if (err != ERROR_SUCCESS)
Expand All @@ -64,7 +64,7 @@ namespace InMemoryScanner
}
}

std::vector<Rule> YaraInterface::scanMemory(addr_t regionBase, std::span<const MappedRegion> mappedRegions)
std::vector<Rule> YaraInterface::scanMemory(std::span<const MappedRegion> mappedRegions)
{
std::vector<Rule> results;

Expand All @@ -73,23 +73,30 @@ namespace InMemoryScanner
for (const auto& mappedRegion : mappedRegions)
{
iteratorContext.blocks.emplace_back(mappedRegion.num_pages * pageSizeInBytes,
mappedRegion.guestBaseVA - regionBase,
mappedRegion.guestBaseVA,
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)
if (auto err = yr_rules_scan_mem_blocks(rules,
&iterator,
SCAN_FLAGS_PROCESS_MEMORY | SCAN_FLAGS_REPORT_RULES_MATCHING,
yaraCallback,
&results,
scanTimeout);
err != ERROR_SUCCESS)
{
if (err == ERROR_SCAN_TIMEOUT)
{
throw YaraTimeoutException("Scan timeout");
}

throw YaraException(fmt::format("Error scanning memory. Error code: {}", err));
}

Expand Down
6 changes: 3 additions & 3 deletions plugins/inmemoryscanner/src/lib/YaraInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace InMemoryScanner
class YaraInterface : public IYaraInterface
{
public:
explicit YaraInterface(const std::string& rulesFile);
YaraInterface(const std::string& rulesFile, int scanTimeout);

YaraInterface(const YaraInterface& other) = delete;

Expand All @@ -36,10 +36,10 @@ namespace InMemoryScanner

~YaraInterface() override;

std::vector<Rule> scanMemory(VmiCore::addr_t regionBase,
std::span<const VmiCore::MappedRegion> mappedRegions) override;
std::vector<Rule> scanMemory(std::span<const VmiCore::MappedRegion> mappedRegions) override;

private:
int scanTimeout;
YR_RULES* rules = nullptr;

static int yaraCallback(YR_SCAN_CONTEXT* context, int message, void* message_data, void* user_data);
Expand Down
3 changes: 1 addition & 2 deletions plugins/inmemoryscanner/test/FakeYaraInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
namespace InMemoryScanner
{
std::vector<Rule>
FakeYaraInterface::scanMemory([[maybe_unused]] VmiCore::addr_t regionBase,
[[maybe_unused]] std::span<const VmiCore::MappedRegion> mappedRegions)
FakeYaraInterface::scanMemory([[maybe_unused]] std::span<const VmiCore::MappedRegion> mappedRegions)
{
concurrentThreads++;
if (concurrentThreads > YR_MAX_THREADS)
Expand Down
3 changes: 1 addition & 2 deletions plugins/inmemoryscanner/test/FakeYaraInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ namespace InMemoryScanner
class FakeYaraInterface : public IYaraInterface
{
public:
std::vector<Rule> scanMemory(VmiCore::addr_t regionBase,
std::span<const VmiCore::MappedRegion> mappedRegions) override;
std::vector<Rule> scanMemory(std::span<const VmiCore::MappedRegion> mappedRegions) override;

bool max_threads_exceeded = false;

Expand Down
4 changes: 2 additions & 2 deletions plugins/inmemoryscanner/test/Scanner_unittest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ namespace InMemoryScanner
auto dumping = std::make_unique<NiceMock<MockDumping>>();
dumpingRawPointer = dumping.get();
auto yara = std::make_unique<NiceMock<MockYaraInterface>>();
ON_CALL(*yara, scanMemory(_, _)).WillByDefault(Return(std::vector<Rule>{}));
ON_CALL(*yara, scanMemory(_)).WillByDefault(Return(std::vector<Rule>{}));
scanner.emplace(pluginInterface.get(), configuration, std::move(yara), std::move(dumping));
};
};
Expand Down Expand Up @@ -173,7 +173,7 @@ namespace InMemoryScanner
});
auto dumping = std::make_unique<Dumping>(pluginInterface.get(), configuration);
auto yara = std::make_unique<NiceMock<MockYaraInterface>>();
ON_CALL(*yara, scanMemory(_, _)).WillByDefault(Return(std::vector<Rule>{}));
ON_CALL(*yara, scanMemory(_)).WillByDefault(Return(std::vector<Rule>{}));
scanner.emplace(pluginInterface.get(), configuration, std::move(yara), std::move(dumping));
};

Expand Down
22 changes: 11 additions & 11 deletions plugins/inmemoryscanner/test/YaraInterface_unittest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,11 @@ namespace InMemoryScanner
all of them
}
)";
auto yaraInterface = YaraInterface(compileYaraRules(rules));
auto yaraInterface = YaraInterface(compileYaraRules(rules), 0);
auto subRegion1 = constructPageWithContent("ABCD");
std::vector<VmiCore::MappedRegion> memoryRegions{{0x0, subRegion1}};

auto matches = yaraInterface.scanMemory(memoryRegions.front().guestBaseVA, memoryRegions);
auto matches = yaraInterface.scanMemory(memoryRegions);

EXPECT_EQ(matches.size(), 0);
}
Expand All @@ -84,12 +84,12 @@ namespace InMemoryScanner
all of them
}
)";
auto yaraInterface = YaraInterface(compileYaraRules(rules));
auto yaraInterface = YaraInterface(compileYaraRules(rules), 0);
auto subRegion1 = constructPageWithContent("ABCD", true);
auto subRegion2 = constructPageWithContent("DCBA", false);
std::vector<VmiCore::MappedRegion> memoryRegions{{0x0, subRegion1}, {pageSizeInBytes, subRegion2}};

auto matches = yaraInterface.scanMemory(memoryRegions.front().guestBaseVA, memoryRegions);
auto matches = yaraInterface.scanMemory(memoryRegions);

EXPECT_EQ(matches.size(), 0);
}
Expand All @@ -107,14 +107,14 @@ namespace InMemoryScanner
all of them
}
)";
auto yaraInterface = YaraInterface(compileYaraRules(rules));
auto yaraInterface = YaraInterface(compileYaraRules(rules), 0);
auto subRegion1 = constructPageWithContent("ABCD");
auto subRegion2 = constructPageWithContent("DCBA");
std::vector<VmiCore::MappedRegion> memoryRegion1{{0x0, subRegion1}};
std::vector<VmiCore::MappedRegion> memoryRegion2{{4 * pageSizeInBytes, subRegion2}};

auto matches1 = yaraInterface.scanMemory(memoryRegion1.front().guestBaseVA, memoryRegion1);
auto matches2 = yaraInterface.scanMemory(memoryRegion2.front().guestBaseVA, memoryRegion2);
auto matches1 = yaraInterface.scanMemory(memoryRegion1);
auto matches2 = yaraInterface.scanMemory(memoryRegion2);

EXPECT_EQ(matches1.size(), 0);
EXPECT_EQ(matches2.size(), 0);
Expand All @@ -133,13 +133,13 @@ namespace InMemoryScanner
all of them
}
)";
auto yaraInterface = YaraInterface(compileYaraRules(rules));
auto yaraInterface = YaraInterface(compileYaraRules(rules), 0);
auto subRegion1 = constructPageWithContent("ABCD");
auto subRegion2 = constructPageWithContent("DCBA");
std::vector<VmiCore::MappedRegion> memoryRegions{{0x0, subRegion1}, {4 * pageSizeInBytes, subRegion2}};
Rule expectedMatch{"testRule", "default", {{"$test", 0x0}, {"$test2", 4 * pageSizeInBytes}}};

auto matches = yaraInterface.scanMemory(memoryRegions.front().guestBaseVA, memoryRegions);
auto matches = yaraInterface.scanMemory(memoryRegions);

ASSERT_EQ(matches.size(), 1);
EXPECT_THAT(matches, UnorderedElementsAre(expectedMatch));
Expand Down Expand Up @@ -168,7 +168,7 @@ namespace InMemoryScanner
all of them
}
)";
auto yaraInterface = YaraInterface(compileYaraRules(rules));
auto yaraInterface = YaraInterface(compileYaraRules(rules), 0);
auto subRegion1 = constructPageWithContent("ABCD");
auto subRegion2 = constructPageWithContent("DCBA");
auto subRegion3 = constructPageWithContent("EFGH");
Expand All @@ -178,7 +178,7 @@ namespace InMemoryScanner
Rule expectedMatch2{
"testRule2", "default", {{"$test", 8 * pageSizeInBytes}, {"$test2", 8 * pageSizeInBytes + 1}}};

auto matches = yaraInterface.scanMemory(memoryRegions.front().guestBaseVA, memoryRegions);
auto matches = yaraInterface.scanMemory(memoryRegions);

ASSERT_EQ(matches.size(), 2);
EXPECT_THAT(matches, UnorderedElementsAre(expectedMatch1, expectedMatch2));
Expand Down
1 change: 1 addition & 0 deletions plugins/inmemoryscanner/test/mock_Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ namespace InMemoryScanner
MOCK_METHOD(void, parseConfiguration, (const VmiCore::Plugin::IPluginConfig&), (override));
MOCK_METHOD(std::filesystem::path, getSignatureFile, (), (const, override));
MOCK_METHOD(std::filesystem::path, getOutputPath, (), (const, override));
MOCK_METHOD(int, getScanTimeout, (), (const, override));
MOCK_METHOD(bool, isProcessIgnored, (const std::string& processName), (const, override));
MOCK_METHOD(bool, isScanAllRegionsActivated, (), (const, override));
MOCK_METHOD(bool, isDumpingMemoryActivated, (), (const, override));
Expand Down
5 changes: 1 addition & 4 deletions plugins/inmemoryscanner/test/mock_YaraInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@ namespace InMemoryScanner
class MockYaraInterface : public IYaraInterface
{
public:
MOCK_METHOD(std::vector<Rule>,
scanMemory,
(VmiCore::addr_t, std::span<const VmiCore::MappedRegion>),
(override));
MOCK_METHOD(std::vector<Rule>, scanMemory, (std::span<const VmiCore::MappedRegion>), (override));
};
}
Loading