yara-ffi 4.0.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/copilot-instructions.md +127 -9
- data/CHANGELOG.md +31 -0
- data/Gemfile.lock +1 -1
- data/README.md +30 -125
- data/USAGE.md +747 -0
- data/lib/yara/compiler.rb +161 -0
- data/lib/yara/ffi.rb +200 -0
- data/lib/yara/pattern_match.rb +178 -0
- data/lib/yara/scan_result.rb +428 -15
- data/lib/yara/scanner.rb +209 -6
- data/lib/yara/version.rb +1 -1
- data/lib/yara.rb +3 -0
- data/yara-ffi.gemspec +1 -1
- metadata +5 -2
@@ -0,0 +1,161 @@
|
|
1
|
+
module Yara
|
2
|
+
# Public: Wrapper around the YARA-X YRX_COMPILER API.
|
3
|
+
#
|
4
|
+
# This class allows adding multiple sources, defining globals before
|
5
|
+
# compilation, and building a YRX_RULES object that can be used by
|
6
|
+
# {Yara::Scanner}.
|
7
|
+
class Compiler
|
8
|
+
class CompileError < StandardError; end
|
9
|
+
|
10
|
+
def initialize(flags = 0)
|
11
|
+
@compiler_ptr_holder = ::FFI::MemoryPointer.new(:pointer)
|
12
|
+
result = Yara::FFI.yrx_compiler_create(flags, @compiler_ptr_holder)
|
13
|
+
if result != Yara::FFI.yrx_last_error && result != Yara::FFI::YRX_SUCCESS
|
14
|
+
# Defensive: use yrx_last_error for message but prefer success check
|
15
|
+
end
|
16
|
+
|
17
|
+
if result != Yara::FFI::YRX_SUCCESS
|
18
|
+
raise CompileError, "Failed to create compiler: #{Yara::FFI.yrx_last_error}"
|
19
|
+
end
|
20
|
+
@compiler = @compiler_ptr_holder.get_pointer(0)
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_source(src, origin = nil)
|
24
|
+
if origin
|
25
|
+
result = Yara::FFI.yrx_compiler_add_source_with_origin(@compiler, src, origin)
|
26
|
+
else
|
27
|
+
result = Yara::FFI.yrx_compiler_add_source(@compiler, src)
|
28
|
+
end
|
29
|
+
|
30
|
+
if result != Yara::FFI::YRX_SUCCESS
|
31
|
+
raise CompileError, "Failed to add source: #{Yara::FFI.yrx_last_error}"
|
32
|
+
end
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def define_global_str(ident, value)
|
37
|
+
result = Yara::FFI.yrx_compiler_define_global_str(@compiler, ident, value)
|
38
|
+
if result != Yara::FFI::YRX_SUCCESS
|
39
|
+
raise CompileError, "Failed to define global str #{ident}: #{Yara::FFI.yrx_last_error}"
|
40
|
+
end
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
|
44
|
+
def define_global_bool(ident, value)
|
45
|
+
result = Yara::FFI.yrx_compiler_define_global_bool(@compiler, ident, !!value)
|
46
|
+
if result != Yara::FFI::YRX_SUCCESS
|
47
|
+
raise CompileError, "Failed to define global bool #{ident}: #{Yara::FFI.yrx_last_error}"
|
48
|
+
end
|
49
|
+
nil
|
50
|
+
end
|
51
|
+
|
52
|
+
def define_global_int(ident, value)
|
53
|
+
result = Yara::FFI.yrx_compiler_define_global_int(@compiler, ident, value)
|
54
|
+
if result != Yara::FFI::YRX_SUCCESS
|
55
|
+
raise CompileError, "Failed to define global int #{ident}: #{Yara::FFI.yrx_last_error}"
|
56
|
+
end
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
|
60
|
+
def define_global_float(ident, value)
|
61
|
+
result = Yara::FFI.yrx_compiler_define_global_float(@compiler, ident, value)
|
62
|
+
if result != Yara::FFI::YRX_SUCCESS
|
63
|
+
raise CompileError, "Failed to define global float #{ident}: #{Yara::FFI.yrx_last_error}"
|
64
|
+
end
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
|
68
|
+
# Build and return a pointer to YRX_RULES. The caller is responsible for
|
69
|
+
# calling yrx_rules_destroy on the returned pointer when finished.
|
70
|
+
def build
|
71
|
+
rules_ptr = Yara::FFI.yrx_compiler_build(@compiler)
|
72
|
+
if rules_ptr.nil? || rules_ptr.null?
|
73
|
+
raise CompileError, "Failed to build rules: #{Yara::FFI.yrx_last_error}"
|
74
|
+
end
|
75
|
+
rules_ptr
|
76
|
+
end
|
77
|
+
|
78
|
+
# Public: Build and return a serialized representation of compiled rules.
|
79
|
+
#
|
80
|
+
# Compiles the currently added sources (via add_source) into a YRX_RULES
|
81
|
+
# object and serializes it into a binary blob suitable for persistence or
|
82
|
+
# transport. The returned String contains the serialized rules and can be
|
83
|
+
# deserialized later with Yara::Scanner.from_serialized or the underlying
|
84
|
+
# yrx_rules_deserialize FFI call.
|
85
|
+
#
|
86
|
+
# Returns a String containing binary serialized rules (raw bytes).
|
87
|
+
# Raises CompileError if compilation or serialization fails.
|
88
|
+
def build_serialized
|
89
|
+
rules_ptr = build
|
90
|
+
|
91
|
+
buf_ptr_holder = ::FFI::MemoryPointer.new(:pointer)
|
92
|
+
result = Yara::FFI.yrx_rules_serialize(rules_ptr, buf_ptr_holder)
|
93
|
+
|
94
|
+
# Destroy the rules pointer after serialization (we own it from build)
|
95
|
+
Yara::FFI.yrx_rules_destroy(rules_ptr)
|
96
|
+
|
97
|
+
if result != Yara::FFI::YRX_SUCCESS
|
98
|
+
raise CompileError, "Failed to serialize rules: #{Yara::FFI.yrx_last_error}"
|
99
|
+
end
|
100
|
+
|
101
|
+
buf_ptr = buf_ptr_holder.get_pointer(0)
|
102
|
+
buffer = Yara::FFI::YRX_BUFFER.new(buf_ptr)
|
103
|
+
begin
|
104
|
+
data_ptr = buffer[:data]
|
105
|
+
length = buffer[:length]
|
106
|
+
str = data_ptr.get_bytes(0, length)
|
107
|
+
return str
|
108
|
+
ensure
|
109
|
+
Yara::FFI.yrx_buffer_destroy(buf_ptr)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Return compilation errors as a parsed JSON object (Array of error objects).
|
114
|
+
# This uses yrx_compiler_errors_json which returns a YRX_BUFFER containing
|
115
|
+
# the JSON serialization. The buffer is destroyed after being converted.
|
116
|
+
def errors_json
|
117
|
+
buf_ptr_holder = ::FFI::MemoryPointer.new(:pointer)
|
118
|
+
result = Yara::FFI.yrx_compiler_errors_json(@compiler, buf_ptr_holder)
|
119
|
+
if result != Yara::FFI::YRX_SUCCESS
|
120
|
+
raise CompileError, "Failed to get errors JSON: #{Yara::FFI.yrx_last_error}"
|
121
|
+
end
|
122
|
+
|
123
|
+
buf_ptr = buf_ptr_holder.get_pointer(0)
|
124
|
+
buffer = Yara::FFI::YRX_BUFFER.new(buf_ptr)
|
125
|
+
data_ptr = buffer[:data]
|
126
|
+
length = buffer[:length]
|
127
|
+
json_str = data_ptr.read_string_length(length)
|
128
|
+
Yara::FFI.yrx_buffer_destroy(buf_ptr)
|
129
|
+
|
130
|
+
JSON.parse(json_str)
|
131
|
+
end
|
132
|
+
|
133
|
+
# Return compilation warnings as parsed JSON (Array of warning objects).
|
134
|
+
def warnings_json
|
135
|
+
buf_ptr_holder = ::FFI::MemoryPointer.new(:pointer)
|
136
|
+
result = Yara::FFI.yrx_compiler_warnings_json(@compiler, buf_ptr_holder)
|
137
|
+
if result != Yara::FFI::YRX_SUCCESS
|
138
|
+
raise CompileError, "Failed to get warnings JSON: #{Yara::FFI.yrx_last_error}"
|
139
|
+
end
|
140
|
+
|
141
|
+
buf_ptr = buf_ptr_holder.get_pointer(0)
|
142
|
+
buffer = Yara::FFI::YRX_BUFFER.new(buf_ptr)
|
143
|
+
data_ptr = buffer[:data]
|
144
|
+
length = buffer[:length]
|
145
|
+
json_str = data_ptr.read_string_length(length)
|
146
|
+
Yara::FFI.yrx_buffer_destroy(buf_ptr)
|
147
|
+
|
148
|
+
JSON.parse(json_str)
|
149
|
+
end
|
150
|
+
|
151
|
+
def destroy
|
152
|
+
Yara::FFI.yrx_compiler_destroy(@compiler) if @compiler
|
153
|
+
@compiler = nil
|
154
|
+
end
|
155
|
+
|
156
|
+
# Ensure resources are cleaned up.
|
157
|
+
def finalize
|
158
|
+
destroy
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
data/lib/yara/ffi.rb
CHANGED
@@ -178,6 +178,99 @@ module Yara
|
|
178
178
|
# C Signature: enum YRX_RESULT yrx_scanner_scan(struct YRX_SCANNER *scanner, const uint8_t *data, size_t len)
|
179
179
|
attach_function :yrx_scanner_scan, [:pointer, :pointer, :size_t], :int
|
180
180
|
|
181
|
+
# Public: Set timeout (in milliseconds) for a scanner.
|
182
|
+
#
|
183
|
+
# scanner - A Pointer to the scanner object
|
184
|
+
# timeout - A uint64_t value representing timeout in milliseconds
|
185
|
+
#
|
186
|
+
# Returns an Integer result code (YRX_SUCCESS on success).
|
187
|
+
# C Signature: enum YRX_RESULT yrx_scanner_set_timeout(struct YRX_SCANNER *scanner, uint64_t timeout)
|
188
|
+
attach_function :yrx_scanner_set_timeout, [:pointer, :ulong_long], :int
|
189
|
+
|
190
|
+
# Public: Set a global string variable for the scanner.
|
191
|
+
# C Signature: enum YRX_RESULT yrx_scanner_set_global_str(struct YRX_SCANNER *scanner, const char *ident, const char *value)
|
192
|
+
attach_function :yrx_scanner_set_global_str, [:pointer, :string, :string], :int
|
193
|
+
|
194
|
+
# Public: Set a global boolean variable for the scanner.
|
195
|
+
# C Signature: enum YRX_RESULT yrx_scanner_set_global_bool(struct YRX_SCANNER *scanner, const char *ident, bool value)
|
196
|
+
attach_function :yrx_scanner_set_global_bool, [:pointer, :string, :bool], :int
|
197
|
+
|
198
|
+
# Public: Set a global integer variable for the scanner.
|
199
|
+
# C Signature: enum YRX_RESULT yrx_scanner_set_global_int(struct YRX_SCANNER *scanner, const char *ident, int64_t value)
|
200
|
+
attach_function :yrx_scanner_set_global_int, [:pointer, :string, :long_long], :int
|
201
|
+
|
202
|
+
# Public: Set a global float variable for the scanner.
|
203
|
+
# C Signature: enum YRX_RESULT yrx_scanner_set_global_float(struct YRX_SCANNER *scanner, const char *ident, double value)
|
204
|
+
attach_function :yrx_scanner_set_global_float, [:pointer, :string, :double], :int
|
205
|
+
|
206
|
+
# YRX_COMPILER APIs
|
207
|
+
# Create a compiler: enum YRX_RESULT yrx_compiler_create(uint32_t flags, struct YRX_COMPILER **compiler)
|
208
|
+
attach_function :yrx_compiler_create, [:uint32, :pointer], :int
|
209
|
+
|
210
|
+
# Destroy a compiler: void yrx_compiler_destroy(struct YRX_COMPILER *compiler)
|
211
|
+
attach_function :yrx_compiler_destroy, [:pointer], :void
|
212
|
+
|
213
|
+
# Add source: enum YRX_RESULT yrx_compiler_add_source(struct YRX_COMPILER *compiler, const char *src)
|
214
|
+
attach_function :yrx_compiler_add_source, [:pointer, :string], :int
|
215
|
+
|
216
|
+
# Add source with origin: enum YRX_RESULT yrx_compiler_add_source_with_origin(struct YRX_COMPILER *compiler, const char *src, const char *origin)
|
217
|
+
attach_function :yrx_compiler_add_source_with_origin, [:pointer, :string, :string], :int
|
218
|
+
|
219
|
+
# Define globals on compiler
|
220
|
+
attach_function :yrx_compiler_define_global_str, [:pointer, :string, :string], :int
|
221
|
+
attach_function :yrx_compiler_define_global_bool, [:pointer, :string, :bool], :int
|
222
|
+
attach_function :yrx_compiler_define_global_int, [:pointer, :string, :long_long], :int
|
223
|
+
attach_function :yrx_compiler_define_global_float, [:pointer, :string, :double], :int
|
224
|
+
|
225
|
+
# Build compiler into rules: struct YRX_RULES *yrx_compiler_build(struct YRX_COMPILER *compiler)
|
226
|
+
attach_function :yrx_compiler_build, [:pointer], :pointer
|
227
|
+
|
228
|
+
# YRX_BUFFER utilities
|
229
|
+
# C Signature: void yrx_buffer_destroy(struct YRX_BUFFER *buf)
|
230
|
+
attach_function :yrx_buffer_destroy, [:pointer], :void
|
231
|
+
|
232
|
+
# Compiler diagnostics as JSON
|
233
|
+
# C Signature: enum YRX_RESULT yrx_compiler_errors_json(struct YRX_COMPILER *compiler, struct YRX_BUFFER **buf)
|
234
|
+
attach_function :yrx_compiler_errors_json, [:pointer, :pointer], :int
|
235
|
+
|
236
|
+
# C Signature: enum YRX_RESULT yrx_compiler_warnings_json(struct YRX_COMPILER *compiler, struct YRX_BUFFER **buf)
|
237
|
+
attach_function :yrx_compiler_warnings_json, [:pointer, :pointer], :int
|
238
|
+
|
239
|
+
# Serialize rules into a YRX_BUFFER
|
240
|
+
# C Signature: enum YRX_RESULT yrx_rules_serialize(const struct YRX_RULES *rules, struct YRX_BUFFER **buf)
|
241
|
+
attach_function :yrx_rules_serialize, [:pointer, :pointer], :int
|
242
|
+
|
243
|
+
# Deserialize rules from bytes
|
244
|
+
# C Signature: enum YRX_RESULT yrx_rules_deserialize(const uint8_t *data, size_t len, struct YRX_RULES **rules)
|
245
|
+
attach_function :yrx_rules_deserialize, [:pointer, :size_t, :pointer], :int
|
246
|
+
|
247
|
+
# Struct mapping for YRX_BUFFER
|
248
|
+
class YRX_BUFFER < ::FFI::Struct
|
249
|
+
layout :data, :pointer,
|
250
|
+
:length, :size_t
|
251
|
+
end
|
252
|
+
|
253
|
+
# Struct mapping for YRX_MATCH
|
254
|
+
class YRX_MATCH < ::FFI::Struct
|
255
|
+
layout :offset, :size_t,
|
256
|
+
:length, :size_t
|
257
|
+
end
|
258
|
+
|
259
|
+
# Struct mapping for YRX_METADATA
|
260
|
+
# Note: The value field is a union. We'll use a simple approach
|
261
|
+
# and access the value as a pointer, then interpret based on type.
|
262
|
+
class YRX_METADATA < ::FFI::Struct
|
263
|
+
layout :identifier, :pointer,
|
264
|
+
:value_type, :int,
|
265
|
+
:value, [:char, 16] # Union represented as byte array (largest possible union member)
|
266
|
+
end
|
267
|
+
|
268
|
+
# Struct mapping for YRX_METADATA_BYTES
|
269
|
+
class YRX_METADATA_BYTES < ::FFI::Struct
|
270
|
+
layout :length, :size_t,
|
271
|
+
:data, :pointer
|
272
|
+
end
|
273
|
+
|
181
274
|
# Public: Extract the identifier (name) from a rule object.
|
182
275
|
#
|
183
276
|
# This function retrieves the rule name from a YRX_RULE pointer, typically
|
@@ -278,6 +371,85 @@ module Yara
|
|
278
371
|
# C Signature: enum YRX_RESULT yrx_pattern_identifier(const struct YRX_PATTERN *pattern, const uint8_t **ident, size_t *len)
|
279
372
|
attach_function :yrx_pattern_identifier, [:pointer, :pointer, :pointer], :int
|
280
373
|
|
374
|
+
# Internal: Callback function type for match iteration.
|
375
|
+
#
|
376
|
+
# This callback is invoked for each match found during pattern match
|
377
|
+
# iteration. The callback receives pointers to the match and user data.
|
378
|
+
#
|
379
|
+
# match - A Pointer to the YRX_MATCH structure
|
380
|
+
# user_data - A Pointer to optional user-provided data
|
381
|
+
#
|
382
|
+
# C Signature: typedef void (*YRX_MATCH_CALLBACK)(const struct YRX_MATCH *match, void *user_data)
|
383
|
+
callback :match_callback, [:pointer, :pointer], :void
|
384
|
+
|
385
|
+
# Internal: Callback function type for tag iteration.
|
386
|
+
#
|
387
|
+
# This callback is invoked for each tag during rule tag iteration.
|
388
|
+
# The callback receives a pointer to the tag string and user data.
|
389
|
+
#
|
390
|
+
# tag - A Pointer to null-terminated string representing the tag
|
391
|
+
# user_data - A Pointer to optional user-provided data
|
392
|
+
#
|
393
|
+
# C Signature: typedef void (*YRX_TAG_CALLBACK)(const char *tag, void *user_data)
|
394
|
+
callback :tag_callback, [:pointer, :pointer], :void
|
395
|
+
|
396
|
+
# Public: Iterate through all matches for a specific pattern.
|
397
|
+
#
|
398
|
+
# This function calls the provided callback for each match found for the
|
399
|
+
# given pattern during scanning. Each match provides the offset and length
|
400
|
+
# of where the pattern matched in the scanned data.
|
401
|
+
#
|
402
|
+
# pattern - A Pointer to the YRX_PATTERN structure
|
403
|
+
# callback - A Proc matching the match_callback signature
|
404
|
+
# user_data - A Pointer to optional data passed to callback (can be nil)
|
405
|
+
#
|
406
|
+
# Examples
|
407
|
+
#
|
408
|
+
# callback = proc { |match_ptr, user_data| puts "Found match" }
|
409
|
+
# result = Yara::FFI.yrx_pattern_iter_matches(pattern_ptr, callback, nil)
|
410
|
+
#
|
411
|
+
# Returns an Integer result code (YRX_SUCCESS on success).
|
412
|
+
# C Signature: enum YRX_RESULT yrx_pattern_iter_matches(const struct YRX_PATTERN *pattern, YRX_MATCH_CALLBACK callback, void *user_data)
|
413
|
+
attach_function :yrx_pattern_iter_matches, [:pointer, :match_callback, :pointer], :int
|
414
|
+
|
415
|
+
# Public: Extract the namespace from a rule object.
|
416
|
+
#
|
417
|
+
# This function retrieves the rule namespace from a YRX_RULE pointer.
|
418
|
+
# The namespace is returned as a pointer and length rather than a
|
419
|
+
# null-terminated string.
|
420
|
+
#
|
421
|
+
# rule - A Pointer to the YRX_RULE structure
|
422
|
+
# ns - A FFI::MemoryPointer that will receive the namespace pointer
|
423
|
+
# len - A FFI::MemoryPointer that will receive the namespace length
|
424
|
+
#
|
425
|
+
# Examples
|
426
|
+
#
|
427
|
+
# ns_ptr = FFI::MemoryPointer.new(:pointer)
|
428
|
+
# len_ptr = FFI::MemoryPointer.new(:size_t)
|
429
|
+
# result = Yara::FFI.yrx_rule_namespace(rule_ptr, ns_ptr, len_ptr)
|
430
|
+
#
|
431
|
+
# Returns an Integer result code (YRX_SUCCESS on success).
|
432
|
+
# C Signature: enum YRX_RESULT yrx_rule_namespace(const struct YRX_RULE *rule, const uint8_t **ns, size_t *len)
|
433
|
+
attach_function :yrx_rule_namespace, [:pointer, :pointer, :pointer], :int
|
434
|
+
|
435
|
+
# Public: Iterate through all tags in a rule.
|
436
|
+
#
|
437
|
+
# This function calls the provided callback for each tag defined
|
438
|
+
# in the rule. Tags are used for categorizing and organizing rules.
|
439
|
+
#
|
440
|
+
# rule - A Pointer to the YRX_RULE structure
|
441
|
+
# callback - A Proc matching the tag_callback signature
|
442
|
+
# user_data - A Pointer to optional data passed to callback (can be nil)
|
443
|
+
#
|
444
|
+
# Examples
|
445
|
+
#
|
446
|
+
# callback = proc { |tag_ptr, user_data| puts "Found tag: #{tag_ptr.read_string}" }
|
447
|
+
# result = Yara::FFI.yrx_rule_iter_tags(rule_ptr, callback, nil)
|
448
|
+
#
|
449
|
+
# Returns an Integer result code (YRX_SUCCESS on success).
|
450
|
+
# C Signature: enum YRX_RESULT yrx_rule_iter_tags(const struct YRX_RULE *rule, YRX_TAG_CALLBACK callback, void *user_data)
|
451
|
+
attach_function :yrx_rule_iter_tags, [:pointer, :tag_callback, :pointer], :int
|
452
|
+
|
281
453
|
# Public: YARA-X result codes for operation status.
|
282
454
|
#
|
283
455
|
# These constants represent the possible return values from YARA-X functions.
|
@@ -301,5 +473,33 @@ module Yara
|
|
301
473
|
|
302
474
|
# Public: Invalid argument passed to function.
|
303
475
|
YRX_INVALID_ARGUMENT = 5
|
476
|
+
|
477
|
+
# Public: Metadata type constants for YRX_METADATA_TYPE enum.
|
478
|
+
#
|
479
|
+
# These constants represent the possible types of metadata values in YARA-X.
|
480
|
+
# They correspond to the YRX_METADATA_TYPE enum values in the C API.
|
481
|
+
|
482
|
+
# Public: 64-bit signed integer metadata value.
|
483
|
+
YRX_METADATA_TYPE_I64 = 0
|
484
|
+
|
485
|
+
# Public: 64-bit floating point metadata value.
|
486
|
+
YRX_METADATA_TYPE_F64 = 1
|
487
|
+
|
488
|
+
# Public: Boolean metadata value.
|
489
|
+
YRX_METADATA_TYPE_BOOLEAN = 2
|
490
|
+
|
491
|
+
# Public: String metadata value.
|
492
|
+
YRX_METADATA_TYPE_STRING = 3
|
493
|
+
|
494
|
+
# Public: Bytes metadata value.
|
495
|
+
YRX_METADATA_TYPE_BYTES = 4
|
496
|
+
|
497
|
+
# Public: Alternative naming following YARA-X C API documentation.
|
498
|
+
# Maps to the same values as above for compatibility.
|
499
|
+
YRX_I64 = 0
|
500
|
+
YRX_F64 = 1
|
501
|
+
YRX_BOOLEAN = 2
|
502
|
+
YRX_STRING = 3
|
503
|
+
YRX_BYTES = 4
|
304
504
|
end
|
305
505
|
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
module Yara
|
2
|
+
# Public: Represents a single pattern match found during YARA scanning.
|
3
|
+
#
|
4
|
+
# A PatternMatch contains detailed information about where and how a specific
|
5
|
+
# YARA pattern matched within the scanned data. This includes the exact offset
|
6
|
+
# and length of the match, allowing for precise forensic analysis and data
|
7
|
+
# extraction.
|
8
|
+
#
|
9
|
+
# PatternMatch instances are typically created internally during the scanning
|
10
|
+
# process and accessed through ScanResult methods like pattern_matches.
|
11
|
+
#
|
12
|
+
# Examples
|
13
|
+
#
|
14
|
+
# # Access pattern matches through scan results
|
15
|
+
# results.each do |result|
|
16
|
+
# result.pattern_matches.each do |pattern_name, matches|
|
17
|
+
# matches.each do |match|
|
18
|
+
# puts "Pattern #{pattern_name} matched at offset #{match.offset}"
|
19
|
+
# puts "Matched text: '#{match.matched_data(scanned_data)}'"
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
class PatternMatch
|
24
|
+
# Public: The byte offset where this pattern match begins in the scanned data.
|
25
|
+
attr_reader :offset
|
26
|
+
|
27
|
+
# Public: The length in bytes of this pattern match.
|
28
|
+
attr_reader :length
|
29
|
+
|
30
|
+
# Public: Initialize a new PatternMatch.
|
31
|
+
#
|
32
|
+
# This constructor is typically called internally when processing YARA-X
|
33
|
+
# match results. It captures the precise location and size of a pattern
|
34
|
+
# match within scanned data.
|
35
|
+
#
|
36
|
+
# offset - An Integer byte offset where the match begins
|
37
|
+
# length - An Integer length in bytes of the match
|
38
|
+
#
|
39
|
+
# Examples
|
40
|
+
#
|
41
|
+
# # Typically created internally during scanning
|
42
|
+
# match = PatternMatch.new(42, 10)
|
43
|
+
# match.offset # => 42
|
44
|
+
# match.length # => 10
|
45
|
+
def initialize(offset, length)
|
46
|
+
@offset = offset
|
47
|
+
@length = length
|
48
|
+
end
|
49
|
+
|
50
|
+
# Public: Extract the actual matched data from the scanned content.
|
51
|
+
#
|
52
|
+
# This method returns the exact bytes that matched the pattern by extracting
|
53
|
+
# the appropriate slice from the original scanned data. This is useful for
|
54
|
+
# forensic analysis, debugging rules, and understanding what triggered a match.
|
55
|
+
#
|
56
|
+
# data - A String containing the original data that was scanned
|
57
|
+
#
|
58
|
+
# Examples
|
59
|
+
#
|
60
|
+
# # Extract what actually matched
|
61
|
+
# scan_data = "hello world test data"
|
62
|
+
# match = PatternMatch.new(6, 5) # matches "world"
|
63
|
+
# match.matched_data(scan_data) # => "world"
|
64
|
+
#
|
65
|
+
# Returns a String containing the matched bytes.
|
66
|
+
# Returns empty String if offset/length are outside data bounds.
|
67
|
+
def matched_data(data)
|
68
|
+
return "" if offset < 0 || offset >= data.bytesize
|
69
|
+
return "" if length <= 0 || offset + length > data.bytesize
|
70
|
+
|
71
|
+
data.byteslice(offset, length)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Public: Get the end offset of this match (exclusive).
|
75
|
+
#
|
76
|
+
# This convenience method calculates the byte position immediately after
|
77
|
+
# the last byte of this match, which is useful for range operations and
|
78
|
+
# avoiding overlapping matches.
|
79
|
+
#
|
80
|
+
# Examples
|
81
|
+
#
|
82
|
+
# match = PatternMatch.new(10, 5)
|
83
|
+
# match.end_offset # => 15
|
84
|
+
#
|
85
|
+
# Returns an Integer representing the end offset (exclusive).
|
86
|
+
def end_offset
|
87
|
+
offset + length
|
88
|
+
end
|
89
|
+
|
90
|
+
# Public: Check if this match overlaps with another match.
|
91
|
+
#
|
92
|
+
# This method determines whether two pattern matches have any overlapping
|
93
|
+
# bytes. This is useful for analyzing complex rules with multiple patterns
|
94
|
+
# or detecting redundant matches.
|
95
|
+
#
|
96
|
+
# other - Another PatternMatch instance to compare against
|
97
|
+
#
|
98
|
+
# Examples
|
99
|
+
#
|
100
|
+
# match1 = PatternMatch.new(10, 5) # bytes 10-14
|
101
|
+
# match2 = PatternMatch.new(12, 5) # bytes 12-16
|
102
|
+
# match1.overlaps?(match2) # => true
|
103
|
+
#
|
104
|
+
# match3 = PatternMatch.new(20, 5) # bytes 20-24
|
105
|
+
# match1.overlaps?(match3) # => false
|
106
|
+
#
|
107
|
+
# Returns a Boolean indicating whether the matches overlap.
|
108
|
+
def overlaps?(other)
|
109
|
+
offset < other.end_offset && end_offset > other.offset
|
110
|
+
end
|
111
|
+
|
112
|
+
# Public: Get a human-readable string representation of this match.
|
113
|
+
#
|
114
|
+
# This method provides a concise string representation showing the key
|
115
|
+
# details of the match, useful for debugging and logging purposes.
|
116
|
+
#
|
117
|
+
# Examples
|
118
|
+
#
|
119
|
+
# match = PatternMatch.new(42, 10)
|
120
|
+
# match.to_s # => "PatternMatch(offset: 42, length: 10)"
|
121
|
+
#
|
122
|
+
# Returns a String representation of this match.
|
123
|
+
def to_s
|
124
|
+
"PatternMatch(offset: #{offset}, length: #{length})"
|
125
|
+
end
|
126
|
+
|
127
|
+
# Public: Detailed inspection string with all attributes.
|
128
|
+
#
|
129
|
+
# Provides a complete string representation including all match attributes,
|
130
|
+
# useful for debugging and development purposes.
|
131
|
+
#
|
132
|
+
# Examples
|
133
|
+
#
|
134
|
+
# match = PatternMatch.new(42, 10)
|
135
|
+
# match.inspect # => "#<Yara::PatternMatch:0x... @offset=42, @length=10>"
|
136
|
+
#
|
137
|
+
# Returns a String with detailed object information.
|
138
|
+
def inspect
|
139
|
+
"#<#{self.class}:0x#{object_id.to_s(16)} @offset=#{@offset}, @length=#{@length}>"
|
140
|
+
end
|
141
|
+
|
142
|
+
# Public: Compare two PatternMatch objects for equality.
|
143
|
+
#
|
144
|
+
# Two matches are considered equal if they have the same offset and length.
|
145
|
+
# This is useful for deduplicating matches or comparing results.
|
146
|
+
#
|
147
|
+
# other - Another PatternMatch instance to compare against
|
148
|
+
#
|
149
|
+
# Examples
|
150
|
+
#
|
151
|
+
# match1 = PatternMatch.new(10, 5)
|
152
|
+
# match2 = PatternMatch.new(10, 5)
|
153
|
+
# match1 == match2 # => true
|
154
|
+
#
|
155
|
+
# Returns a Boolean indicating equality.
|
156
|
+
def ==(other)
|
157
|
+
other.is_a?(PatternMatch) && offset == other.offset && length == other.length
|
158
|
+
end
|
159
|
+
|
160
|
+
# Public: Generate hash code for this match.
|
161
|
+
#
|
162
|
+
# Uses offset and length to generate a hash code, enabling PatternMatch
|
163
|
+
# instances to be used as hash keys or in sets.
|
164
|
+
#
|
165
|
+
# Examples
|
166
|
+
#
|
167
|
+
# match = PatternMatch.new(42, 10)
|
168
|
+
# {match => "info"} # Can be used as hash key
|
169
|
+
#
|
170
|
+
# Returns an Integer hash code.
|
171
|
+
def hash
|
172
|
+
[offset, length].hash
|
173
|
+
end
|
174
|
+
|
175
|
+
# Public: Enable hash equality based on hash code.
|
176
|
+
alias_method :eql?, :==
|
177
|
+
end
|
178
|
+
end
|