yara-normalize 0.2.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f56ffeec6846ec1ae4d2aea3bfa6cc89ca9643d4b31211b39e8381025377a2ff
4
- data.tar.gz: 50aa592ac824fa3ee427f90c8279b92dc79c6dae1c4254b585e54df8c9a31ba3
3
+ metadata.gz: 13f41905bf9f1f9e8d8f30146578908c1752bffb95f8058cd849f985655104d0
4
+ data.tar.gz: f03ec512ead274a1e8990fe2d6448c2f62361e3a5729db0f0c7f24ea10dc69d9
5
5
  SHA512:
6
- metadata.gz: 6e9513389ae6008ae04f6b9c9ed4940c3ed5e49181930064c3c43fec3772ec7dde919cac1bab5ad7e979b1cdd1146d6e4220273f83131efa368d388f08069137
7
- data.tar.gz: b9d19eb5019b239d877da6b6c42622b98cc214c407a6d4315f87ecd69b81140f42f80f8a2831b5cdf1c708b07caade83e2abe52e60f0bf15aff960707dc1274b
6
+ metadata.gz: 485d6e7e454a7ea967b11d9065f31403a7e8b7a08b9b218d674d4007f07cbe2ec702258794acf021b75b4a35627dcbe6dc4eda2d91b17f37ee392f235fdc5005
7
+ data.tar.gz: 2379f78b6e1b4d283a4e0f209e76a18597da2b8a8e0b4e596c85e86ad4e59a2c69c748f72e857c86186ebaa460a0e91fb3d58e7308d1308bcd7898042caccb14
data/README.rdoc CHANGED
@@ -1,35 +1,34 @@
1
1
  = yara-normalize
2
2
 
3
- Normalizes Yara Signatures into a repeatable hash even when non-transforming changes are made}
4
- To enable consistent comparisons between yara rules (signature), a uniform hashing standard was needed.
3
+ Normalizes YARA signatures into a repeatable, stable hash even when
4
+ non-semantic changes are made (whitespace, comments, tag ordering, variable
5
+ renaming, etc.).
5
6
 
6
- This modules takes just the strings from the strings section, sorts them, then generate a sha1 hash.
7
- Then, in the conditions section, reorder the boolean expression to make groups first and then replace all variables
8
- with $a $b $c, etc. Then hash the result of this.
7
+ To enable consistent comparisons between YARA rules, a uniform fingerprinting
8
+ standard is applied:
9
9
 
10
- Then, the signature ID is the concatenation of the truncated md5 sum of the sorted strings and the truncated md5 sum of the normalized conditions. E.g., yn01:488085c947cb22ed:d936fceffe.
10
+ 1. *Strings section* each string value (the part after the '=') is extracted,
11
+ sorted alphabetically, and the sorted list is hashed with SHA-256. Variable
12
+ names ($a, $mshtmlExec_1, …) are excluded from the hash so that renaming
13
+ does not change the fingerprint.
11
14
 
12
- == Usage
15
+ 2. *Condition section* — variable references ($name, #name) are replaced with
16
+ positional tokens ($0, $1, …) in order of first appearance, so cosmetic
17
+ renames do not affect the hash. The resulting text is hashed with SHA-256.
18
+
19
+ The rule fingerprint is:
20
+
21
+ yn<VERSION>:<last-16-hex-chars-of-strings-SHA256>:<last-10-hex-chars-of-condition-SHA256>
13
22
 
14
- See test cases.
23
+ Prior to version 0.4.0 the fingerprint used MD5 and carried the prefix +yn01+.
24
+ Since 0.4.0 the fingerprint uses SHA-256 and carries the prefix +yn02+. The
25
+ two identifier series are not interchangeable.
26
+
27
+ == Usage
15
28
 
16
29
  require 'yara-normalize'
17
- sig =<<EOS
18
- rule DataConversion__wide : IntegerParsing DataConversion {
19
- meta:
20
- weight =1
21
- strings:
22
- $="wtoi" nocase
23
- $ ="wtol" nocase
24
- $= "wtof" nocase
25
- $ = "wtodb" nocase
26
- condition:
27
- any of them
28
- }
29
- EOS
30
- yn = YaraTools::YaraRule.new(sig)
31
- puts yn.hash # => yn01:488085c947cb22ed:d936fceffe
32
- puts yn.normalize # =>
30
+
31
+ sig = <<~EOS
33
32
  rule DataConversion__wide : IntegerParsing DataConversion {
34
33
  meta:
35
34
  weight = 1
@@ -41,22 +40,57 @@ See test cases.
41
40
  condition:
42
41
  any of them
43
42
  }
44
- puts yn.name # => DataConversion__wide
45
- pp yn.tags # => ["IntegerParsing","DataConversion"]
46
-
43
+ EOS
44
+
45
+ yn = YaraTools::YaraRule.new(sig)
46
+
47
+ puts yn.hash
48
+ # => yn02:6783b7082bed88dc:6821e3f6a3
49
+
50
+ puts yn.name # => DataConversion__wide
51
+ pp yn.tags # => ["IntegerParsing", "DataConversion"]
52
+ pp yn.meta # => {"weight"=>"1"}
53
+ pp yn.strings # => ["$ = \"wtoi\" nocase", ...]
54
+
55
+ puts yn.normalize
56
+ # => rule DataConversion__wide : IntegerParsing DataConversion {
57
+ # meta:
58
+ # weight = 1
59
+ # strings:
60
+ # $ = "wtoi" nocase
61
+ # $ = "wtol" nocase
62
+ # $ = "wtof" nocase
63
+ # $ = "wtodb" nocase
64
+ # condition:
65
+ # any of them
66
+ # }
67
+
68
+ Splitting a multi-rule file:
69
+
70
+ rules = YaraTools::Splitter.split(File.read("ruleset.yar"))
71
+ rules.each { |r| puts "#{r.name}: #{r.hash}" }
72
+
73
+ == Security notes
74
+
75
+ * Fingerprints use SHA-256 (as of yn02). MD5-based yn01 hashes should be
76
+ considered legacy and re-computed.
77
+ * +YaraRule#hash+ overrides Ruby's +Object#hash+. Do *not* use +YaraRule+
78
+ objects as Hash keys; the method returns a String fingerprint, not the
79
+ Integer that Ruby's Hash tables require.
47
80
 
48
81
  == Contributing to yara-normalize
49
-
50
- * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
51
- * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
82
+
83
+ * Check out the latest master to make sure the feature hasn't been implemented
84
+ or the bug hasn't been fixed yet.
85
+ * Check out the issue tracker to make sure someone already hasn't requested it
86
+ and/or contributed it.
52
87
  * Fork the project.
53
88
  * Start a feature/bugfix branch.
54
89
  * Commit and push until you are happy with your contribution.
55
- * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
56
- * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
90
+ * Make sure to add tests for it. This is important so I don't break it in a
91
+ future version unintentionally.
92
+ * Please try not to mess with the Rakefile, version, or history.
57
93
 
58
94
  == Copyright
59
95
 
60
- Copyright (c) 2012 chrislee35. See LICENSE.txt for
61
- further details.
62
-
96
+ Copyright (c) 2012 chrislee35. See LICENSE.txt for further details.
@@ -1,110 +1,155 @@
1
- require 'digest/md5'
2
- require 'pp'
1
+ require 'digest'
2
+
3
3
  module YaraTools
4
- VERSION = "01"
5
- class YaraRule
6
- attr_reader :original, :name, :tags, :meta, :strings, :condition, :normalized_strings
7
- def initialize(ruletext)
8
- ruletext = ruletext.gsub(/[\r\n]+/,"\n").gsub(/^\s*\/\/.*$/,'')
9
- @original = ruletext
10
- @lookup_table = {}
11
- @next_replacement = 0
12
-
13
- if ruletext =~ /rule\s+([\w\-]+)(\s*:\s*(\w[\w\s]+\w))?\s*\{\s*(meta:\s*(.*?))?strings:\s*(.*?)\s*condition:\s*(.*?)\s*\}/m
14
- name,_,tags,_,meta,strings,condition = $~.captures
15
- @name = name
16
- @tags = tags.strip.split(/[,\s]+/) if tags
17
- @meta = {}
18
- if meta
19
- meta.split(/\n/).each do |m|
20
- k,v = m.strip.split(/\s*=\s*/,2)
21
- if v
22
- @meta[k] = v
23
- end
24
- end
25
- end
26
- @normalized_strings = []
27
- @strings = strings.split(/\n/).map do |s|
28
- # strip off the spaces from the edges and then replace the first = with ' = '.
29
- s = s.strip
30
- if s[/\s*=\s*/,0]
31
- s[/\s*=\s*/,0] = " = "
32
- end
33
- if s =~ /= \{([0-9a-fA-F\s]+)\}/
34
- # normalize the hex string
35
- hexstr = $1.gsub(/\s+/,'').downcase.scan(/../).join(" ")
36
- s = s.gsub(/= \{([0-9a-fA-F\s]+)\}/, "= { #{hexstr} }")
37
- end
38
- _, val = s.split(/ = /,2)
39
- if val
40
- @normalized_strings << val
41
- else
42
- @normalized_strings << s
43
- end
44
- s
45
- end
46
- @normalized_strings.sort!
47
- @condition = condition.split(/\n/).map{|x| x.strip}
48
- @normalized_condition = @condition.map{|x| _normalize_condition(x)}
49
- end
50
- end
51
-
52
- def _normalize_condition(condition)
53
- condition.gsub(/[\$\#]\w+/) do |x|
54
- key = x[1,1000]
55
- if not @lookup_table[key]
56
- @lookup_table[key] = @next_replacement.to_s
57
- @next_replacement += 1
58
- end
59
- x[0].chr+@lookup_table[key]
60
- end
61
- end
62
-
63
- def normalize
64
- text = "rule #{@name} "
65
- if @tags and @tags.length > 0
66
- text += ": #{@tags.join(' ')} "
67
- end
68
- text += "{\n"
69
- if @meta and @meta.length > 0
70
- text += " meta:\n"
71
- @meta.each do |k,v|
72
- text += " #{k} = #{v}\n"
73
- end
74
- end
75
- if @strings and @strings.length > 0
76
- text += " strings:\n"
77
- @strings.each do |s|
78
- if s =~ /\w/
79
- text += " #{s}\n"
80
- end
81
- end
82
- end
83
- if @condition and @condition.length > 0
84
- text += " condition:\n"
85
- @condition.each do |c|
86
- if c =~ /\w/
87
- text += " #{c}\n"
88
- end
89
- end
90
- end
91
- text + "}"
92
- end
93
-
94
- def hash
95
- normalized_strings = @normalized_strings.join("%")
96
- normalized_condition = @normalized_condition.join("%")
97
- strings_hash = Digest::MD5.hexdigest(normalized_strings)
98
- condition_hash = Digest::MD5.hexdigest(normalized_condition)
99
- "yn#{VERSION}:#{strings_hash[-16,16]}:#{condition_hash[-10,10]}"
100
- end
101
- end
102
-
103
- class Splitter
104
- def Splitter.split(ruleset)
105
- ruleset.gsub(/[\r\n]+/,"\n").gsub(/^\s*\/\/.*$/,'').scan(/(rule\s+([\w\-]+)(\s*:\s*(\w[\w\s]+\w))?\s*\{\s*(meta:\s*(.*?))?strings:\s*(.*?)\s*condition:\s*(.*?)\s*\})/m).map do |rule|
106
- YaraRule.new(rule[0])
107
- end
108
- end
109
- end
4
+ # Hash format version embedded in every yn-hash identifier.
5
+ # Increment when the normalization algorithm changes so consumers can
6
+ # detect that two hashes are not directly comparable (e.g. yn01 vs yn02).
7
+ VERSION = "02"
8
+
9
+ class YaraRule
10
+ attr_reader :original, :name, :tags, :meta, :strings, :condition, :normalized_strings
11
+
12
+ def initialize(ruletext)
13
+ # Normalize line endings and strip single-line (//) comments before
14
+ # any further parsing so they never appear in meta/strings/condition.
15
+ ruletext = ruletext.gsub(/[\r\n]+/, "\n").gsub(/^\s*\/\/.*$/, '')
16
+ @original = ruletext
17
+
18
+ # Lookup table used by _normalize_condition to replace variable names
19
+ # ($foo, #foo) with stable positional tokens ($0, $1, …) so that
20
+ # cosmetic renames do not affect the normalized condition hash.
21
+ @lookup_table = {}
22
+ @next_replacement = 0
23
+
24
+ # Single-pass regex parse. The rule grammar is:
25
+ # rule <name> [: <tags>] { [meta: …] strings: … condition: … }
26
+ # The .*? quantifiers are non-greedy so they stop at the first matching
27
+ # delimiter keyword rather than consuming the whole file.
28
+ rule_re = /rule\s+([\w\-]+)(\s*:\s*(\w[\w\s]+\w))?\s*\{\s*(meta:\s*(.*?))?strings:\s*(.*?)\s*condition:\s*(.*?)\s*\}/m
29
+ if ruletext =~ rule_re
30
+ name, _, tags, _, meta, strings, condition = $~.captures
31
+
32
+ @name = name
33
+
34
+ # Tags are optional; split on whitespace/commas when present.
35
+ @tags = tags.strip.split(/[,\s]+/) if tags
36
+
37
+ # Parse the meta section into a key/value Hash. Each line has the
38
+ # form: key = value (value may contain spaces and quotes).
39
+ @meta = {}
40
+ if meta
41
+ meta.split(/\n/).each do |m|
42
+ k, v = m.strip.split(/\s*=\s*/, 2)
43
+ @meta[k] = v if v
44
+ end
45
+ end
46
+
47
+ # Parse the strings section, normalizing whitespace around '=' and
48
+ # canonicalizing any hex byte strings (e.g. { 4D 5A } → { 4d 5a }).
49
+ @normalized_strings = []
50
+ @strings = strings.split(/\n/).map do |s|
51
+ s = s.strip
52
+
53
+ # Collapse any amount of whitespace around '=' to a single ' = '.
54
+ s[/\s*=\s*/, 0] = " = " if s[/\s*=\s*/, 0]
55
+
56
+ # Hex byte strings: normalise spacing and case so that
57
+ # { 4D5A } and { 4d 5a } produce the same output.
58
+ if s =~ /= \{([0-9a-fA-F\s]+)\}/
59
+ hexstr = $1.gsub(/\s+/, '').downcase.scan(/../).join(" ")
60
+ s = s.gsub(/= \{([0-9a-fA-F\s]+)\}/, "= { #{hexstr} }")
61
+ end
62
+
63
+ # Collect only the value portion (right of ' = ') for hashing,
64
+ # so that variable renames ($a → $b) do not change the hash.
65
+ _, val = s.split(/ = /, 2)
66
+ @normalized_strings << (val || s)
67
+ s
68
+ end
69
+ @normalized_strings.sort!
70
+
71
+ @condition = condition.split(/\n/).map(&:strip)
72
+ @normalized_condition = @condition.map { |x| _normalize_condition(x) }
73
+ end
74
+ end
75
+
76
+ # Replace named variable references in a condition line with positional
77
+ # tokens so that renaming $mshtmlExec_1 → $a does not change the hash.
78
+ # Both count (#) and match ($) sigils are preserved.
79
+ # NOTE: This method is intentionally prefixed with _ to signal that it is
80
+ # an internal implementation detail; do not call it from outside this class.
81
+ def _normalize_condition(condition)
82
+ condition.gsub(/[\$\#]\w+/) do |x|
83
+ key = x[1, 1000]
84
+ @lookup_table[key] ||= begin
85
+ val = @next_replacement.to_s
86
+ @next_replacement += 1
87
+ val
88
+ end
89
+ x[0].chr + @lookup_table[key]
90
+ end
91
+ end
92
+
93
+ # Return a canonical, human-readable rendering of the rule with
94
+ # consistent indentation and ordering. Tags, meta, strings, and
95
+ # condition are preserved in their original order.
96
+ def normalize
97
+ text = "rule #{@name} "
98
+ text += ": #{@tags.join(' ')} " if @tags && !@tags.empty?
99
+ text += "{\n"
100
+
101
+ if @meta && !@meta.empty?
102
+ text += " meta:\n"
103
+ @meta.each { |k, v| text += " #{k} = #{v}\n" }
104
+ end
105
+
106
+ if @strings && !@strings.empty?
107
+ text += " strings:\n"
108
+ @strings.each { |s| text += " #{s}\n" if s =~ /\w/ }
109
+ end
110
+
111
+ if @condition && !@condition.empty?
112
+ text += " condition:\n"
113
+ @condition.each { |c| text += " #{c}\n" if c =~ /\w/ }
114
+ end
115
+
116
+ text + "}"
117
+ end
118
+
119
+ # Return a stable identifier for this rule in the form:
120
+ # yn<VERSION>:<strings_fingerprint>:<condition_fingerprint>
121
+ #
122
+ # The strings fingerprint is the last 16 hex chars of the SHA-256 digest
123
+ # of the sorted, normalised string values joined by '%'.
124
+ # The condition fingerprint is the last 10 hex chars of the SHA-256 digest
125
+ # of the normalised condition lines joined by '%'.
126
+ #
127
+ # Using SHA-256 (replacing the previous MD5) gives 256-bit collision
128
+ # resistance and avoids MD5's well-known preimage and collision weaknesses.
129
+ #
130
+ # SECURITY NOTE: This method is named `hash` to match the public API, but
131
+ # it overrides Ruby's built-in Object#hash, which is expected to return an
132
+ # Integer for use as a Hash table key. Do NOT use YaraRule objects as Hash
133
+ # keys; use .hash (this method) only for YARA rule fingerprinting.
134
+ def hash
135
+ normalized_strings = @normalized_strings.join("%")
136
+ normalized_condition = @normalized_condition.join("%")
137
+ strings_digest = Digest::SHA256.hexdigest(normalized_strings)
138
+ condition_digest = Digest::SHA256.hexdigest(normalized_condition)
139
+ "yn#{VERSION}:#{strings_digest[-16, 16]}:#{condition_digest[-10, 10]}"
140
+ end
141
+ end
142
+
143
+ # Splits a multi-rule YARA file into individual YaraRule objects.
144
+ class Splitter
145
+ # Parse a string containing one or more YARA rules and return an Array of
146
+ # YaraRule instances, one per rule found in +ruleset+.
147
+ def self.split(ruleset)
148
+ # Strip line endings and single-line comments before scanning so that
149
+ # comment text cannot interfere with the rule boundary regex.
150
+ clean = ruleset.gsub(/[\r\n]+/, "\n").gsub(/^\s*\/\/.*$/, '')
151
+ rule_re = /(rule\s+([\w\-]+)(\s*:\s*(\w[\w\s]+\w))?\s*\{\s*(meta:\s*(.*?))?strings:\s*(.*?)\s*condition:\s*(.*?)\s*\})/m
152
+ clean.scan(rule_re).map { |rule| YaraRule.new(rule[0]) }
153
+ end
154
+ end
110
155
  end
metadata CHANGED
@@ -1,113 +1,117 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yara-normalize
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
- - chrislee35
8
- autorequire:
7
+ - Chris Lee
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-05-01 00:00:00.000000000 Z
11
+ date: 2026-04-25 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: test-unit
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.6'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: shoulda
15
29
  requirement: !ruby/object:Gem::Requirement
16
30
  requirements:
17
- - - ">="
31
+ - - "~>"
18
32
  - !ruby/object:Gem::Version
19
33
  version: '4'
20
34
  type: :development
21
35
  prerelease: false
22
36
  version_requirements: !ruby/object:Gem::Requirement
23
37
  requirements:
24
- - - ">="
38
+ - - "~>"
25
39
  - !ruby/object:Gem::Version
26
40
  version: '4'
27
41
  - !ruby/object:Gem::Dependency
28
- name: rdoc
42
+ name: rspec
29
43
  requirement: !ruby/object:Gem::Requirement
30
44
  requirements:
31
45
  - - "~>"
32
46
  - !ruby/object:Gem::Version
33
- version: '6.4'
47
+ version: '3.12'
34
48
  type: :development
35
49
  prerelease: false
36
50
  version_requirements: !ruby/object:Gem::Requirement
37
51
  requirements:
38
52
  - - "~>"
39
53
  - !ruby/object:Gem::Version
40
- version: '6.4'
54
+ version: '3.12'
41
55
  - !ruby/object:Gem::Dependency
42
- name: bundler
56
+ name: rake
43
57
  requirement: !ruby/object:Gem::Requirement
44
58
  requirements:
45
59
  - - "~>"
46
60
  - !ruby/object:Gem::Version
47
- version: '2.3'
61
+ version: '13.3'
48
62
  type: :development
49
63
  prerelease: false
50
64
  version_requirements: !ruby/object:Gem::Requirement
51
65
  requirements:
52
66
  - - "~>"
53
67
  - !ruby/object:Gem::Version
54
- version: '2.3'
68
+ version: '13.3'
55
69
  - !ruby/object:Gem::Dependency
56
- name: jeweler
70
+ name: bundler
57
71
  requirement: !ruby/object:Gem::Requirement
58
72
  requirements:
59
73
  - - "~>"
60
74
  - !ruby/object:Gem::Version
61
- version: 2.3.9
75
+ version: '2.7'
62
76
  type: :development
63
77
  prerelease: false
64
78
  version_requirements: !ruby/object:Gem::Requirement
65
79
  requirements:
66
80
  - - "~>"
67
81
  - !ruby/object:Gem::Version
68
- version: 2.3.9
82
+ version: '2.7'
69
83
  - !ruby/object:Gem::Dependency
70
- name: test-unit
84
+ name: rdoc
71
85
  requirement: !ruby/object:Gem::Requirement
72
86
  requirements:
73
87
  - - "~>"
74
88
  - !ruby/object:Gem::Version
75
- version: 3.5.3
89
+ version: '6.6'
76
90
  type: :development
77
91
  prerelease: false
78
92
  version_requirements: !ruby/object:Gem::Requirement
79
93
  requirements:
80
94
  - - "~>"
81
95
  - !ruby/object:Gem::Version
82
- version: 3.5.3
83
- description: To enable consistent comparisons between yara rules (signature), a uniform
84
- hashing standard was needed.
85
- email: rubygems@chrislee.dhs.org
96
+ version: '6.6'
97
+ description: Provides normalization and hashing utilities for Yara rule comparisons.
98
+ email:
99
+ - rubygems@chrislee.dhs.org
86
100
  executables:
87
101
  - yaratool
88
102
  extensions: []
89
- extra_rdoc_files:
90
- - LICENSE.txt
91
- - README.rdoc
103
+ extra_rdoc_files: []
92
104
  files:
93
- - ".document"
94
- - Gemfile
95
- - Gemfile.lock
96
105
  - LICENSE.txt
97
106
  - README.rdoc
98
- - Rakefile
99
- - VERSION
100
107
  - bin/yaratool
101
108
  - lib/yara-normalize.rb
102
109
  - lib/yara-normalize/yara-normalize.rb
103
- - test/helper.rb
104
- - test/test_yara-normalize.rb
105
- - yara-normalize.gemspec
106
- homepage: http://github.com/chrislee35/yara-normalize
110
+ homepage: https://github.com/chrislee35/yara-normalize
107
111
  licenses:
108
112
  - MIT
109
113
  metadata: {}
110
- post_install_message:
114
+ post_install_message:
111
115
  rdoc_options: []
112
116
  require_paths:
113
117
  - lib
@@ -115,16 +119,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
115
119
  requirements:
116
120
  - - ">="
117
121
  - !ruby/object:Gem::Version
118
- version: '0'
122
+ version: '3.0'
119
123
  required_rubygems_version: !ruby/object:Gem::Requirement
120
124
  requirements:
121
125
  - - ">="
122
126
  - !ruby/object:Gem::Version
123
127
  version: '0'
124
128
  requirements: []
125
- rubygems_version: 3.2.3
126
- signing_key:
129
+ rubygems_version: 3.4.20
130
+ signing_key:
127
131
  specification_version: 4
128
- summary: Normalizes Yara Signatures into a repeatable hash even when non-transforming
129
- changes are made
132
+ summary: Normalizes Yara signatures into a repeatable hash even when non-transforming
133
+ changes are made.
130
134
  test_files: []
data/.document DELETED
@@ -1,5 +0,0 @@
1
- lib/**/*.rb
2
- bin/*
3
- -
4
- features/**/*.feature
5
- LICENSE.txt
data/Gemfile DELETED
@@ -1,14 +0,0 @@
1
- source "http://rubygems.org"
2
- # Add dependencies required to use your gem here.
3
- # Example:
4
- # gem "activesupport", ">= 2.3.5"
5
-
6
- # Add dependencies to develop your gem here.
7
- # Include everything needed to run rake, tests, features, etc.
8
- group :development do
9
- gem "shoulda", ">= 4"
10
- gem "rdoc", "~> 6.4"
11
- gem "bundler", "~> 2.3"
12
- gem "jeweler", "~> 2.3.9"
13
- gem "test-unit", "~> 3.5.3"
14
- end
data/Gemfile.lock DELETED
@@ -1,88 +0,0 @@
1
- GEM
2
- remote: http://rubygems.org/
3
- specs:
4
- activesupport (7.0.2.4)
5
- concurrent-ruby (~> 1.0, >= 1.0.2)
6
- i18n (>= 1.6, < 2)
7
- minitest (>= 5.1)
8
- tzinfo (~> 2.0)
9
- addressable (2.4.0)
10
- builder (3.2.4)
11
- concurrent-ruby (1.1.10)
12
- descendants_tracker (0.0.4)
13
- thread_safe (~> 0.3, >= 0.3.1)
14
- faraday (0.9.2)
15
- multipart-post (>= 1.2, < 3)
16
- git (1.11.0)
17
- rchardet (~> 1.8)
18
- github_api (0.16.0)
19
- addressable (~> 2.4.0)
20
- descendants_tracker (~> 0.0.4)
21
- faraday (~> 0.8, < 0.10)
22
- hashie (>= 3.4)
23
- mime-types (>= 1.16, < 3.0)
24
- oauth2 (~> 1.0)
25
- hashie (5.0.0)
26
- highline (2.0.3)
27
- i18n (1.10.0)
28
- concurrent-ruby (~> 1.0)
29
- jeweler (2.3.9)
30
- builder
31
- bundler
32
- git (>= 1.2.5)
33
- github_api (~> 0.16.0)
34
- highline (>= 1.6.15)
35
- nokogiri (>= 1.5.10)
36
- psych
37
- rake
38
- rdoc
39
- semver2
40
- jwt (2.3.0)
41
- mime-types (2.99.3)
42
- minitest (5.15.0)
43
- multi_json (1.15.0)
44
- multi_xml (0.6.0)
45
- multipart-post (2.1.1)
46
- nokogiri (1.13.4-x86_64-linux)
47
- racc (~> 1.4)
48
- oauth2 (1.4.8)
49
- faraday (>= 0.8, < 3.0)
50
- jwt (>= 1.0, < 3.0)
51
- multi_json (~> 1.3)
52
- multi_xml (~> 0.5)
53
- rack (>= 1.2, < 3)
54
- power_assert (2.0.1)
55
- psych (4.0.3)
56
- stringio
57
- racc (1.6.0)
58
- rack (2.2.3)
59
- rake (13.0.6)
60
- rchardet (1.8.0)
61
- rdoc (6.4.0)
62
- psych (>= 4.0.0)
63
- semver2 (3.4.2)
64
- shoulda (4.0.0)
65
- shoulda-context (~> 2.0)
66
- shoulda-matchers (~> 4.0)
67
- shoulda-context (2.0.0)
68
- shoulda-matchers (4.5.1)
69
- activesupport (>= 4.2.0)
70
- stringio (3.0.1)
71
- test-unit (3.5.3)
72
- power_assert
73
- thread_safe (0.3.6)
74
- tzinfo (2.0.4)
75
- concurrent-ruby (~> 1.0)
76
-
77
- PLATFORMS
78
- x86_64-linux
79
-
80
- DEPENDENCIES
81
- bundler (~> 2.3)
82
- jeweler (~> 2.3.9)
83
- rdoc (~> 6.4)
84
- shoulda (>= 4)
85
- test-unit (~> 3.5.3)
86
-
87
- BUNDLED WITH
88
- 2.3.12
data/Rakefile DELETED
@@ -1,46 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require 'rubygems'
4
- require 'bundler'
5
- begin
6
- Bundler.setup(:default, :development)
7
- rescue Bundler::BundlerError => e
8
- $stderr.puts e.message
9
- $stderr.puts "Run `bundle install` to install missing gems"
10
- exit e.status_code
11
- end
12
- require 'rake'
13
-
14
- require 'jeweler'
15
- Jeweler::Tasks.new do |gem|
16
- # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
- gem.name = "yara-normalize"
18
- gem.homepage = "http://github.com/chrislee35/yara-normalize"
19
- gem.license = "MIT"
20
- gem.summary = %Q{Normalizes Yara Signatures into a repeatable hash even when non-transforming changes are made}
21
- gem.description = %Q{To enable consistent comparisons between yara rules (signature), a uniform hashing standard was needed.}
22
- gem.email = "rubygems@chrislee.dhs.org"
23
- gem.authors = ["chrislee35"]
24
- #gem.signing_key = "#{File.dirname(__FILE__)}/../gem-private_key.pem"
25
- #gem.cert_chain = ["#{File.dirname(__FILE__)}/../gem-public_cert.pem"]
26
- end
27
- Jeweler::RubygemsDotOrgTasks.new
28
-
29
- require 'rake/testtask'
30
- Rake::TestTask.new(:test) do |test|
31
- test.libs << 'test'
32
- test.pattern = FileList['test/test*.rb']
33
- test.verbose = true
34
- end
35
-
36
- task :default => :test
37
-
38
- require 'rdoc/task'
39
- Rake::RDocTask.new do |rdoc|
40
- version = File.exist?('VERSION') ? File.read('VERSION') : ""
41
-
42
- rdoc.rdoc_dir = 'rdoc'
43
- rdoc.title = "yara-normalize #{version}"
44
- rdoc.rdoc_files.include('README*')
45
- rdoc.rdoc_files.include('lib/**/*.rb')
46
- end
data/VERSION DELETED
@@ -1 +0,0 @@
1
- 0.2.0
data/test/helper.rb DELETED
@@ -1,20 +0,0 @@
1
- require 'rubygems'
2
- require 'bundler'
3
- begin
4
- Bundler.setup(:default, :development)
5
- rescue Bundler::BundlerError => e
6
- $stderr.puts e.message
7
- $stderr.puts "Run `bundle install` to install missing gems"
8
- exit e.status_code
9
- end
10
-
11
- require 'test/unit'
12
- require 'shoulda'
13
-
14
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
15
- $LOAD_PATH.unshift(File.dirname(__FILE__))
16
-
17
- require 'yara-normalize'
18
-
19
- class Test::Unit::TestCase
20
- end
@@ -1,113 +0,0 @@
1
- require 'helper'
2
- require 'pp'
3
-
4
- class TestYaraNormalize < Test::Unit::TestCase
5
- should "normalize a simple signature" do
6
- sig =<<EOS
7
- rule newIE0daymshtmlExec
8
- {
9
- meta:
10
- author = "redacted @ gmail.com"
11
- ref = "http://blog.vulnhunt.com/index.php/2012/09/17/ie-execcommand-fuction-use-after-free-vulnerability-0day_en/"
12
- description = "Internet Explorer CMshtmlEd::Exec() 0day"
13
- cve = "CVE-2012-XXXX"
14
- version = "1"
15
- impact = 4
16
- hide = false
17
- strings:
18
- $mshtmlExec_1 = /document\.execCommand\(['"]selectAll['"]\)/ nocase fullword
19
- $mshtmlExec_2 = /YMjf\\u0c08\\u0c0cKDogjsiIejengNEkoPDjfiJDIWUAzdfghjAAuUFGGBSIPPPUDFJKSOQJGH/ nocase fullword
20
- $mshtmlExec_3 = /\<body\son(load|select)=['"]\w*?\(\)\;['"]\son(load|select)=['"]\w*?\(\)['"]/ nocase
21
- $mshtmlExec_4 = /var\s\w{1,}\s=\snew\sArray\(\)/ nocase
22
- $mshtmlExec_5 = /window\.document\.createElement\(['"]img['"]\)/ nocase
23
- $mshtmlExec_6 = /\w{1,}\[0\]\[['"]src['"]\]\s\=\s['"]\w{1,}['"]/ nocase
24
- $mshtmlExec_7 = /\<iframe\ssrc=['"].*?['"]/ nocase
25
- condition:
26
- ($mshtmlExec_1 and $mshtmlExec_2 and $mshtmlExec_3) or ($mshtmlExec_4 and $mshtmlExec_5 and ($mshtmlExec_6 or $mshtmlExec_7))
27
- }
28
- EOS
29
- puts sig
30
- yn = YaraTools::YaraRule.new(sig)
31
- assert_equal("yn01:66dd624d64a79f17:ecf1725295", yn.hash)
32
- assert_equal("newIE0daymshtmlExec", yn.name)
33
- assert_equal("\"redacted @ gmail.com\"", yn.meta['author'])
34
- assert_equal(["$mshtmlExec_1 = /document.execCommand(['\"]selectAll['\"])/ nocase fullword",
35
- "$mshtmlExec_2 = /YMjf\\u0c08\\u0c0cKDogjsiIejengNEkoPDjfiJDIWUAzdfghjAAuUFGGBSIPPPUDFJKSOQJGH/ nocase fullword",
36
- "$mshtmlExec_3 = /<body on(load|select)=['\"]w*?();['\"] on(load|select)=['\"]w*?()['\"]/ nocase",
37
- "$mshtmlExec_4 = /var w{1,} = new Array()/ nocase",
38
- "$mshtmlExec_5 = /window.document.createElement(['\"]img['\"])/ nocase",
39
- "$mshtmlExec_6 = /w{1,}[0][['\"]src['\"]] = ['\"]w{1,}['\"]/ nocase",
40
- "$mshtmlExec_7 = /<iframe src=['\"].*?['\"]/ nocase"], yn.strings)
41
- assert_equal(
42
- ["($mshtmlExec_1 and $mshtmlExec_2 and $mshtmlExec_3) or ($mshtmlExec_4 and $mshtmlExec_5 and ($mshtmlExec_6 or $mshtmlExec_7))"],
43
- yn.condition
44
- )
45
- hash1 = yn.hash
46
- sig =<<EOS
47
- rule newIE0daymshtmlExec : tag1 tag2 tag3
48
- {
49
- meta:
50
- author = "redacted @ gmail.com"
51
- ref = "http://blog.vulnhunt.com/index.php/2012/09/17/ie-execcommand-fuction-use-after-free-vulnerability-0day_en/"
52
- description = "Internet Explorer CMshtmlEd::Exec() 0day"
53
- cve = "CVE-2012-XXXX"
54
- version = "1"
55
- impact = 4
56
- hide = false
57
- strings:
58
- $mshtmlExec_1 = /document\.execCommand\(['"]selectAll['"]\)/ nocase fullword
59
- $mshtmlExec_2 = /YMjf\\u0c08\\u0c0cKDogjsiIejengNEkoPDjfiJDIWUAzdfghjAAuUFGGBSIPPPUDFJKSOQJGH/ nocase fullword
60
- $mshtmlExec_3 = /\<body\son(load|select)=['"]\w*?\(\)\;['"]\son(load|select)=['"]\w*?\(\)['"]/ nocase
61
- $mshtmlExec_4 = /var\s\w{1,}\s=\snew\sArray\(\)/ nocase
62
- $mshtmlExec_5 = /window\.document\.createElement\(['"]img['"]\)/ nocase
63
- $mshtmlExec_6 = /\w{1,}\[0\]\[['"]src['"]\]\s\=\s['"]\w{1,}['"]/ nocase
64
- $mshtmlExec_7 = /\<iframe\ssrc=['"].*?['"]/ nocase
65
- condition:
66
- ($mshtmlExec_1 and $mshtmlExec_2 and $mshtmlExec_3) or ($mshtmlExec_4 and $mshtmlExec_5 and ($mshtmlExec_6 or $mshtmlExec_7))
67
- }
68
- EOS
69
- yn = YaraTools::YaraRule.new(sig)
70
- assert_equal(hash1, yn.hash)
71
- assert_equal("newIE0daymshtmlExec", yn.name)
72
- assert_equal(["tag1","tag2","tag3"], yn.tags)
73
- assert_equal("\"redacted @ gmail.com\"", yn.meta['author'])
74
- assert_equal(["$mshtmlExec_1 = /document.execCommand(['\"]selectAll['\"])/ nocase fullword",
75
- "$mshtmlExec_2 = /YMjf\\u0c08\\u0c0cKDogjsiIejengNEkoPDjfiJDIWUAzdfghjAAuUFGGBSIPPPUDFJKSOQJGH/ nocase fullword",
76
- "$mshtmlExec_3 = /<body on(load|select)=['\"]w*?();['\"] on(load|select)=['\"]w*?()['\"]/ nocase",
77
- "$mshtmlExec_4 = /var w{1,} = new Array()/ nocase",
78
- "$mshtmlExec_5 = /window.document.createElement(['\"]img['\"])/ nocase",
79
- "$mshtmlExec_6 = /w{1,}[0][['\"]src['\"]] = ['\"]w{1,}['\"]/ nocase",
80
- "$mshtmlExec_7 = /<iframe src=['\"].*?['\"]/ nocase"], yn.strings)
81
- assert_equal(
82
- ["($mshtmlExec_1 and $mshtmlExec_2 and $mshtmlExec_3) or ($mshtmlExec_4 and $mshtmlExec_5 and ($mshtmlExec_6 or $mshtmlExec_7))"],
83
- yn.condition
84
- )
85
- end
86
-
87
- should "normalize a simple signature that has a condition of 'any of them'" do
88
- sig =<<EOS
89
- rule DataConversion__wide : IntegerParsing DataConversion {
90
- meta:
91
- weight = 1
92
- strings:
93
- $ = "wtoi" nocase
94
- $ = "wtol" nocase
95
- $ = "wtof" nocase
96
- $ = "wtodb" nocase
97
- condition:
98
- any of them
99
- }
100
- EOS
101
- yn = YaraTools::YaraRule.new(sig)
102
- assert_equal("yn01:a5fd8576f2da34e2:d936fceffe", yn.hash)
103
- assert_equal("1", yn.meta['weight'])
104
- assert_equal("DataConversion__wide", yn.name)
105
- assert_equal(["IntegerParsing", "DataConversion"], yn.tags)
106
- assert_equal(["$ = \"wtoi\" nocase",
107
- "$ = \"wtol\" nocase",
108
- "$ = \"wtof\" nocase",
109
- "$ = \"wtodb\" nocase"], yn.strings)
110
- assert_equal(["any of them"], yn.condition)
111
- end
112
- end
113
-
@@ -1,60 +0,0 @@
1
- # Generated by jeweler
2
- # DO NOT EDIT THIS FILE DIRECTLY
3
- # Instead, edit Jeweler::Tasks in rakefile, and run 'rake gemspec'
4
- # -*- encoding: utf-8 -*-
5
- # stub: yara-normalize 0.2.0 ruby lib
6
-
7
- Gem::Specification.new do |s|
8
- s.name = "yara-normalize".freeze
9
- s.version = "0.2.0"
10
-
11
- s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
12
- s.require_paths = ["lib".freeze]
13
- s.authors = ["chrislee35".freeze]
14
- s.date = "2022-05-01"
15
- s.description = "To enable consistent comparisons between yara rules (signature), a uniform hashing standard was needed.".freeze
16
- s.email = "rubygems@chrislee.dhs.org".freeze
17
- s.executables = ["yaratool".freeze]
18
- s.extra_rdoc_files = [
19
- "LICENSE.txt",
20
- "README.rdoc"
21
- ]
22
- s.files = [
23
- ".document",
24
- "Gemfile",
25
- "Gemfile.lock",
26
- "LICENSE.txt",
27
- "README.rdoc",
28
- "Rakefile",
29
- "VERSION",
30
- "bin/yaratool",
31
- "lib/yara-normalize.rb",
32
- "lib/yara-normalize/yara-normalize.rb",
33
- "test/helper.rb",
34
- "test/test_yara-normalize.rb",
35
- "yara-normalize.gemspec"
36
- ]
37
- s.homepage = "http://github.com/chrislee35/yara-normalize".freeze
38
- s.licenses = ["MIT".freeze]
39
- s.rubygems_version = "3.2.3".freeze
40
- s.summary = "Normalizes Yara Signatures into a repeatable hash even when non-transforming changes are made".freeze
41
-
42
- if s.respond_to? :specification_version then
43
- s.specification_version = 4
44
- end
45
-
46
- if s.respond_to? :add_runtime_dependency then
47
- s.add_development_dependency(%q<shoulda>.freeze, [">= 4"])
48
- s.add_development_dependency(%q<rdoc>.freeze, ["~> 6.4"])
49
- s.add_development_dependency(%q<bundler>.freeze, ["~> 2.3"])
50
- s.add_development_dependency(%q<jeweler>.freeze, ["~> 2.3.9"])
51
- s.add_development_dependency(%q<test-unit>.freeze, ["~> 3.5.3"])
52
- else
53
- s.add_dependency(%q<shoulda>.freeze, [">= 4"])
54
- s.add_dependency(%q<rdoc>.freeze, ["~> 6.4"])
55
- s.add_dependency(%q<bundler>.freeze, ["~> 2.3"])
56
- s.add_dependency(%q<jeweler>.freeze, ["~> 2.3.9"])
57
- s.add_dependency(%q<test-unit>.freeze, ["~> 3.5.3"])
58
- end
59
- end
60
-