msfrpc-simple 0.0.4 → 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.
- data/lib/msfrpc-simple/client.rb +98 -11
- data/lib/msfrpc-simple/features/framework.rb +111 -137
- data/lib/msfrpc-simple/version.rb +1 -1
- metadata +1 -1
data/lib/msfrpc-simple/client.rb
CHANGED
|
@@ -3,6 +3,7 @@ require 'msfrpc-client'
|
|
|
3
3
|
require 'features/framework'
|
|
4
4
|
require 'features/pro'
|
|
5
5
|
require 'module_mapper'
|
|
6
|
+
require 'timeout'
|
|
6
7
|
require 'logger'
|
|
7
8
|
|
|
8
9
|
module Msf
|
|
@@ -13,30 +14,26 @@ module Msf
|
|
|
13
14
|
include Msf::RPC::Simple::Features::Framework
|
|
14
15
|
include Msf::RPC::Simple::Features::Pro
|
|
15
16
|
|
|
16
|
-
attr_accessor :options
|
|
17
|
+
#attr_accessor :options
|
|
17
18
|
|
|
18
19
|
# Public: Create a simple client object.
|
|
19
20
|
#
|
|
20
|
-
#
|
|
21
|
+
# user_options - hash of options to include in our initial connection.
|
|
21
22
|
# project - project name we want to use for this connection.
|
|
22
23
|
#
|
|
23
24
|
# Returns nothing.
|
|
24
25
|
def initialize(user_options)
|
|
25
26
|
|
|
26
|
-
#
|
|
27
|
-
# db password
|
|
28
|
-
|
|
29
|
-
#
|
|
30
|
-
# Merge our project in, and set this as the project we'll
|
|
31
|
-
# use going forward.
|
|
32
|
-
#
|
|
27
|
+
# configure default options
|
|
33
28
|
@options = {
|
|
34
29
|
:project => user_options[:project] || "default",
|
|
35
30
|
:port => user_options[:project] || 55553,
|
|
36
31
|
:user => user_options[:rpc_user],
|
|
37
32
|
:pass => user_options[:rpc_pass],
|
|
33
|
+
:db_host => user_options[:db_host] || "localhost",
|
|
38
34
|
:db_user => user_options[:db_user],
|
|
39
|
-
:db_pass => user_options[:db_pass]
|
|
35
|
+
:db_pass => user_options[:db_pass],
|
|
36
|
+
:db => user_options[:db_name] || "msf"
|
|
40
37
|
}
|
|
41
38
|
|
|
42
39
|
@options.merge!(user_options)
|
|
@@ -46,18 +43,108 @@ module Msf
|
|
|
46
43
|
#
|
|
47
44
|
@client = Msf::RPC::Client.new(@options)
|
|
48
45
|
|
|
46
|
+
#
|
|
47
|
+
# Connect to the database based on the included options
|
|
48
|
+
#
|
|
49
|
+
_connect_database
|
|
50
|
+
|
|
51
|
+
#
|
|
52
|
+
# Add a new workspace
|
|
53
|
+
#
|
|
54
|
+
@workspace_name = Time.now.utc.to_s.gsub(" ","_").gsub(":","_")
|
|
55
|
+
_create_workspace
|
|
56
|
+
|
|
49
57
|
#
|
|
50
58
|
# Create a logger
|
|
51
59
|
#
|
|
52
60
|
#@logger = Msf::RPC::Simple::Logger.new
|
|
53
61
|
end
|
|
54
62
|
|
|
63
|
+
# Public: Creates and retuns an xml report
|
|
55
64
|
#
|
|
56
|
-
#
|
|
65
|
+
# This method is ugly for a number of reasons, but there doesn't
|
|
66
|
+
# appear to be a way to be notified when the command is completed
|
|
67
|
+
# nor when the
|
|
68
|
+
#
|
|
57
69
|
#
|
|
70
|
+
# returns a valid xml string
|
|
71
|
+
def create_report
|
|
72
|
+
report_path = "/tmp/metasploit_#{@workspace_name}.xml"
|
|
73
|
+
|
|
74
|
+
# Create the report using the db_export command
|
|
75
|
+
_send_command("db_export #{report_path}\n")
|
|
76
|
+
|
|
77
|
+
# We've sent the command, so let's sit back and wait for th
|
|
78
|
+
# output to hit the disk.
|
|
79
|
+
begin
|
|
80
|
+
xml_string = ""
|
|
81
|
+
status = Timeout::timeout(240) {
|
|
82
|
+
# We don't know when the file is going to show up, so
|
|
83
|
+
# wait for it...
|
|
84
|
+
until File.exists? report_path do
|
|
85
|
+
sleep 1
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Read and clean up the file when it exists...
|
|
89
|
+
until xml_string.include? "</MetasploitV4>" do
|
|
90
|
+
sleep 5
|
|
91
|
+
xml_string = File.read(report_path)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
File.delete(report_path)
|
|
95
|
+
}
|
|
96
|
+
rescue Timeout::Error
|
|
97
|
+
xml_string = "<MetasploitV4></MetasploitV4>"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
xml_string
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def cleanup
|
|
104
|
+
#_send_command("workspace -d #{@workspace_name}")
|
|
105
|
+
_send_command("db_disconnect")
|
|
106
|
+
end
|
|
107
|
+
|
|
58
108
|
def connected?
|
|
59
109
|
return true if @client.call("core.version")
|
|
60
110
|
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
def _connect_database
|
|
115
|
+
_send_command("db_connect #{@options[:db_user]}:#{@options[:db_pass]}@#{@options[:db_host]}/#{@options[:db]}")
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def _create_workspace
|
|
119
|
+
_send_command("workspace -a #{@workspace_name}")
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def _send_command(command)
|
|
123
|
+
# Create the console and get its id
|
|
124
|
+
console = @client.call("console.create")
|
|
125
|
+
|
|
126
|
+
# Do an initial read / discard to pull out any info on the console
|
|
127
|
+
# then write the command to the console
|
|
128
|
+
@client.call("console.read", console["id"])
|
|
129
|
+
@client.call("console.write", console["id"], "#{command}\n")
|
|
130
|
+
|
|
131
|
+
# Initial read
|
|
132
|
+
output_string = ""
|
|
133
|
+
output = @client.call("console.read", console["id"])
|
|
134
|
+
|
|
135
|
+
# Read until finished
|
|
136
|
+
while (output["busy"] == true) do
|
|
137
|
+
output_string += "#{output['data']}"
|
|
138
|
+
output = @client.call("console.read", console["id"])
|
|
139
|
+
output_string = "Error" if output["result"] == "failure"
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Clean up console
|
|
143
|
+
#@client.call("console.destroy", console["id"])
|
|
144
|
+
|
|
145
|
+
output_string
|
|
146
|
+
end
|
|
147
|
+
|
|
61
148
|
end
|
|
62
149
|
end
|
|
63
150
|
end
|
|
@@ -3,116 +3,12 @@ module Msf
|
|
|
3
3
|
module Simple
|
|
4
4
|
module Features
|
|
5
5
|
module Framework
|
|
6
|
-
|
|
7
|
-
def create_report
|
|
8
|
-
|
|
9
|
-
# Create the console and get its id
|
|
10
|
-
console = @client.call("console.create")
|
|
11
|
-
console_id = console["id"]
|
|
12
|
-
|
|
13
|
-
# Do an initial read / discard to pull out the banner
|
|
14
|
-
@client.call("console.read", console_id)
|
|
15
|
-
|
|
16
|
-
# Move to the context of our module
|
|
17
|
-
@client.call("console.write", console_id, "db_connect #{self.options[:db_user]}:#{self.options[:db_pass]}@localhost/msf3\n")
|
|
18
|
-
@client.call("console.write", console_id, "db_export /tmp/metasploit.xml\n")
|
|
19
|
-
|
|
20
|
-
# do an initial read of the module's output
|
|
21
|
-
output = @client.call("console.read", console_id)
|
|
22
|
-
output_string = "#{output['data']}"
|
|
23
|
-
|
|
24
|
-
return "Module Error" if output["result"] == "failure"
|
|
25
|
-
|
|
26
|
-
until (output["busy"] == false) do
|
|
27
|
-
output = @client.call("console.read", console_id)
|
|
28
|
-
output_string += "#{output['data']}"
|
|
29
|
-
return "Module Error" if output["result"] == "failure"
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
# Clean up
|
|
33
|
-
@client.call("console.destroy", console_id)
|
|
34
|
-
|
|
35
|
-
File.open("/tmp/metasploit.xml").read
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
#
|
|
39
|
-
# This module simply runs a module
|
|
40
|
-
#
|
|
41
|
-
def execute_module_and_return_output(options)
|
|
42
|
-
|
|
43
|
-
module_name = options[:module_name]
|
|
44
|
-
#module_options = options[:module_options]
|
|
45
|
-
module_option_string = options[:module_option_string]
|
|
46
|
-
|
|
47
|
-
# split up the module name into type / name
|
|
48
|
-
module_type = module_name.split("/").first
|
|
49
|
-
raise "Error, bad module name" unless ["exploit", "auxiliary", "post", "encoder", "nop"].include? module_type
|
|
50
|
-
|
|
51
|
-
#module_options["TARGET"] = 0 unless module_options["TARGET"]
|
|
52
|
-
|
|
53
|
-
#info = @client.call("module.execute", module_type, module_name, module_options)
|
|
54
|
-
#@client.call("job.info", info["job_id"])
|
|
55
|
-
|
|
56
|
-
# The module output will be not available when run this way; to
|
|
57
|
-
# capture the result of the print_* commands, you have to set the
|
|
58
|
-
# output driver of the module to something you can read from (Buffer,
|
|
59
|
-
# File, etc). For your use case, the best bet is to run the module
|
|
60
|
-
# via the Console API instead of module.execute, and use that to read
|
|
61
|
-
# the output from the console itself, which provides buffer output for you.
|
|
62
|
-
|
|
63
|
-
# Create the console and get its id
|
|
64
|
-
console = @client.call("console.create")
|
|
65
|
-
console_id = console["id"]
|
|
66
|
-
|
|
67
|
-
# Do an initial read / discard to pull out the banner
|
|
68
|
-
@client.call("console.read", console_id)
|
|
69
|
-
|
|
70
|
-
# Move to the context of our module
|
|
71
|
-
@client.call("console.write", console_id, "use #{module_name}\n")
|
|
72
|
-
|
|
73
|
-
# Set up the module's datastore
|
|
74
|
-
module_option_string.split(",").each do |module_option|
|
|
75
|
-
@client.call "console.write", console_id, "set #{module_option}\n"
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
# Do an another read / discard to pull out the option confirmation
|
|
79
|
-
@client.call("console.read", console_id)
|
|
80
|
-
|
|
81
|
-
# Depending on the module_type, kick off the module
|
|
82
|
-
if module_type == "auxiliary"
|
|
83
|
-
@client.call "console.write", console_id, "run\n"
|
|
84
|
-
elsif module_type == "exploit"
|
|
85
|
-
@client.call "console.write", console_id, "exploit\n"
|
|
86
|
-
else
|
|
87
|
-
return "Unsupported"
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
# do an initial read of the module's output
|
|
91
|
-
module_output = @client.call("console.read", console_id)
|
|
92
|
-
module_output_data_string = "#{module_output['data']}"
|
|
93
|
-
|
|
94
|
-
return "Module Error" if module_output["result"] == "failure"
|
|
95
|
-
|
|
96
|
-
until (module_output["busy"] == false) do
|
|
97
|
-
module_output = @client.call("console.read", console_id)
|
|
98
|
-
module_output_data_string += "#{module_output['data']}"
|
|
99
|
-
return "Module Error" if module_output["result"] == "failure"
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
# Clean up
|
|
103
|
-
@client.call("console.destroy", console_id)
|
|
104
|
-
|
|
105
|
-
module_output_data_string
|
|
106
|
-
end
|
|
107
|
-
|
|
108
6
|
|
|
109
7
|
#
|
|
110
8
|
# This module runs a number of discovery modules
|
|
111
9
|
#
|
|
112
|
-
def
|
|
10
|
+
def discover_range(range)
|
|
113
11
|
|
|
114
|
-
#
|
|
115
|
-
#
|
|
116
12
|
# Other Potential options
|
|
117
13
|
# - auxiliary/scanner/smb/pipe_auditor
|
|
118
14
|
# - auxiliary/scanner/smb/pipe_dcerpc_auditor
|
|
@@ -120,45 +16,45 @@ module Msf
|
|
|
120
16
|
# - auxiliary/scanner/smb/smb_enumusers
|
|
121
17
|
modules_and_options = [
|
|
122
18
|
{:module_name => "auxiliary/scanner/http/http_version",
|
|
123
|
-
:module_option_string => "RHOSTS #{
|
|
19
|
+
:module_option_string => "RHOSTS #{range}" },
|
|
124
20
|
#{:module_name => "auxiliary/scanner/http/cert",
|
|
125
|
-
# :module_option_string => "RHOSTS #{
|
|
21
|
+
# :module_option_string => "RHOSTS #{range}" },
|
|
126
22
|
{:module_name => "auxiliary/scanner/ftp/ftp_version",
|
|
127
|
-
:module_option_string => "RHOSTS #{
|
|
23
|
+
:module_option_string => "RHOSTS #{range}" },
|
|
128
24
|
{:module_name => "auxiliary/scanner/h323/h323_version",
|
|
129
|
-
:module_option_string => "RHOSTS #{
|
|
25
|
+
:module_option_string => "RHOSTS #{range}" },
|
|
130
26
|
{:module_name => "auxiliary/scanner/imap/imap_version",
|
|
131
|
-
:module_option_string => "RHOSTS #{
|
|
27
|
+
:module_option_string => "RHOSTS #{range}" },
|
|
132
28
|
#{:module_name => "auxiliary/scanner/portscan/syn",
|
|
133
|
-
#:module_option_string => "RHOSTS #{
|
|
29
|
+
#:module_option_string => "RHOSTS #{range}" },
|
|
134
30
|
#{:module_name => "auxiliary/scanner/portscan/tcp",
|
|
135
|
-
#:module_option_string => "RHOSTS #{
|
|
31
|
+
#:module_option_string => "RHOSTS #{range}" },
|
|
136
32
|
#{:module_name => "auxiliary/scanner/lotus/lotus_domino_version",
|
|
137
|
-
#:module_option_string => "RHOSTS #{
|
|
33
|
+
#:module_option_string => "RHOSTS #{range}" },
|
|
138
34
|
{:module_name => "auxiliary/scanner/mysql/mysql_version",
|
|
139
|
-
:module_option_string => "RHOSTS #{
|
|
35
|
+
:module_option_string => "RHOSTS #{range}" },
|
|
140
36
|
#{:module_name => "auxiliary/scanner/netbios/nbname",
|
|
141
|
-
#:module_option_string => "RHOSTS #{
|
|
37
|
+
#:module_option_string => "RHOSTS #{range}" },
|
|
142
38
|
#{:module_name => "auxiliary/scanner/netbios/nbname_probe",
|
|
143
|
-
#:module_option_string => "RHOSTS #{
|
|
39
|
+
#:module_option_string => "RHOSTS #{range}" },
|
|
144
40
|
#{:module_name => "auxiliary/scanner/pcanywhere/pcanywhere_tcp",
|
|
145
|
-
#:module_option_string => "RHOSTS #{
|
|
41
|
+
#:module_option_string => "RHOSTS #{range}" },
|
|
146
42
|
#{:module_name => "auxiliary/scanner/pcanywhere/pcanywhere_udp",
|
|
147
|
-
#:module_option_string => "RHOSTS #{
|
|
43
|
+
#:module_option_string => "RHOSTS #{range}" },
|
|
148
44
|
{:module_name => "auxiliary/scanner/pop3/pop3_version",
|
|
149
|
-
:module_option_string => "RHOSTS #{
|
|
45
|
+
:module_option_string => "RHOSTS #{range}" },
|
|
150
46
|
{:module_name => "auxiliary/scanner/postgres/postgres_version",
|
|
151
|
-
:module_option_string => "RHOSTS #{
|
|
47
|
+
:module_option_string => "RHOSTS #{range}" },
|
|
152
48
|
{:module_name => "auxiliary/scanner/smb/smb_version",
|
|
153
|
-
:module_option_string => "RHOSTS #{
|
|
49
|
+
:module_option_string => "RHOSTS #{range}" },
|
|
154
50
|
{:module_name => "auxiliary/scanner/snmp/snmp_enum",
|
|
155
|
-
:module_option_string => "RHOSTS #{
|
|
51
|
+
:module_option_string => "RHOSTS #{range}" },
|
|
156
52
|
{:module_name => "auxiliary/scanner/ssh/ssh_version",
|
|
157
|
-
:module_option_string => "RHOSTS #{
|
|
53
|
+
:module_option_string => "RHOSTS #{range}" },
|
|
158
54
|
{:module_name => "auxiliary/scanner/telnet/telnet_version",
|
|
159
|
-
:module_option_string => "RHOSTS #{
|
|
55
|
+
:module_option_string => "RHOSTS #{range}" },
|
|
160
56
|
#{:module_name => "auxiliary/scanner/vmware/vmauthd_version",
|
|
161
|
-
#:module_option_string => "RHOSTS #{
|
|
57
|
+
#:module_option_string => "RHOSTS #{range}" },
|
|
162
58
|
]
|
|
163
59
|
|
|
164
60
|
# This is a naive and horrible way of doing it, but let's just knock
|
|
@@ -182,29 +78,29 @@ module Msf
|
|
|
182
78
|
#
|
|
183
79
|
# This module runs a number of _login modules
|
|
184
80
|
#
|
|
185
|
-
def
|
|
81
|
+
def bruteforce_range(range)
|
|
186
82
|
|
|
187
83
|
modules_and_options = [
|
|
188
84
|
{:module_name => "auxiliary/scanner/ftp/ftp_login",
|
|
189
|
-
:module_option_string => "RHOSTS #{
|
|
85
|
+
:module_option_string => "RHOSTS #{range}" },
|
|
190
86
|
{:module_name => "auxiliary/scanner/http/http_login",
|
|
191
|
-
:module_option_string => "RHOSTS #{
|
|
87
|
+
:module_option_string => "RHOSTS #{range}" },
|
|
192
88
|
{:module_name => "auxiliary/scanner/smb/smb_login",
|
|
193
|
-
:module_option_string => "RHOSTS #{
|
|
89
|
+
:module_option_string => "RHOSTS #{range}" },
|
|
194
90
|
{:module_name => "auxiliary/scanner/mssql/mssql_login",
|
|
195
|
-
:module_option_string => "RHOSTS #{
|
|
91
|
+
:module_option_string => "RHOSTS #{range}" },
|
|
196
92
|
{:module_name => "auxiliary/scanner/mysql/mysql_login",
|
|
197
|
-
:module_option_string => "RHOSTS #{
|
|
93
|
+
:module_option_string => "RHOSTS #{range}" },
|
|
198
94
|
{:module_name => "auxiliary/scanner/pop3/pop3_login",
|
|
199
|
-
:module_option_string => "RHOSTS #{
|
|
95
|
+
:module_option_string => "RHOSTS #{range}" },
|
|
200
96
|
{:module_name => "auxiliary/scanner/smb/smb_login",
|
|
201
|
-
:module_option_string => "RHOSTS #{
|
|
97
|
+
:module_option_string => "RHOSTS #{range}" },
|
|
202
98
|
{:module_name => "auxiliary/scanner/snmp/snmp_login",
|
|
203
|
-
:module_option_string => "RHOSTS #{
|
|
99
|
+
:module_option_string => "RHOSTS #{range}" },
|
|
204
100
|
{:module_name => "auxiliary/scanner/ssh/ssh_login",
|
|
205
|
-
:module_option_string => "RHOSTS #{
|
|
101
|
+
:module_option_string => "RHOSTS #{range}" },
|
|
206
102
|
{:module_name => "auxiliary/scanner/telnet/telnet_login",
|
|
207
|
-
:module_option_string => "RHOSTS #{
|
|
103
|
+
:module_option_string => "RHOSTS #{range}" },
|
|
208
104
|
]
|
|
209
105
|
|
|
210
106
|
# This is a naive and horrible way of doing it, but let's just knock
|
|
@@ -228,10 +124,88 @@ module Msf
|
|
|
228
124
|
#
|
|
229
125
|
# This module runs a number of exploit modules
|
|
230
126
|
#
|
|
231
|
-
def
|
|
127
|
+
def exploit_range(range)
|
|
232
128
|
return "Not Implemented"
|
|
233
129
|
end
|
|
234
130
|
|
|
131
|
+
#
|
|
132
|
+
# This module simply runs a module
|
|
133
|
+
#
|
|
134
|
+
def execute_module_and_return_output(options)
|
|
135
|
+
module_name = options[:module_name]
|
|
136
|
+
#module_options = options[:module_options]
|
|
137
|
+
module_option_string = options[:module_option_string]
|
|
138
|
+
|
|
139
|
+
# split up the module name into type / name
|
|
140
|
+
module_type = module_name.split("/").first
|
|
141
|
+
raise "Error, bad module name" unless ["exploit", "auxiliary", "post", "encoder", "nop"].include? module_type
|
|
142
|
+
|
|
143
|
+
#module_options["TARGET"] = 0 unless module_options["TARGET"]
|
|
144
|
+
|
|
145
|
+
#info = @client.call("module.execute", module_type, module_name, module_options)
|
|
146
|
+
#@client.call("job.info", info["job_id"])
|
|
147
|
+
|
|
148
|
+
# The module output will be not available when run this way; to
|
|
149
|
+
# capture the result of the print_* commands, you have to set the
|
|
150
|
+
# output driver of the module to something you can read from (Buffer,
|
|
151
|
+
# File, etc). For your use case, the best bet is to run the module
|
|
152
|
+
# via the Console API instead of module.execute, and use that to read
|
|
153
|
+
# the output from the console itself, which provides buffer output for you.
|
|
154
|
+
|
|
155
|
+
# Create the console and get its id
|
|
156
|
+
console = @client.call("console.create")
|
|
157
|
+
console_id = console["id"]
|
|
158
|
+
|
|
159
|
+
# Do an initial read / discard to pull out the banner
|
|
160
|
+
@client.call("console.read", console_id)
|
|
161
|
+
|
|
162
|
+
# Move to the context of our module
|
|
163
|
+
@client.call("console.write", console_id, "use #{module_name}\n")
|
|
164
|
+
|
|
165
|
+
# Set up the module's datastore
|
|
166
|
+
module_option_string.split(",").each do |module_option|
|
|
167
|
+
@client.call "console.write", console_id, "set #{module_option}\n"
|
|
168
|
+
@client.call("console.read", console_id)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Ugh, this is horrible, but the read call is currently racey
|
|
172
|
+
5.times do
|
|
173
|
+
@client.call("console.read", console_id)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Depending on the module_type, kick off the module
|
|
177
|
+
if module_type == "auxiliary"
|
|
178
|
+
@client.call "console.write", console_id, "run\n"
|
|
179
|
+
elsif module_type == "exploit"
|
|
180
|
+
@client.call "console.write", console_id, "exploit\n"
|
|
181
|
+
else
|
|
182
|
+
return "Unsupported"
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# do an initial read of the module's output
|
|
186
|
+
module_output = @client.call("console.read", console_id)
|
|
187
|
+
module_output_data_string = "#{module_output['data']}"
|
|
188
|
+
|
|
189
|
+
return "Module Error" if module_output["result"] == "failure"
|
|
190
|
+
|
|
191
|
+
while module_output["busy"] do
|
|
192
|
+
module_output = @client.call("console.read", console_id)
|
|
193
|
+
module_output_data_string += "#{module_output['data']}"
|
|
194
|
+
return "Module Error" if module_output["result"] == "failure"
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Ugh, this is horrible, but the read call is currently racey
|
|
198
|
+
5.times do
|
|
199
|
+
module_output = @client.call("console.read", console_id)
|
|
200
|
+
module_output_data_string += "#{module_output['data']}"
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Clean up
|
|
204
|
+
@client.call("console.destroy", console_id)
|
|
205
|
+
|
|
206
|
+
module_output_data_string
|
|
207
|
+
end
|
|
208
|
+
|
|
235
209
|
end
|
|
236
210
|
end
|
|
237
211
|
end
|