Skip to content

Instantly share code, notes, and snippets.

@oopsmishap
Last active October 29, 2024 22:18
Show Gist options
  • Save oopsmishap/a4ae6f5f23f844dd0afda5b3ad400373 to your computer and use it in GitHub Desktop.
Save oopsmishap/a4ae6f5f23f844dd0afda5b3ad400373 to your computer and use it in GitHub Desktop.
Yara Pattern Search
#include <vector>
#include <string_view>
#include <cstdint>
#include <cctype>
#include <limits>
#include <array>
namespace util
{
namespace detail
{
constexpr std::array<int8_t, 128> create_hex_lookup_table()
{
std::array<int8_t, 128> table{};
for (int i = 0; i < 128; ++i)
{
table[i] = -1; // Default to invalid character
}
for (int i = '0'; i <= '9'; ++i)
{
table[i] = i - '0';
}
for (int i = 'a'; i <= 'f'; ++i)
{
table[i] = i - 'a' + 10;
}
for (int i = 'A'; i <= 'F'; ++i)
{
table[i] = i - 'A' + 10;
}
table['?'] = -2; // Wildcard nibble
return table;
}
} // namespace detail
int8_t hex_to_byte(char c)
{
static constexpr auto hex_lookup_table = detail::create_hex_lookup_table();
if (c < 0)
{
return -1;
}
return hex_lookup_table[static_cast<unsigned char>(c)];
}
} // namespace util
namespace search
{
/**
* @struct pattern_element
* @brief Represents an element of a pattern used for pattern matching.
*/
struct pattern_element
{
/**
* @enum type_t
* @brief Enumerates the types of pattern elements.
*/
enum class type_t
{
byte_value, ///< A specific byte value with an optional mask.
wildcard, ///< A wildcard byte (any byte).
jump, ///< A jump (skip) over a range of bytes.
alternatives, ///< A set of alternative sequences.
none ///< No type (uninitialized).
};
type_t type = type_t::none; ///< The type of the pattern element.
uint8_t value = 0; ///< The byte value.
uint8_t mask = 0xFF; ///< The mask applied to the byte value.
size_t jump_min = 0; ///< Minimum number of bytes to jump.
size_t jump_max = 0; ///< Maximum number of bytes to jump.
/**
* @brief A list of alternative sequences (for alternatives).
*
* Each alternative is itself a vector of pattern_elements representing a sequence.
*/
std::vector<std::vector<pattern_element>> alternatives_;
/**
* @brief Creates a pattern_element of type byte_value.
* @param v The byte value.
* @param m The mask to apply to the byte value.
* @return A pattern_element representing a byte_value.
*/
static pattern_element byte_value(uint8_t v, uint8_t m)
{
pattern_element elem;
elem.type = type_t::byte_value;
elem.value = v;
elem.mask = m;
return elem;
}
/**
* @brief Creates a pattern_element of type wildcard.
* @return A pattern_element representing a wildcard.
*/
static pattern_element wildcard()
{
pattern_element elem;
elem.type = type_t::wildcard;
elem.value = 0;
elem.mask = 0x00;
return elem;
}
/**
* @brief Creates a pattern_element of type jump.
* @param min The minimum number of bytes to jump.
* @param max The maximum number of bytes to jump.
* @return A pattern_element representing a jump.
*/
static pattern_element jump(size_t min, size_t max)
{
pattern_element elem;
elem.type = type_t::jump;
elem.jump_min = min;
elem.jump_max = max;
return elem;
}
/**
* @brief Creates a pattern_element of type alternatives.
* @param alts A vector of alternative sequences.
* @return A pattern_element representing alternatives.
*/
static pattern_element alternatives(std::vector<std::vector<pattern_element>>& alts)
{
pattern_element elem;
elem.type = type_t::alternatives;
elem.alternatives_ = std::move(alts);
return elem;
}
};
/**
* @brief Parses a decimal number from the beginning of a string_view.
* @param sv The string_view to parse.
* @param value [out] The parsed decimal number.
* @return True if parsing was successful, false otherwise.
*/
bool parse_decimal_number(std::string_view& sv, size_t& value)
{
value = 0;
size_t i = 0;
// Parse digits and construct the number
while (i < sv.size() && std::isdigit(static_cast<unsigned char>(sv[i])))
{
value = value * 10 + static_cast<size_t>(sv[i] - '0');
++i;
}
if (i == 0)
{
// No digits were found
return false;
}
sv.remove_prefix(i); // Advance the string_view
return true;
}
/**
* @brief Parses a hexadecimal byte (possibly with wildcards) from the beginning of a string_view.
* @param sv The string_view to parse.
* @param value [out] The parsed byte value.
* @param mask [out] The mask to apply to the value.
* @return True if parsing was successful, false otherwise.
*/
bool parse_hex_byte(std::string_view& sv, uint8_t& value, uint8_t& mask)
{
if (sv.size() < 2)
{
return false;
}
auto high = util::hex_to_byte(sv[0]);
auto low = util::hex_to_byte(sv[1]);
if (high == -1 || low == -1)
{
return false;
}
value = 0;
mask = 0x00;
if (high >= 0)
{
value |= static_cast<uint8_t>(high << 4);
mask |= 0xF0;
}
if (low >= 0)
{
value |= static_cast<uint8_t>(low);
mask |= 0x0F;
}
sv.remove_prefix(2); // Advance the string_view
return true;
}
/**
* @brief Parses a single pattern element from the beginning of a string_view.
* @param sv The string_view to parse.
* @param elem [out] The parsed pattern element.
* @param allow_special_tokens Whether to allow special tokens like jumps and alternatives.
* @return True if parsing was successful, false otherwise.
*/
bool parse_single_element(
std::string_view& sv, pattern_element& elem, bool allow_special_tokens
);
/**
* @brief Parses a jump (e.g., "[min-max]") from the beginning of a string_view.
* @param sv The string_view to parse.
* @param min [out] The minimum number of bytes to jump.
* @param max [out] The maximum number of bytes to jump.
* @return True if parsing was successful, false otherwise.
*/
bool parse_jump(std::string_view& sv, size_t& min, size_t& max)
{
if (sv.empty() || sv.front() != '[')
{
return false;
}
sv.remove_prefix(1); // Remove '['
if (!parse_decimal_number(sv, min))
{
return false;
}
if (sv.empty())
{
return false;
}
if (sv.front() == '-')
{
sv.remove_prefix(1); // Remove '-'
if (!parse_decimal_number(sv, max))
{
// If max is not specified, set to maximum possible value
max = std::numeric_limits<size_t>::max();
}
}
else
{
// If no '-', min and max are the same
max = min;
}
if (sv.empty() || sv.front() != ']')
{
return false;
}
sv.remove_prefix(1); // Remove ']'
return true;
}
/**
* @brief Parses a set of alternative sequences from the beginning of a string_view.
* @param sv The string_view to parse.
* @param alternatives [out] The parsed alternatives.
* @return True if parsing was successful, false otherwise.
*/
bool parse_alternatives(
std::string_view& sv, std::vector<std::vector<pattern_element>>& alternatives
)
{
if (sv.empty() || sv.front() != '(')
{
return false;
}
sv.remove_prefix(1); // Remove '('
while (true)
{
std::vector<pattern_element> alt_sequence;
pattern_element elem;
// Parse elements until we hit a '|' or ')'
while (parse_single_element(sv, elem, false))
{
alt_sequence.emplace_back(elem);
}
if (alt_sequence.empty())
{
return false;
}
alternatives.emplace_back(alt_sequence);
// Skip any whitespace
while (!sv.empty() && std::isspace(static_cast<unsigned char>(sv.front())))
{
sv.remove_prefix(1);
}
if (!sv.empty() && sv.front() == '|')
{
sv.remove_prefix(1); // Remove '|'
continue; // Parse next alternative
}
else if (!sv.empty() && sv.front() == ')')
{
sv.remove_prefix(1); // Remove ')'
break; // End of alternatives
}
else
{
return false;
}
}
return true;
}
/**
* @brief Parses a single pattern element from the beginning of a string_view.
* @param sv The string_view to parse.
* @param elem [out] The parsed pattern element.
* @param allow_special_tokens Whether to allow special tokens like jumps and alternatives.
* @return True if parsing was successful, false otherwise.
*/
bool parse_single_element(std::string_view& sv, pattern_element& elem, bool allow_special_tokens)
{
// Skip any whitespace
while (!sv.empty() && std::isspace(static_cast<unsigned char>(sv.front())))
{
sv.remove_prefix(1);
}
if (sv.empty())
{
return false;
}
if (sv.size() >= 2 && sv.substr(0, 2) == "??")
{
// Wildcard byte
sv.remove_prefix(2);
elem = pattern_element::wildcard();
return true;
}
else if (allow_special_tokens && sv.front() == '[')
{
// Jump element
size_t min = 0, max = 0;
if (!parse_jump(sv, min, max))
{
return false;
}
elem = pattern_element::jump(min, max);
return true;
}
else if (allow_special_tokens && sv.front() == '(')
{
// Alternatives
std::vector<std::vector<pattern_element>> alternatives;
if (!parse_alternatives(sv, alternatives))
{
return false;
}
elem = pattern_element::alternatives(alternatives);
return true;
}
else if (std::isxdigit(static_cast<unsigned char>(sv.front())) || sv.front() == '?')
{
// Hex byte or mask
uint8_t value = 0, mask = 0;
if (!parse_hex_byte(sv, value, mask))
{
return false;
}
elem = pattern_element::byte_value(value, mask);
return true;
}
else
{
// Unknown token
return false;
}
}
/**
* @brief Parses a pattern string into a sequence of pattern elements.
* @param sv The pattern string to parse.
* @param pattern [out] The parsed pattern elements.
* @return True if parsing was successful, false otherwise.
*/
bool parse_pattern(std::string_view sv, std::vector<pattern_element>& pattern)
{
pattern_element elem;
while (parse_single_element(sv, elem, true))
{
pattern.emplace_back(elem);
}
return sv.empty();
}
/**
* @brief Matches a pattern against data starting from given positions.
* @param data The data to match against.
* @param data_size The size of the data.
* @param dp The current position in the data.
* @param pattern The pattern to match.
* @param pp The current position in the pattern.
* @return True if the pattern matches the data from the given positions, false otherwise.
*/
bool match_from(
const uint8_t* data,
size_t data_size,
size_t dp,
const std::vector<pattern_element>& pattern,
size_t pp
)
{
// Loop through the pattern elements
while (pp < pattern.size())
{
if (dp >= data_size)
{
// Reached end of data without matching the pattern
return false;
}
const auto& elem = pattern[pp];
switch (elem.type)
{
case pattern_element::type_t::byte_value:
case pattern_element::type_t::wildcard:
{
// Match byte value with mask
if ((data[dp] & elem.mask) != (elem.value & elem.mask))
{
return false;
}
dp++;
pp++;
break;
}
case pattern_element::type_t::jump:
{
size_t min_jump = elem.jump_min;
size_t max_jump = elem.jump_max;
if (max_jump > data_size - dp)
{
// Adjust max_jump to remaining data size
max_jump = data_size - dp;
}
// Try all possible jump sizes within [min_jump, max_jump]
for (size_t jump = min_jump; jump <= max_jump; ++jump)
{
if (match_from(data, data_size, dp + jump, pattern, pp + 1))
{
return true;
}
}
return false;
}
case pattern_element::type_t::alternatives:
{
// Try each alternative sequence
for (const auto& alt_sequence : elem.alternatives_)
{
size_t dp_tmp = dp;
size_t i = 0;
// Match the alternative sequence
for (; i < alt_sequence.size(); ++i)
{
if (dp_tmp >= data_size)
{
break;
}
const auto& alt_elem = alt_sequence[i];
if ((data[dp_tmp] & alt_elem.mask)
!= (alt_elem.value & alt_elem.mask))
{
break;
}
dp_tmp++;
}
if (i == alt_sequence.size())
{
// Alternative sequence matched, continue with the rest of the pattern
if (match_from(data, data_size, dp_tmp, pattern, pp + 1))
{
return true;
}
}
}
return false;
}
default:
// Unknown pattern element type
return false;
}
}
// Successfully matched entire pattern
return true;
}
/**
* @brief Searches for a pattern in data similar to YARA pattern matching.
* @param start The starting address of the data.
* @param size The size of the data.
* @param pattern_str The pattern string to search for.
* @return The address where the pattern is found, or 0 if not found.
*/
uintptr_t yara(uintptr_t start, size_t size, std::string_view pattern_str)
{
const uint8_t* data = reinterpret_cast<const uint8_t*>(start);
std::vector<pattern_element> pattern;
if (!parse_pattern(pattern_str, pattern))
{
// Pattern parsing failed
return 0;
}
// Iterate over the data
for (size_t data_pos = 0; data_pos < size; ++data_pos)
{
if (match_from(data, size, data_pos, pattern, 0))
{
// Pattern matched at this position
return reinterpret_cast<uintptr_t>(data + data_pos);
}
}
// Pattern not found
return 0;
}
} // namespace search
#pragma once
#include <string_view>
#include <cstdint>
namespace search
{
uintptr_t yara(uintptr_t start, size_t size, std::string_view pattern_str);
} // namespace search
#include <catch2/catch_test_macros.hpp>
#include <cstdint>
#include <string_view>
#include <limits>
#include <yara.hpp>
TEST_CASE("YARA Pattern Matching Tests", "[search::yara]")
{
// Pattern 1: F4 23 (62 B4 | 56) 45
uint8_t data_match1[] = {0x00, 0xF4, 0x23, 0x62, 0xB4, 0x45, 0x00};
uint8_t data_match2[] = {0x00, 0xF4, 0x23, 0x56, 0x45, 0x00};
uint8_t data_no_match1[] = {0xF4, 0x23, 0x62, 0xB4, 0x00};
uint8_t data_no_match2[] = {0xF4, 0x23, 0x56, 0x00};
std::string_view pattern1 = "F4 23 (62 B4 | 56) 45";
SECTION("Pattern 1 matches data with 'F4 23 62 B4 45'")
{
uintptr_t result = search::yara(
reinterpret_cast<uintptr_t>(data_match1), sizeof(data_match1), pattern1
);
REQUIRE(result != 0);
REQUIRE(result == reinterpret_cast<uintptr_t>(data_match1 + 1));
}
SECTION("Pattern 1 matches data with 'F4 23 56 45'")
{
uintptr_t result = search::yara(
reinterpret_cast<uintptr_t>(data_match2), sizeof(data_match2), pattern1
);
REQUIRE(result != 0);
REQUIRE(result == reinterpret_cast<uintptr_t>(data_match2 + 1));
}
SECTION("Pattern 1 does not match data without ending '45'")
{
uintptr_t result = search::yara(
reinterpret_cast<uintptr_t>(data_no_match1), sizeof(data_no_match1), pattern1
);
REQUIRE(result == 0);
}
// Pattern 2: E2 34 ?? C8 A? FB
uint8_t data_match3[] = {0xE2, 0x34, 0xAB, 0xC8, 0xA5, 0xFB};
uint8_t data_no_match3[] = {0xE2, 0x34, 0xAB, 0xC8, 0x95, 0xFB};
std::string_view pattern2 = "E2 34 ?? C8 A? FB";
SECTION("Pattern 2 matches data with nibble wildcard 'A?'")
{
uintptr_t result = search::yara(
reinterpret_cast<uintptr_t>(data_match3), sizeof(data_match3), pattern2
);
REQUIRE(result != 0);
REQUIRE(result == reinterpret_cast<uintptr_t>(data_match3));
}
SECTION("Pattern 2 does not match data where 'A?' does not match")
{
uintptr_t result = search::yara(
reinterpret_cast<uintptr_t>(data_no_match3), sizeof(data_no_match3), pattern2
);
REQUIRE(result == 0);
}
// Pattern 3: F4 23 [4-6] 62 B4
uint8_t data_match4[] = {0xF4, 0x23, 0xAA, 0xBB, 0xCC, 0xDD, 0x62, 0xB4};
// Additional test data with 6 bytes between 'F4 23' and '62 B4'
uint8_t data_match4_six[] = {
0xF4, 0x23, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x62, 0xB4
};
std::string_view pattern3 = "F4 23 [4-6] 62 B4";
SECTION("Pattern 3 matches data with 4 bytes between 'F4 23' and '62 B4'")
{
uintptr_t result = search::yara(
reinterpret_cast<uintptr_t>(data_match4), sizeof(data_match4), pattern3
);
REQUIRE(result != 0);
REQUIRE(result == reinterpret_cast<uintptr_t>(data_match4));
}
SECTION("Pattern 3 matches data with 6 bytes between 'F4 23' and '62 B4'")
{
uintptr_t result = search::yara(
reinterpret_cast<uintptr_t>(data_match4_six), sizeof(data_match4_six), pattern3
);
REQUIRE(result != 0);
REQUIRE(result == reinterpret_cast<uintptr_t>(data_match4_six));
}
SECTION("Pattern 3 does not match data with less than 4 bytes between 'F4 "
"23' and '62 B4'")
{
// Data with only 3 bytes between 'F4 23' and '62 B4'
uint8_t data_no_match4[] = {0xF4, 0x23, 0xAA, 0xBB, 0xCC, 0x62, 0xB4};
uintptr_t result = search::yara(
reinterpret_cast<uintptr_t>(data_no_match4), sizeof(data_no_match4), pattern3
);
REQUIRE(result == 0);
}
// Pattern 4: FE 39 45 [10-] 89 00
uint8_t data_match5[15];
data_match5[0] = 0xFE;
data_match5[1] = 0x39;
data_match5[2] = 0x45;
for (int i = 3; i < 13; ++i)
{
data_match5[i] = 0x00;
}
data_match5[13] = 0x89;
data_match5[14] = 0x00;
std::string_view pattern4 = "FE 39 45 [10-] 89 00";
SECTION("Pattern 4 matches data with at least 10 bytes between 'FE 39 45' "
"and '89 00'")
{
uintptr_t result = search::yara(
reinterpret_cast<uintptr_t>(data_match5), sizeof(data_match5), pattern4
);
REQUIRE(result != 0);
REQUIRE(result == reinterpret_cast<uintptr_t>(data_match5));
}
// Pattern 5: FE 39 45 [-] 89 00
uint8_t data_match6[] = {0xFE, 0x39, 0x45, 0x89, 0x00};
uint8_t data_match7[50];
data_match7[0] = 0xFE;
data_match7[1] = 0x39;
data_match7[2] = 0x45;
for (int i = 3; i < 30; ++i)
{
data_match7[i] = 0x00;
}
data_match7[30] = 0x89;
data_match7[31] = 0x00;
std::string_view pattern5 = "FE 39 45 [-] 89 00";
SECTION("Pattern 5 matches data with any number of bytes between 'FE 39 "
"45' and '89 00'")
{
uintptr_t result = search::yara(
reinterpret_cast<uintptr_t>(data_match6), sizeof(data_match6), pattern5
);
REQUIRE(result != 0);
REQUIRE(result == reinterpret_cast<uintptr_t>(data_match6));
}
SECTION(
"Pattern 5 matches data with many bytes between 'FE 39 45' and '89 00'"
)
{
uintptr_t result = search::yara(
reinterpret_cast<uintptr_t>(data_match7), sizeof(data_match7), pattern5
);
REQUIRE(result != 0);
REQUIRE(result == reinterpret_cast<uintptr_t>(data_match7));
}
// Pattern 6: F4 23 (62 B4 | 56 | 45 ?? 67) 45
uint8_t data_match8[] = {0xF4, 0x23, 0x62, 0xB4, 0x45};
uint8_t data_match9[] = {0xF4, 0x23, 0x56, 0x45};
uint8_t data_match10[] = {0xF4, 0x23, 0x45, 0xAB, 0x67, 0x45};
uint8_t data_no_match5[] = {0xF4, 0x23, 0x45, 0xAB, 0x66, 0x45};
std::string_view pattern6 = "F4 23 (62 B4 | 56 | 45 ?? 67) 45";
SECTION("Pattern 6 matches data with 'F4 23 62 B4 45'")
{
uintptr_t result = search::yara(
reinterpret_cast<uintptr_t>(data_match8), sizeof(data_match8), pattern6
);
REQUIRE(result != 0);
REQUIRE(result == reinterpret_cast<uintptr_t>(data_match8));
}
SECTION("Pattern 6 matches data with 'F4 23 56 45'")
{
uintptr_t result = search::yara(
reinterpret_cast<uintptr_t>(data_match9), sizeof(data_match9), pattern6
);
REQUIRE(result != 0);
REQUIRE(result == reinterpret_cast<uintptr_t>(data_match9));
}
SECTION("Pattern 6 matches data with 'F4 23 45 ?? 67 45'")
{
uintptr_t result = search::yara(
reinterpret_cast<uintptr_t>(data_match10), sizeof(data_match10), pattern6
);
REQUIRE(result != 0);
REQUIRE(result == reinterpret_cast<uintptr_t>(data_match10));
}
SECTION("Pattern 6 does not match data where '45 ?? 67' does not match")
{
uintptr_t result = search::yara(
reinterpret_cast<uintptr_t>(data_no_match5), sizeof(data_no_match5), pattern6
);
REQUIRE(result == 0);
}
// Pattern 7: AA ?? ?? BB
uint8_t data_match11[] = {0xAA, 0x01, 0x02, 0xBB};
uint8_t data_no_match11[] = {0xAA, 0x01, 0x02, 0xCC};
std::string_view pattern7 = "AA ?? ?? BB";
SECTION("Pattern 7 matches data with two wildcards in between")
{
uintptr_t result = search::yara(
reinterpret_cast<uintptr_t>(data_match11), sizeof(data_match11), pattern7
);
REQUIRE(result != 0);
REQUIRE(result == reinterpret_cast<uintptr_t>(data_match11));
}
SECTION("Pattern 7 does not match data where ending byte does not match")
{
uintptr_t result = search::yara(
reinterpret_cast<uintptr_t>(data_no_match11), sizeof(data_no_match11), pattern7
);
REQUIRE(result == 0);
}
// Pattern 8: 12 34 [1-3] ?? 78
uint8_t data_match12[] = {0x12, 0x34, 0xAA, 0x56, 0x78};
uint8_t data_match13[] = {0x12, 0x34, 0xAA, 0xBB, 0xCC, 0x56, 0x78};
uint8_t data_no_match12[] = {0x12, 0x34, 0x56, 0x78};
std::string_view pattern8 = "12 34 [1-3] ?? 78";
SECTION("Pattern 8 matches data with 1-byte jump followed by wildcard")
{
uintptr_t result = search::yara(
reinterpret_cast<uintptr_t>(data_match12), sizeof(data_match12), pattern8
);
REQUIRE(result != 0);
REQUIRE(result == reinterpret_cast<uintptr_t>(data_match12));
}
SECTION("Pattern 8 matches data with 3-byte jump followed by wildcard")
{
uintptr_t result = search::yara(
reinterpret_cast<uintptr_t>(data_match13), sizeof(data_match13), pattern8
);
REQUIRE(result != 0);
REQUIRE(result == reinterpret_cast<uintptr_t>(data_match13));
}
SECTION("Pattern 8 does not match data with no jump")
{
uintptr_t result = search::yara(
reinterpret_cast<uintptr_t>(data_no_match12), sizeof(data_no_match12), pattern8
);
REQUIRE(result == 0);
}
// clang-format off
// Pattern 10: DE AD BE EF
uint8_t data_match17[] = {
0x00, 0xDE, 0xAD, 0xBE, 0xEF, // First occurrence
0x11, 0xDE, 0xAD, 0xBE, 0xEF, // Second occurrence
0x22, 0xDE, 0xAD, 0xBE, 0xEF // Third occurrence
};
// clang-format on
std::string_view pattern10 = "DE AD BE EF";
SECTION("Pattern 10 matches the first occurrence in the data")
{
uintptr_t result = search::yara(
reinterpret_cast<uintptr_t>(data_match17), sizeof(data_match17), pattern10
);
REQUIRE(result != 0);
REQUIRE(result == reinterpret_cast<uintptr_t>(data_match17 + 1));
}
// Pattern 11: ?? ?? ??
uint8_t data_match18[] = {0x01, 0x02, 0x03};
uint8_t data_no_match14[] = {0x01, 0x02};
std::string_view pattern11 = "?? ?? ??";
SECTION("Pattern 11 matches any sequence of 3 bytes")
{
uintptr_t result = search::yara(
reinterpret_cast<uintptr_t>(data_match18), sizeof(data_match18), pattern11
);
REQUIRE(result != 0);
REQUIRE(result == reinterpret_cast<uintptr_t>(data_match18));
}
SECTION("Pattern 11 does not match data shorter than 3 bytes")
{
uintptr_t result = search::yara(
reinterpret_cast<uintptr_t>(data_no_match14), sizeof(data_no_match14), pattern11
);
REQUIRE(result == 0);
}
// Pattern 12: AA [0-0] BB
uint8_t data_match19[] = {0xAA, 0xBB};
uint8_t data_no_match15[] = {0xAA, 0x00, 0xBB};
std::string_view pattern12 = "AA [0-0] BB";
SECTION("Pattern 12 matches data with no bytes between 'AA' and 'BB'")
{
uintptr_t result = search::yara(
reinterpret_cast<uintptr_t>(data_match19), sizeof(data_match19), pattern12
);
REQUIRE(result != 0);
REQUIRE(result == reinterpret_cast<uintptr_t>(data_match19));
}
SECTION(
"Pattern 12 does not match data with extra bytes between 'AA' and 'BB'"
)
{
uintptr_t result = search::yara(
reinterpret_cast<uintptr_t>(data_no_match15), sizeof(data_no_match15), pattern12
);
REQUIRE(result == 0);
}
// Pattern 13: ZZ 12 34 (Invalid hex character 'Z')
std::string_view pattern13 = "ZZ 12 34";
SECTION("Pattern 13 fails gracefully on invalid hex character")
{
uintptr_t result = search::yara(
reinterpret_cast<uintptr_t>(data_match1), sizeof(data_match1), pattern13
);
REQUIRE(result == 0);
}
// Pattern 14: DE [2] AD [2] BE [2] EF
uint8_t data_match20[] = {
0xDE, 0x00, 0x00, 0xAD, 0x00, 0x00, 0xBE, 0x00, 0x00, 0xEF
};
std::string_view pattern14 = "DE [2] AD [2] BE [2] EF";
SECTION("Pattern 14 matches data with 2 zero bytes between each pair")
{
uintptr_t result = search::yara(
reinterpret_cast<uintptr_t>(data_match20), sizeof(data_match20), pattern14
);
REQUIRE(result != 0);
REQUIRE(result == reinterpret_cast<uintptr_t>(data_match20));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment