Class: Yara::ScanResult
- Inherits:
-
Object
- Object
- Yara::ScanResult
- Defined in:
- lib/yara/scan_result.rb
Overview
Public: Represents a single rule match result from YARA scanning.
A ScanResult contains information about a YARA rule that matched during scanning, including the rule name, metadata, string patterns, and detailed pattern match information. This class provides access to rule information extracted from both the YARA-X API and parsed rule source code.
The enhanced version provides detailed pattern match information including exact offsets and lengths of each pattern match, allowing for precise forensic analysis and data extraction.
Examples
# Typically created by Scanner during scanning
scanner.scan(data) do |result|
puts "Matched rule: #{result.rule_name}"
puts "Author: #{result.[:author]}"
# New: Access detailed pattern matches
result.pattern_matches.each do |pattern_name, matches|
puts "Pattern #{pattern_name}: #{matches.size} matches"
matches.each do |match|
matched_text = data[match.offset, match.length]
puts " At offset #{match.offset}: '#{matched_text}'"
end
end
end
Instance Attribute Summary collapse
-
#namespace ⇒ Object
readonly
Public: Namespace of the rule, if defined.
-
#pattern_matches ⇒ Object
readonly
Public: Hash of pattern names to arrays of PatternMatch objects.
-
#rule_meta ⇒ Object
readonly
Public: Hash of metadata key-value pairs extracted from the rule.
-
#rule_name ⇒ Object
readonly
Public: The name identifier of the matched rule.
-
#rule_ptr ⇒ Object
readonly
Public: FFI pointer to the underlying YRX_RULE structure.
-
#rule_strings ⇒ Object
readonly
Public: Hash of string pattern names and their values from the rule.
-
#tags ⇒ Object
readonly
Public: Array of rule tags for categorization and organization.
Instance Method Summary collapse
-
#all_matches ⇒ Object
Public: Get all match locations as a flattened array.
-
#extract_namespace ⇒ Object
Internal: Extract rule namespace using YARA-X API.
-
#extract_pattern_matches ⇒ Object
Internal: Extract detailed pattern match information using YARA-X API.
-
#extract_structured_metadata ⇒ Object
Internal: Extract structured metadata using YARA-X API.
-
#extract_tags ⇒ Object
Internal: Extract rule tags using YARA-X API.
-
#has_tag?(tag) ⇒ Boolean
Public: Check if the rule has a specific tag.
-
#initialize(rule_name, rule_ptr, is_match = true, rule_source = nil, scanned_data = nil) ⇒ ScanResult
constructor
Public: Initialize a new ScanResult.
-
#match? ⇒ Boolean
Public: Check if this result represents a rule match.
-
#matches_for_pattern(pattern_name) ⇒ Object
Public: Get all matches for a specific pattern by name.
-
#metadata_bool(key) ⇒ Object
Public: Get a boolean metadata value by key.
-
#metadata_float(key) ⇒ Object
Public: Get a float metadata value by key.
-
#metadata_int(key) ⇒ Object
Public: Get an integer metadata value by key.
-
#metadata_string(key) ⇒ Object
Public: Get a string metadata value by key.
-
#metadata_value(key) ⇒ Object
Public: Get a typed metadata value by key.
-
#parse_meta_value(value) ⇒ Object
Internal: Parse and convert metadata values to appropriate Ruby types.
-
#parse_metadata_from_source ⇒ Object
Internal: Parse metadata from the original rule source code.
-
#parse_strings_from_source ⇒ Object
Internal: Parse string patterns from the original rule source code.
-
#pattern_matched?(pattern_name) ⇒ Boolean
Public: Check if a specific pattern had any matches.
-
#qualified_name ⇒ Object
Public: Get the qualified rule name including namespace.
-
#total_matches ⇒ Object
Public: Get the total number of pattern matches across all patterns.
Constructor Details
#initialize(rule_name, rule_ptr, is_match = true, rule_source = nil, scanned_data = nil) ⇒ ScanResult
Public: Initialize a new ScanResult.
This constructor is typically called internally by Scanner when a rule matches during scanning. It extracts available information from both the YARA-X API and the original rule source code, including detailed pattern match information.
rule_name - A String containing the rule identifier/name rule_ptr - An FFI Pointer to the YRX_RULE structure is_match - A Boolean indicating if this represents a match (default true) rule_source - An optional String containing the original rule source for parsing scanned_data - An optional String containing the data that was scanned (needed for pattern matches)
Examples
# Typically created internally by Scanner
result = ScanResult.new("MyRule", rule_ptr, true, rule_source, scanned_data)
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
# File 'lib/yara/scan_result.rb', line 79 def initialize(rule_name, rule_ptr, is_match = true, rule_source = nil, scanned_data = nil) @rule_name = rule_name @rule_ptr = rule_ptr @is_match = is_match @rule_source = rule_source @scanned_data = scanned_data @rule_meta = {} @rule_strings = {} @pattern_matches = {} @tags = [] @namespace = nil # Extract information using YARA-X API when rule pointer is available if @rule_ptr && !@rule_ptr.null? # TODO: Re-enable structured metadata after fixing union handling # extract_structured_metadata extract_namespace extract_pattern_matches end # Parse metadata and strings from source (primary method for now) if @rule_source parse_strings_from_source end end |
Instance Attribute Details
#namespace ⇒ Object (readonly)
Public: Namespace of the rule, if defined.
YARA rules can be organized into namespaces to avoid naming conflicts and provide logical grouping. This contains the namespace name or nil if the rule is in the default namespace.
60 61 62 |
# File 'lib/yara/scan_result.rb', line 60 def namespace @namespace end |
#pattern_matches ⇒ Object (readonly)
Public: Hash of pattern names to arrays of PatternMatch objects.
This provides detailed information about exactly where each pattern matched in the scanned data, including offset and length information.
46 47 48 |
# File 'lib/yara/scan_result.rb', line 46 def pattern_matches @pattern_matches end |
#rule_meta ⇒ Object (readonly)
Public: Hash of metadata key-value pairs extracted from the rule.
37 38 39 |
# File 'lib/yara/scan_result.rb', line 37 def @rule_meta end |
#rule_name ⇒ Object (readonly)
Public: The name identifier of the matched rule.
31 32 33 |
# File 'lib/yara/scan_result.rb', line 31 def rule_name @rule_name end |
#rule_ptr ⇒ Object (readonly)
Public: FFI pointer to the underlying YRX_RULE structure.
34 35 36 |
# File 'lib/yara/scan_result.rb', line 34 def rule_ptr @rule_ptr end |
#rule_strings ⇒ Object (readonly)
Public: Hash of string pattern names and their values from the rule.
40 41 42 |
# File 'lib/yara/scan_result.rb', line 40 def rule_strings @rule_strings end |
#tags ⇒ Object (readonly)
Public: Array of rule tags for categorization and organization.
Tags are labels attached to rules that help categorize and organize rule sets. Common tags include malware family names, platforms, or behavior categories.
53 54 55 |
# File 'lib/yara/scan_result.rb', line 53 def @tags end |
Instance Method Details
#all_matches ⇒ Object
Public: Get all match locations as a flattened array.
This method returns all pattern matches across all patterns as a single array, sorted by offset. Useful for getting an overview of all match locations in the data.
Examples
# Get all matches sorted by location
all_matches = result.all_matches.sort_by(&:offset)
all_matches.each { |m| puts "Match at #{m.offset}" }
Returns an Array of PatternMatch objects sorted by offset.
167 168 169 |
# File 'lib/yara/scan_result.rb', line 167 def all_matches @pattern_matches.values.flatten.sort_by(&:offset) end |
#extract_namespace ⇒ Object
Internal: Extract rule namespace using YARA-X API.
This method uses the YARA-X C API to access the namespace that contains this rule. Namespaces provide logical grouping and avoid naming conflicts.
Returns nothing (modifies @namespace attribute).
470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 |
# File 'lib/yara/scan_result.rb', line 470 def extract_namespace return unless @rule_ptr && !@rule_ptr.null? # Get namespace information ns_ptr = ::FFI::MemoryPointer.new(:pointer) len_ptr = ::FFI::MemoryPointer.new(:size_t) result = Yara::FFI.yrx_rule_namespace(@rule_ptr, ns_ptr, len_ptr) return unless result == Yara::FFI::YRX_SUCCESS namespace_ptr = ns_ptr.get_pointer(0) return if namespace_ptr.nil? || namespace_ptr.null? namespace_len = len_ptr.get_ulong(0) @namespace = namespace_len > 0 ? namespace_ptr.read_string(namespace_len) : nil rescue # Set to nil if extraction fails @namespace = nil end |
#extract_pattern_matches ⇒ Object
Internal: Extract detailed pattern match information using YARA-X API.
This method uses the YARA-X C API to iterate through all patterns defined in the matched rule and collect detailed match information including exact offsets and lengths for each match.
This replaces the need to parse pattern information from rule source code and provides precise forensic data about what matched and where.
Returns nothing (modifies @pattern_matches hash).
330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 |
# File 'lib/yara/scan_result.rb', line 330 def extract_pattern_matches return unless @rule_ptr && !@rule_ptr.null? # Collect pattern match data by iterating through patterns pattern_callback = proc do |pattern_ptr, user_data| next if pattern_ptr.nil? || pattern_ptr.null? # Get pattern identifier ident_ptr = ::FFI::MemoryPointer.new(:pointer) len_ptr = ::FFI::MemoryPointer.new(:size_t) result = Yara::FFI.yrx_pattern_identifier(pattern_ptr, ident_ptr, len_ptr) next unless result == Yara::FFI::YRX_SUCCESS identifier_ptr = ident_ptr.get_pointer(0) next if identifier_ptr.nil? || identifier_ptr.null? identifier_len = len_ptr.get_ulong(0) pattern_name = identifier_ptr.read_string(identifier_len).to_sym # Initialize match array for this pattern @pattern_matches[pattern_name] ||= [] # Iterate through matches for this pattern match_callback = proc do |match_ptr, match_user_data| next if match_ptr.nil? || match_ptr.null? # Extract match details using FFI struct match = Yara::FFI::YRX_MATCH.new(match_ptr) pattern_match = PatternMatch.new(match[:offset], match[:length]) @pattern_matches[pattern_name] << pattern_match end # Iterate through all matches for this pattern Yara::FFI.yrx_pattern_iter_matches(pattern_ptr, match_callback, nil) end # Iterate through all patterns in the rule Yara::FFI.yrx_rule_iter_patterns(@rule_ptr, pattern_callback, nil) end |
#extract_structured_metadata ⇒ Object
Internal: Extract structured metadata using YARA-X API.
This method uses the YARA-X C API to access rule metadata with proper type information, replacing the regex-based parsing approach with reliable structured access.
Returns nothing (modifies @rule_meta hash).
378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 |
# File 'lib/yara/scan_result.rb', line 378 def return unless @rule_ptr && !@rule_ptr.null? # Callback to process each metadata entry = proc do |, user_data| next if .nil? || .null? begin # Extract metadata using FFI struct = Yara::FFI::YRX_METADATA.new() identifier_ptr = [:identifier] next if identifier_ptr.nil? || identifier_ptr.null? identifier = identifier_ptr.read_string.to_sym value_type = [:value_type] # Extract value based on type using union access # Note: We need to read from the value union at the correct offset value_ptr = .pointer + .offset_of(:value) case value_type when Yara::FFI::YRX_I64 value = value_ptr.read_long_long when Yara::FFI::YRX_F64 value = value_ptr.read_double when Yara::FFI::YRX_BOOLEAN value = value_ptr.read_char != 0 when Yara::FFI::YRX_STRING string_ptr_ptr = value_ptr.read_pointer value = string_ptr_ptr.nil? || string_ptr_ptr.null? ? "" : string_ptr_ptr.read_string when Yara::FFI::YRX_BYTES bytes_ptr = value_ptr.read_pointer if bytes_ptr.nil? || bytes_ptr.null? value = "" else # Read the YRX_METADATA_BYTES struct length = bytes_ptr.read_size_t data_ptr = bytes_ptr.read_pointer(8) # offset past the length field value = length > 0 && !data_ptr.null? ? data_ptr.read_string(length) : "" end else value = nil # Unknown type end @rule_meta[identifier] = value unless value.nil? rescue # Skip problematic metadata entries rather than failing entirely # This ensures partial extraction works even if some entries have issues end end # Iterate through all metadata entries Yara::FFI.(@rule_ptr, , nil) rescue # If structured metadata extraction fails, fall back to source parsing # This ensures backwards compatibility end |
#extract_tags ⇒ Object
Internal: Extract rule tags using YARA-X API.
This method uses the YARA-X C API to access all tags defined for the rule. Tags provide categorization and organization capabilities for rule sets.
Returns nothing (modifies @tags array).
442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 |
# File 'lib/yara/scan_result.rb', line 442 def return unless @rule_ptr && !@rule_ptr.null? # Callback to process each tag tag_callback = proc do |tag_ptr, user_data| next if tag_ptr.nil? || tag_ptr.null? begin tag = tag_ptr.read_string @tags << tag unless tag.empty? rescue # Skip problematic tags rather than failing entirely end end # Iterate through all tags Yara::FFI.(@rule_ptr, tag_callback, nil) # If iteration fails, ensure @tags is at least an empty array @tags ||= [] end |
#has_tag?(tag) ⇒ Boolean
Public: Check if the rule has a specific tag.
This method checks whether the rule includes the specified tag. Tag comparison is case-sensitive.
tag - A String representing the tag to check for
Examples
if result.has_tag?("malware")
puts "This rule is tagged as malware"
end
Returns a Boolean indicating whether the rule has the tag.
203 204 205 206 |
# File 'lib/yara/scan_result.rb', line 203 def has_tag?(tag) return false if tag.nil? @tags.include?(tag.to_s) end |
#match? ⇒ Boolean
Public: Check if this result represents a rule match.
Examples
if result.match?
puts "Rule #{result.rule_name} matched!"
end
Returns a Boolean indicating whether the rule matched.
116 117 118 |
# File 'lib/yara/scan_result.rb', line 116 def match? @is_match end |
#matches_for_pattern(pattern_name) ⇒ Object
Public: Get all matches for a specific pattern by name.
This method returns an array of PatternMatch objects for the specified pattern identifier, or an empty array if the pattern didn’t match or doesn’t exist.
pattern_name - A String or Symbol identifying the pattern (e.g., “$text1”)
Examples
# Get matches for a specific pattern
matches = result.matches_for_pattern("$suspicious_string")
matches.each { |m| puts "Found at offset #{m.offset}" }
Returns an Array of PatternMatch objects.
135 136 137 138 |
# File 'lib/yara/scan_result.rb', line 135 def matches_for_pattern(pattern_name) key = pattern_name.is_a?(Symbol) ? pattern_name : pattern_name.to_sym @pattern_matches[key] || [] end |
#metadata_bool(key) ⇒ Object
Public: Get a boolean metadata value by key.
This method provides a convenient way to access boolean metadata with automatic type checking.
key - A String or Symbol identifying the metadata key
Examples
result.(:active) # => true
result.(:enabled) # => false
Returns a Boolean value, or nil if key doesn’t exist or isn’t a boolean.
297 298 299 300 |
# File 'lib/yara/scan_result.rb', line 297 def (key) value = (key) [true, false].include?(value) ? value : nil end |
#metadata_float(key) ⇒ Object
Public: Get a float metadata value by key.
This method provides a convenient way to access float metadata with automatic type checking.
key - A String or Symbol identifying the metadata key
Examples
result.(:confidence) # => 0.95
result.(:ratio) # => 3.14
Returns a Float value, or nil if key doesn’t exist or isn’t a float.
315 316 317 318 |
# File 'lib/yara/scan_result.rb', line 315 def (key) value = (key) value.is_a?(Float) ? value : nil end |
#metadata_int(key) ⇒ Object
Public: Get an integer metadata value by key.
This method provides a convenient way to access integer metadata with automatic type checking.
key - A String or Symbol identifying the metadata key
Examples
result.(:severity) # => 8
result.(:version) # => 2
Returns an Integer value, or nil if key doesn’t exist or isn’t an integer.
261 262 263 264 |
# File 'lib/yara/scan_result.rb', line 261 def (key) value = (key) value.is_a?(Integer) ? value : nil end |
#metadata_string(key) ⇒ Object
Public: Get a string metadata value by key.
This method provides a convenient way to access string metadata with automatic type checking.
key - A String or Symbol identifying the metadata key
Examples
result.(:author) # => "Security Team"
result.(:description) # => "Detects malware"
Returns a String value, or nil if key doesn’t exist or isn’t a string.
279 280 281 282 |
# File 'lib/yara/scan_result.rb', line 279 def (key) value = (key) value.is_a?(String) ? value : nil end |
#metadata_value(key) ⇒ Object
Public: Get a typed metadata value by key.
This method provides type-safe access to metadata values, returning the actual Ruby type (String, Integer, Boolean, Float) instead of requiring manual type conversion.
key - A String or Symbol identifying the metadata key
Examples
result.(:severity) # => 8 (Integer)
result.("author") # => "Security Team" (String)
result.(:active) # => true (Boolean)
Returns the metadata value in its native Ruby type, or nil if not found.
243 244 245 246 |
# File 'lib/yara/scan_result.rb', line 243 def (key) return nil if key.nil? @rule_meta[key.to_sym] end |
#parse_meta_value(value) ⇒ Object
Internal: Parse and convert metadata values to appropriate Ruby types.
This method handles basic type conversion for metadata values extracted from rule source code. It recognizes quoted strings, boolean literals, and numeric values, converting them to appropriate Ruby types.
value - A String containing the raw metadata value from rule source
Examples
('"hello"') # => "hello"
('true') # => true
('42') # => 42
('other') # => "other"
Returns the parsed value in the appropriate Ruby type.
589 590 591 592 593 594 595 596 597 598 599 600 601 602 |
# File 'lib/yara/scan_result.rb', line 589 def (value) case value when /^".*"$/ value[1...-1] # Remove quotes when /^true$/i true when /^false$/i false when /^\d+$/ value.to_i else value end end |
#parse_metadata_from_source ⇒ Object
Internal: Parse metadata from the original rule source code.
This method uses regular expressions to extract key-value pairs from the rule’s meta section. It handles string, boolean, and numeric values with basic type conversion. This is a temporary implementation until YARA-X provides direct API access to rule metadata.
Examples
# Given rule source with:
# meta:
# author = "security_team"
# version = 1
# active = true
result.[:author] # => "security_team"
result.[:version] # => 1
result.[:active] # => true
Returns nothing (modifies @rule_meta hash).
510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 |
# File 'lib/yara/scan_result.rb', line 510 def return unless @rule_source # Extract metadata section more carefully if @rule_source =~ /meta:\s*(.*?)(?:strings:|condition:)/m = $1.strip # Parse each line in the meta section .split("\n").each do |line| line = line.strip next if line.empty? if line =~ /^(\w+)\s*=\s*(.+)$/ key, value = $1, $2 parsed_value = (value.strip) @rule_meta[key.to_sym] = parsed_value end end end end |
#parse_strings_from_source ⇒ Object
Internal: Parse string patterns from the original rule source code.
This method uses regular expressions to extract pattern definitions from the rule’s strings section. It captures both the pattern variable names (like $string1) and their values, cleaning up quotes and regex delimiters. This is a temporary implementation until YARA-X provides direct API access.
Examples
# Given rule source with:
# strings:
# $text = "hello world"
# $regex = /pattern[0-9]+/
# $hex = { 41 42 43 }
result.rule_strings[:$text] # => "hello world"
result.rule_strings[:$regex] # => "pattern[0-9]+"
result.rule_strings[:$hex] # => "{ 41 42 43 }"
Returns nothing (modifies @rule_strings hash).
551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 |
# File 'lib/yara/scan_result.rb', line 551 def parse_strings_from_source return unless @rule_source # Extract strings section more carefully if @rule_source =~ /strings:\s*(.*?)(?:condition:)/m strings_section = $1.strip # Parse each line in the strings section strings_section.split("\n").each do |line| line = line.strip next if line.empty? if line =~ /^(\$\w+)\s*=\s*(.+)$/ name, pattern = $1, $2 # Clean up the pattern (remove quotes, regex delimiters) cleaned_pattern = pattern.strip.gsub(/^["\/]|["\/]$/, '') @rule_strings[name.to_sym] = cleaned_pattern end end end end |
#pattern_matched?(pattern_name) ⇒ Boolean
Public: Check if a specific pattern had any matches.
This convenience method checks whether the specified pattern identifier had any matches during scanning.
pattern_name - A String or Symbol identifying the pattern
Examples
if result.pattern_matched?("$malware_signature")
puts "Malware signature detected!"
end
Returns a Boolean indicating whether the pattern matched.
185 186 187 |
# File 'lib/yara/scan_result.rb', line 185 def pattern_matched?(pattern_name) matches_for_pattern(pattern_name).any? end |
#qualified_name ⇒ Object
Public: Get the qualified rule name including namespace.
This method returns the fully qualified rule name, including the namespace if present. For rules in the default namespace, this is the same as rule_name.
Examples
result.qualified_name # => "malware.suspicious_behavior"
# or just "rule_name" if no namespace
Returns a String containing the qualified rule name.
220 221 222 223 224 225 226 |
# File 'lib/yara/scan_result.rb', line 220 def qualified_name if @namespace && !@namespace.empty? "#{@namespace}.#{@rule_name}" else @rule_name end end |
#total_matches ⇒ Object
Public: Get the total number of pattern matches across all patterns.
This convenience method counts the total matches across all patterns that triggered for this rule.
Examples
puts "Rule matched with #{result.total_matches} pattern matches"
Returns an Integer count of total matches.
150 151 152 |
# File 'lib/yara/scan_result.rb', line 150 def total_matches @pattern_matches.values.map(&:size).sum end |