yara 1.4.1

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.
@@ -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