heitt 0.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.
- checksums.yaml +7 -0
- data/bin/heitt +157 -0
- data/lib/heitt/database.rb +2516 -0
- data/lib/heitt/version.rb +6 -0
- data/lib/heitt.rb +357 -0
- metadata +61 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: fbc73d80ab64c2fa158725b6278866a05696a7eb14e9fb4a1f97182e26b64cc3
|
|
4
|
+
data.tar.gz: 4b0a23a93530cf86b5791e3bec8757bb0f959949980d6481c37c9c2010a4b6b6
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 1b89a08f063a90d0a26ccec811425914f8af7ff5973e33b07599ea240c8ce76b070a1803271039f9e2f381269095e1ec5b874c4727cbf2b5cc64a241d03eb998
|
|
7
|
+
data.tar.gz: 786a8226e42a20d8466bc256fe1b0ab37cf8fe725b5a20df029410abe52985ea4c91f00a5cf654aee8625abf3b26c8faf207f86ca03c1130793e2e50ad6fda07
|
data/bin/heitt
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
require 'optparse'
|
|
3
|
+
require 'io/console'
|
|
4
|
+
require 'heitt'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
module HEITT
|
|
8
|
+
class CLI
|
|
9
|
+
attr_accessor :inputs, :extended, :verbose, :output, :json, :database, :show_regex_match, :min_entropy
|
|
10
|
+
|
|
11
|
+
def initialize
|
|
12
|
+
@inputs = []
|
|
13
|
+
@extended = false
|
|
14
|
+
@verbose = false
|
|
15
|
+
@output = ""
|
|
16
|
+
@json = false
|
|
17
|
+
@database = ""
|
|
18
|
+
@show_regex_match = false
|
|
19
|
+
@min_entropy = 3.5
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def parse!
|
|
24
|
+
OptionParser.new do |opts|
|
|
25
|
+
opts.banner = ""
|
|
26
|
+
opts.program_name = "heitt"
|
|
27
|
+
opts.separator "#{header("=========================================================================")}"
|
|
28
|
+
opts.separator "#{header("HEITT v#{HEITT::VERSION} - Hash Extraction, Identification & Triage Tool")}"
|
|
29
|
+
opts.separator "#{header("=========================================================================")}"
|
|
30
|
+
opts.separator ""
|
|
31
|
+
opts.separator "Extract and identify hashes from any input."
|
|
32
|
+
opts.separator "Input may be hash string, a file or read from stin"
|
|
33
|
+
opts.separator ""
|
|
34
|
+
opts.separator "Usage: heitt [<INPUT(S)>] [OPTIONS]"
|
|
35
|
+
opts.separator ""
|
|
36
|
+
opts.separator "ARGUMENTS:"
|
|
37
|
+
opts.separator " [<INPUT(S)>] Hash string or filepath"
|
|
38
|
+
opts.separator ""
|
|
39
|
+
opts.separator "GENERAL OPTIONS:"
|
|
40
|
+
opts.on("-h", "--help", "Show this help message and exit"){puts opts; exit}
|
|
41
|
+
opts.on("-v", "--version", "Show version information"){puts "heitt v#{HEITT::VERSION} by jobotow (#{HEITT::GITHUB})"; exit}
|
|
42
|
+
opts.separator ""
|
|
43
|
+
|
|
44
|
+
opts.separator ""
|
|
45
|
+
opts.separator "OUTPUT OPTIONS:"
|
|
46
|
+
opts.on("-V", "--verbose", "Show description and notes for each candidate") {@verbose = true}
|
|
47
|
+
opts.on("-j", "--json","Output in json format") {@json = true}
|
|
48
|
+
opts.on("-o", "--output FILEPATH", String, "File to write output to") {|v| @output = v}
|
|
49
|
+
|
|
50
|
+
opts.separator ""
|
|
51
|
+
opts.separator "FILTERING OPTIONS:"
|
|
52
|
+
opts.on("-e", "--extended", "Show extended candidates") {@extended = true}
|
|
53
|
+
opts.on("-r", "--regex-match", "Show regex-matched candidates") {@show_regex_match = true}
|
|
54
|
+
opts.on("-E", "--min-entropy FLOAT", Float, "Minimum entropy threshold[default: 3.5]") {|v| puts "MIN ENTROPY: #{v}"; @min_entropy = v}
|
|
55
|
+
|
|
56
|
+
opts.separator ""
|
|
57
|
+
opts.separator "DATABASE OPTIONS:"
|
|
58
|
+
opts.on("-D", "--database FILEPATH", String, "Use custom database") {|v| @database = v}
|
|
59
|
+
|
|
60
|
+
opts.separator ""
|
|
61
|
+
opts.separator "EXAMPLES:"
|
|
62
|
+
opts.separator " heitt b1946ac92492d2347c6235b4d2611184"
|
|
63
|
+
opts.separator " heitt auth.log"
|
|
64
|
+
opts.separator " heitt auth.log --json --output result.json"
|
|
65
|
+
opts.separator " heitt auth.log --extended --regex-match"
|
|
66
|
+
opts.separator " heitt auth.log --min-entropy 4.0"
|
|
67
|
+
opts.separator " cat auth.log | heitt "
|
|
68
|
+
opts.separator ""
|
|
69
|
+
opts.separator ""
|
|
70
|
+
opts.separator "NOTES:"
|
|
71
|
+
opts.separator " JSON format is default when output is redirected or piped."
|
|
72
|
+
opts.separator " Regex-match candidates are hidden by default, use '-r' to show."
|
|
73
|
+
opts.separator " Running without input starts interactive mode."
|
|
74
|
+
opts.separator "#{header("=========================================================================")}"
|
|
75
|
+
opts.separator "#{header("END OF HELP")}"
|
|
76
|
+
opts.separator "#{header("=========================================================================")}"
|
|
77
|
+
end.parse!
|
|
78
|
+
|
|
79
|
+
@inputs = if ARGV.empty?
|
|
80
|
+
if $stdin.tty?
|
|
81
|
+
# Interactive mode
|
|
82
|
+
puts "#{header("=== HEITT INTERACTIVE MODE ====\n")}"
|
|
83
|
+
puts "Enter a hash or file"
|
|
84
|
+
loop do
|
|
85
|
+
print "heitt> "
|
|
86
|
+
input = $stdin.gets&.strip
|
|
87
|
+
break if input.nil? || input.empty?
|
|
88
|
+
results = HEITT::Scanner.scan(input, min_entropy: @min_entropy)
|
|
89
|
+
groups = HEITT::Grouper.group(results)
|
|
90
|
+
format = @json ? :json : :tree
|
|
91
|
+
output = case format
|
|
92
|
+
when :json
|
|
93
|
+
HEITT::Formatter.json(groups, extended: @extended, show_regex_match: @show_regex_match)
|
|
94
|
+
else
|
|
95
|
+
HEITT::Formatter.tree(groups, extended: @extended, show_regex_match: @show_regex_match)
|
|
96
|
+
end
|
|
97
|
+
puts output
|
|
98
|
+
puts ""
|
|
99
|
+
end
|
|
100
|
+
else
|
|
101
|
+
[$stdin.read]
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
else
|
|
106
|
+
ARGV.dup
|
|
107
|
+
end
|
|
108
|
+
self
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def run
|
|
112
|
+
database = @database.empty? ? HEITT::DATABASE : load_custom_database(@database)
|
|
113
|
+
|
|
114
|
+
format = @json || !$stdout.tty? ? :json : :tree
|
|
115
|
+
output = @inputs.map do |input|
|
|
116
|
+
results = HEITT::Scanner.scan(input, database: database, min_entropy: @min_entropy)
|
|
117
|
+
groups = HEITT::Grouper.group(results)
|
|
118
|
+
|
|
119
|
+
case format
|
|
120
|
+
when :json
|
|
121
|
+
HEITT::Formatter.json(groups, extended: @extended, show_regex_match: @show_regex_match)
|
|
122
|
+
else
|
|
123
|
+
HEITT::Formatter.tree(groups, extended: @extended, show_regex_match: @show_regex_match)
|
|
124
|
+
end
|
|
125
|
+
end.join("\n")
|
|
126
|
+
|
|
127
|
+
unless @output.empty?
|
|
128
|
+
File.write(@output, output)
|
|
129
|
+
else
|
|
130
|
+
puts output
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
private
|
|
135
|
+
def header(text)
|
|
136
|
+
terminal_width = IO.console.winsize[1] rescue 70
|
|
137
|
+
text.center(terminal_width)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def load_custom_database(filepath)
|
|
142
|
+
unless File.exist?(filepath)
|
|
143
|
+
puts HEITT::Color.colorize("[ERROR] Database file #{filepath} does not exists", :bold, :red)
|
|
144
|
+
exit(1)
|
|
145
|
+
end
|
|
146
|
+
begin
|
|
147
|
+
custom_database = JSON.parse(File.read(filepath), symbolize_names: true)
|
|
148
|
+
HEITT::DATABASE + custom_database
|
|
149
|
+
rescue JSON::ParserError => e
|
|
150
|
+
puts "#{HEITT::Color.colorize("[ERROR] Invalid JSON in database file: ", :bold, :red)}#{e.message}"
|
|
151
|
+
exit(1)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
cli = HEITT::CLI.new.parse!.run #if __FILE__ == $0
|