# # $Id$ # $Revision$ # $:.unshift(File.join(File.expand_path(File.dirname(__FILE__)), '..', 'lib', 'lab')) require 'yaml' require 'lab_controller' module Msf class Plugin::Lab < Msf::Plugin class LabCommandDispatcher include Msf::Ui::Console::CommandDispatcher attr_accessor :controller attr_accessor :target_map def initialize(driver) super(driver) @controller = nil @target_map = nil end # # Returns the hash of commands supported by this dispatcher. # def commands { ### Lab Management! "lab_help" => "lab_help - Show that command's description.", "lab_load_file" => "lab_load_file [file] - load a lab definition from disk.", "lab_load_running" => "lab_load_running - use the running vms to create a lab definition.", "lab_load_dir" => "lab_load_dir [file] - load a lab definition from a directory.", "lab_clear" => "lab_clear - clear the running lab_definition.", "lab_save" => "lab_save [filename] - save a lab_definition to filename.", "lab_show" => "lab_show - show all vms in the lab.", "lab_show_running" => "lab_show_running - show running vms.", "lab_start" => "lab_start [vmid+|all] start all targets for this exploit.", "lab_reset" => "lab_reset [vmid+|all] reset all targets for this exploit.", "lab_suspend" => "lab_suspend [vmid+|all] suspend all targets for this exploit.", "lab_stop" => "lab_stop [vmid+|all] stop all targets for this exploit.", "lab_revert" => "lab_revert [vmid+|all] [snapshot] revert all targets for this exploit.", "lab_run_command" => "lab_run_command [vmid+|all] [command] run a command on all targets.", "lab_snapshot" => "lab_snapshot [vmid+|all] [snapshot] snapshot all targets for this exploit.", "lab_browse_to" => "lab_browse_to [vmid+|all] [uri] use the default browser to browse to a uri.", ### Target map management "target_map_load" => "target_map_load", "target_map_unload" => "target_map_unload", "target_map_save" => "targets_map_save", "target_map_show" => "target_map_show", ### Target (module-specific mappings) commands "targets_show" => "targets_show - show all targets for this exploit", "targets_add" => "target_add [vmid] - add a target for this exploit", # "targets_add_running" => "target_add_running [vmid] - add a target for this exploit", "targets_remove" => "target_remove [vmid] - remove a target for this exploit", "targets_clear" => "targets_clear - clear targets list for this exploit", "targets_start" => "targets_start - start all targets for this exploit", "targets_stop" => "targets_stop - stop all targets for this exploit", "targets_reset" => "targets_reset - reset all targets for this exploit", "targets_suspend" => "targets_suspend - suspend all targets for this exploit", "targets_snapshot" => "targets_snapshot [snapshot] - snapshot all targets for this exploit", "targets_revert" => "targets_revert [revert] - snapshot all targets for this exploit", # "targets_run_command" => "targets_run_command run a command on all targets", # "targets_browse_to" => "targets_browse_to [uri] use the default browser to browse to a uri" # "targets_exploit" => "targets_exploit exploit all targets", # "targets_verify" => "targets_verify - exploit and verify against all targets", # "targets_auto_verify" => "targets_verify - exploit and verify by querying targets that should work, and matching to the lab" } end def name "Lab" end ## ## Commands for help ## def longest_cmd_size commands.keys.map {|x| x.size}.sort.last end # No extended help yet, but this is where more detailed documentation # on particular commands would live. Key is command, (not cmd_command), # value is the documentation. def extended_help { "lab_fake_cmd" => "This is a fake command. It's got its own special docs.\n" + (" " * longest_cmd_size) + "It might be long so so deal with formatting somehow." } end # Map for usages def lab_usage caller[0][/`cmd_(.*)'/] cmd = $1 if extended_help[cmd] || commands[cmd] cmd_lab_help cmd else # Should never really get here... print_error "Unknown command. Try 'help'" end end def cmd_lab_help(*args) if args.empty? commands.each_pair {|k,v| print_line "%-#{longest_cmd_size}s - %s" % [k,v] } else args.each do |c| if extended_help[c] || commands[c] print_line "%-#{longest_cmd_size}s - %s" % [c,extended_help[c] || commands[c]] else print_error "Unknown command '#{c}'" end end end end def cmd_lab_load_file(*args) if args[0] labdef = YAML::load_file(args[0]) @controller = LabController.new(labdef) @target_map = {} hlp_print_lab else lab_usage end end def cmd_lab_load_dir(*args) if args[0] x = @controller.build_lab_from_files(args[0]) @target_map = {} hlp_print_lab else lab_usage end end def cmd_lab_load_running(*args) @controller.build_lab_from_running @target_map = {} hlp_print_lab end def cmd_lab_clear(*args) @controller = LabController.new({}) hlp_print_lab end def cmd_lab_save(*args) return lab_usage if args.empty? File.open(args[0], 'w') {|f| f.write(@controller.labdef.to_yaml) } hlp_print_lab end ## ## Commands for dealing with a currently-loaded lab ## def cmd_lab_show(*args) hlp_print_lab end def cmd_lab_show_running(*args) hlp_print_lab_running end def cmd_lab_start(*args) return lab_usage if args.empty? if args[0] == "all" print_line "Starting all lab vms.\n" @controller.start_lab else args.each do |arg| if @controller.exists?(arg) if !@controller.running?(arg) print_line "Starting lab vm '" + arg + "'." @controller.start_lab_vm(arg) else print_error "Lab vm '" + arg + "' " + "already running." end end end end hlp_print_lab_running end def cmd_lab_stop(*args) return lab_usage if args.empty? if args[0] == "all" print_line "Stopping all running lab vms.\n" @controller.stop_lab else args.each do |arg| if @controller.exists?(arg) if @controller.running?(arg) print_line "Stopping lab vm '" + arg + "'." @controller.stop_lab_vm(arg) else print_error "Lab vm '" + arg + "' " + "not running." end end end end hlp_print_lab_running end def cmd_lab_suspend(*args) return lab_usage if args.empty? if args[0] == "all" print_line "Suspending all running lab vms.\n" @controller.suspend_lab else args.each do |arg| if @controller.exists?(arg) if @controller.running?(arg) print_line "Suspending lab vm '" + arg + "'." @controller.suspend_lab_vm(arg) else print_error "Lab vm '" + arg + "' " + "not running." end end end end hlp_print_lab_running end def cmd_lab_reset(*args) return lab_usage if args.empty? if args[0] == "all" print_line "Resetting all running lab vms.\n" @controller.reset_lab else args.each do |arg| if @controller.exists?(arg) if @controller.running?(arg) print_line "Resetting lab vm '" + arg + "'." @controller.reset_lab_vm(arg) else print_error "Lab vm '" + arg + "' " + "not running." end end end end hlp_print_lab_running end def cmd_lab_snapshot(*args) return lab_usage if args.empty? snapshot = args[args.count-1] if args[0] == "all" print_line "Snapshotting all running lab vms.\n" @controller.snapshot_lab(snapshot) else args.each do |arg| if @controller.exists?(arg) print_line "snapshotting lab vm '" + arg + "'." @controller.snapshot_lab_vm(arg,snapshot) end end end end def cmd_lab_revert(*args) return lab_usage if args.empty? snapshot = args[args.count-1] if args[0] == "all" print_line "Reverting all running lab vms.\n" @controller.revert_lab(snapshot) else args.each do |arg| if @controller.exists?(arg) print_line "Reverting lab vm '" + arg + "'." @controller.revert_lab_vm(arg, snapshot) end end end end def cmd_lab_run_command(*args) return lab_usage if args.empty? command = args[args.count-1] if args[0] == "all" print_line "Running command '" + command + "' on all running lab vms.\n" @controller.run_command_on_lab(args[1]) else args.each do |arg| if arg != command ## Make sure we don't use the command as a vmid if @controller.exists?(arg) if @controller.running?(arg) print_line "Running command '" + command + "' on lab vm '" + arg + "'." @controller.run_command_on_lab_vm(arg, command) else print_error "Lab vm '" + arg + "' " + "not running." end else print_error "Unknown lab vm '" + arg + "'." end end end end # hlp_print_lab_running end def cmd_lab_browse_to(*args) return lab_usage if args.empty? uri = args[args.count-1] ## where's that final argument, that's our command, boiiii if args[0] == "all" print_line "Browsing to uri '" + url + "' on all running lab vms.\n" @controller.run_command_on_lab(args[1]) else args.each do |arg| if arg != uri ## Make sure we don't use the uri as a vmid if @controller.exists?(arg) if @controller.running?(arg) @controller.run_browser_on_lab_vm(arg,uri) else print_error "Lab vm '" + arg + "' " + "not running." end else print_error "Unknown lab vm '" + arg + "'." end end end end # hlp_print_lab_running end ## ## Commands for dealing with a target map ## def cmd_target_map_show(*args) hlp_print_targets end def cmd_target_map_load(*args) ## Set up a mapping of exploits -> vms default_target_map_file = args[0] @target_map = YAML::load_file(default_target_map_file) hlp_print_targets end def cmd_target_map_unload(*args) @target_map = {} hlp_print_targets end def cmd_target_map_save(*args) File.open(args[0], 'w') {|f| f.write(@target_map.to_yaml) } hlp_print_targets end ## Commands for dealing with targets for a loaded def cmd_targets_show(*args) if (not active_module) then print_error "No active module. Try 'use [modulename]' first." return nil end hlp_print_targets(active_module.fullname) end def cmd_targets_add(*args) if (not active_module) then print_error "No active module. Try 'use [modulename]' first." return nil end if args[0] if @controller.exists?(args[0]) if @target_map == {} @target_map[active_module.fullname] = [] @target_map[active_module.fullname] << args[0] else @target_map[active_module.fullname] << args[0] end else print_error "VM: '" + "' doesn't exist in this lab." end end hlp_print_targets(active_module.fullname) end def cmd_targets_remove(*args) if (not active_module) then print_error "No active module. Try 'use [modulename]' first." return nil end if args[0] if @controller.exists?(args[0]) @target_map[active_module.fullname].delete(args[0]) else print_error "VM: '" + "' doesn't exist in this lab." end end hlp_print_targets(active_module.fullname) end def cmd_targets_start(*args) if (not active_module) then print_error "No active module selected. Please select a module first." return nil end @target_map[active_module.fullname].each { |target| @controller.start_lab_vm(target) } hlp_print_targets_running(active_module.fullname) end def cmd_targets_stop(*args) if (not active_module) then print_error "No active module selected. Please select a module first." return nil end @target_map[active_module.fullname].each { |target| @controller.stop_lab_vm(target) } hlp_print_targets_running(active_module.fullname) end def cmd_targets_reset(*args) if (not active_module) then print_error "No active module selected. Please select a module first." return nil end @target_map[active_module.fullname].each { |target| @controller.reset_lab_vm(target) } hlp_print_targets_running(active_module.fullname) end def cmd_targets_suspend(*args) if (not active_module) then print_error "No active module selected. Please select a module first." return nil end @target_map[active_module.fullname].each { |target| @controller.suspend_lab_vm(target) } hlp_print_targets_running(active_module.fullname) end def cmd_targets_snapshot(*args) if (not active_module) then print_error "No active module selected. Please select a module first." return nil end if args[0] @target_map[active_module.fullname].each { |target| @controller.snapshot_lab_vm(target, args[0]) } else print_error "Please enter a snapshot name." end hlp_print_targets_running(active_module.fullname) end def cmd_targets_revert(*args) if (not active_module) then print_error "No active module selected. Please select a module first." return nil end if args[0] @target_map[active_module.fullname].each { |target| @controller.revert_lab_vm(target, args[0]) } else print_error "Please enter a snapshot name." end hlp_print_targets_running(active_module.fullname) end ## ## This stuff needs a lot more thought before it should be exposed ## def cmd_targets_exploit(*args) if (not active_module) then print_error "No active module selected. Please select a module first." return nil end if @target_map @target_map[active_module.fullname].each do |target| ## auto start targets that aren't running if !@controller.running?(target) print_status target + " isn't running, starting it for you :)" @controller.start_lab_vm(target) print_status "I'll hang out for a second while it gets going. Note, this value may need tweaking if you've got a slow box." sleep 20 ##resonable default? Guess? end hostname = @controller.labdef[target]['hostname'] username = @controller.labdef[target]['username'] password = @controller.labdef[target]['password'] os = @controller.labdef[target]['os'] #x64 = @controller.labdef[target]['x64'] active_module.datastore["RHOST"] = hostname ### G.H.E.TT.O. if os == "windows" active_module.datastore["PAYLOAD"] = "windows/meterpreter/bind_tcp" elsif os == "linux" active_module.datastore["PAYLOAD"] = "cmd/unix/bind_perl" else active_module.datastore["PAYLOAD"] = "windows/meterpreter/bind_tcp" print_error "Unknown OS, please set it in the lab. Guessing windows." end ### Hmm, this is good for demo purposes, but how do we abstract this if active_module.fullname == "windows/smb/psexec" active_module.datastore["SMBUser"] = username active_module.datastore["SMBPass"] = password end active_module.exploit ### Now, our on-session handler should handle this end else print_error "No target map found. Something went wrong. Try loading a ne" end # hlp_print_targets_running(active_module.fullname) end def cmd_targets_clear(*args) if (not active_module) then print_error "No active module. Try 'use [modulename]' first." return nil end @target_map[active_module.fullname] = {} hlp_print_targets(active_module.fullname) end private def hlp_print_lab indent = ' ' tbl = Rex::Ui::Text::Table.new( 'Header' => 'Available Lab VMs', 'Indent' => indent.length, 'Columns' => [ 'vmid', 'file', "powered on" ] ) @controller.labdef.each do |key, value| tbl << [ key, @controller.get_full_path(key), @controller.running?(key)] end print_line tbl.to_s end def hlp_print_lab_running indent = ' ' tbl = Rex::Ui::Text::Table.new( 'Header' => 'Running Lab VMs', 'Indent' => indent.length, 'Columns' => [ 'vmid', 'file', 'powered on' ] ) @controller.labdef.each do |key, value| if @controller.running?(key) tbl << [ key, @controller.get_full_path(key), true] end end print_line tbl.to_s end def hlp_print_targets(mod_id=nil) indent = ' ' if @target_map[mod_id] begin if mod_id tbl = Rex::Ui::Text::Table.new( 'Header' => 'Mapped Targets for ' + mod_id, 'Indent' => indent.length, 'Columns' => [ 'vmid', 'file', "powered on" ] ) @target_map[mod_id].each do |target| tbl << [ target.to_s, @controller.get_full_path(target), @controller.running?(target)] end else tbl = Rex::Ui::Text::Table.new( 'Header' => 'All Mapped Targets', 'Indent' => indent.length, 'Columns' => [ 'module', 'vmid', 'file', "powered on" ] ) @target_map.each do |key, value| value.each do |target| tbl << [ key.to_s, target.to_s, @controller.get_full_path(target.to_s), @controller.running?(target.to_s)] end end end rescue Exception => e end else print_error "No target map" end print_line tbl.to_s end def hlp_print_targets_running(mod_id = nil) indent = ' ' begin if @target_map if mod_id tbl = Rex::Ui::Text::Table.new( 'Header' => 'Running Targets for ' + mod_id, 'Indent' => indent.length, 'Columns' => [ 'vmid', 'file', "powered on" ] ) @target_map[mod_id].each do |target| tbl << [ target.to_s, @controller.get_full_path(target), @controller.running?(target)] end else tbl = Rex::Ui::Text::Table.new( 'Header' => 'All Running Targets', 'Indent' => indent.length, 'Columns' => [ 'module', 'vmid', 'file', "powered on" ] ) @target_map.each do |key, value| value.each do |target| if @controller.running?(target) tbl << [ key.to_s, target.to_s, @controller.get_full_path(target), true] end end end end end rescue Exception => e end print_line tbl.to_s end end # # The constructor is called when an instance of the plugin is created. The # framework instance that the plugin is being associated with is passed in # the framework parameter. Plugins should call the parent constructor when # inheriting from Msf::Plugin to ensure that the framework attribute on # their instance gets set. # def initialize(framework, opts) super #self.framework.events.add_session_subscriber(self) ## Register the commands above console_dispatcher = add_console_dispatcher(LabCommandDispatcher) ## Set up a lab here. Note that we're going directly to the controller, so all data's gonna ## have to get stored here, and not in a separate lab class (probably the right way to do) #default_lab_file = File.join(File.dirname(__FILE__), "..", "data", "lab", "test_lab.yml" ) #labdef = YAML::load_file(default_lab_file) labdef = {} @lab_controller = LabController.new(labdef) ## Set up a mapping of exploits -> vms ## default_target_map_file = File.join(File.dirname(__FILE__), "..", "data", "lab", "test_targets.yml" ) #@lab_target_map = YAML::load_file(default_target_map_file) @lab_target_map = {} ## Share the controller & target map with the console_dispatcher console_dispatcher.controller = @lab_controller console_dispatcher.target_map = @lab_target_map ## Great Success! end # # The cleanup routine for plugins gives them a chance to undo any actions # they may have done to the framework. For instance, if a console # dispatcher was added, then it should be removed in the cleanup routine. # def cleanup # If we had previously registered a console dispatcher with the console, # deregister it now. remove_console_dispatcher('Lab') end # # This method returns a short, friendly name for the plugin. # def name "lab" end # # This method returns a brief description of the plugin. It should be no # more than 60 characters, but there are no hard limits. # def desc "Adds the ability to manage vms and targets" end #def on_session_open(session) #return if not session.type == 'meterpreter' #session.load_stdapi #sb = Rex::Socket::SwitchBoard.instance #session.net.config.each_route { |route| # # Remove multicast and loopback interfaces # next if route.subnet =~ /^(224\.|127\.)/ # next if route.subnet == '0.0.0.0' # next if route.netmask == '255.255.255.255' # if not sb.route_exists?(route.subnet, route.netmask) # print_status("AutoAddRoute: Routing new subnet #{route.subnet}/#{route.netmask} through session #{session.sid}") # sb.add_route(route.subnet, route.netmask, session) # end #} #end end ## End Class end ## End Module