1
0
mirror of synced 2025-02-20 04:01:01 +01:00

impr: Restructure yara rule handling again

This commit is contained in:
WerWolv 2024-02-22 20:49:21 +01:00
parent 5db041adb7
commit 3a44b840be
5 changed files with 94 additions and 115 deletions

View File

@ -17,21 +17,14 @@ namespace hex::plugin::yara {
void drawContent() override;
private:
struct YaraMatch {
YaraRule::Match match;
mutable u32 highlightId;
mutable u32 tooltipId;
};
private:
PerProvider<std::vector<std::pair<std::fs::path, std::fs::path>>> m_rulePaths;
PerProvider<std::vector<YaraMatch>> m_matches;
PerProvider<std::vector<YaraMatch*>> m_sortedMatches;
PerProvider<std::vector<YaraRule::Rule>> m_matchedRules;
PerProvider<std::vector<std::string>> m_consoleMessages;
PerProvider<u32> m_selectedRule;
PerProvider<std::vector<u32>> m_tooltipIds, m_highlightingIds;
TaskHolder m_matcherTask;
void applyRules();

View File

@ -18,14 +18,21 @@ namespace hex::plugin::yara {
static void cleanup();
struct Match {
std::string identifier;
std::string variable;
Region region;
bool wholeDataMatch;
};
struct Result {
struct Rule {
std::string identifier;
std::map<std::string, std::string> metadata;
std::vector<std::string> tags;
std::vector<Match> matches;
};
struct Result {
std::vector<Rule> matchedRules;
std::vector<std::string> consoleMessages;
};

View File

@ -16,6 +16,16 @@ namespace hex::plugin::yara {
InformationYaraRules() : InformationSection("hex.yara.information_section.yara_rules") { }
~InformationYaraRules() override = default;
struct Category {
struct Comperator {
bool operator()(const YaraRule::Rule &a, const YaraRule::Rule &b) const {
return a.identifier < b.identifier;
}
};
std::set<YaraRule::Rule, Comperator> matchedRules;
};
void process(Task &task, prv::Provider *provider, Region region) override {
const auto &ruleFilePaths = romfs::list("rules");
task.setMaxValue(ruleFilePaths.size());
@ -24,10 +34,16 @@ namespace hex::plugin::yara {
for (const auto &ruleFilePath : ruleFilePaths) {
const std::string fileContent = romfs::get(ruleFilePath).data<const char>();
YaraRule rule(fileContent);
auto result = rule.match(provider, region);
YaraRule yaraRule(fileContent);
const auto result = yaraRule.match(provider, region);
if (result.has_value()) {
m_matches[ruleFilePath.filename().string()] = result.value().matches;
const auto &rules = result.value().matchedRules;
for (const auto &rule : rules) {
if (!rule.metadata.contains("category")) continue;
const auto &categoryName = rule.metadata.at("category");
m_categories[categoryName].matchedRules.insert(rule);
}
}
task.update(progress);
@ -36,7 +52,7 @@ namespace hex::plugin::yara {
}
void reset() override {
m_matches.clear();
m_categories.clear();
}
void drawContent() override {
@ -46,15 +62,16 @@ namespace hex::plugin::yara {
ImGui::TableNextRow();
for (auto &[name, matches] : m_matches) {
if (matches.empty())
for (auto &[categoryName, category] : m_categories) {
if (category.matchedRules.empty())
continue;
ImGui::TableNextColumn();
ImGuiExt::BeginSubWindow(name.c_str());
ImGuiExt::BeginSubWindow(categoryName.c_str());
{
for (const auto &match : matches) {
ImGui::TextUnformatted(match.identifier.c_str());
for (const auto &match : category.matchedRules) {
const auto &ruleName = match.metadata.contains("name") ? match.metadata.at("name") : match.identifier;
ImGui::TextUnformatted(ruleName.c_str());
}
}
ImGuiExt::EndSubWindow();
@ -65,7 +82,7 @@ namespace hex::plugin::yara {
}
private:
std::map<std::string, std::vector<YaraRule::Match>> m_matches;
std::map<std::string, Category> m_categories;
};
void registerDataInformationSections() {

View File

@ -48,7 +48,7 @@ namespace hex::plugin::yara {
if (!rules.is_array())
return false;
m_matches.get(provider).clear();
m_matchedRules.get(provider).clear();
for (auto &rule : rules) {
if (!rule.contains("name") || !rule.contains("path"))
@ -130,7 +130,6 @@ namespace hex::plugin::yara {
ImGui::NewLine();
if (ImGui::Button("hex.yara_rules.view.yara.match"_lang)) this->applyRules();
ImGui::SameLine();
if (m_matcherTask.isRunning()) {
ImGui::SameLine();
@ -143,76 +142,34 @@ namespace hex::plugin::yara {
matchesTableSize.y *= 3.75 / 5.0;
matchesTableSize.y -= ImGui::GetTextLineHeightWithSpacing();
if (ImGui::BeginTable("matches", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_Sortable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY, matchesTableSize)) {
if (ImGui::BeginTable("matches", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_Sortable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY, matchesTableSize)) {
ImGui::TableSetupScrollFreeze(0, 1);
ImGui::TableSetupColumn("hex.yara_rules.view.yara.matches.identifier"_lang, ImGuiTableColumnFlags_PreferSortAscending, 0, ImGui::GetID("identifier"));
ImGui::TableSetupColumn("hex.yara_rules.view.yara.matches.variable"_lang, ImGuiTableColumnFlags_PreferSortAscending, 0, ImGui::GetID("variable"));
ImGui::TableSetupColumn("hex.ui.common.address"_lang, ImGuiTableColumnFlags_PreferSortAscending, 0, ImGui::GetID("address"));
ImGui::TableSetupColumn("hex.ui.common.size"_lang, ImGuiTableColumnFlags_PreferSortAscending, 0, ImGui::GetID("size"));
ImGui::TableHeadersRow();
auto sortSpecs = ImGui::TableGetSortSpecs();
if (!m_matches->empty() && (sortSpecs->SpecsDirty || m_sortedMatches->empty())) {
m_sortedMatches->clear();
std::transform(m_matches->begin(), m_matches->end(), std::back_inserter(*m_sortedMatches), [](auto &match) {
return &match;
});
std::sort(m_sortedMatches->begin(), m_sortedMatches->end(), [&sortSpecs](const YaraMatch *left, const YaraMatch *right) -> bool {
if (sortSpecs->Specs->ColumnUserID == ImGui::GetID("identifier"))
return left->match.identifier < right->match.identifier;
else if (sortSpecs->Specs->ColumnUserID == ImGui::GetID("variable"))
return left->match.variable < right->match.variable;
else if (sortSpecs->Specs->ColumnUserID == ImGui::GetID("address"))
return left->match.region.getStartAddress() < right->match.region.getStartAddress();
else if (sortSpecs->Specs->ColumnUserID == ImGui::GetID("size"))
return left->match.region.getSize() < right->match.region.getSize();
else
return false;
});
if (sortSpecs->Specs->SortDirection == ImGuiSortDirection_Descending)
std::reverse(m_sortedMatches->begin(), m_sortedMatches->end());
sortSpecs->SpecsDirty = false;
}
if (!m_matcherTask.isRunning()) {
ImGuiListClipper clipper;
clipper.Begin(m_sortedMatches->size());
for (const auto &rule : *m_matchedRules) {
if (rule.matches.empty()) continue;
while (clipper.Step()) {
for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) {
auto &[match, highlightId, tooltipId] = *(*m_sortedMatches)[i];
auto &[identifier, variableName, region, wholeDataMatch] = match;
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::PushID(i);
if (ImGui::Selectable("match", false, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap)) {
ImHexApi::HexEditor::setSelection(region.getStartAddress(), region.getSize());
}
ImGui::PopID();
ImGui::SameLine();
ImGui::TextUnformatted(identifier.c_str());
ImGui::TableNextColumn();
ImGui::TextUnformatted(variableName.c_str());
ImGui::TableNextRow();
ImGui::TableNextColumn();
if (!wholeDataMatch) {
if (ImGui::TreeNode(rule.identifier.c_str())) {
for (const auto &match : rule.matches) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGuiExt::TextFormatted("0x{0:X} : 0x{1:X}", region.getStartAddress(), region.getEndAddress());
ImGui::TextUnformatted(match.variable.c_str());
ImGui::TableNextColumn();
ImGuiExt::TextFormatted("0x{0:X}", region.getSize());
} else {
ImGui::TextUnformatted(hex::format("0x{0:08X}", match.region.getStartAddress()).c_str());
ImGui::TableNextColumn();
ImGuiExt::TextFormattedColored(ImVec4(0.92F, 0.25F, 0.2F, 1.0F), "{}", "hex.yara_rules.view.yara.whole_data"_lang);
ImGui::TableNextColumn();
ImGui::TextUnformatted("");
ImGui::TextUnformatted(hex::format("0x{0:08X}", match.region.getSize()).c_str());
}
ImGui::TreePop();
}
}
clipper.End();
}
ImGui::EndTable();
@ -237,12 +194,14 @@ namespace hex::plugin::yara {
}
void ViewYara::clearResult() {
for (const auto &match : *m_matches) {
ImHexApi::HexEditor::removeBackgroundHighlight(match.highlightId);
ImHexApi::HexEditor::removeTooltip(match.tooltipId);
}
for (const auto &id : *m_highlightingIds)
ImHexApi::HexEditor::removeBackgroundHighlight(id);
for (const auto &id : *m_tooltipIds)
ImHexApi::HexEditor::removeTooltip(id);
m_matches->clear();
m_highlightingIds->clear();
m_tooltipIds->clear();
m_matchedRules->clear();
m_consoleMessages->clear();
}
@ -280,33 +239,19 @@ namespace hex::plugin::yara {
}
TaskManager::doLater([this, results = std::move(results)] {
for (const auto &match : *m_matches) {
ImHexApi::HexEditor::removeBackgroundHighlight(match.highlightId);
ImHexApi::HexEditor::removeTooltip(match.tooltipId);
}
this->clearResult();
for (auto &result : results) {
for (auto &match : result.matches) {
m_matches->emplace_back(YaraMatch { std::move(match), 0, 0 });
}
m_consoleMessages->append_range(result.consoleMessages);
m_matchedRules->append_range(std::move(result.matchedRules));
m_consoleMessages->append_range(std::move(result.consoleMessages));
}
auto uniques = std::set(m_matches->begin(), m_matches->end(), [](const auto &leftMatch, const auto &rightMatch) {
const auto &l = leftMatch.match;
const auto &r = rightMatch.match;
return std::tie(l.region.address, l.wholeDataMatch, l.identifier, l.variable) <
std::tie(r.region.address, r.wholeDataMatch, r.identifier, r.variable);
});
m_matches->clear();
std::move(uniques.begin(), uniques.end(), std::back_inserter(*m_matches));
constexpr static color_t YaraColor = 0x70B4771F;
for (const YaraMatch &yaraMatch : uniques) {
yaraMatch.highlightId = ImHexApi::HexEditor::addBackgroundHighlight(yaraMatch.match.region, YaraColor);
yaraMatch.tooltipId = ImHexApi::HexEditor::addTooltip(yaraMatch.match.region, hex::format("{0} [{1}]", yaraMatch.match.identifier, yaraMatch.match.variable), YaraColor);
for (YaraRule::Rule &rule : *m_matchedRules) {
for (auto &match : rule.matches) {
m_highlightingIds->push_back(ImHexApi::HexEditor::addBackgroundHighlight(match.region, YaraColor));
m_tooltipIds->push_back(ImHexApi::HexEditor::addTooltip(match.region, hex::format("{0} : {1} [{2}]", rule.identifier, fmt::join(rule.tags, ", "), match.variable), YaraColor));
}
}
});
});

View File

@ -17,7 +17,7 @@ namespace hex::plugin::yara {
struct ResultContext {
YaraRule *rule;
std::vector<YaraRule::Match> newMatches;
std::vector<YaraRule::Rule> matchedRules;
std::vector<std::string> consoleMessages;
std::string includeBuffer;
@ -42,35 +42,52 @@ namespace hex::plugin::yara {
}
static int scanFunction(YR_SCAN_CONTEXT *context, int message, void *data, void *userData) {
auto &results = *static_cast<ResultContext *>(userData);
auto &resultContext = *static_cast<ResultContext *>(userData);
switch (message) {
case CALLBACK_MSG_RULE_MATCHING: {
auto rule = static_cast<YR_RULE *>(data);
const auto *rule = static_cast<const YR_RULE *>(data);
if (rule->strings != nullptr) {
YR_STRING *string = nullptr;
YR_MATCH *match = nullptr;
YR_STRING *string;
yr_rule_strings_foreach(rule, string) {
YaraRule::Rule newRule;
newRule.identifier = rule->identifier;
YR_MATCH *match;
yr_string_matches_foreach(context, string, match) {
results.newMatches.push_back({ rule->identifier, string->identifier, Region { u64(match->offset), size_t(match->match_length) }, false });
newRule.matches.push_back({ string->identifier, Region { u64(match->offset), size_t(match->match_length) }, false });
}
YR_META *meta;
yr_rule_metas_foreach(rule, meta) {
newRule.metadata[meta->identifier] = meta->string;
}
const char *tag;
yr_rule_tags_foreach(rule, tag) {
newRule.tags.emplace_back(tag);
}
resultContext.matchedRules.emplace_back(std::move(newRule));
}
} else {
results.newMatches.push_back({ rule->identifier, "", Region::Invalid(), true });
YaraRule::Rule newRule;
newRule.identifier = rule->identifier;
newRule.matches.push_back({ "", Region::Invalid(), true });
}
break;
}
case CALLBACK_MSG_CONSOLE_LOG: {
results.consoleMessages.emplace_back(static_cast<const char *>(data));
resultContext.consoleMessages.emplace_back(static_cast<const char *>(data));
break;
}
default:
break;
}
return results.rule->isInterrupted() ? CALLBACK_ABORT : CALLBACK_CONTINUE;
return resultContext.rule->isInterrupted() ? CALLBACK_ABORT : CALLBACK_CONTINUE;
}
wolv::util::Expected<YaraRule::Result, YaraRule::Error> YaraRule::match(prv::Provider *provider, Region region) {
@ -183,7 +200,7 @@ namespace hex::plugin::yara {
if (m_interrupted)
return wolv::util::Unexpected(Error { Error::Type::Interrupted, "" });
return Result { resultContext.newMatches, resultContext.consoleMessages };
return Result { resultContext.matchedRules, resultContext.consoleMessages };
}
void YaraRule::interrupt() {