Last active
October 29, 2024 22:18
-
-
Save oopsmishap/a4ae6f5f23f844dd0afda5b3ad400373 to your computer and use it in GitHub Desktop.
Yara Pattern Search
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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