line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Parse::YARA; |
2
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
27373
|
use strict; |
|
1
|
|
|
|
|
4
|
|
|
1
|
|
|
|
|
45
|
|
4
|
1
|
|
|
1
|
|
6
|
use warnings; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
37
|
|
5
|
1
|
|
|
1
|
|
5
|
use Carp qw(carp); |
|
1
|
|
|
|
|
14
|
|
|
1
|
|
|
|
|
92
|
|
6
|
1
|
|
|
1
|
|
1013
|
use Tie::IxHash; |
|
1
|
|
|
|
|
6656
|
|
|
1
|
|
|
|
|
32
|
|
7
|
1
|
|
|
1
|
|
9
|
use File::Basename; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
21953
|
|
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
our $VERSION = '0.02'; |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
=head1 NAME |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
Parse::YARA - Parse and create YARA rules |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
=head1 VERSION |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
Version 0.02 |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
=head1 SYNOPSIS |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
use Parse::YARA; |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
my $rule_string = 'rule sample_rule : sample |
24
|
|
|
|
|
|
|
{ |
25
|
|
|
|
|
|
|
meta: |
26
|
|
|
|
|
|
|
info = "sample rule" |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
strings: |
29
|
|
|
|
|
|
|
$ = "anon1" |
30
|
|
|
|
|
|
|
$ = "anon2" |
31
|
|
|
|
|
|
|
$test_string = "test_string" |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
condition: |
34
|
|
|
|
|
|
|
any of them |
35
|
|
|
|
|
|
|
}'; |
36
|
|
|
|
|
|
|
my $rule_string_obj = Parse::YARA->new(); |
37
|
|
|
|
|
|
|
$rule_string_obj->parse($rule_string); |
38
|
|
|
|
|
|
|
$rule_string_obj->add_string_modifier('sample_rule', '$test_string', 'all'); |
39
|
|
|
|
|
|
|
print $rule_string_obj->as_string; |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
my $rule_element_hashref = { |
42
|
|
|
|
|
|
|
modifier => 'private', |
43
|
|
|
|
|
|
|
rule_id => 'sample_hash_rule', |
44
|
|
|
|
|
|
|
tag => [ |
45
|
|
|
|
|
|
|
'tag1', |
46
|
|
|
|
|
|
|
'tag2' |
47
|
|
|
|
|
|
|
], |
48
|
|
|
|
|
|
|
meta => { |
49
|
|
|
|
|
|
|
'info' => 'sample hash rule', |
50
|
|
|
|
|
|
|
'site' => 'http://search.cpan.org/~moofu/' |
51
|
|
|
|
|
|
|
}, |
52
|
|
|
|
|
|
|
strings => { |
53
|
|
|
|
|
|
|
'$' => { |
54
|
|
|
|
|
|
|
value => 'anon1', |
55
|
|
|
|
|
|
|
type => 'text', |
56
|
|
|
|
|
|
|
}, |
57
|
|
|
|
|
|
|
'$$' => { |
58
|
|
|
|
|
|
|
value => 'anon2', |
59
|
|
|
|
|
|
|
type => 'text' |
60
|
|
|
|
|
|
|
}, |
61
|
|
|
|
|
|
|
'$test_string' => { |
62
|
|
|
|
|
|
|
value => 'test_string', |
63
|
|
|
|
|
|
|
type => 'text' |
64
|
|
|
|
|
|
|
}, |
65
|
|
|
|
|
|
|
'$test_hex_string' => { |
66
|
|
|
|
|
|
|
value => '{ AA BB CC DD }', |
67
|
|
|
|
|
|
|
type => 'hex' |
68
|
|
|
|
|
|
|
}, |
69
|
|
|
|
|
|
|
'$test_regex_string' => { |
70
|
|
|
|
|
|
|
value => '/.*/', |
71
|
|
|
|
|
|
|
type => 'regex' |
72
|
|
|
|
|
|
|
}, |
73
|
|
|
|
|
|
|
}, |
74
|
|
|
|
|
|
|
condition => 'true' |
75
|
|
|
|
|
|
|
}; |
76
|
|
|
|
|
|
|
my $rule_hash_obj = Parse::YARA->new(rulehash => $rule_element_hashref); |
77
|
|
|
|
|
|
|
print $rule_hash_obj->as_string; |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
my $rule_file = '/path/to/rules.yar'; |
80
|
|
|
|
|
|
|
my $rule_file_obj = Parse::YARA->new(file => $rule_file); |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
my $rule_obj = Parse::YARA->new(); |
83
|
|
|
|
|
|
|
$rule_obj->set_rule_modifier('new_rule', 'global'); |
84
|
|
|
|
|
|
|
$rule_obj->set_condition('new_rule', 'one of them'); |
85
|
|
|
|
|
|
|
$rule_obj->add_tag('new_rule', 'test_only'); |
86
|
|
|
|
|
|
|
$rule_obj->add_meta('new_rule', 'author', 'Leigh'); |
87
|
|
|
|
|
|
|
$rule_obj->add_meta('new_rule', 'site', 'http://search.cpan.org/~moofu/'); |
88
|
|
|
|
|
|
|
$rule_obj->add_anonymous_string('new_rule', 'anonymous', 'text'); |
89
|
|
|
|
|
|
|
$rule_obj->add_string('new_rule', '$string1', 'A test string', 'text'); |
90
|
|
|
|
|
|
|
$rule_obj->add_string('new_rule', '$string2', 'Another example', 'text'); |
91
|
|
|
|
|
|
|
$rule_obj->add_string_modifier('new_rule', '$string1', 'ascii'); |
92
|
|
|
|
|
|
|
print $rule_obj->as_string; |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
$rule_obj->modify_meta('new_rule', 'author', 'Leigh Thompson'); |
95
|
|
|
|
|
|
|
$rule_obj->modify_string('new_rule', '$string1', 'An example string'); |
96
|
|
|
|
|
|
|
print $rule_obj->as_string; |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
$rule_obj->remove_string_modifier('new_rule', '$string1', 'ascii'); |
99
|
|
|
|
|
|
|
$rule_obj->remove_tag('new_rule', 'test_only'); |
100
|
|
|
|
|
|
|
$rule_obj->remove_meta('new_rule', 'site'); |
101
|
|
|
|
|
|
|
$rule_obj->remove_anonymous_string('new_rule', 'anonymous'); |
102
|
|
|
|
|
|
|
$rule_obj->remove_string('new_rule', '$string2'); |
103
|
|
|
|
|
|
|
print $rule_obj->as_string; |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
=head1 NOTE FOR PERL >= 5.18 |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
Hash order will not be guaranteed so the use of Tie::IxHash is required for passing hashes into the module if order within the YARA rule is required. |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
For the example given above, the following steps would need to be taken: |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
use Tie::IxHash; |
112
|
|
|
|
|
|
|
my $rule_element_hashref; |
113
|
|
|
|
|
|
|
my $rule_element_hashref_knot = tie(%{$rule_element_hashref}, 'Tie::IxHash'); |
114
|
|
|
|
|
|
|
my $meta_hashref; |
115
|
|
|
|
|
|
|
my $meta_hashref_knot = tie(%{$meta_hashref}, 'Tie::IxHash'); |
116
|
|
|
|
|
|
|
my $strings_hashref; |
117
|
|
|
|
|
|
|
my $strings_hashref_knot = tie(%{$strings_hashref}, 'Tie::IxHash'); |
118
|
|
|
|
|
|
|
$meta_hashref->{info} = 'sample hash rule'; |
119
|
|
|
|
|
|
|
$meta_hashref->{site} = 'http://search.cpan.org/~moofu/'; |
120
|
|
|
|
|
|
|
$strings_hashref->{'$'} = { value => 'anon1', type => 'text' }; |
121
|
|
|
|
|
|
|
$strings_hashref->{'$$'} = { value => 'anon2', type => 'text' }; |
122
|
|
|
|
|
|
|
$strings_hashref->{'$test_string'}= { value => 'test_string', type => 'text' }; |
123
|
|
|
|
|
|
|
$strings_hashref->{'$test_hex_string'} = { value => '{ AA BB CC DD }', type => 'hex' }; |
124
|
|
|
|
|
|
|
$strings_hashref->{'$test_regex_string'} = { value => '/.*/', type => 'regex' }; |
125
|
|
|
|
|
|
|
$rule_element_hashref = { |
126
|
|
|
|
|
|
|
modifier => 'private', |
127
|
|
|
|
|
|
|
rule_id => 'sample_hash_rule_tied', |
128
|
|
|
|
|
|
|
tag => [ |
129
|
|
|
|
|
|
|
'tag1', |
130
|
|
|
|
|
|
|
'tag2' |
131
|
|
|
|
|
|
|
], |
132
|
|
|
|
|
|
|
meta => $meta_hashref, |
133
|
|
|
|
|
|
|
strings => $strings_hashref, |
134
|
|
|
|
|
|
|
condition => 'true' |
135
|
|
|
|
|
|
|
}; |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
=cut |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
# Set some reserved words |
140
|
|
|
|
|
|
|
our @RESERVED_ARRAY = qw/ all in and include any index ascii indexes at int8 condition int16 contains int32 entrypoint matches false meta filesize nocase fullword not for or global of private rule rva section strings them true uint8 uint16 uint32 wide /; |
141
|
|
|
|
|
|
|
our %RESERVED_WORDS = map { $_ => 1 } @RESERVED_ARRAY; |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
=head1 METHODS |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
These are the object methods that can be used to read, add or modify any part of a YARA rule. |
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
=over |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
=item new() |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
Create a new C object, and return it. There are a couple of options when creating the object: |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
=over 4 |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
=item new(disable_includes => 0, $verbose => 0) |
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
Create an unpopulated object, that can be filled in using the individual rule element methods, or can be populated with the read_file method. |
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
=item new(rule => $rule, disable_includes => 0, $verbose => 0) |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
Create an object by providing a YARA rule as a string value. |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
=item new(file => $file, $disable_includes => 0, $verbose => 0) |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
Parse a file containing one or more YARA rules and create objects for each rule. |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
The include option is turned on by default, this will ensure that all files included in the file being parsed are read in by the parser. Turn this off by setting disable_includes => 1. |
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
=item new(rulehash => $rule_element_hashref, $disable_includes => 0, $verbose => 0) |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
Create an object based on a prepared hash reference. |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
my $rule_element_hashref = { |
174
|
|
|
|
|
|
|
modifier => 'global', |
175
|
|
|
|
|
|
|
rule_id => 'sample_hash_rule', |
176
|
|
|
|
|
|
|
tag => [ |
177
|
|
|
|
|
|
|
'tag1', |
178
|
|
|
|
|
|
|
'tag2' |
179
|
|
|
|
|
|
|
], |
180
|
|
|
|
|
|
|
meta => { |
181
|
|
|
|
|
|
|
'info' => 'sample hash rule', |
182
|
|
|
|
|
|
|
'site' => 'http://search.cpan.org/~moofu/' |
183
|
|
|
|
|
|
|
}, |
184
|
|
|
|
|
|
|
strings => { |
185
|
|
|
|
|
|
|
'$' => { |
186
|
|
|
|
|
|
|
value => 'anon1', |
187
|
|
|
|
|
|
|
type => 'text', |
188
|
|
|
|
|
|
|
}, |
189
|
|
|
|
|
|
|
'$$' => { |
190
|
|
|
|
|
|
|
value => 'anon2', |
191
|
|
|
|
|
|
|
type => 'text' |
192
|
|
|
|
|
|
|
}, |
193
|
|
|
|
|
|
|
'$test_string' => { |
194
|
|
|
|
|
|
|
value => 'test_string', |
195
|
|
|
|
|
|
|
type => 'text' |
196
|
|
|
|
|
|
|
}, |
197
|
|
|
|
|
|
|
'$test_hex_string' => { |
198
|
|
|
|
|
|
|
value => '{ AA BB CC DD }', |
199
|
|
|
|
|
|
|
type => 'hex' |
200
|
|
|
|
|
|
|
}, |
201
|
|
|
|
|
|
|
'$test_regex_string' => { |
202
|
|
|
|
|
|
|
value => '/.*/', |
203
|
|
|
|
|
|
|
type => 'regex' |
204
|
|
|
|
|
|
|
}, |
205
|
|
|
|
|
|
|
}, |
206
|
|
|
|
|
|
|
condition => 'all of them' |
207
|
|
|
|
|
|
|
}; |
208
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
=cut |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
sub new { |
212
|
8
|
|
|
8
|
1
|
2958
|
my ($class, %args) = @_; |
213
|
8
|
|
|
|
|
20
|
my $self = {}; |
214
|
|
|
|
|
|
|
|
215
|
8
|
|
|
|
|
25
|
bless($self, $class); |
216
|
8
|
|
|
|
|
41
|
$self->_init(%args); |
217
|
8
|
|
|
|
|
32
|
return $self; |
218
|
|
|
|
|
|
|
} |
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
# _init |
221
|
|
|
|
|
|
|
# |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
sub _init { |
224
|
8
|
|
|
8
|
|
23
|
my ($self, %args) = @_; |
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
# Turn on verbose if requested |
227
|
8
|
50
|
|
|
|
31
|
if($args{verbose}) { |
228
|
0
|
|
|
|
|
0
|
$self->{verbose} = 1; |
229
|
0
|
|
|
|
|
0
|
delete($args{verbose}); |
230
|
|
|
|
|
|
|
} |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
# Turn on includes if requested |
233
|
8
|
100
|
|
|
|
26
|
if(!$args{disable_includes}) { |
234
|
6
|
|
|
|
|
23
|
$self->{include} = 1; |
235
|
6
|
|
|
|
|
13
|
delete($args{disable_includes}); |
236
|
|
|
|
|
|
|
} |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
# Tie a hash to contain all rules so we can assure order |
239
|
8
|
|
|
|
|
12
|
$self->{rules_knot} = tie(%{$self->{rules}}, 'Tie::IxHash'); |
|
8
|
|
|
|
|
59
|
|
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
# Check what we were passed and behave appropriately |
242
|
8
|
100
|
|
|
|
147
|
if($args{rule}) { |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
243
|
1
|
|
|
|
|
5
|
$self->parse($args{rule}); |
244
|
|
|
|
|
|
|
} elsif($args{file}) { |
245
|
|
|
|
|
|
|
# This in turn will call $self->parse on the contents of the file |
246
|
2
|
|
|
|
|
9
|
$self->read_file($args{file}); |
247
|
|
|
|
|
|
|
} elsif($args{rulehash}) { |
248
|
|
|
|
|
|
|
# We can't proceed without a rule_id so make sure it's set |
249
|
|
|
|
|
|
|
# then add it to the new rules object |
250
|
1
|
50
|
|
|
|
6
|
if($args{rulehash}->{rule_id}) { |
251
|
1
|
|
|
|
|
4
|
my $rule_id = $args{rulehash}->{rule_id}; |
252
|
1
|
|
|
|
|
3
|
delete($args{rulehash}->{rule_id}); |
253
|
|
|
|
|
|
|
# Loop through the remaining components of the rulehash |
254
|
|
|
|
|
|
|
# and add them to the rules object |
255
|
1
|
|
|
|
|
9
|
foreach my $key (keys(%{$args{rulehash}})) { |
|
1
|
|
|
|
|
5
|
|
256
|
5
|
|
|
|
|
56
|
my $sub = "_$key"; |
257
|
5
|
|
|
|
|
22
|
$self->$sub($rule_id, $args{rulehash}->{$key}); |
258
|
|
|
|
|
|
|
} |
259
|
|
|
|
|
|
|
} else { |
260
|
0
|
|
|
|
|
0
|
carp("Cannot add rule with no rule_id\n"); |
261
|
|
|
|
|
|
|
} |
262
|
|
|
|
|
|
|
} |
263
|
|
|
|
|
|
|
|
264
|
8
|
|
|
|
|
98
|
return $self; |
265
|
|
|
|
|
|
|
} |
266
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
# _modifier |
268
|
|
|
|
|
|
|
# Adds any modifiers passed to new() as a hashref |
269
|
|
|
|
|
|
|
# by calling set_rule_modifier |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
sub _modifier { |
272
|
1
|
|
|
1
|
|
3
|
my ($self, $rule_id, $modifier) = @_; |
273
|
|
|
|
|
|
|
|
274
|
1
|
|
|
|
|
5
|
$self->set_rule_modifier($rule_id, $modifier); |
275
|
|
|
|
|
|
|
} |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
# _tag |
278
|
|
|
|
|
|
|
# Adds any tags passed to new() as a hashref |
279
|
|
|
|
|
|
|
# by calling add_tag for each tag in the array |
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
sub _tag { |
282
|
1
|
|
|
1
|
|
2
|
my ($self, $rule_id, $tags) = @_; |
283
|
|
|
|
|
|
|
|
284
|
1
|
|
|
|
|
3
|
foreach(@{$tags}) { |
|
1
|
|
|
|
|
3
|
|
285
|
2
|
|
|
|
|
20
|
$self->add_tag($rule_id, $_); |
286
|
|
|
|
|
|
|
} |
287
|
|
|
|
|
|
|
} |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
# _meta_ |
290
|
|
|
|
|
|
|
# Adds any meta attributes passed to new() as a hashref |
291
|
|
|
|
|
|
|
# by calling add_meta for each meta name/value in the hash |
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
sub _meta { |
294
|
1
|
|
|
1
|
|
2
|
my ($self, $rule_id, $meta_hash) = @_; |
295
|
|
|
|
|
|
|
|
296
|
1
|
|
|
|
|
2
|
foreach my $meta_name (keys(%{$meta_hash})) { |
|
1
|
|
|
|
|
7
|
|
297
|
2
|
|
|
|
|
49
|
$self->add_meta($rule_id, $meta_name, $meta_hash->{$meta_name}); |
298
|
|
|
|
|
|
|
} |
299
|
|
|
|
|
|
|
} |
300
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
# _strings |
302
|
|
|
|
|
|
|
# Adds any string attributes passed to new() as a hashref |
303
|
|
|
|
|
|
|
# by calling add_anonymous_string or add_string as appropriate |
304
|
|
|
|
|
|
|
# for each string name/value in the hash |
305
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
sub _strings { |
307
|
1
|
|
|
1
|
|
3
|
my ($self, $rule_id, $string_hash) = @_; |
308
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
# We need to make sure the strings hash element exists |
310
|
|
|
|
|
|
|
#if(!$self->{rules}->{$rule_id}->{strings}) { |
311
|
|
|
|
|
|
|
# $self->{rules}->{$rule_id}->{strings} = undef; |
312
|
|
|
|
|
|
|
#} |
313
|
|
|
|
|
|
|
|
314
|
|
|
|
|
|
|
# Loop through each string in the hashref |
315
|
1
|
|
|
|
|
2
|
foreach my $string_name (keys(%{$string_hash})) { |
|
1
|
|
|
|
|
5
|
|
316
|
|
|
|
|
|
|
# If we find an array we need to loop through that too |
317
|
2
|
50
|
|
|
|
49
|
if(ref($string_hash->{$string_name}) eq "ARRAY") { |
318
|
0
|
|
|
|
|
0
|
foreach(@{$string_hash->{$string_name}}) { |
|
0
|
|
|
|
|
0
|
|
319
|
|
|
|
|
|
|
# We can add multiple anonymous strings so look for them then add them |
320
|
0
|
0
|
|
|
|
0
|
if($string_name eq "\$") { |
321
|
0
|
|
|
|
|
0
|
$self->add_anonymous_string($rule_id, $_->{value}, $_->{type}); |
322
|
|
|
|
|
|
|
# Otherwise we need to bail as we can't add multiple strings with the same string name |
323
|
|
|
|
|
|
|
} else { |
324
|
0
|
|
|
|
|
0
|
carp("$rule_id: error parsing strings, found multiple strings with name $string_name"); |
325
|
|
|
|
|
|
|
} |
326
|
|
|
|
|
|
|
} |
327
|
|
|
|
|
|
|
} else { |
328
|
|
|
|
|
|
|
# For unique string names, simply add the string |
329
|
2
|
|
|
|
|
23
|
$self->add_string($rule_id, $string_name, $string_hash->{$string_name}->{value}, $string_hash->{$string_name}->{type}); |
330
|
|
|
|
|
|
|
} |
331
|
|
|
|
|
|
|
} |
332
|
|
|
|
|
|
|
} |
333
|
|
|
|
|
|
|
|
334
|
|
|
|
|
|
|
# _condition |
335
|
|
|
|
|
|
|
# Adds the condition passed to new() as a hashref |
336
|
|
|
|
|
|
|
# by calling set_condition |
337
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
sub _condition { |
339
|
1
|
|
|
1
|
|
2
|
my ($self, $rule_id, $condition) = @_; |
340
|
|
|
|
|
|
|
|
341
|
1
|
|
|
|
|
4
|
$self->set_condition($rule_id, $condition); |
342
|
|
|
|
|
|
|
} |
343
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
# _is_valid |
345
|
|
|
|
|
|
|
# Checks to see if a word is valid given context |
346
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
sub _is_valid { |
348
|
512
|
|
|
512
|
|
4672
|
my ($self, $str, $type) = @_; |
349
|
512
|
|
|
|
|
648
|
my $valid = 1; |
350
|
|
|
|
|
|
|
|
351
|
512
|
50
|
|
|
|
808
|
if($str) { |
352
|
512
|
100
|
|
|
|
1336
|
if($type eq "rule_id") { |
|
|
50
|
|
|
|
|
|
353
|
|
|
|
|
|
|
# Can contain any alphanumeric character and the underscore character, but the first character can not be a digit |
354
|
170
|
50
|
|
|
|
848
|
if($str =~ /^\d/) { |
|
|
50
|
|
|
|
|
|
355
|
0
|
|
|
|
|
0
|
carp("$str: rule_id cannot start with a digit"); |
356
|
0
|
|
|
|
|
0
|
$valid = 0; |
357
|
|
|
|
|
|
|
} elsif($str !~ /^[a-zA-Z0-9_]+$/) { |
358
|
0
|
|
|
|
|
0
|
carp("$str: rule_id can only contain alphanumeric and underscore characters"); |
359
|
0
|
|
|
|
|
0
|
$valid = 0; |
360
|
|
|
|
|
|
|
} |
361
|
|
|
|
|
|
|
} elsif($type eq "string_name") { |
362
|
|
|
|
|
|
|
# Must start with a $ |
363
|
342
|
100
|
|
|
|
1114
|
if($str !~ /^\$/) { |
364
|
1
|
|
|
|
|
102
|
carp("String identifier must start with a \$: $str"); |
365
|
1
|
|
|
|
|
130
|
$valid = 0; |
366
|
|
|
|
|
|
|
} |
367
|
|
|
|
|
|
|
} |
368
|
|
|
|
|
|
|
} else { |
369
|
0
|
|
|
|
|
0
|
carp("Can't check empty string"); |
370
|
0
|
|
|
|
|
0
|
$valid = 0; |
371
|
|
|
|
|
|
|
} |
372
|
|
|
|
|
|
|
|
373
|
512
|
|
|
|
|
1344
|
return $valid; |
374
|
|
|
|
|
|
|
} |
375
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
# _check_reserved |
377
|
|
|
|
|
|
|
# Checks to see if a word is reserved and exits if one is found |
378
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
sub _check_reserved { |
380
|
381
|
|
|
381
|
|
575
|
my ($self, $str, $type) = @_; |
381
|
381
|
|
|
|
|
373
|
my $reserved; |
382
|
|
|
|
|
|
|
|
383
|
381
|
50
|
|
|
|
709
|
if($str) { |
384
|
|
|
|
|
|
|
map { |
385
|
381
|
100
|
|
|
|
962
|
if(exists($RESERVED_WORDS{$_})) { |
|
851
|
|
|
|
|
4464
|
|
386
|
|
|
|
|
|
|
# These lists are through trial and error |
387
|
243
|
100
|
66
|
|
|
2838
|
if($type eq "meta" and $_ !~ /^(?:include|indexes)$/) { |
|
|
100
|
100
|
|
|
|
|
|
|
100
|
|
|
|
|
|
388
|
1
|
|
|
|
|
4
|
$reserved = 1; |
389
|
|
|
|
|
|
|
} elsif($type eq "condition" and /^(?:include|ascii|condition|meta|nocase|fullword|strings|wide)$/) { |
390
|
1
|
|
|
|
|
22
|
$reserved = 1; |
391
|
|
|
|
|
|
|
} elsif($type !~ /^(?:meta|condition)$/) { |
392
|
1
|
|
|
|
|
6
|
$reserved = 1; |
393
|
|
|
|
|
|
|
} |
394
|
|
|
|
|
|
|
} |
395
|
|
|
|
|
|
|
} split(/\s+/, $str); |
396
|
|
|
|
|
|
|
} else { |
397
|
0
|
|
|
|
|
0
|
carp("Can't check empty string\n"); |
398
|
|
|
|
|
|
|
} |
399
|
|
|
|
|
|
|
|
400
|
381
|
|
|
|
|
2326
|
return $reserved; |
401
|
|
|
|
|
|
|
} |
402
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
=back |
404
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
=item parse($rule_string) |
406
|
|
|
|
|
|
|
|
407
|
|
|
|
|
|
|
Reads in a string of one or more rules and parses it into a hashref that can be manipulated by the other functions. |
408
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
=cut |
410
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
sub parse { |
412
|
10
|
|
|
10
|
1
|
42
|
my ($self, $rule_string) = @_; |
413
|
10
|
|
|
|
|
18
|
my $modifier; |
414
|
|
|
|
|
|
|
my $rule_id; |
415
|
0
|
|
|
|
|
0
|
my $tags; |
416
|
10
|
|
|
|
|
13
|
my $position = 1; |
417
|
10
|
|
|
|
|
19
|
my $rule_data = {}; |
418
|
10
|
|
|
|
|
17
|
my $knot = tie(%{$rule_data}, 'Tie::IxHash'); |
|
10
|
|
|
|
|
53
|
|
419
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
# Strip comments, I have replaced the comments with a newline as otherwise it was stripping the newline, this hasn't broken anything so far. |
421
|
|
|
|
|
|
|
# For an explanation, see: http://perldoc.perl.org/perlfaq6.html#How-do-I-use-a-regular-expression-to-strip-C-style-comments-from-a-file%3F |
422
|
10
|
100
|
|
|
|
281
|
$rule_string =~ s#/\*[^*]*\*+([^/*][^*]*\*+)*/|//([^\\]|[^\n][\n]?)*?\n|("(\\.|[^"\\])*"|'(\\.|[^'\\])*'|.[^/"'\\]*)#defined $3 ? $3 : "\n"#gse; |
|
534
|
|
|
|
|
3771
|
|
423
|
10
|
|
|
|
|
126
|
$rule_string =~ s/\n\/\/.*//g; |
424
|
|
|
|
|
|
|
|
425
|
|
|
|
|
|
|
# Tidy up any strings that come in with strange formatting |
426
|
|
|
|
|
|
|
# Rules with the close brace for previous rule on the same line |
427
|
10
|
|
|
|
|
807
|
$rule_string =~ s/\n\s*}\s*(rule.*)/\n}\n$1/g; |
428
|
|
|
|
|
|
|
# String / Meta names on one line but values on the next |
429
|
10
|
|
|
|
|
21306
|
$rule_string =~ s/\s*(\S+)\s*=\s*\n\s*(\S+)/\n\t\t$1 = $2\n/g; |
430
|
|
|
|
|
|
|
# Multiple strings on the same line |
431
|
10
|
|
|
|
|
86
|
$rule_string =~ s/(\/)(\$\S+\s*=)/$1\n\t\t$2/g; |
432
|
10
|
|
|
|
|
77
|
$rule_string =~ s/(")(\$\S+\s*=)/$1\n\t\t$2/g; |
433
|
10
|
|
|
|
|
80
|
$rule_string =~ s/(})(\$\S+\s*=)/$1\n\t\t$2/g; |
434
|
|
|
|
|
|
|
|
435
|
|
|
|
|
|
|
# Parse the rule line by line |
436
|
10
|
|
|
|
|
64
|
while($rule_string =~ /([^\n]+\n)?/g) { |
437
|
1582
|
|
|
|
|
14397
|
my $line = $1; |
438
|
|
|
|
|
|
|
|
439
|
|
|
|
|
|
|
# Need to find a rule_id before we can start |
440
|
1582
|
100
|
100
|
|
|
17599
|
if($line and $line =~ /^(?:(global|private)\s+)?rule\s+([a-zA-Z0-9_]+)(?:\s*:\s*([^{]*))?\s*({.*})?/) { |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
441
|
170
|
|
|
|
|
263
|
chomp($line); |
442
|
170
|
|
|
|
|
300
|
$rule_id = $2; |
443
|
170
|
|
|
|
|
643
|
$rule_data->{$rule_id}->{modifier} = $1; |
444
|
170
|
|
|
|
|
4112
|
$rule_data->{$rule_id}->{tags} = $3; |
445
|
|
|
|
|
|
|
# Make sure we don't set the rule_id to a YARA reserved word |
446
|
170
|
50
|
|
|
|
1567
|
if($self->_check_reserved($rule_id, 'rule_id')) { |
|
|
50
|
|
|
|
|
|
447
|
0
|
|
|
|
|
0
|
carp("Cannot use reserved word as rule identifier: $rule_id"); |
448
|
0
|
|
|
|
|
0
|
next; |
449
|
|
|
|
|
|
|
} elsif(!$self->_is_valid($rule_id, 'rule_id')) { |
450
|
|
|
|
|
|
|
# Or to an invalid one |
451
|
0
|
|
|
|
|
0
|
next; |
452
|
|
|
|
|
|
|
} |
453
|
|
|
|
|
|
|
|
454
|
170
|
|
|
|
|
674
|
$rule_data->{$rule_id}->{raw} = ''; |
455
|
|
|
|
|
|
|
# If $4 exists, we have found a single line rule so add all the data to raw |
456
|
170
|
50
|
|
|
|
1944
|
if($4) { |
457
|
0
|
|
|
|
|
0
|
$rule_data->{$rule_id}->{raw} = $4; |
458
|
|
|
|
|
|
|
} |
459
|
|
|
|
|
|
|
# Because their is no rule_id set we can't |
460
|
|
|
|
|
|
|
# add the line to the rule_data |
461
|
|
|
|
|
|
|
} elsif(!$rule_id) { |
462
|
26
|
|
|
|
|
113
|
next; |
463
|
|
|
|
|
|
|
# Now we have a rule_id, add the current |
464
|
|
|
|
|
|
|
# line to the rule_data ready for parsing |
465
|
|
|
|
|
|
|
} elsif($line) { |
466
|
1236
|
|
|
|
|
4938
|
$rule_data->{$rule_id}->{raw} .= $line; |
467
|
|
|
|
|
|
|
} |
468
|
|
|
|
|
|
|
} |
469
|
|
|
|
|
|
|
|
470
|
|
|
|
|
|
|
# Extract meta, strings and conditions from |
471
|
|
|
|
|
|
|
# each rule and add it to the hashref |
472
|
10
|
|
|
|
|
17
|
foreach my $rule (keys(%{$rule_data})) { |
|
10
|
|
|
|
|
60
|
|
473
|
|
|
|
|
|
|
# Tidy up the raw rule string to make sure we can easily parse this |
474
|
|
|
|
|
|
|
# line by line |
475
|
170
|
|
|
|
|
1574
|
$rule_data->{$rule}->{raw} =~ s/(strings:|meta:|condition:)/\n\t$1\n\t\t/g; |
476
|
170
|
|
|
|
|
3628
|
$rule_data->{$rule}->{raw} =~ s/}\s*$/\n}/; |
477
|
170
|
|
|
|
|
2616
|
$self->_parse_meta($rule, $rule_data->{$rule}->{raw}); |
478
|
170
|
|
|
|
|
944
|
$self->_parse_strings($rule, $rule_data->{$rule}->{raw}); |
479
|
170
|
|
|
|
|
814
|
$self->_parse_condition($rule, $rule_data->{$rule}->{raw}); |
480
|
170
|
100
|
|
|
|
2784
|
if($rule_data->{$rule}->{modifier}) { |
481
|
10
|
|
|
|
|
91
|
$self->set_rule_modifier($rule, $rule_data->{$rule}->{modifier}); |
482
|
|
|
|
|
|
|
} |
483
|
|
|
|
|
|
|
|
484
|
|
|
|
|
|
|
# If we found any tags add each one as an element |
485
|
|
|
|
|
|
|
# of an array to the tags key |
486
|
170
|
100
|
|
|
|
1576
|
if($rule_data->{$rule}->{tags}) { |
487
|
18
|
|
|
|
|
156
|
foreach(split(/\s+/, $rule_data->{$rule}->{tags})) { |
488
|
28
|
|
|
|
|
243
|
$self->add_tag($rule, $_); |
489
|
|
|
|
|
|
|
} |
490
|
|
|
|
|
|
|
} |
491
|
|
|
|
|
|
|
# This is useful for testing |
492
|
170
|
50
|
|
|
|
1480
|
if($self->{verbose}) { |
493
|
0
|
|
|
|
|
0
|
print "Added rule: $rule"; |
494
|
0
|
0
|
0
|
|
|
0
|
if($self->{rules}->{$rule}->{tags} and scalar($self->{rules}->{$rule}->{tags}) > 0) { |
495
|
0
|
|
|
|
|
0
|
print " :"; |
496
|
0
|
|
|
|
|
0
|
foreach my $tag (@{$self->{rules}->{$rule}->{tags}}) { |
|
0
|
|
|
|
|
0
|
|
497
|
0
|
|
|
|
|
0
|
print " $tag"; |
498
|
|
|
|
|
|
|
} |
499
|
|
|
|
|
|
|
} |
500
|
0
|
|
|
|
|
0
|
print "\n"; |
501
|
|
|
|
|
|
|
} |
502
|
|
|
|
|
|
|
} |
503
|
|
|
|
|
|
|
} |
504
|
|
|
|
|
|
|
|
505
|
|
|
|
|
|
|
=item read_file ( $file ) |
506
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
Reads in a YARA rules file and any included files (if not disabled) and calls $self->parse() on the contents of the file. |
508
|
|
|
|
|
|
|
|
509
|
|
|
|
|
|
|
=cut |
510
|
|
|
|
|
|
|
|
511
|
|
|
|
|
|
|
sub read_file { |
512
|
8
|
|
|
8
|
1
|
24
|
my ($self, $file) = @_; |
513
|
8
|
|
|
|
|
13
|
my $rules = ""; |
514
|
8
|
|
|
|
|
13
|
my @include_files; |
515
|
|
|
|
|
|
|
|
516
|
8
|
50
|
|
|
|
29
|
if($self->{verbose}) { print "Parsing file: $file\n" }; |
|
0
|
|
|
|
|
0
|
|
517
|
|
|
|
|
|
|
|
518
|
8
|
50
|
|
|
|
627
|
open(RULESFILE, "<", $file) or die $!; |
519
|
|
|
|
|
|
|
# Loop through rules file and find all YARA rules |
520
|
8
|
|
|
|
|
285
|
while() { |
521
|
|
|
|
|
|
|
# If we are including files, push to an array so we can |
522
|
|
|
|
|
|
|
# read them all in later |
523
|
1720
|
100
|
100
|
|
|
7649
|
if($self->{include} and /^include\s+"(.*?)"/) { |
|
|
100
|
|
|
|
|
|
524
|
4
|
|
|
|
|
208
|
push(@include_files, dirname($file) . "/" . $1); |
525
|
|
|
|
|
|
|
} elsif(!/^include\s+"(.*?)"/) { |
526
|
1712
|
|
|
|
|
4398
|
$rules .= $_; |
527
|
|
|
|
|
|
|
} |
528
|
|
|
|
|
|
|
} |
529
|
8
|
|
|
|
|
291
|
close(RULESFILE); |
530
|
|
|
|
|
|
|
|
531
|
8
|
|
|
|
|
30
|
$self->parse($rules); |
532
|
|
|
|
|
|
|
|
533
|
|
|
|
|
|
|
# Parse any include's we found earlier on |
534
|
8
|
|
|
|
|
337
|
foreach my $include_file (@include_files) { |
535
|
4
|
|
|
|
|
16
|
$self->read_file($include_file); |
536
|
|
|
|
|
|
|
} |
537
|
|
|
|
|
|
|
|
538
|
|
|
|
|
|
|
} |
539
|
|
|
|
|
|
|
|
540
|
|
|
|
|
|
|
# _parse_meta |
541
|
|
|
|
|
|
|
# Extracts meta attributes from a YARA rule string and adds them to the rule hashref. |
542
|
|
|
|
|
|
|
|
543
|
|
|
|
|
|
|
sub _parse_meta { |
544
|
170
|
|
|
170
|
|
1164
|
my ($self, $rule_id, $raw) = @_; |
545
|
170
|
|
|
|
|
185
|
my $flag; |
546
|
|
|
|
|
|
|
|
547
|
|
|
|
|
|
|
# Parse the rule data line by line |
548
|
170
|
|
|
|
|
683
|
while($raw =~ /([^\n]+)\n?/g) { |
549
|
1832
|
|
|
|
|
3346
|
my $line = $1; |
550
|
|
|
|
|
|
|
|
551
|
|
|
|
|
|
|
# Once we find the meta tag set a flag |
552
|
|
|
|
|
|
|
# so that we know to start adding to |
553
|
|
|
|
|
|
|
# to the rule object |
554
|
1832
|
100
|
|
|
|
3769
|
if($line =~ /meta:/) { |
555
|
6
|
|
|
|
|
8
|
$flag = 1; |
556
|
6
|
|
|
|
|
20
|
next; |
557
|
|
|
|
|
|
|
} |
558
|
|
|
|
|
|
|
|
559
|
|
|
|
|
|
|
# When we get passed the meta section we should encounter |
560
|
|
|
|
|
|
|
# either a strings or condition section, if we find these |
561
|
|
|
|
|
|
|
# set the flag to 0 so we stop adding to the rule object |
562
|
1826
|
100
|
100
|
|
|
3701
|
if($flag and $line =~ /^\s*(?:strings:|condition:)\s*$/) { |
563
|
6
|
|
|
|
|
10
|
$flag = 0; |
564
|
6
|
|
|
|
|
19
|
next; |
565
|
|
|
|
|
|
|
} |
566
|
|
|
|
|
|
|
|
567
|
|
|
|
|
|
|
# Now we're in the meta section, so if we find a valid |
568
|
|
|
|
|
|
|
# meta attribute/value pair then add it to the rule object |
569
|
1820
|
100
|
100
|
|
|
8443
|
if($flag and $line =~ /^\s*(\S[^=\s]*)\s*=\s*(\S+.*)$/) { |
570
|
16
|
|
|
|
|
30
|
my $meta_name = $1; |
571
|
16
|
|
|
|
|
30
|
my $meta_val = $2; |
572
|
16
|
100
|
|
|
|
63
|
if($meta_val !~ /^(\d+|true|false)$/) { |
573
|
8
|
|
|
|
|
31
|
$meta_val =~ s/^\s*"//; |
574
|
8
|
|
|
|
|
33
|
$meta_val =~ s/"\s*$//; |
575
|
|
|
|
|
|
|
} |
576
|
16
|
|
|
|
|
45
|
$self->add_meta($rule_id, $meta_name, $meta_val); |
577
|
|
|
|
|
|
|
} |
578
|
|
|
|
|
|
|
} |
579
|
|
|
|
|
|
|
} |
580
|
|
|
|
|
|
|
|
581
|
|
|
|
|
|
|
|
582
|
|
|
|
|
|
|
# _parse_strings |
583
|
|
|
|
|
|
|
# Extracts string attributes from a YARA rule string and adds them to the rule hashref. |
584
|
|
|
|
|
|
|
|
585
|
|
|
|
|
|
|
sub _parse_strings { |
586
|
170
|
|
|
170
|
|
1257
|
my ($self, $rule_id, $raw) = @_; |
587
|
170
|
|
|
|
|
208
|
my $flag; |
588
|
|
|
|
|
|
|
# Parse the rule data line by line |
589
|
170
|
|
|
|
|
787
|
while($raw =~ /([^\n]+)\n?/g) { |
590
|
1832
|
|
|
|
|
3940
|
my $line = $1; |
591
|
|
|
|
|
|
|
|
592
|
|
|
|
|
|
|
# Once we find the strings tag set a flag |
593
|
|
|
|
|
|
|
# so that we know to start adding to |
594
|
|
|
|
|
|
|
# to the rule object |
595
|
1832
|
100
|
|
|
|
4382
|
if($line =~ /strings:/) { |
596
|
122
|
|
|
|
|
151
|
$flag = 1; |
597
|
122
|
|
|
|
|
416
|
next; |
598
|
|
|
|
|
|
|
} |
599
|
|
|
|
|
|
|
|
600
|
|
|
|
|
|
|
# When we get passed the strings section we should encounter |
601
|
|
|
|
|
|
|
# either a meta or condition section, if we find these |
602
|
|
|
|
|
|
|
# set the flag to 0 so we stop adding to the rule object |
603
|
1710
|
100
|
100
|
|
|
6001
|
if($flag and $line =~ /^\s*(?:meta:|condition:)\s*$/) { |
604
|
122
|
|
|
|
|
141
|
$flag = 0; |
605
|
122
|
|
|
|
|
429
|
next; |
606
|
|
|
|
|
|
|
} |
607
|
|
|
|
|
|
|
|
608
|
|
|
|
|
|
|
# Now we're in the strings section, so if we find a valid |
609
|
|
|
|
|
|
|
# strings attribute/value pair then add it to the rule object |
610
|
1588
|
100
|
100
|
|
|
8888
|
if($flag and $line =~ /^\s*(\$[a-zA-Z0-9_]*)\s*=\s*((?:"|\/|{)\s*\S+.*(?:"|\/|}))\s*(.*)$/) { |
611
|
382
|
|
|
|
|
655
|
my $str_name = $1; |
612
|
382
|
|
|
|
|
628
|
my $str_val = $2; |
613
|
382
|
|
|
|
|
524
|
my $str_mods = $3; |
614
|
382
|
|
|
|
|
569
|
my $str_type = "text"; |
615
|
|
|
|
|
|
|
# In YARA hex strings are bounded by {} |
616
|
382
|
100
|
|
|
|
1227
|
if($str_val =~ /^{.*?}$/) { |
|
|
100
|
|
|
|
|
|
617
|
182
|
|
|
|
|
249
|
$str_type = "hex"; |
618
|
|
|
|
|
|
|
# And regex are bounded by // |
619
|
|
|
|
|
|
|
} elsif($str_val =~ /^\/.*?\/$/) { |
620
|
10
|
|
|
|
|
17
|
$str_type = "regex"; |
621
|
|
|
|
|
|
|
# Everything else should be a string. |
622
|
|
|
|
|
|
|
# I remove the quotes now for easier use throughout the module and re-add them when calling as_string |
623
|
|
|
|
|
|
|
} else { |
624
|
190
|
|
|
|
|
691
|
$str_val =~ s/^"//; |
625
|
190
|
|
|
|
|
715
|
$str_val =~ s/"$//; |
626
|
|
|
|
|
|
|
} |
627
|
382
|
100
|
|
|
|
737
|
if($str_name eq "\$") { |
628
|
44
|
|
|
|
|
103
|
$self->add_anonymous_string($rule_id, $str_val, $str_type); |
629
|
|
|
|
|
|
|
} else { |
630
|
338
|
|
|
|
|
785
|
$self->add_string($rule_id, $str_name, $str_val, $str_type); |
631
|
|
|
|
|
|
|
} |
632
|
382
|
100
|
|
|
|
10098
|
if($str_mods) { |
633
|
26
|
|
|
|
|
68
|
foreach(split(/\s+/, $str_mods)) { |
634
|
36
|
|
|
|
|
204
|
$self->add_string_modifier($rule_id, $str_name, $_) |
635
|
|
|
|
|
|
|
} |
636
|
|
|
|
|
|
|
} |
637
|
|
|
|
|
|
|
} |
638
|
|
|
|
|
|
|
} |
639
|
|
|
|
|
|
|
} |
640
|
|
|
|
|
|
|
|
641
|
|
|
|
|
|
|
# _parse_condition |
642
|
|
|
|
|
|
|
# Extracts the condition from a YARA rule string and adds it to the rule hashref. |
643
|
|
|
|
|
|
|
|
644
|
|
|
|
|
|
|
sub _parse_condition { |
645
|
170
|
|
|
170
|
|
1318
|
my ($self, $rule_id, $raw) = @_; |
646
|
170
|
|
|
|
|
193
|
my $flag; |
647
|
170
|
|
|
|
|
209
|
my $condition = ""; |
648
|
|
|
|
|
|
|
|
649
|
|
|
|
|
|
|
# Parse the rule data line by line |
650
|
170
|
|
|
|
|
614
|
while($raw =~ /([^\n]+)\n?/g) { |
651
|
1832
|
|
|
|
|
3173
|
my $line = $1; |
652
|
|
|
|
|
|
|
|
653
|
|
|
|
|
|
|
# Once we find the condition tag set a flag |
654
|
|
|
|
|
|
|
# so that we know to start adding to |
655
|
|
|
|
|
|
|
# to the rule object |
656
|
1832
|
100
|
|
|
|
4090
|
if($line =~ /condition:/) { |
657
|
170
|
|
|
|
|
180
|
$flag = 1; |
658
|
170
|
|
|
|
|
559
|
next; |
659
|
|
|
|
|
|
|
} |
660
|
|
|
|
|
|
|
|
661
|
|
|
|
|
|
|
# When we get passed the condition section we should encounter |
662
|
|
|
|
|
|
|
# either a meta or strings section (or EOR), if we find these |
663
|
|
|
|
|
|
|
# set the flag to 0 so we stop adding to the rule object |
664
|
1662
|
50
|
66
|
|
|
4885
|
if($flag and $line =~ /^\s*(?:meta:|strings:)\s*$/) { |
665
|
0
|
|
|
|
|
0
|
$flag = 0; |
666
|
0
|
|
|
|
|
0
|
next; |
667
|
|
|
|
|
|
|
} |
668
|
|
|
|
|
|
|
|
669
|
|
|
|
|
|
|
# Now we're in the condition section, so if we find a valid |
670
|
|
|
|
|
|
|
# condition then append it to the condition string |
671
|
1662
|
100
|
100
|
|
|
10057
|
if($flag and $line =~ /^\s*(\S+.*)$/ and $line !~ /^\s*({|})\s*$/) { |
|
|
|
100
|
|
|
|
|
672
|
|
|
|
|
|
|
# To deal with multi-line conditions we check first then append |
673
|
|
|
|
|
|
|
# to the condition string til we're done before setting the |
674
|
|
|
|
|
|
|
# condition below |
675
|
174
|
100
|
|
|
|
327
|
if(length($condition) == 0) { |
676
|
170
|
|
|
|
|
898
|
$condition = "$1\n"; |
677
|
|
|
|
|
|
|
} else { |
678
|
4
|
|
|
|
|
24
|
$condition .= "\t\t$1\n"; |
679
|
|
|
|
|
|
|
} |
680
|
|
|
|
|
|
|
} |
681
|
|
|
|
|
|
|
} |
682
|
|
|
|
|
|
|
# Remove any trailing new line and then set the condition |
683
|
170
|
|
|
|
|
221
|
chomp($condition); |
684
|
170
|
|
|
|
|
371
|
$self->set_condition($rule_id, $condition); |
685
|
|
|
|
|
|
|
} |
686
|
|
|
|
|
|
|
|
687
|
|
|
|
|
|
|
=item set_rule_modifier($rule_id, $modifier) |
688
|
|
|
|
|
|
|
|
689
|
|
|
|
|
|
|
Set a modifier on a rule. The value for modifier must be one of the following strings: |
690
|
|
|
|
|
|
|
private |
691
|
|
|
|
|
|
|
global |
692
|
|
|
|
|
|
|
|
693
|
|
|
|
|
|
|
If modifier is set to undef the current modifier (if any) will be removed. |
694
|
|
|
|
|
|
|
|
695
|
|
|
|
|
|
|
=cut |
696
|
|
|
|
|
|
|
|
697
|
|
|
|
|
|
|
sub set_rule_modifier { |
698
|
15
|
|
|
15
|
1
|
1267
|
my ($self, $rule_id, $modifier) = @_; |
699
|
|
|
|
|
|
|
|
700
|
15
|
100
|
|
|
|
32
|
if($modifier) { |
701
|
|
|
|
|
|
|
# Their are only two valid rule modifiers, private and global |
702
|
|
|
|
|
|
|
# make sure we are only setting to these values, otherwise bail |
703
|
14
|
100
|
|
|
|
56
|
if($modifier =~ /^(?:private|global)$/) { |
704
|
13
|
|
|
|
|
77
|
$self->{rules}->{$rule_id}->{modifier} = $modifier; |
705
|
|
|
|
|
|
|
} else { |
706
|
1
|
|
|
|
|
176
|
carp("$rule_id: unable to set rule modifier to invalid value: $modifier"); |
707
|
|
|
|
|
|
|
} |
708
|
|
|
|
|
|
|
} else { |
709
|
|
|
|
|
|
|
# If this is set to undef, assume the rule modifier requires deletion |
710
|
1
|
|
|
|
|
6
|
delete($self->{rules}->{$rule_id}->{modifier}); |
711
|
|
|
|
|
|
|
} |
712
|
|
|
|
|
|
|
} |
713
|
|
|
|
|
|
|
|
714
|
|
|
|
|
|
|
=item set_condition($rule_id, $condition) |
715
|
|
|
|
|
|
|
|
716
|
|
|
|
|
|
|
Sets the value of the condition to $condition. |
717
|
|
|
|
|
|
|
|
718
|
|
|
|
|
|
|
=cut |
719
|
|
|
|
|
|
|
|
720
|
|
|
|
|
|
|
sub set_condition { |
721
|
173
|
|
|
173
|
1
|
1316
|
my ($self, $rule_id, $condition) = @_; |
722
|
173
|
|
|
|
|
194
|
my $flag = 1; |
723
|
|
|
|
|
|
|
|
724
|
173
|
50
|
|
|
|
269
|
if($condition) { |
725
|
|
|
|
|
|
|
# Make sure we are only setting the condition to something valid |
726
|
173
|
100
|
|
|
|
364
|
if($self->_check_reserved($condition, 'condition')) { |
727
|
1
|
|
|
|
|
99
|
carp("$rule_id: cannot set condition to a reserved word: $condition"); |
728
|
|
|
|
|
|
|
} else { |
729
|
|
|
|
|
|
|
# Then set the condition directly |
730
|
172
|
|
|
|
|
785
|
$self->{rules}->{$rule_id}->{condition} = $condition; |
731
|
|
|
|
|
|
|
} |
732
|
|
|
|
|
|
|
} else { |
733
|
0
|
|
|
|
|
0
|
carp("$rule_id: can't set a null condition"); |
734
|
|
|
|
|
|
|
} |
735
|
|
|
|
|
|
|
} |
736
|
|
|
|
|
|
|
|
737
|
|
|
|
|
|
|
=item add_tag($rule_id, $tag) |
738
|
|
|
|
|
|
|
|
739
|
|
|
|
|
|
|
Adds a tag to the rule. |
740
|
|
|
|
|
|
|
|
741
|
|
|
|
|
|
|
=cut |
742
|
|
|
|
|
|
|
|
743
|
|
|
|
|
|
|
sub add_tag { |
744
|
32
|
|
|
32
|
1
|
571
|
my ($self, $rule_id, $tag) = @_; |
745
|
32
|
|
|
|
|
45
|
my $flag = 1; |
746
|
|
|
|
|
|
|
|
747
|
|
|
|
|
|
|
# Maybe not the most efficient, but I don't expect |
748
|
|
|
|
|
|
|
# the use of a large number of tags |
749
|
|
|
|
|
|
|
# Check to see that the tag is both valid, and not |
750
|
|
|
|
|
|
|
# already set. If either of these checks fails |
751
|
|
|
|
|
|
|
# set the flag to 0 and print an error. |
752
|
32
|
|
|
|
|
41
|
foreach(@{$self->{rules}->{$rule_id}->{tags}}) { |
|
32
|
|
|
|
|
123
|
|
753
|
18
|
50
|
|
|
|
129
|
if($_ eq $tag) { |
|
|
100
|
|
|
|
|
|
754
|
0
|
|
|
|
|
0
|
$flag = 0; |
755
|
0
|
|
|
|
|
0
|
carp("$rule_id: $tag already set."); |
756
|
0
|
|
|
|
|
0
|
last; |
757
|
|
|
|
|
|
|
} elsif($self->_check_reserved($tag, 'tag')) { |
758
|
1
|
|
|
|
|
2
|
$flag = 0; |
759
|
1
|
|
|
|
|
112
|
carp("$rule_id: cannot set tag to a reserved word: $tag"); |
760
|
1
|
|
|
|
|
89
|
last; |
761
|
|
|
|
|
|
|
} |
762
|
|
|
|
|
|
|
} |
763
|
|
|
|
|
|
|
|
764
|
|
|
|
|
|
|
# If we didn't find any issues above, push the new tag to the tags array |
765
|
32
|
100
|
|
|
|
203
|
if($flag) { |
766
|
31
|
|
|
|
|
36
|
push(@{$self->{rules}->{$rule_id}->{tags}}, $tag); |
|
31
|
|
|
|
|
143
|
|
767
|
|
|
|
|
|
|
} |
768
|
|
|
|
|
|
|
} |
769
|
|
|
|
|
|
|
|
770
|
|
|
|
|
|
|
=item add_meta($rule_id, $meta_name, $meta_val) |
771
|
|
|
|
|
|
|
|
772
|
|
|
|
|
|
|
Adds a meta name/value pair to the rule. |
773
|
|
|
|
|
|
|
|
774
|
|
|
|
|
|
|
=cut |
775
|
|
|
|
|
|
|
|
776
|
|
|
|
|
|
|
sub add_meta { |
777
|
20
|
|
|
20
|
1
|
931
|
my ($self, $rule_id, $meta_name, $meta_val) = @_; |
778
|
|
|
|
|
|
|
|
779
|
|
|
|
|
|
|
# Make sure the meta hash element exists |
780
|
20
|
100
|
|
|
|
98
|
if(!$self->{rules}->{$rule_id}->{meta}) { |
781
|
|
|
|
|
|
|
# If not, we need to tie a new hash to ensure meta order is maintained |
782
|
7
|
|
|
|
|
178
|
$self->{rules}->{$rule_id}->{meta_knot} = tie(%{$self->{rules}->{$rule_id}->{meta}}, 'Tie::IxHash'); |
|
7
|
|
|
|
|
31
|
|
783
|
|
|
|
|
|
|
} |
784
|
|
|
|
|
|
|
# Check validity of meta name before adding it to the rule object |
785
|
20
|
100
|
|
|
|
343
|
if($self->_check_reserved($meta_name, 'meta')) { |
|
|
50
|
|
|
|
|
|
786
|
1
|
|
|
|
|
114
|
carp("$rule_id: $meta_name contains a reserved word, please try again"); |
787
|
|
|
|
|
|
|
# Make sure we don't add duplicate meta names as this is invalid |
788
|
|
|
|
|
|
|
} elsif($self->{rules}->{$rule_id}->{meta}->{$meta_name}) { |
789
|
0
|
|
|
|
|
0
|
carp("$rule_id: $meta_name already set, select a new name or try modify_meta()"); |
790
|
|
|
|
|
|
|
} else { |
791
|
19
|
|
|
|
|
273
|
$self->{rules}->{$rule_id}->{meta}->{$meta_name} = $meta_val; |
792
|
|
|
|
|
|
|
} |
793
|
|
|
|
|
|
|
} |
794
|
|
|
|
|
|
|
|
795
|
|
|
|
|
|
|
=item add_string_modifier($rule_id, $str_name, $modifier) |
796
|
|
|
|
|
|
|
|
797
|
|
|
|
|
|
|
Set a modifier on a string. The value for the modifier must be one of the following strings: |
798
|
|
|
|
|
|
|
wide |
799
|
|
|
|
|
|
|
nocase |
800
|
|
|
|
|
|
|
ascii |
801
|
|
|
|
|
|
|
fullword |
802
|
|
|
|
|
|
|
|
803
|
|
|
|
|
|
|
Use of the keyword 'all' will set all modifiers on a string. |
804
|
|
|
|
|
|
|
|
805
|
|
|
|
|
|
|
=cut |
806
|
|
|
|
|
|
|
|
807
|
|
|
|
|
|
|
sub add_string_modifier { |
808
|
42
|
|
|
42
|
1
|
1089
|
my ($self, $rule_id, $str_name, $modifier) = @_; |
809
|
|
|
|
|
|
|
|
810
|
42
|
50
|
|
|
|
75
|
if($modifier) { |
811
|
|
|
|
|
|
|
# There are only four valid string modifiers so make sure we only add one of these |
812
|
42
|
100
|
|
|
|
145
|
if($modifier =~ /^(?:wide|nocase|ascii|fullword)$/) { |
|
|
100
|
|
|
|
|
|
813
|
40
|
|
|
|
|
43
|
push(@{$self->{rules}->{$rule_id}->{strings}->{$str_name}->{modifier}}, $modifier); |
|
40
|
|
|
|
|
165
|
|
814
|
|
|
|
|
|
|
# Unless we use the keyword 'all' in which case set all four string modifiers |
815
|
|
|
|
|
|
|
} elsif($modifier eq "all") { |
816
|
1
|
|
|
|
|
8
|
$self->{rules}->{$rule_id}->{strings}->{$str_name}->{modifier} = [ 'wide', 'ascii', 'nocase', 'fullword' ]; |
817
|
|
|
|
|
|
|
} else { |
818
|
1
|
|
|
|
|
98
|
carp("$rule_id: unable to set string modifier to invalid value: $modifier"); |
819
|
|
|
|
|
|
|
} |
820
|
|
|
|
|
|
|
} else { |
821
|
0
|
|
|
|
|
0
|
carp("$rule_id: cannot set undefined modifier"); |
822
|
|
|
|
|
|
|
} |
823
|
|
|
|
|
|
|
} |
824
|
|
|
|
|
|
|
|
825
|
|
|
|
|
|
|
=item remove_string_modifier($rule_id, $str_name, $modifier) |
826
|
|
|
|
|
|
|
|
827
|
|
|
|
|
|
|
Remove a modifier on a string. The value for the modifier must be one of the following strings: |
828
|
|
|
|
|
|
|
wide |
829
|
|
|
|
|
|
|
nocase |
830
|
|
|
|
|
|
|
ascii |
831
|
|
|
|
|
|
|
fullword |
832
|
|
|
|
|
|
|
|
833
|
|
|
|
|
|
|
Use of the keyword 'all' will remove all modifiers from a string. |
834
|
|
|
|
|
|
|
|
835
|
|
|
|
|
|
|
=cut |
836
|
|
|
|
|
|
|
|
837
|
|
|
|
|
|
|
sub remove_string_modifier { |
838
|
5
|
|
|
5
|
1
|
9374
|
my ($self, $rule_id, $str_name, $modifier) = @_; |
839
|
|
|
|
|
|
|
|
840
|
5
|
50
|
|
|
|
15
|
if($modifier) { |
841
|
|
|
|
|
|
|
# There are only four valid string modifiers so make sure we only both trying to remove these |
842
|
5
|
100
|
|
|
|
40
|
if($modifier =~ /^(?:wide|nocase|ascii|fullword)$/) { |
|
|
50
|
|
|
|
|
|
843
|
4
|
|
|
|
|
6
|
@{$self->{rules}->{$rule_id}->{strings}->{$str_name}->{modifier}} = grep { $_ ne $modifier } @{$self->{rules}->{$rule_id}->{strings}->{$str_name}->{modifier}}; |
|
4
|
|
|
|
|
15
|
|
|
10
|
|
|
|
|
406
|
|
|
4
|
|
|
|
|
196
|
|
844
|
|
|
|
|
|
|
# Unless we use the keyword 'all' in which case remove all four string modifiers |
845
|
|
|
|
|
|
|
} elsif($modifier eq "all") { |
846
|
1
|
|
|
|
|
6
|
$self->{rules}->{$rule_id}->{strings}->{$str_name}->{modifier} = []; |
847
|
|
|
|
|
|
|
} else { |
848
|
0
|
|
|
|
|
0
|
carp("$rule_id: unabled to remove invalid modifier: $modifier"); |
849
|
|
|
|
|
|
|
} |
850
|
|
|
|
|
|
|
} else { |
851
|
0
|
|
|
|
|
0
|
carp("$rule_id: cannot remove undefined modifier"); |
852
|
|
|
|
|
|
|
} |
853
|
|
|
|
|
|
|
} |
854
|
|
|
|
|
|
|
|
855
|
|
|
|
|
|
|
=item add_anonymous_string($rule_id, $str_val, $str_type) |
856
|
|
|
|
|
|
|
|
857
|
|
|
|
|
|
|
Allows the addition of anonymous strings |
858
|
|
|
|
|
|
|
|
859
|
|
|
|
|
|
|
=cut |
860
|
|
|
|
|
|
|
|
861
|
|
|
|
|
|
|
sub add_anonymous_string { |
862
|
46
|
|
|
46
|
1
|
993
|
my ($self, $rule_id, $str_val, $str_type) = @_; |
863
|
|
|
|
|
|
|
|
864
|
|
|
|
|
|
|
# Check if we've previously added strings |
865
|
46
|
100
|
|
|
|
188
|
if(!$self->{rules}->{$rule_id}->{strings}) { |
866
|
|
|
|
|
|
|
# If not, we need to tie a new hash to ensure string order is maintained |
867
|
8
|
|
|
|
|
192
|
$self->{rules}->{$rule_id}->{strings_knot} = tie(%{$self->{rules}->{$rule_id}->{strings}}, 'Tie::IxHash'); |
|
8
|
|
|
|
|
62
|
|
868
|
|
|
|
|
|
|
} |
869
|
46
|
|
|
|
|
596
|
my $val = { value => $str_val, type => $str_type, modifier => [] }; |
870
|
46
|
|
|
|
|
62
|
my $last_anon_string = undef; |
871
|
|
|
|
|
|
|
# So that we can add multiple anonymous strings as new hash elements |
872
|
|
|
|
|
|
|
# I am cheating and adding an extra $ for each new anon string |
873
|
|
|
|
|
|
|
# Here we find the latest anon string and then append a $ before |
874
|
|
|
|
|
|
|
# setting the value against this new string name |
875
|
46
|
|
|
|
|
51
|
foreach(keys(%{$self->{rules}->{$rule_id}->{strings}})) { |
|
46
|
|
|
|
|
161
|
|
876
|
153
|
100
|
|
|
|
1615
|
if(/^\$+$/) { |
877
|
149
|
|
|
|
|
228
|
$last_anon_string = $_; |
878
|
|
|
|
|
|
|
} |
879
|
|
|
|
|
|
|
} |
880
|
46
|
|
|
|
|
262
|
$last_anon_string .= '$'; |
881
|
46
|
|
|
|
|
178
|
$self->{rules}->{$rule_id}->{strings}->{$last_anon_string} = $val; |
882
|
|
|
|
|
|
|
} |
883
|
|
|
|
|
|
|
|
884
|
|
|
|
|
|
|
=item add_string($rule_id, $str_name, $str_val, $str_type) |
885
|
|
|
|
|
|
|
|
886
|
|
|
|
|
|
|
Allows the addition of a new string name/value pair. |
887
|
|
|
|
|
|
|
|
888
|
|
|
|
|
|
|
=cut |
889
|
|
|
|
|
|
|
|
890
|
|
|
|
|
|
|
sub add_string { |
891
|
343
|
|
|
343
|
1
|
1670
|
my ($self, $rule_id, $str_name, $str_val, $str_type) = @_; |
892
|
|
|
|
|
|
|
|
893
|
|
|
|
|
|
|
# Check if we've previously added strings |
894
|
343
|
100
|
|
|
|
1594
|
if(!$self->{rules}->{$rule_id}->{strings}) { |
895
|
|
|
|
|
|
|
# If not, we need to tie a new hash to ensure string order is maintained |
896
|
115
|
|
|
|
|
2544
|
$self->{rules}->{$rule_id}->{strings_knot} = tie(%{$self->{rules}->{$rule_id}->{strings}}, 'Tie::IxHash'); |
|
115
|
|
|
|
|
431
|
|
897
|
|
|
|
|
|
|
} |
898
|
|
|
|
|
|
|
# Make sure we don't add duplicate strings as this is invalid |
899
|
343
|
100
|
|
|
|
5837
|
if($self->{rules}->{$rule_id}->{strings}->{$str_name}) { |
|
|
100
|
|
|
|
|
|
900
|
1
|
|
|
|
|
118
|
carp("$rule_id: $str_name already set, pick a new name or try modify_string()"); |
901
|
|
|
|
|
|
|
# Check validity of string before adding it to the rule object |
902
|
|
|
|
|
|
|
} elsif($self->_is_valid($str_name, 'string_name')) { |
903
|
341
|
|
|
|
|
1535
|
my $val = { value => $str_val, type => $str_type, modifier => [] }; |
904
|
341
|
|
|
|
|
1431
|
$self->{rules}->{$rule_id}->{strings}->{$str_name} = $val; |
905
|
|
|
|
|
|
|
} |
906
|
|
|
|
|
|
|
} |
907
|
|
|
|
|
|
|
|
908
|
|
|
|
|
|
|
=item remove_tag($rule_id, $tag) |
909
|
|
|
|
|
|
|
|
910
|
|
|
|
|
|
|
Removes a tag from the rule as identified by $tag. |
911
|
|
|
|
|
|
|
|
912
|
|
|
|
|
|
|
=cut |
913
|
|
|
|
|
|
|
|
914
|
|
|
|
|
|
|
sub remove_tag { |
915
|
1
|
|
|
1
|
1
|
828
|
my ($self, $rule_id, $tag) = @_; |
916
|
|
|
|
|
|
|
|
917
|
|
|
|
|
|
|
# Remove the tag from the tags array |
918
|
1
|
|
|
|
|
2
|
@{$self->{rules}->{$rule_id}->{tags}} = grep { $_ ne $tag } @{$self->{rules}->{$rule_id}->{tags}}; |
|
1
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
14
|
|
|
1
|
|
|
|
|
6
|
|
919
|
|
|
|
|
|
|
# If their are no tags left, delete the tags hash element so we don't print a : |
920
|
1
|
50
|
|
|
|
10
|
if(scalar(@{$self->{rules}->{$rule_id}->{tags}} < 1)) { |
|
1
|
|
|
|
|
5
|
|
921
|
0
|
|
|
|
|
0
|
delete($self->{rules}->{$rule_id}->{tags}); |
922
|
|
|
|
|
|
|
} |
923
|
|
|
|
|
|
|
} |
924
|
|
|
|
|
|
|
|
925
|
|
|
|
|
|
|
=item remove_meta($rule_id, $meta_name) |
926
|
|
|
|
|
|
|
|
927
|
|
|
|
|
|
|
Removes a meta name/value pair as identified by $meta_name. |
928
|
|
|
|
|
|
|
|
929
|
|
|
|
|
|
|
=cut |
930
|
|
|
|
|
|
|
|
931
|
|
|
|
|
|
|
sub remove_meta { |
932
|
1
|
|
|
1
|
1
|
462
|
my ($self, $rule_id, $meta_name) = @_; |
933
|
|
|
|
|
|
|
|
934
|
|
|
|
|
|
|
# Just delete the hash element for the given meta name |
935
|
1
|
|
|
|
|
6
|
delete($self->{rules}->{$rule_id}->{meta}->{$meta_name}); |
936
|
|
|
|
|
|
|
} |
937
|
|
|
|
|
|
|
|
938
|
|
|
|
|
|
|
=item remove_anonymous_string($rule_id, $str_val) |
939
|
|
|
|
|
|
|
|
940
|
|
|
|
|
|
|
Removes an anonymous string with the value specified. |
941
|
|
|
|
|
|
|
|
942
|
|
|
|
|
|
|
=cut |
943
|
|
|
|
|
|
|
|
944
|
|
|
|
|
|
|
sub remove_anonymous_string { |
945
|
1
|
|
|
1
|
1
|
694
|
my ($self, $rule_id, $str_val) = @_; |
946
|
|
|
|
|
|
|
|
947
|
|
|
|
|
|
|
# Loop through all the strings to find anonymous strings |
948
|
1
|
|
|
|
|
2
|
foreach my $str_name (keys(%{$self->{rules}->{$rule_id}->{strings}})) { |
|
1
|
|
|
|
|
7
|
|
949
|
|
|
|
|
|
|
# Find the anonymous string with the correct value |
950
|
3
|
100
|
66
|
|
|
55
|
if($str_name =~ /^\$+$/ and $self->{rules}->{$rule_id}->{strings}->{$str_name}->{value} eq $str_val) { |
951
|
|
|
|
|
|
|
# Delete the hash element for the given string value then exit the loop |
952
|
1
|
|
|
|
|
21
|
delete($self->{rules}->{$rule_id}->{strings}->{$str_name}); |
953
|
1
|
|
|
|
|
30
|
last; |
954
|
|
|
|
|
|
|
} |
955
|
|
|
|
|
|
|
} |
956
|
|
|
|
|
|
|
} |
957
|
|
|
|
|
|
|
|
958
|
|
|
|
|
|
|
=item remove_string($rule_id, $str_name) |
959
|
|
|
|
|
|
|
|
960
|
|
|
|
|
|
|
Removes a string name/value pair, but only if it contains a single value. |
961
|
|
|
|
|
|
|
|
962
|
|
|
|
|
|
|
=cut |
963
|
|
|
|
|
|
|
|
964
|
|
|
|
|
|
|
sub remove_string { |
965
|
2
|
|
|
2
|
1
|
1070
|
my ($self, $rule_id, $str_name) = @_; |
966
|
|
|
|
|
|
|
|
967
|
|
|
|
|
|
|
# Because their may be more than one anonymous string, bail at this point because we need the value for that string |
968
|
2
|
100
|
|
|
|
8
|
if($str_name eq '$') { |
969
|
1
|
|
|
|
|
139
|
carp("$rule_id: trying to remove anonymous string, use remove_anonymous_string() for this"); |
970
|
|
|
|
|
|
|
} else { |
971
|
|
|
|
|
|
|
# Otherwise we can delete the hash element for the given string name |
972
|
1
|
|
|
|
|
6
|
delete($self->{rules}->{$rule_id}->{strings}->{$str_name}); |
973
|
|
|
|
|
|
|
} |
974
|
|
|
|
|
|
|
} |
975
|
|
|
|
|
|
|
|
976
|
|
|
|
|
|
|
=item modify_meta($rule_id, $meta_name, $meta_val) |
977
|
|
|
|
|
|
|
|
978
|
|
|
|
|
|
|
Modifies the value of $meta_name and sets it to $meta_val. |
979
|
|
|
|
|
|
|
|
980
|
|
|
|
|
|
|
=cut |
981
|
|
|
|
|
|
|
|
982
|
|
|
|
|
|
|
sub modify_meta { |
983
|
1
|
|
|
1
|
1
|
584
|
my ($self, $rule_id, $meta_name, $meta_val) = @_; |
984
|
|
|
|
|
|
|
|
985
|
|
|
|
|
|
|
# Check meta name already exists |
986
|
1
|
50
|
|
|
|
7
|
if(!$self->{rules}->{$rule_id}->{meta}->{$meta_name}) { |
987
|
0
|
|
|
|
|
0
|
carp("$rule_id: $meta_name not set, select the correct name or try add_meta()"); |
988
|
|
|
|
|
|
|
} else { |
989
|
|
|
|
|
|
|
# If it does, set the new value |
990
|
1
|
|
|
|
|
19
|
$self->{rules}->{$rule_id}->{meta}->{$meta_name} = $meta_val; |
991
|
|
|
|
|
|
|
} |
992
|
|
|
|
|
|
|
} |
993
|
|
|
|
|
|
|
|
994
|
|
|
|
|
|
|
=item modify_string($rule_id, $str_name, $str_val) |
995
|
|
|
|
|
|
|
|
996
|
|
|
|
|
|
|
Modifies the value of a string name/value pair, but only if it contains a single value. |
997
|
|
|
|
|
|
|
|
998
|
|
|
|
|
|
|
Sets the value of $str_name to $str_val. |
999
|
|
|
|
|
|
|
|
1000
|
|
|
|
|
|
|
=cut |
1001
|
|
|
|
|
|
|
|
1002
|
|
|
|
|
|
|
sub modify_string { |
1003
|
1
|
|
|
1
|
1
|
413
|
my ($self, $rule_id, $str_name, $str_val) = @_; |
1004
|
|
|
|
|
|
|
|
1005
|
|
|
|
|
|
|
# Can't modify an anonymous string without knowing the current value |
1006
|
1
|
50
|
|
|
|
11
|
if($str_name =~ /^\$+$/) { |
|
|
50
|
|
|
|
|
|
1007
|
0
|
|
|
|
|
0
|
carp("$rule_id: cannot modify value of anonymous string as their may be multiple values and I don't know which one to modify."); |
1008
|
|
|
|
|
|
|
# Can't modify a string that doesn't exist |
1009
|
|
|
|
|
|
|
} elsif(!$self->{rules}->{$rule_id}->{strings}->{$str_name}) { |
1010
|
0
|
|
|
|
|
0
|
carp("$rule_id: cannot modify $str_name as it does not exist, try add_string()."); |
1011
|
|
|
|
|
|
|
} else { |
1012
|
|
|
|
|
|
|
# Set the new value directly |
1013
|
1
|
|
|
|
|
19
|
$self->{rules}->{$rule_id}->{strings}->{$str_name}->{value} = $str_val; |
1014
|
|
|
|
|
|
|
} |
1015
|
|
|
|
|
|
|
} |
1016
|
|
|
|
|
|
|
|
1017
|
|
|
|
|
|
|
# _rule_as_string |
1018
|
|
|
|
|
|
|
# Parses the rule hash(es) contained within $self or if a $rule_id is provided parses that rule. |
1019
|
|
|
|
|
|
|
# Returns a string of the rule printed in YARA format. |
1020
|
|
|
|
|
|
|
|
1021
|
|
|
|
|
|
|
sub _rule_as_string { |
1022
|
171
|
|
|
171
|
|
237
|
my ($self, $rule_id) = @_; |
1023
|
171
|
|
|
|
|
231
|
my $ret = ''; |
1024
|
171
|
|
|
|
|
159
|
my @missing; |
1025
|
|
|
|
|
|
|
|
1026
|
|
|
|
|
|
|
# Check for condition, if not the rule is invalid |
1027
|
171
|
50
|
|
|
|
611
|
if(!exists($self->{rules}->{$rule_id}->{condition})) { |
1028
|
0
|
|
|
|
|
0
|
carp("$rule_id does not contain a condition."); |
1029
|
|
|
|
|
|
|
} else { |
1030
|
171
|
100
|
|
|
|
1565
|
if($self->{rules}->{$rule_id}->{modifier}) { |
1031
|
11
|
|
|
|
|
103
|
$ret .= $self->{rules}->{$rule_id}->{modifier} . " "; |
1032
|
|
|
|
|
|
|
} |
1033
|
|
|
|
|
|
|
|
1034
|
171
|
|
|
|
|
1235
|
$ret .= "rule $rule_id"; |
1035
|
|
|
|
|
|
|
|
1036
|
|
|
|
|
|
|
# If tags are set, add a : after the rule_id and then space separate each tag |
1037
|
171
|
100
|
|
|
|
575
|
if($self->{rules}->{$rule_id}->{tags}) { |
1038
|
19
|
|
|
|
|
129
|
$ret .= " :"; |
1039
|
19
|
|
|
|
|
21
|
foreach my $tag (@{$self->{rules}->{$rule_id}->{tags}}) { |
|
19
|
|
|
|
|
71
|
|
1040
|
30
|
|
|
|
|
170
|
$ret .= " $tag"; |
1041
|
|
|
|
|
|
|
} |
1042
|
|
|
|
|
|
|
} |
1043
|
|
|
|
|
|
|
|
1044
|
|
|
|
|
|
|
# Now add the opening brace on a new line |
1045
|
171
|
|
|
|
|
1237
|
$ret .= "\n{"; |
1046
|
|
|
|
|
|
|
|
1047
|
|
|
|
|
|
|
# If their is a meta element, loop through each entry and add to the rule string |
1048
|
171
|
100
|
|
|
|
570
|
if($self->{rules}->{$rule_id}->{meta}) { |
1049
|
7
|
|
|
|
|
65
|
$ret .= "\n"; |
1050
|
7
|
|
|
|
|
12
|
$ret .= "\tmeta:\n"; |
1051
|
7
|
|
|
|
|
10
|
foreach my $meta_name (keys(%{$self->{rules}->{$rule_id}->{meta}})) { |
|
7
|
|
|
|
|
29
|
|
1052
|
18
|
|
|
|
|
193
|
my $meta_val; |
1053
|
18
|
100
|
|
|
|
67
|
if($self->{rules}->{$rule_id}->{meta}->{$meta_name} =~ /^(\d+|true|false)$/) { |
1054
|
8
|
|
|
|
|
130
|
$meta_val = $self->{rules}->{$rule_id}->{meta}->{$meta_name}; |
1055
|
|
|
|
|
|
|
} else { |
1056
|
10
|
|
|
|
|
180
|
$meta_val = "\"$self->{rules}->{$rule_id}->{meta}->{$meta_name}\""; |
1057
|
|
|
|
|
|
|
} |
1058
|
18
|
|
|
|
|
262
|
$ret .= "\t\t$meta_name = $meta_val\n"; |
1059
|
|
|
|
|
|
|
} |
1060
|
|
|
|
|
|
|
} |
1061
|
|
|
|
|
|
|
|
1062
|
|
|
|
|
|
|
# If their is a strings element, loop through each entry and add to the rule string |
1063
|
171
|
100
|
|
|
|
1450
|
if($self->{rules}->{$rule_id}->{strings}) { |
1064
|
123
|
|
|
|
|
838
|
$ret .= "\n"; |
1065
|
123
|
|
|
|
|
140
|
$ret .= "\tstrings:\n"; |
1066
|
123
|
|
|
|
|
141
|
foreach my $string_name (keys(%{$self->{rules}->{$rule_id}->{strings}})) { |
|
123
|
|
|
|
|
427
|
|
1067
|
384
|
|
|
|
|
3779
|
my $display_name = $string_name; |
1068
|
384
|
|
|
|
|
393
|
my $display_val; |
1069
|
384
|
100
|
|
|
|
1100
|
if($string_name =~ /^\$+$/) { |
1070
|
44
|
|
|
|
|
104
|
$display_name = '$'; |
1071
|
|
|
|
|
|
|
} |
1072
|
384
|
100
|
|
|
|
1356
|
if($self->{rules}->{$rule_id}->{strings}->{$string_name}->{type} eq "text") { |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
1073
|
192
|
|
|
|
|
2674
|
$display_val = "\"$self->{rules}->{$rule_id}->{strings}->{$string_name}->{value}\""; |
1074
|
192
|
|
|
|
|
2161
|
foreach my $str_mod (@{$self->{rules}->{$rule_id}->{strings}->{$string_name}->{modifier}}) { |
|
192
|
|
|
|
|
707
|
|
1075
|
36
|
|
|
|
|
314
|
$display_val .= " $str_mod"; |
1076
|
|
|
|
|
|
|
} |
1077
|
|
|
|
|
|
|
} elsif($self->{rules}->{$rule_id}->{strings}->{$string_name}->{type} eq "hex") { |
1078
|
182
|
|
|
|
|
4424
|
$display_val = $self->{rules}->{$rule_id}->{strings}->{$string_name}->{value}; |
1079
|
|
|
|
|
|
|
} elsif($self->{rules}->{$rule_id}->{strings}->{$string_name}->{type} eq "regex") { |
1080
|
10
|
|
|
|
|
535
|
$display_val = $self->{rules}->{$rule_id}->{strings}->{$string_name}->{value}; |
1081
|
|
|
|
|
|
|
} |
1082
|
384
|
|
|
|
|
4935
|
$ret .= "\t\t$display_name = $display_val\n"; |
1083
|
|
|
|
|
|
|
} |
1084
|
|
|
|
|
|
|
} |
1085
|
|
|
|
|
|
|
|
1086
|
|
|
|
|
|
|
# Add the condition and closing brace |
1087
|
171
|
|
|
|
|
856
|
$ret .= "\n"; |
1088
|
171
|
|
|
|
|
204
|
$ret .= "\tcondition:\n"; |
1089
|
171
|
|
|
|
|
586
|
$ret .= "\t\t$self->{rules}->{$rule_id}->{condition}\n"; |
1090
|
171
|
|
|
|
|
1211
|
$ret .= "}"; |
1091
|
|
|
|
|
|
|
} |
1092
|
|
|
|
|
|
|
|
1093
|
171
|
|
|
|
|
706
|
return $ret; |
1094
|
|
|
|
|
|
|
} |
1095
|
|
|
|
|
|
|
|
1096
|
|
|
|
|
|
|
=item as_string() |
1097
|
|
|
|
|
|
|
|
1098
|
|
|
|
|
|
|
Can take zero or one argument. With no arugments this return all rules within the rule hashref as a string. |
1099
|
|
|
|
|
|
|
|
1100
|
|
|
|
|
|
|
=over 4 |
1101
|
|
|
|
|
|
|
|
1102
|
|
|
|
|
|
|
=item as_string($rule_id) |
1103
|
|
|
|
|
|
|
|
1104
|
|
|
|
|
|
|
Extracts a single rule from the hashref and returns it as a string value. |
1105
|
|
|
|
|
|
|
|
1106
|
|
|
|
|
|
|
=cut |
1107
|
|
|
|
|
|
|
|
1108
|
|
|
|
|
|
|
sub as_string { |
1109
|
7
|
|
|
7
|
1
|
2463
|
my ($self, $rule_id) = @_; |
1110
|
7
|
|
|
|
|
13
|
my $ret = ''; |
1111
|
|
|
|
|
|
|
|
1112
|
|
|
|
|
|
|
# Check to see if their is a rule_id and return that rule as a string |
1113
|
7
|
50
|
|
|
|
27
|
if($rule_id) { |
1114
|
0
|
|
|
|
|
0
|
$ret = $self->_rule_as_string($rule_id); |
1115
|
|
|
|
|
|
|
} else { |
1116
|
|
|
|
|
|
|
# Otherwise loop through the hash and return all rules as a string |
1117
|
7
|
|
|
|
|
10
|
foreach my $rule_id (keys(%{$self->{rules}})) { |
|
7
|
|
|
|
|
37
|
|
1118
|
171
|
|
|
|
|
1142
|
$ret .= $self->_rule_as_string($rule_id) . "\n\n"; |
1119
|
|
|
|
|
|
|
} |
1120
|
7
|
|
|
|
|
80
|
chomp($ret); |
1121
|
|
|
|
|
|
|
} |
1122
|
|
|
|
|
|
|
|
1123
|
7
|
|
|
|
|
150
|
return $ret; |
1124
|
|
|
|
|
|
|
} |
1125
|
|
|
|
|
|
|
|
1126
|
|
|
|
|
|
|
=back |
1127
|
|
|
|
|
|
|
|
1128
|
|
|
|
|
|
|
=item get_referenced_rule($rule_id) |
1129
|
|
|
|
|
|
|
|
1130
|
|
|
|
|
|
|
Check to see if $rule_id references any other rules and return any matched rule ID's as an array. |
1131
|
|
|
|
|
|
|
|
1132
|
|
|
|
|
|
|
=cut |
1133
|
|
|
|
|
|
|
|
1134
|
|
|
|
|
|
|
sub get_referenced_rule { |
1135
|
1
|
|
|
1
|
1
|
2214
|
my ($self, $rule_id) = @_; |
1136
|
1
|
|
|
|
|
2
|
my @ret; |
1137
|
|
|
|
|
|
|
|
1138
|
|
|
|
|
|
|
# For each rule, check to see if the rule_id passed is contained |
1139
|
|
|
|
|
|
|
# within the rule's condition, if so add it to an array to be returned |
1140
|
1
|
100
|
|
|
|
7
|
map { if (exists($self->{rules}->{$_})) { push(@ret, $_); } } split(/\s+/, $self->{rules}->{$rule_id}->{condition}); |
|
3
|
|
|
|
|
36
|
|
|
1
|
|
|
|
|
12
|
|
1141
|
|
|
|
|
|
|
|
1142
|
1
|
|
|
|
|
5
|
return @ret; |
1143
|
|
|
|
|
|
|
} |
1144
|
|
|
|
|
|
|
|
1145
|
|
|
|
|
|
|
=item position_rule($rule_id, $position, $relative_rule_id) |
1146
|
|
|
|
|
|
|
|
1147
|
|
|
|
|
|
|
Position a rule before or after another rule where: |
1148
|
|
|
|
|
|
|
|
1149
|
|
|
|
|
|
|
$rule_id is the rule_id of the rule to be moved |
1150
|
|
|
|
|
|
|
$position is either before or after |
1151
|
|
|
|
|
|
|
$relative_rule_id is the rule_id of the rule to move this rule around |
1152
|
|
|
|
|
|
|
|
1153
|
|
|
|
|
|
|
=cut |
1154
|
|
|
|
|
|
|
|
1155
|
|
|
|
|
|
|
sub position_rule { |
1156
|
2
|
|
|
2
|
1
|
1443
|
my ($self, $rule_id, $position, $relative_rule_id) = @_; |
1157
|
2
|
|
|
|
|
4
|
my $rule_found = 0; |
1158
|
2
|
|
|
|
|
6
|
my $relative_rule_found = 0; |
1159
|
|
|
|
|
|
|
|
1160
|
|
|
|
|
|
|
# Make sure we pick a valid movement (before or after) |
1161
|
2
|
50
|
|
|
|
23
|
if($position !~ /^(before|after)$/) { |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
1162
|
0
|
|
|
|
|
0
|
carp("$rule_id: position must be set to before or after $position"); |
1163
|
|
|
|
|
|
|
# Make sure the rule_id requested for movement exists |
1164
|
|
|
|
|
|
|
} elsif(!$self->{rules}->{$rule_id}) { |
1165
|
0
|
|
|
|
|
0
|
carp("Cannot position rule that does not exist: $rule_id"); |
1166
|
|
|
|
|
|
|
# Make sure the relative_rule_id to which the rule_id should be moved before or after exists |
1167
|
|
|
|
|
|
|
} elsif(!$self->{rules}->{$relative_rule_id}) { |
1168
|
0
|
|
|
|
|
0
|
carp("Cannot position around rule that does not exist: $relative_rule_id"); |
1169
|
|
|
|
|
|
|
} else { |
1170
|
2
|
50
|
|
|
|
40
|
if($self->{verbose}) { |
1171
|
0
|
|
|
|
|
0
|
print "Moving $rule_id $position $relative_rule_id\n"; |
1172
|
|
|
|
|
|
|
} |
1173
|
|
|
|
|
|
|
# Create an array with the current order of keys |
1174
|
2
|
|
|
|
|
10
|
my @new_order = $self->{rules_knot}->Keys; |
1175
|
|
|
|
|
|
|
# Get the current position of the rule_id and relative_rule_id |
1176
|
2
|
|
|
|
|
57
|
my $rule_pos = $self->{rules_knot}->Indices($rule_id); |
1177
|
2
|
|
|
|
|
15
|
my $relative_rule_pos = $self->{rules_knot}->Indices($relative_rule_id); |
1178
|
|
|
|
|
|
|
# Adjust the position accordingly |
1179
|
2
|
50
|
66
|
|
|
29
|
if($position eq "after" and $rule_pos > $relative_rule_pos) { |
|
|
50
|
66
|
|
|
|
|
1180
|
0
|
|
|
|
|
0
|
$rule_pos++; |
1181
|
|
|
|
|
|
|
} elsif($position eq "before" and $rule_pos < $relative_rule_pos) { |
1182
|
0
|
|
|
|
|
0
|
$relative_rule_pos--; |
1183
|
|
|
|
|
|
|
} |
1184
|
|
|
|
|
|
|
# Reorder within the array then set the order using Tie:IxHash->Reorder |
1185
|
2
|
|
|
|
|
7
|
splice(@new_order, $relative_rule_pos, 0, splice(@new_order, $rule_pos, 1)); |
1186
|
2
|
|
|
|
|
9
|
$self->{rules_knot}->Reorder(@new_order); |
1187
|
|
|
|
|
|
|
} |
1188
|
|
|
|
|
|
|
} |
1189
|
|
|
|
|
|
|
|
1190
|
|
|
|
|
|
|
1; |
1191
|
|
|
|
|
|
|
|
1192
|
|
|
|
|
|
|
__END__ |