lab 0.0.3 → 0.0.4
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.
- data/Gemfile +4 -0
- data/Gemfile.lock +16 -0
- data/README +77 -0
- data/Rakefile +1 -0
- data/TODO +15 -0
- data/lab.gemspec +24 -0
- data/lib/lab.rb +4 -0
- data/lib/lab/controller/dynagen_controller.rb +14 -0
- data/lib/lab/controller/fog_controller.rb +6 -0
- data/lib/lab/controller/remote_esx_controller.rb +62 -0
- data/lib/lab/controller/remote_workstation_controller.rb +22 -0
- data/lib/lab/controller/virtualbox_controller.rb +25 -0
- data/lib/lab/controller/workstation_controller.rb +17 -0
- data/lib/lab/controller/workstation_vixr_controller.rb +19 -0
- data/lib/lab/controllers.rb +10 -0
- data/lib/lab/driver/dynagen_driver.rb +47 -0
- data/lib/lab/driver/fog_driver.rb +164 -0
- data/lib/lab/driver/remote_esx_driver.rb +177 -0
- data/lib/lab/driver/remote_workstation_driver.rb +198 -0
- data/lib/lab/driver/virtualbox_driver.rb +142 -0
- data/lib/lab/driver/vm_driver.rb +180 -0
- data/lib/lab/driver/workstation_driver.rb +188 -0
- data/lib/lab/driver/workstation_vixr_driver.rb +126 -0
- data/lib/lab/drivers.rb +8 -0
- data/lib/lab/modifier/backtrack5_modifier.rb +16 -0
- data/lib/lab/modifier/dos_modifier.rb +14 -0
- data/lib/lab/modifier/meterpreter_modifier.rb +167 -0
- data/lib/lab/modifier/test_modifier.rb +16 -0
- data/lib/lab/modifiers.rb +4 -0
- data/lib/lab/version.rb +3 -0
- data/lib/lab/vm.rb +253 -0
- data/lib/lab/vm_controller.rb +238 -0
- data/test/.gitkeep +0 -0
- metadata +39 -6
data/lib/lab/drivers.rb
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
require 'driver/workstation_driver'
|
2
|
+
require 'driver/virtualbox_driver'
|
3
|
+
require 'driver/fog_driver'
|
4
|
+
require 'driver/dynagen_driver'
|
5
|
+
require 'driver/remote_workstation_driver'
|
6
|
+
require 'driver/remote_esx_driver'
|
7
|
+
#require 'driver/qemu_driver'
|
8
|
+
#require 'driver/qemudo_driver'
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Lab
|
2
|
+
module Modifier
|
3
|
+
module Backtrack5
|
4
|
+
|
5
|
+
def nmap(options)
|
6
|
+
run_command("nmap #{filter_input(options)}")
|
7
|
+
end
|
8
|
+
|
9
|
+
def testssl(site)
|
10
|
+
run_command("/pentest/scanners/testssl/testssl.sh #{filter_input(site)}")
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
@@ -0,0 +1,167 @@
|
|
1
|
+
$:.unshift(File.join(File.dirname(__FILE__), '..', '..'))
|
2
|
+
|
3
|
+
module Lab
|
4
|
+
module Modifier
|
5
|
+
module Meterpreter
|
6
|
+
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
# This allows us to override the default way of running commands
|
13
|
+
# Currently useful for the esx controller
|
14
|
+
|
15
|
+
module Lab
|
16
|
+
class Vm
|
17
|
+
|
18
|
+
attr_accessor :framework
|
19
|
+
attr_accessor :session
|
20
|
+
attr_accessor :session_input
|
21
|
+
attr_accessor :session_output
|
22
|
+
|
23
|
+
def create_framework
|
24
|
+
return if @framework
|
25
|
+
@framework = Msf::Simple::Framework.create
|
26
|
+
end
|
27
|
+
|
28
|
+
# perform the setup only once
|
29
|
+
def setup_session
|
30
|
+
return if @session
|
31
|
+
|
32
|
+
# require the framework (assumes this sits in lib/lab/modifiers)
|
33
|
+
require 'msf/base'
|
34
|
+
|
35
|
+
create_framework ## TODO - this should use a single framework
|
36
|
+
## for all hosts, not one-per-host
|
37
|
+
|
38
|
+
@session = nil
|
39
|
+
@session_input = Rex::Ui::Text::Input::Buffer.new
|
40
|
+
@session_output = Rex::Ui::Text::Output::Buffer.new
|
41
|
+
|
42
|
+
if @os == "windows"
|
43
|
+
exploit_name = 'windows/smb/psexec'
|
44
|
+
|
45
|
+
# TODO - check for x86, choose the appropriate payload
|
46
|
+
|
47
|
+
payload_name = 'windows/meterpreter/bind_tcp'
|
48
|
+
options = { "RHOST" => @hostname,
|
49
|
+
"SMBUser" => @vm_user,
|
50
|
+
"SMBPass" => @vm_pass}
|
51
|
+
|
52
|
+
puts "DEBUG: using options #{options}"
|
53
|
+
|
54
|
+
# Initialize the exploit instance
|
55
|
+
exploit = @framework.exploits.create(exploit_name)
|
56
|
+
|
57
|
+
begin
|
58
|
+
# Fire it off.
|
59
|
+
@session = exploit.exploit_simple(
|
60
|
+
'Payload' => payload_name,
|
61
|
+
'Options' => options,
|
62
|
+
'LocalInput' => @session_input,
|
63
|
+
'LocalOutput' => @session_output)
|
64
|
+
@session.load_stdapi
|
65
|
+
|
66
|
+
puts "DEBUG: Generated session: #{@session}"
|
67
|
+
|
68
|
+
rescue Exception => e
|
69
|
+
puts "DEBUG: Unable to exploit"
|
70
|
+
puts e.to_s
|
71
|
+
end
|
72
|
+
|
73
|
+
else
|
74
|
+
module_name = 'scanner/ssh/ssh_login'
|
75
|
+
|
76
|
+
# TODO - check for x86, choose the appropriate payload
|
77
|
+
|
78
|
+
payload_name = 'linux/x86/shell_bind_tcp'
|
79
|
+
options = { "RHOSTS" => @hostname,
|
80
|
+
"USERNAME" => @vm_user,
|
81
|
+
"PASSWORD" => @vm_pass,
|
82
|
+
"BLANK_PASSWORDS" => false,
|
83
|
+
"USER_AS_PASS" => false,
|
84
|
+
"VERBOSE" => false}
|
85
|
+
|
86
|
+
puts "DEBUG: using options #{options}"
|
87
|
+
|
88
|
+
# Initialize the module instance
|
89
|
+
aux = @framework.auxiliary.create(module_name)
|
90
|
+
|
91
|
+
puts "DEBUG: created module: #{aux}"
|
92
|
+
|
93
|
+
begin
|
94
|
+
# Fire it off.
|
95
|
+
aux.run_simple(
|
96
|
+
'Payload' => payload_name,
|
97
|
+
'Options' => options,
|
98
|
+
'LocalInput' => @session_input,
|
99
|
+
'LocalOutput' => @session_output)
|
100
|
+
|
101
|
+
@session = @framework.sessions.first.last
|
102
|
+
puts "DEBUG: Generated session: #{@session}"
|
103
|
+
rescue Exception => e
|
104
|
+
puts "DEBUG: Unable to exploit"
|
105
|
+
puts e.to_s
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
def run_command(command, timeout=60)
|
114
|
+
|
115
|
+
setup_session
|
116
|
+
puts "Using session #{@session}"
|
117
|
+
|
118
|
+
# TODO: pass the timeout down
|
119
|
+
|
120
|
+
if @session
|
121
|
+
if @session.type == "shell"
|
122
|
+
puts "Running command via shell: #{command}"
|
123
|
+
@session.shell_command_token(command, timeout)
|
124
|
+
elsif @session.type == "meterpreter"
|
125
|
+
puts "Running command via meterpreter: #{command}"
|
126
|
+
@session.shell_command(command) #, timeout)
|
127
|
+
end
|
128
|
+
else
|
129
|
+
raise "No session"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
|
134
|
+
# This isn't part of the normal API, but too good to pass up.
|
135
|
+
def run_script(script, options)
|
136
|
+
if @session.type == "meterpreter"
|
137
|
+
@session.execute_script(script, options)
|
138
|
+
else
|
139
|
+
raise "Unsupported on #{@session.type}"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# For meterpreter API compatibility
|
144
|
+
#def execute_file(script,options)
|
145
|
+
# run_script(script,options)
|
146
|
+
#end
|
147
|
+
|
148
|
+
def copy_to(local,remote)
|
149
|
+
setup_session
|
150
|
+
if @session.type == "meterpreter"
|
151
|
+
@session.run_cmd("upload #{local} #{remote}")
|
152
|
+
else
|
153
|
+
@driver.copy_to(local,remote)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def copy_from(local, remote)
|
158
|
+
setup_session
|
159
|
+
if @session.type == "meterpreter"
|
160
|
+
@session.run_cmd("download #{local} #{remote}")
|
161
|
+
else
|
162
|
+
@driver.copy_from(local,remote)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# This assumes you're on a recent ubuntu
|
2
|
+
# TODO - enforce this, or split it out...
|
3
|
+
|
4
|
+
module Lab
|
5
|
+
module Modifier
|
6
|
+
module Test
|
7
|
+
def install_nmap
|
8
|
+
run_command("sudo apt-get install nmap")
|
9
|
+
end
|
10
|
+
|
11
|
+
def nmap(options)
|
12
|
+
run_command("nmap #{filter_input(options)}")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/lab/version.rb
ADDED
data/lib/lab/vm.rb
ADDED
@@ -0,0 +1,253 @@
|
|
1
|
+
##
|
2
|
+
## $Id$
|
3
|
+
##
|
4
|
+
|
5
|
+
module Lab
|
6
|
+
class Vm
|
7
|
+
|
8
|
+
attr_accessor :vmid
|
9
|
+
attr_accessor :hostname
|
10
|
+
attr_accessor :name
|
11
|
+
attr_accessor :description
|
12
|
+
attr_accessor :location
|
13
|
+
attr_accessor :driver
|
14
|
+
attr_accessor :credentials
|
15
|
+
attr_accessor :tools
|
16
|
+
attr_accessor :type
|
17
|
+
attr_accessor :user
|
18
|
+
attr_accessor :host
|
19
|
+
attr_accessor :os
|
20
|
+
attr_accessor :arch
|
21
|
+
|
22
|
+
## Initialize takes a vm configuration hash of the form
|
23
|
+
## - vmid (unique identifier)
|
24
|
+
## driver (vm technology)
|
25
|
+
## user (if applicable - remote system)
|
26
|
+
## host (if applicable - remote system)
|
27
|
+
## pass (if applicable - remote system)
|
28
|
+
## location (file / uri)
|
29
|
+
## credentials (of the form [ {'user'=>"user",'pass'=>"pass", 'admin' => false}, ... ])
|
30
|
+
## os (currently only linux / windows)
|
31
|
+
## arch (currently only 32 / 64
|
32
|
+
|
33
|
+
def initialize(config = {})
|
34
|
+
|
35
|
+
# TODO - This is a mess. clean up, and pass stuff down to drivers
|
36
|
+
# and then rework the code that uses this api.
|
37
|
+
@vmid = config['vmid'].to_s
|
38
|
+
raise "Invalid VMID" unless @vmid
|
39
|
+
|
40
|
+
# Grab the hostname if specified, otherwise use the vmid
|
41
|
+
# VMID will be different in the case of ESX
|
42
|
+
@hostname = config['hostname']
|
43
|
+
if !@hostname
|
44
|
+
@hostname = @vmid
|
45
|
+
end
|
46
|
+
|
47
|
+
@driver_type = filter_input(config['driver'])
|
48
|
+
@driver_type.downcase!
|
49
|
+
|
50
|
+
@location = filter_input(config['location'])
|
51
|
+
#@name = config['name'] || ""
|
52
|
+
@description = config['description'] || ""
|
53
|
+
@tools = config['tools'] || ""
|
54
|
+
@os = config['os'] || ""
|
55
|
+
@arch = config['arch'] || ""
|
56
|
+
@type = filter_input(config['type']) || "unspecified"
|
57
|
+
@credentials = config['credentials'] || []
|
58
|
+
|
59
|
+
# TODO - Currently only implemented for the first set
|
60
|
+
if @credentials.count > 0
|
61
|
+
@vm_user = filter_input(@credentials[0]['user']) || "\'\'"
|
62
|
+
@vm_pass = filter_input(@credentials[0]['pass']) || "\'\'"
|
63
|
+
@vm_keyfile = filter_input(@credentials[0]['keyfile'])
|
64
|
+
end
|
65
|
+
|
66
|
+
# Only applicable to remote systems
|
67
|
+
@user = filter_input(config['user']) || nil
|
68
|
+
@host = filter_input(config['host']) || nil
|
69
|
+
@port = filter_input(config['port']) || nil
|
70
|
+
@pass = filter_input(config['pass']) || nil
|
71
|
+
|
72
|
+
#Only dynagen systems need this
|
73
|
+
@platform = config['platform']
|
74
|
+
|
75
|
+
#Only fog systems need this
|
76
|
+
@fog_config = config['fog_config']
|
77
|
+
|
78
|
+
#puts "Passing driver config: #{config}"
|
79
|
+
|
80
|
+
# Process the correct driver
|
81
|
+
if @driver_type == "workstation"
|
82
|
+
@driver = Lab::Drivers::WorkstationDriver.new(config)
|
83
|
+
elsif @driver_type == "virtualbox"
|
84
|
+
@driver = Lab::Drivers::VirtualBoxDriver.new(config)
|
85
|
+
elsif @driver_type == "fog"
|
86
|
+
@driver = Lab::Drivers::FogDriver.new(config, config['fog_config'])
|
87
|
+
elsif @driver_type == "dynagen"
|
88
|
+
@driver = Lab::Drivers::DynagenDriver.new(config, config['dynagen_config'])
|
89
|
+
elsif @driver_type == "remote_esx"
|
90
|
+
@driver = Lab::Drivers::RemoteEsxDriver.new(config)
|
91
|
+
elsif @driver_type == "remote_workstation"
|
92
|
+
@driver = Lab::Drivers::RemoteWorkstationDriver.new(config)
|
93
|
+
#elsif @driver_type == "qemu"
|
94
|
+
# @driver = Lab::Drivers::QemuDriver.new
|
95
|
+
#elsif @driver_type == "qemudo"
|
96
|
+
# @driver = Lab::Drivers::QemudoDriver.new
|
97
|
+
else
|
98
|
+
raise "Unknown Driver Type"
|
99
|
+
end
|
100
|
+
|
101
|
+
# Load in a list of modifiers. These provide additional methods
|
102
|
+
# Currently it is up to the user to verify that
|
103
|
+
# modifiers are properly used with the correct VM image.
|
104
|
+
#
|
105
|
+
# If not, the results are likely to be disasterous.
|
106
|
+
@modifiers = config['modifiers']
|
107
|
+
|
108
|
+
if @modifiers
|
109
|
+
begin
|
110
|
+
@modifiers.each { |modifier| self.class.send(:include, eval("Lab::Modifier::#{modifier}"))}
|
111
|
+
rescue Exception => e
|
112
|
+
# modifier likely didn't exist
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def running?
|
118
|
+
@driver.running?
|
119
|
+
end
|
120
|
+
|
121
|
+
def location
|
122
|
+
@driver.location
|
123
|
+
end
|
124
|
+
|
125
|
+
def start
|
126
|
+
@driver.start
|
127
|
+
end
|
128
|
+
|
129
|
+
def stop
|
130
|
+
@driver.stop
|
131
|
+
end
|
132
|
+
|
133
|
+
def pause
|
134
|
+
@driver.pause
|
135
|
+
end
|
136
|
+
|
137
|
+
def suspend
|
138
|
+
@driver.suspend
|
139
|
+
end
|
140
|
+
|
141
|
+
def reset
|
142
|
+
@driver.reset
|
143
|
+
end
|
144
|
+
|
145
|
+
def resume
|
146
|
+
@driver.resume
|
147
|
+
end
|
148
|
+
|
149
|
+
def create_snapshot(snapshot)
|
150
|
+
@driver.create_snapshot(snapshot)
|
151
|
+
end
|
152
|
+
|
153
|
+
def revert_snapshot(snapshot)
|
154
|
+
@driver.revert_snapshot(snapshot)
|
155
|
+
end
|
156
|
+
|
157
|
+
def delete_snapshot(snapshot)
|
158
|
+
@driver.delete_snapshot(snapshot)
|
159
|
+
end
|
160
|
+
|
161
|
+
def revert_and_start(snapshot)
|
162
|
+
@driver.revert_snapshot(snapshot)
|
163
|
+
@driver.start
|
164
|
+
end
|
165
|
+
|
166
|
+
def copy_to(from,to)
|
167
|
+
@driver.copy_to(from,to)
|
168
|
+
end
|
169
|
+
|
170
|
+
def copy_from(from,to)
|
171
|
+
@driver.copy_from(from,to)
|
172
|
+
end
|
173
|
+
|
174
|
+
def run_command(command)
|
175
|
+
@driver.run_command(command)
|
176
|
+
end
|
177
|
+
|
178
|
+
def check_file_exists(file)
|
179
|
+
@driver.check_file_exists(file)
|
180
|
+
end
|
181
|
+
|
182
|
+
def create_directory(directory)
|
183
|
+
@driver.create_directory(directory)
|
184
|
+
end
|
185
|
+
|
186
|
+
def open_uri(uri)
|
187
|
+
# we don't filter the uri, as it's getting tossed into a script
|
188
|
+
# by the driver
|
189
|
+
if @os == "windows"
|
190
|
+
command = "\"C:\\program files\\internet explorer\\iexplore.exe\" #{uri}"
|
191
|
+
else
|
192
|
+
command = "firefox #{uri}"
|
193
|
+
end
|
194
|
+
|
195
|
+
@driver.run_command(command)
|
196
|
+
end
|
197
|
+
|
198
|
+
def to_s
|
199
|
+
return "#{@vmid}"
|
200
|
+
end
|
201
|
+
|
202
|
+
def to_yaml
|
203
|
+
|
204
|
+
# TODO - push this down to the drivers.
|
205
|
+
|
206
|
+
# Standard configuration options
|
207
|
+
out = " - vmid: #{@vmid}\n"
|
208
|
+
out += " driver: #{@driver_type}\n"
|
209
|
+
out += " location: #{@location}\n"
|
210
|
+
out += " type: #{@type}\n"
|
211
|
+
out += " tools: #{@tools}\n"
|
212
|
+
out += " os: #{@os}\n"
|
213
|
+
out += " arch: #{@arch}\n"
|
214
|
+
|
215
|
+
if @user or @host # Remote vm/drivers only
|
216
|
+
out += " user: #{@user}\n"
|
217
|
+
out += " host: #{@host}\n"
|
218
|
+
end
|
219
|
+
|
220
|
+
if @platform
|
221
|
+
out += " platform: #{@platform}\n"
|
222
|
+
end
|
223
|
+
|
224
|
+
if @fog_config
|
225
|
+
out += @fog_config.to_yaml
|
226
|
+
end
|
227
|
+
|
228
|
+
if @dynagen_config
|
229
|
+
out += @dynagen_config.to_yaml
|
230
|
+
end
|
231
|
+
|
232
|
+
out += " credentials:\n"
|
233
|
+
@credentials.each do |credential|
|
234
|
+
out += " - user: #{credential['user']}\n"
|
235
|
+
out += " pass: #{credential['pass']}\n"
|
236
|
+
end
|
237
|
+
|
238
|
+
return out
|
239
|
+
end
|
240
|
+
private
|
241
|
+
|
242
|
+
def filter_input(string)
|
243
|
+
return "" unless string # nil becomes empty string
|
244
|
+
return unless string.class == String # Allow other types
|
245
|
+
|
246
|
+
unless /^[(!)\d*\w*\s*\[\]\{\}\/\\\.\-\"\(\)]*$/.match string
|
247
|
+
raise "WARNING! Invalid character in: #{string}"
|
248
|
+
end
|
249
|
+
|
250
|
+
string
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|