yara 1.4.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,203 @@
1
+ #include "errors.h"
2
+ #include "Rules.h"
3
+ #include "Match.h"
4
+ #include <stdio.h>
5
+
6
+ static VALUE class_Rules = Qnil;
7
+
8
+ void rules_mark(YARA_CONTEXT *ctx) { }
9
+
10
+ void rules_free(YARA_CONTEXT *ctx) {
11
+ yr_destroy_context(ctx);
12
+ }
13
+
14
+ VALUE rules_allocate(VALUE klass) {
15
+ YARA_CONTEXT *ctx = yr_create_context();
16
+
17
+ return Data_Wrap_Struct(klass, rules_mark, rules_free, ctx);
18
+ }
19
+
20
+ VALUE rules_compile_file(VALUE self, VALUE rb_fname) {
21
+ FILE * file;
22
+ char * fname;
23
+ YARA_CONTEXT *ctx;
24
+ char error_message[256];
25
+
26
+ Check_Type(rb_fname, T_STRING);
27
+ fname = RSTRING_PTR(rb_fname);
28
+
29
+ if( !(file=fopen(fname, "r")) ) {
30
+ rb_raise(error_CompileError, "No such file: %s", fname);
31
+ } else {
32
+ Data_Get_Struct(self, YARA_CONTEXT, ctx);
33
+
34
+ if( yr_compile_file(file, ctx) != 0 ) {
35
+ yr_get_error_message(ctx, error_message, sizeof(error_message));
36
+ fclose(file);
37
+ rb_raise(error_CompileError, "Syntax Error - %s(%d): %s", fname, ctx->last_error_line, error_message);
38
+ }
39
+
40
+ yr_push_file_name(ctx, fname);
41
+ fclose(file);
42
+ return Qtrue;
43
+ }
44
+ }
45
+
46
+ VALUE rules_compile_string(VALUE self, VALUE rb_rules) {
47
+ YARA_CONTEXT *ctx;
48
+ char *rules;
49
+ char error_message[256];
50
+
51
+ Check_Type(rb_rules, T_STRING);
52
+ rules = RSTRING_PTR(rb_rules);
53
+ Data_Get_Struct(self, YARA_CONTEXT, ctx);
54
+
55
+ if( yr_compile_string(rules, ctx) != 0) {
56
+ yr_get_error_message(ctx, error_message, sizeof(error_message));
57
+ rb_raise(error_CompileError, "Syntax Error - line(%d): %s", ctx->last_error_line, error_message);
58
+ }
59
+
60
+ return Qtrue;
61
+ }
62
+
63
+ VALUE rules_weight(VALUE self) {
64
+ YARA_CONTEXT *ctx;
65
+ Data_Get_Struct(self, YARA_CONTEXT, ctx);
66
+ return INT2NUM(yr_calculate_rules_weight(ctx));
67
+ }
68
+
69
+
70
+ VALUE rules_current_namespace(VALUE self) {
71
+ YARA_CONTEXT *ctx;
72
+ Data_Get_Struct(self, YARA_CONTEXT, ctx);
73
+ if(ctx->current_namespace && ctx->current_namespace->name)
74
+ return rb_str_new2(ctx->current_namespace->name);
75
+ else
76
+ return Qnil;
77
+ }
78
+
79
+ VALUE rules_namespaces(VALUE self) {
80
+ YARA_CONTEXT *ctx;
81
+ NAMESPACE *ns;
82
+ VALUE ary = rb_ary_new();
83
+
84
+ Data_Get_Struct(self, YARA_CONTEXT, ctx);
85
+ ns = ctx->namespaces;
86
+ while(ns && ns->name) {
87
+ rb_ary_push(ary, rb_str_new2(ns->name));
88
+ ns = ns->next;
89
+ }
90
+ return ary;
91
+ }
92
+
93
+ NAMESPACE * find_namespace(YARA_CONTEXT *ctx, const char *name) {
94
+ NAMESPACE *ns = ctx->namespaces;
95
+
96
+ while(ns && ns->name) {
97
+ if(strcmp(name, ns->name) == 0)
98
+ return(ns);
99
+ else
100
+ ns = ns->next;
101
+ }
102
+ return (NAMESPACE*) NULL;
103
+ }
104
+
105
+ VALUE rules_set_namespace(VALUE self, VALUE rb_namespace) {
106
+ YARA_CONTEXT *ctx;
107
+ NAMESPACE *ns = NULL;
108
+ const char *name;
109
+
110
+ Check_Type(rb_namespace, T_STRING);
111
+ name = RSTRING_PTR(rb_namespace);
112
+
113
+ Data_Get_Struct(self, YARA_CONTEXT, ctx);
114
+
115
+ if (!(ns = find_namespace(ctx, name)))
116
+ ns = yr_create_namespace(ctx, name);
117
+
118
+ if (ns) {
119
+ ctx->current_namespace = ns;
120
+ return rb_namespace;
121
+ } else {
122
+ return Qnil;
123
+ }
124
+
125
+ }
126
+
127
+ static int
128
+ scan_callback(RULE *rule, unsigned char *buffer, unsigned int buffer_size, void *data) {
129
+ int match_ret;
130
+ VALUE match = Qnil;
131
+ VALUE results = *((VALUE *) data);
132
+
133
+ Check_Type(results, T_ARRAY);
134
+
135
+ match_ret = Match_NEW_from_rule(rule, buffer, &match);
136
+ if(match_ret == 0 && !NIL_P(match))
137
+ rb_ary_push(results,match);
138
+
139
+ return match_ret;
140
+ }
141
+
142
+
143
+ VALUE rules_scan_file(VALUE self, VALUE rb_fname) {
144
+ YARA_CONTEXT *ctx;
145
+ VALUE results;
146
+ unsigned int ret;
147
+ char *fname;
148
+
149
+ Check_Type(rb_fname, T_STRING);
150
+ results = rb_ary_new();
151
+ Data_Get_Struct(self, YARA_CONTEXT, ctx);
152
+ fname = RSTRING_PTR(rb_fname);
153
+
154
+ ret = yr_scan_file(fname, ctx, scan_callback, &results);
155
+ if (ret == ERROR_COULD_NOT_OPEN_FILE)
156
+ rb_raise(error_ScanError, "Could not open file: '%s'", fname);
157
+ else if (ret != 0)
158
+ rb_raise(error_ScanError, "A error occurred while scanning: %s",
159
+ ((ret > MAX_SCAN_ERROR)? "unknown error" : SCAN_ERRORS[ret]));
160
+
161
+ return results;
162
+ }
163
+
164
+ VALUE rules_scan_string(VALUE self, VALUE rb_dat) {
165
+ YARA_CONTEXT *ctx;
166
+ VALUE results;
167
+ char *buf;
168
+ long buflen;
169
+ int ret;
170
+
171
+ Check_Type(rb_dat, T_STRING);
172
+ buf = RSTRING_PTR(rb_dat);
173
+ buflen = RSTRING_LEN(rb_dat);
174
+
175
+ results = rb_ary_new();
176
+
177
+ Data_Get_Struct(self, YARA_CONTEXT, ctx);
178
+
179
+ ret = yr_scan_mem(buf, buflen, ctx, scan_callback, &results);
180
+ if (ret != 0)
181
+ rb_raise(error_ScanError, "A error occurred while scanning: %s",
182
+ ((ret > MAX_SCAN_ERROR)? "unknown error" : SCAN_ERRORS[ret]));
183
+
184
+ return results;
185
+ }
186
+
187
+ void init_rules(VALUE rb_ns) {
188
+
189
+ class_Rules = rb_define_class_under(rb_ns, "Rules", rb_cObject);
190
+ rb_define_alloc_func(class_Rules, rules_allocate);
191
+
192
+ rb_define_method(class_Rules, "compile_file", rules_compile_file, 1);
193
+ rb_define_method(class_Rules, "compile_string", rules_compile_string, 1);
194
+ rb_define_method(class_Rules, "weight", rules_weight, 0);
195
+ rb_define_method(class_Rules, "current_namespace", rules_current_namespace, 0);
196
+ rb_define_method(class_Rules, "namespaces", rules_namespaces, 0);
197
+ rb_define_method(class_Rules, "set_namespace", rules_set_namespace, 1);
198
+ rb_define_method(class_Rules, "scan_file", rules_scan_file, 1);
199
+ rb_define_method(class_Rules, "scan_string", rules_scan_string, 1);
200
+
201
+ init_match(rb_ns);
202
+ }
203
+
@@ -0,0 +1,12 @@
1
+
2
+ #ifndef RB_RULES_H_GUARD
3
+ #define RB_RULES_H_GUARD
4
+
5
+ #include <yara.h>
6
+ #include "ruby.h"
7
+
8
+ static VALUE class_Rules;
9
+
10
+ void init_rules(VALUE ruby_namespace);
11
+
12
+ #endif
@@ -0,0 +1,20 @@
1
+ #include "ruby.h"
2
+ #include <yara.h>
3
+
4
+ #include "Yara_native.h"
5
+ #include "Rules.h"
6
+ #include "errors.h"
7
+
8
+ static VALUE module_Yara = Qnil;
9
+
10
+ void Init_yara_native() {
11
+ yr_init();
12
+
13
+ module_Yara = rb_define_module("Yara");
14
+
15
+ init_errors(module_Yara);
16
+ init_rules(module_Yara);
17
+ }
18
+
19
+
20
+
@@ -0,0 +1,9 @@
1
+ #ifndef RB_YARA_H_GUARD
2
+ #define RB_YARA_H_GUARD
3
+
4
+ #include "ruby.h"
5
+ #include <yara.h>
6
+
7
+ static VALUE module_Yara;
8
+
9
+ #endif
@@ -0,0 +1,11 @@
1
+ #include "errors.h"
2
+ #include "ruby.h"
3
+
4
+ VALUE error_CompileError = Qnil;
5
+ VALUE error_ScanError = Qnil;
6
+
7
+ void
8
+ init_errors(VALUE rb_ns) {
9
+ error_CompileError = rb_define_class_under(rb_ns, "CompileError", rb_eStandardError);
10
+ error_ScanError = rb_define_class_under(rb_ns, "ScanError", rb_eStandardError);
11
+ }
@@ -0,0 +1,9 @@
1
+ #ifndef RB_YARA_ERR_H_GUARD
2
+ #define RB_YARA_ERR_H_GUARD
3
+
4
+ #include "ruby.h"
5
+
6
+ extern VALUE error_CompileError;
7
+ extern VALUE error_ScanError;
8
+
9
+ #endif
@@ -0,0 +1,14 @@
1
+ require 'mkmf'
2
+ require 'rbconfig'
3
+
4
+ extension_name = "yara_native"
5
+
6
+ dir_config(extension_name)
7
+
8
+ unless have_library("yara") and
9
+ find_header("yara.h", "/usr/local/include")
10
+ raise "You must install the yara library"
11
+ end
12
+
13
+ create_makefile(extension_name)
14
+
data/lib/yara.rb ADDED
@@ -0,0 +1,45 @@
1
+
2
+ require 'yara_native'
3
+
4
+ module Yara
5
+ class Rules
6
+ end
7
+
8
+ class Match
9
+ def to_hash
10
+ { :rule => self.rule,
11
+ :namespace => self.namespace,
12
+ :tags => self.tags,
13
+ :meta => self.meta,
14
+ :strings => self.strings }
15
+ end
16
+
17
+ def inspect
18
+ h=to_hash
19
+ h.inspect
20
+ end
21
+ end
22
+
23
+ class MatchString
24
+
25
+ alias ident identifier
26
+
27
+ def <=>(other)
28
+ self.offset <=> other.offset
29
+ end
30
+
31
+ def to_a
32
+ [self.offset, self.ident, self.buffer]
33
+ end
34
+
35
+ def to_hash
36
+ { :offset => self.offset, :identifier => self.ident, :buffer => self.buffer}
37
+ end
38
+
39
+ def inspect
40
+ h=to_a
41
+ h.inspect
42
+ end
43
+ end
44
+
45
+ end
data/samples/ispe.rb ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Usage example:
4
+ # ruby ispe.rb /win_c/windows/system32/*.???
5
+ #
6
+ $: << File.join(File.dirname(__FILE__), '..', 'lib')
7
+ require 'yara'
8
+
9
+ ctx = Yara::Rules.new
10
+ ctx.compile_string "rule IsPE { condition: uint16(0) == 0x5A4D and uint32(uint32(0x3C)) == 0x00004550 }"
11
+
12
+ ARGV.each do |fname|
13
+ ctx.scan_file(fname).each {|match| puts "#{fname} -> #{match.rule}" }
14
+ end
data/samples/upx.rb ADDED
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $: << 'lib'
4
+ require 'yara'
5
+ require 'pp'
6
+
7
+ rule_str = <<_EOF_
8
+ rule UPX {
9
+ strings:
10
+ $noep1 = { B8 ?? ?? ?? ?? B9 ?? ?? ?? ?? 33 D2 EB 01 0F 56 EB 01 0F E8 03 00 00 00 EB 01 0F EB 01 0F 5E EB 01 }
11
+ $noep2 = { 5E 89 F7 B9 ?? ?? ?? ?? 8A 07 47 2C E8 3C 01 77 F7 80 3F ?? 75 F2 8B 07 8A 5F 04 66 C1 E8 08 C1 C0 10 86 C4 29 F8 80 EB E8 01 F0 89 07 83 C7 }
12
+ $noep3 = { 01 DB [0-1] 07 8B 1E 83 EE FC 11 DB [1-4] B8 01 00 00 00 01 DB }
13
+ $noep4 = { 9C 60 E8 00 00 00 00 5D B8 B3 85 40 00 2D AC 85 40 00 2B E8 8D B5 D5 FE FF FF 8B 06 83 F8 00 74 11 8D B5 E1 FE FF FF 8B 06 83 F8 01 0F 84 F1 }
14
+ $noep5 = { 8A 06 46 88 07 47 01 DB 75 07 8B 1E 83 EE FC 11 DB }
15
+ $noep6 = { FF D5 80 A7 ?? ?? ?? ?? ?? 58 50 54 50 53 57 FF D5 58 61 8D 44 24 ?? 6A 00 39 C4 75 FA 83 EC 80 E9 }
16
+ $noep7 = { 55 FF 96 ?? ?? ?? ?? 09 C0 74 07 89 03 83 C3 04 EB ?? FF 96 ?? ?? ?? ?? 8B AE ?? ?? ?? ?? 8D BE 00 F0 FF FF BB 00 10 00 00 50 54 6A 04 53 57 }
17
+ $noep8 = { FF D5 8D 87 ?? ?? ?? ?? 80 20 ?? 80 60 ?? ?? 58 50 54 50 53 57 FF D5 58 61 8D 44 24 ?? 6A 00 39 C4 75 FA 83 EC 80 E9 }
18
+ $ep1 = { 60 E8 00 00 00 00 58 83 E8 3D }
19
+ $ep2 = { 60 E8 00 00 00 00 83 CD FF 31 DB 5E }
20
+ $ep3 = { 50 BE ?? ?? ?? ?? 8D BE ?? ?? ?? ?? 57 83 CD }
21
+
22
+ condition: any of ($noep*) or for any of ($ep*) : ($ at entrypoint)
23
+ }
24
+ _EOF_
25
+
26
+ ctx = Yara::Rules.new
27
+ ctx.compile_string rule_str
28
+
29
+ ARGV.each do |fname|
30
+ begin
31
+ ctx.scan_file(fname).each do |match|
32
+ pp match
33
+ end
34
+ rescue Yara::ScanError => e
35
+ STDERR.puts e
36
+ end
37
+ end
38
+
39
+
@@ -0,0 +1,208 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Yara::Rules do
4
+ it "should be a class" do
5
+ Yara::Rules.should be_kind_of(Class)
6
+ end
7
+
8
+ it "should initialize cleanly" do
9
+ lambda { Yara::Rules.new }.should_not raise_error
10
+ end
11
+
12
+ context "Instances" do
13
+ before(:each) do
14
+ @rules = Yara::Rules.new
15
+ end
16
+
17
+ it "should indicate rules weight" do
18
+ @rules.weight.should be_kind_of(Numeric)
19
+ @rules.weight.should == 0
20
+ end
21
+
22
+ it "should compile a file" do
23
+ lambda { @rules.compile_file(sample_file("upx.yara")) }.should_not raise_error
24
+ @rules.weight.should > 0
25
+ end
26
+
27
+ it "should compile an empty file" do
28
+ lambda { @rules.compile_file("/dev/null") }.should_not raise_error
29
+ @rules.weight.should == 0
30
+ end
31
+
32
+
33
+ it "should raise an error if compiling an invalid filename" do
34
+ lambda { @rules.compile_file("so totally bogus a file") }.should raise_error
35
+ @rules.weight.should == 0
36
+ end
37
+
38
+ it "should raise an error if compiling a file with bad syntax" do
39
+ lambda { @rules.compile_file(__FILE__) }.should raise_error(Yara::CompileError)
40
+ @rules.weight.should == 0
41
+ end
42
+
43
+ it "should raise an error if duplicate file data is compiled" do
44
+ lambda { @rules.compile_file(sample_file("upx.yara")) }.should_not raise_error
45
+ lambda { @rules.compile_file(sample_file("upx.yara")) }.should raise_error(Yara::CompileError)
46
+ @rules.weight.should > 0
47
+ end
48
+
49
+ it "should compile a string" do
50
+ rules = File.read(sample_file("upx.yara"))
51
+ lambda { @rules.compile_string(rules) }.should_not raise_error
52
+ @rules.weight.should > 0
53
+ end
54
+
55
+ it "should compile an empty string" do
56
+ lambda { @rules.compile_string("") }.should_not raise_error
57
+ @rules.weight.should == 0
58
+ end
59
+
60
+ it "should raise an error if compiling a string with bad syntax" do
61
+ rules = File.read(sample_file("upx.yara")) << "some bogus stuff\n"
62
+ lambda { @rules.compile_string(rules) }.should raise_error(Yara::CompileError)
63
+ @rules.weight.should > 0 # it parsed everything up to the error
64
+ end
65
+
66
+ it "should raise an error if duplicate string data is compiled" do
67
+ rules = File.read(sample_file("upx.yara"))
68
+ lambda { @rules.compile_string(rules) }.should_not raise_error
69
+ lambda { @rules.compile_string(rules) }.should raise_error(Yara::CompileError)
70
+ @rules.weight.should > 0
71
+ end
72
+
73
+ it "should indicate the current namespace" do
74
+ @rules.current_namespace.should be_kind_of(String)
75
+ @rules.current_namespace.should == "default"
76
+ end
77
+
78
+ it "should indicate all known namespaces" do
79
+ @rules.namespaces.should be_kind_of(Array)
80
+ @rules.namespaces.should == ["default"]
81
+ end
82
+
83
+ it "should support setting a new namespace" do
84
+ @rules.namespaces.should be_kind_of(Array)
85
+ @rules.namespaces.should == ["default"]
86
+
87
+ @rules.set_namespace("a_new_namespace").should == "a_new_namespace"
88
+ @rules.current_namespace.should == "a_new_namespace"
89
+ @rules.namespaces.should be_kind_of(Array)
90
+ @rules.namespaces.should == ["a_new_namespace", "default"]
91
+ end
92
+
93
+ it "should not create duplicate namespaces" do
94
+ @rules.namespaces.should be_kind_of(Array)
95
+ @rules.namespaces.should == ["default"]
96
+
97
+ @rules.set_namespace("a_new_namespace").should == "a_new_namespace"
98
+ @rules.current_namespace.should == "a_new_namespace"
99
+ @rules.namespaces.should be_kind_of(Array)
100
+ @rules.namespaces.should == ["a_new_namespace", "default"]
101
+
102
+ @rules.set_namespace("default").should == "default"
103
+ @rules.current_namespace.should == "default"
104
+ @rules.namespaces.should == ["a_new_namespace", "default"]
105
+
106
+ @rules.set_namespace("a_new_namespace").should == "a_new_namespace"
107
+ @rules.current_namespace.should == "a_new_namespace"
108
+ @rules.namespaces.should == ["a_new_namespace", "default"]
109
+ end
110
+
111
+ it "should scan a file" do
112
+ @rules.compile_file(sample_file("packers.yara"))
113
+ @rules.weight.should > 0
114
+ results = @rules.scan_file(sample_file("DumpMem.exe"))
115
+ results.should be_kind_of(Array)
116
+ results.size.should == 1
117
+ m = results.first
118
+ m.should be_kind_of(Yara::Match)
119
+ m.should be_frozen
120
+
121
+ m.rule.should == "UPX"
122
+ m.rule.should be_frozen
123
+
124
+ m.namespace.should == "default"
125
+ m.namespace.should be_frozen
126
+
127
+ m.tags.should == ["compression", "packer", "shady"]
128
+ m.tags.should be_frozen
129
+ m.tags.map{|v| v.should be_frozen }
130
+
131
+ strings = m.strings.sort
132
+ strings.each do |ms|
133
+ ms.should be_kind_of(Yara::MatchString)
134
+ ms.identifier.should be_frozen
135
+ ms.buffer.should be_frozen
136
+ end
137
+
138
+ strings.map{|ms| [ms.offset, ms.identifier, md5(ms.buffer)] }.should == [
139
+ [2824, "$noep5", "af79592a2fc536596fcbe87409734626"],
140
+ [2830, "$noep3", "04b044f4bfeb6899b6b60ff7d6b1d103"],
141
+ [3010, "$noep2", "8711f47b104922246e5733211cd832b1"],
142
+ [3110, "$noep7", "71be53d1049f47219ad8f26a77255229"],
143
+ [3157, "$noep8", "b9beede7f0d05ee657501cea72e1a453"]
144
+ ]
145
+ end
146
+
147
+ it "should raise an error if scanning an invalid file" do
148
+ @rules.compile_file(sample_file("packers.yara"))
149
+ @rules.weight.should > 0
150
+ lambda { @rules.scan_file(sample_file("not a real file at all")) }.should raise_error(Yara::ScanError)
151
+ lambda { @rules.scan_file(Object.new)}.should raise_error(TypeError)
152
+ lambda { @rules.scan_file(nil)}.should raise_error(TypeError)
153
+ end
154
+
155
+ it "should raise an error if scanning a zero-length file" do
156
+ @rules.compile_file(sample_file("packers.yara"))
157
+ @rules.weight.should > 0
158
+ lambda { @rules.scan_file("/dev/null")}.should raise_error(Yara::ScanError)
159
+ end
160
+
161
+ it "should scan a string" do
162
+ @rules.compile_file(sample_file("packers.yara"))
163
+ @rules.weight.should > 0
164
+ results = @rules.scan_string(File.read(sample_file("DumpMem.exe")))
165
+ results.should be_kind_of(Array)
166
+ results.size.should == 1
167
+ m = results.first
168
+ m.should be_kind_of(Yara::Match)
169
+ m.should be_frozen
170
+
171
+ m.rule.should == "UPX"
172
+ m.rule.should be_frozen
173
+
174
+ m.namespace.should == "default"
175
+ m.namespace.should be_frozen
176
+
177
+ m.tags.should == ["compression", "packer", "shady"]
178
+ m.tags.should be_frozen
179
+ m.tags.map{|v| v.should be_frozen }
180
+
181
+ m.strings.should be_frozen
182
+ strings = m.strings.sort
183
+ strings.each do |ms|
184
+ ms.should be_kind_of(Yara::MatchString)
185
+ ms.identifier.should be_frozen
186
+ ms.buffer.should be_frozen
187
+ end
188
+
189
+ strings.map{|ms| [ms.offset, ms.identifier, md5(ms.buffer)] }.should == [
190
+ [2824, "$noep5", "af79592a2fc536596fcbe87409734626"],
191
+ [2830, "$noep3", "04b044f4bfeb6899b6b60ff7d6b1d103"],
192
+ [3010, "$noep2", "8711f47b104922246e5733211cd832b1"],
193
+ [3110, "$noep7", "71be53d1049f47219ad8f26a77255229"],
194
+ [3157, "$noep8", "b9beede7f0d05ee657501cea72e1a453"]
195
+ ]
196
+
197
+ end
198
+
199
+ it "should raise an error if scanning an invalid string" do
200
+ @rules.compile_file(sample_file("packers.yara"))
201
+ @rules.weight.should > 0
202
+ lambda { @rules.scan_string(Object.new)}.should raise_error(TypeError)
203
+ lambda { @rules.scan_string(nil)}.should raise_error(TypeError)
204
+ end
205
+
206
+
207
+ end
208
+ end
Binary file