pwn 0.4.514 → 0.4.517
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/.rubocop_todo.yml +17 -11
- data/Gemfile +10 -9
- data/README.md +2 -2
- data/bin/pwn_fuzz_net_app_proto +4 -1
- data/bin/pwn_phone +124 -0
- data/bin/pwn_sast +7 -2
- data/lib/pwn/plugins/baresip.rb +632 -0
- data/lib/pwn/plugins/serial.rb +1 -1
- data/lib/pwn/plugins/sock.rb +32 -0
- data/lib/pwn/plugins/thread_pool.rb +19 -5
- data/lib/pwn/plugins.rb +1 -0
- data/lib/pwn/reports/phone.rb +294 -0
- data/lib/pwn/reports.rb +1 -0
- data/lib/pwn/sast/amqp_connect_as_guest.rb +1 -1
- data/lib/pwn/sast/apache_file_system_util_api.rb +1 -1
- data/lib/pwn/sast/aws.rb +1 -1
- data/lib/pwn/sast/banned_function_calls_c.rb +1 -1
- data/lib/pwn/sast/base64.rb +1 -1
- data/lib/pwn/sast/beef_hook.rb +5 -2
- data/lib/pwn/sast/cmd_execution_java.rb +1 -1
- data/lib/pwn/sast/cmd_execution_python.rb +1 -1
- data/lib/pwn/sast/cmd_execution_ruby.rb +1 -1
- data/lib/pwn/sast/cmd_execution_scala.rb +1 -1
- data/lib/pwn/sast/csrf.rb +3 -2
- data/lib/pwn/sast/deserial_java.rb +12 -2
- data/lib/pwn/sast/emoticon.rb +4 -1
- data/lib/pwn/sast/eval.rb +3 -2
- data/lib/pwn/sast/factory.rb +7 -2
- data/lib/pwn/sast/http_authorization_header.rb +1 -1
- data/lib/pwn/sast/inner_html.rb +4 -3
- data/lib/pwn/sast/keystore.rb +5 -2
- data/lib/pwn/sast/location_hash.rb +3 -2
- data/lib/pwn/sast/log4j.rb +1 -1
- data/lib/pwn/sast/logger.rb +1 -1
- data/lib/pwn/sast/outer_html.rb +3 -2
- data/lib/pwn/sast/password.rb +1 -1
- data/lib/pwn/sast/pom_version.rb +5 -2
- data/lib/pwn/sast/port.rb +1 -1
- data/lib/pwn/sast/private_key.rb +1 -1
- data/lib/pwn/sast/redirect.rb +1 -1
- data/lib/pwn/sast/redos.rb +1 -1
- data/lib/pwn/sast/shell.rb +1 -1
- data/lib/pwn/sast/signature.rb +1 -1
- data/lib/pwn/sast/sql.rb +1 -1
- data/lib/pwn/sast/ssl.rb +9 -2
- data/lib/pwn/sast/sudo.rb +1 -1
- data/lib/pwn/sast/task_tag.rb +1 -1
- data/lib/pwn/sast/throw_errors.rb +3 -2
- data/lib/pwn/sast/token.rb +7 -2
- data/lib/pwn/sast/version.rb +6 -2
- data/lib/pwn/sast/window_location_hash.rb +3 -2
- data/lib/pwn/version.rb +1 -1
- data/spec/lib/pwn/reports/phone_spec.rb +15 -0
- metadata +43 -24
@@ -0,0 +1,632 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'nokogiri'
|
4
|
+
|
5
|
+
module PWN
|
6
|
+
module Plugins
|
7
|
+
# This plugin is used for interacting w/ baresip over a screen session.
|
8
|
+
module BareSIP
|
9
|
+
@@logger = PWN::Plugins::PWNLogger.create
|
10
|
+
@session_data = []
|
11
|
+
# Supported Method Parameters::
|
12
|
+
# baresip_http_call(
|
13
|
+
# http_method: 'optional HTTP method (defaults to GET)
|
14
|
+
# cmd: 'required rest call to make per the schema',
|
15
|
+
# )
|
16
|
+
|
17
|
+
private_class_method def self.baresip_http_call(opts = {})
|
18
|
+
baresip_obj = opts[:baresip_obj]
|
19
|
+
cmd = opts[:cmd]
|
20
|
+
http_listen_ip_port = baresip_obj[:http_listen_ip_port]
|
21
|
+
baresip_url = "http://#{http_listen_ip_port}"
|
22
|
+
|
23
|
+
max_conn_attempts = 30
|
24
|
+
conn_attempt = 0
|
25
|
+
|
26
|
+
begin
|
27
|
+
conn_attempt += 1
|
28
|
+
|
29
|
+
rest_client = PWN::Plugins::TransparentBrowser.open(
|
30
|
+
browser_type: :rest
|
31
|
+
)::Request
|
32
|
+
|
33
|
+
response = rest_client.execute(
|
34
|
+
method: :get,
|
35
|
+
url: "#{baresip_url}/?#{cmd}",
|
36
|
+
verify_ssl: false
|
37
|
+
)
|
38
|
+
|
39
|
+
Nokogiri::HTML.parse(response)
|
40
|
+
rescue Errno::ECONNREFUSED
|
41
|
+
raise e if conn_attempt > max_conn_attempts
|
42
|
+
|
43
|
+
sleep 1
|
44
|
+
retry
|
45
|
+
end
|
46
|
+
rescue StandardError => e
|
47
|
+
case e.message
|
48
|
+
when '400 Bad Request', '404 Resource Not Found'
|
49
|
+
"#{e.message}: #{e.response}"
|
50
|
+
else
|
51
|
+
raise e
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Supported Method Parameters::
|
56
|
+
# baresip_obj = PWN::Plugins::BareSIP.start(
|
57
|
+
# src_num: 'Optional source phone number displayed',
|
58
|
+
# baresip_bin: 'Optional path of baresip binary (Defaults to /usr/bin/baresip)',
|
59
|
+
# config_root: 'Optional dir of baresip config (Defaults to ~/.baresip)',
|
60
|
+
# session_root: 'Optional dir of baresip session (Defaults to Dir.pwd)',
|
61
|
+
# screen_session: 'Optional name of screen session (Defaults baresip)'
|
62
|
+
# )
|
63
|
+
|
64
|
+
public_class_method def self.start(opts = {})
|
65
|
+
src_num = opts[:src_num]
|
66
|
+
|
67
|
+
baresip_bin = opts[:baresip_bin] if File.exist?(
|
68
|
+
opts[:baresip_bin].to_s
|
69
|
+
)
|
70
|
+
baresip_bin ||= '/usr/bin/baresip'
|
71
|
+
|
72
|
+
baresip_obj = {}
|
73
|
+
|
74
|
+
session_root = opts[:session_root] if Dir.exist?(opts[:session_root].to_s)
|
75
|
+
|
76
|
+
config_root = opts[:config_root] if Dir.exist?(
|
77
|
+
opts[:config_root].to_s
|
78
|
+
)
|
79
|
+
config_root ||= "#{Dir.home}/.baresip"
|
80
|
+
|
81
|
+
config = "#{config_root}/config"
|
82
|
+
config_lines = File.readlines(config)
|
83
|
+
http_list_entry = config_lines.grep(/^http_listen\s.+$/)
|
84
|
+
|
85
|
+
raise "no http_listen value found in #{config}." if http_list_entry.empty?
|
86
|
+
|
87
|
+
http_listen_ip_port = http_list_entry.last.split.grep(/:/).last
|
88
|
+
|
89
|
+
screen_session = opts[:screen_session]
|
90
|
+
screen_session ||= 'baresip'
|
91
|
+
|
92
|
+
baresip_obj[:config_root] = config_root
|
93
|
+
baresip_obj[:http_listen_ip_port] = http_listen_ip_port
|
94
|
+
baresip_obj[:session_root] = session_root
|
95
|
+
baresip_obj[:screen_session] = screen_session
|
96
|
+
|
97
|
+
screenlog_file = 'screenlog.txt'
|
98
|
+
baresip_obj[:screenlog_file] = screenlog_file
|
99
|
+
|
100
|
+
# Prefer running baresip in detached screen vs --daemon mode
|
101
|
+
# Since sndfile doesn't produce .wav files in --daemon mode
|
102
|
+
system(
|
103
|
+
'screen',
|
104
|
+
'-T',
|
105
|
+
'xterm',
|
106
|
+
'-L',
|
107
|
+
'-Logfile',
|
108
|
+
screenlog_file,
|
109
|
+
'-S',
|
110
|
+
screen_session,
|
111
|
+
'-d',
|
112
|
+
'-m',
|
113
|
+
baresip_bin,
|
114
|
+
'-f',
|
115
|
+
config_root,
|
116
|
+
'-e',
|
117
|
+
'/insmod httpd',
|
118
|
+
'-e',
|
119
|
+
'/insmod sndfile'
|
120
|
+
)
|
121
|
+
|
122
|
+
baresip_obj[:session_thread] = init_session_thread(
|
123
|
+
baresip_obj: baresip_obj
|
124
|
+
)
|
125
|
+
|
126
|
+
ok = 'registered successfully'
|
127
|
+
gone = 'account: No SIP accounts found'
|
128
|
+
forb = '403 Forbidden'
|
129
|
+
|
130
|
+
# TODO: Make this faster.
|
131
|
+
print 'Starting baresip...'
|
132
|
+
loop do
|
133
|
+
break if @session_data.select { |s| s.include?(ok) }.length.positive?
|
134
|
+
|
135
|
+
next unless dump_session_data.select { |s| s.include?(gone) }.length.positive?
|
136
|
+
next unless dump_session_data.select { |s| s.include?(forb) }.length.positive?
|
137
|
+
|
138
|
+
error = gone if dump_session_data.select { |s| s.include?(gone) }.length.positive?
|
139
|
+
error = forbid if dump_session_data.select { |s| s.include?(forb) }.length.positive?
|
140
|
+
raise "Something happened when attempting to start baresip: #{error}"
|
141
|
+
end
|
142
|
+
puts 'ready.'
|
143
|
+
|
144
|
+
baresip_obj
|
145
|
+
rescue StandardError => e
|
146
|
+
raise e
|
147
|
+
end
|
148
|
+
|
149
|
+
# Supported Method Parameters::
|
150
|
+
# session_thread = init_session_thread(
|
151
|
+
# serial_conn: 'required - SerialPort.new object'
|
152
|
+
# )
|
153
|
+
|
154
|
+
private_class_method def self.init_session_thread(opts = {})
|
155
|
+
baresip_obj = opts[:baresip_obj]
|
156
|
+
|
157
|
+
session_root = baresip_obj[:session_root]
|
158
|
+
screenlog_file = baresip_obj[:screenlog_file]
|
159
|
+
|
160
|
+
screenlog = "#{session_root}/#{screenlog_file}"
|
161
|
+
|
162
|
+
# Spin up a baresip_obj session_thread
|
163
|
+
Thread.new do
|
164
|
+
loop do
|
165
|
+
next unless File.exist?(screenlog)
|
166
|
+
|
167
|
+
# Continuously consume contents of screenlog.0
|
168
|
+
@session_data = File.readlines(screenlog)
|
169
|
+
@session_data.delete_if do |line|
|
170
|
+
line.include?('ua: using best effort AF: af=AF_INET')
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
rescue StandardError => e
|
175
|
+
session_thread&.terminate
|
176
|
+
|
177
|
+
raise e
|
178
|
+
end
|
179
|
+
|
180
|
+
# Supported Method Parameters::
|
181
|
+
# session_data = PWN::Plugins::BareSIP.dump_session_data
|
182
|
+
|
183
|
+
public_class_method def self.dump_session_data
|
184
|
+
@session_data
|
185
|
+
rescue StandardError => e
|
186
|
+
raise e
|
187
|
+
end
|
188
|
+
|
189
|
+
# Supported Method Parameters::
|
190
|
+
# session_data = PWN::Plugins::BareSIP.flush_session_data
|
191
|
+
|
192
|
+
public_class_method def self.flush_session_data
|
193
|
+
@session_data.clear
|
194
|
+
rescue StandardError => e
|
195
|
+
raise e
|
196
|
+
end
|
197
|
+
|
198
|
+
# Supported Method Parameters::
|
199
|
+
# cmd_resp = PWN::Plugins::BareSIP.baresip_exec(
|
200
|
+
# baresip_obj: 'Required - baresip obj returned from #start method',
|
201
|
+
# cmd: 'Required - command to send to baresip HTTP daemon'
|
202
|
+
# )
|
203
|
+
|
204
|
+
public_class_method def self.baresip_exec(opts = {})
|
205
|
+
baresip_obj = opts[:baresip_obj]
|
206
|
+
cmd = opts[:cmd]
|
207
|
+
|
208
|
+
baresip_http_call(
|
209
|
+
baresip_obj: baresip_obj,
|
210
|
+
cmd: cmd
|
211
|
+
)
|
212
|
+
rescue StandardError => e
|
213
|
+
raise e
|
214
|
+
end
|
215
|
+
|
216
|
+
# Supported Method Parameters::
|
217
|
+
# PWN::Plugins::BareSIP.stop(
|
218
|
+
# screen_session: 'Required - screen session to stop'
|
219
|
+
# )
|
220
|
+
|
221
|
+
public_class_method def self.stop(opts = {})
|
222
|
+
baresip_obj = opts[:baresip_obj]
|
223
|
+
session_thread = baresip_obj[:session_thread]
|
224
|
+
screen_session = baresip_obj[:screen_session]
|
225
|
+
|
226
|
+
flush_session_data
|
227
|
+
|
228
|
+
session_thread.terminate
|
229
|
+
|
230
|
+
system(
|
231
|
+
'screen',
|
232
|
+
'-X',
|
233
|
+
'-S',
|
234
|
+
screen_session,
|
235
|
+
'quit'
|
236
|
+
)
|
237
|
+
|
238
|
+
baresip_obj = nil
|
239
|
+
rescue StandardError => e
|
240
|
+
raise e
|
241
|
+
end
|
242
|
+
|
243
|
+
# Supported Method Parameters::
|
244
|
+
# PWN::Plugins::BareSIP.parse_target_file(
|
245
|
+
# target_file: 'Required - txt file containing phone numbers to dial',
|
246
|
+
# randomize: 'Optional - randomize list of phone numbers to dial (Defaults to false)'
|
247
|
+
# )
|
248
|
+
|
249
|
+
public_class_method def self.parse_target_file(opts = {})
|
250
|
+
target_file = opts[:target_file]
|
251
|
+
randomize = opts[:randomize]
|
252
|
+
|
253
|
+
# Parse entries from target_file and build out our target range
|
254
|
+
target_lines = File.readlines(target_file)
|
255
|
+
target_range = []
|
256
|
+
print 'Initializing targets...'
|
257
|
+
target_lines.each do |target_line|
|
258
|
+
next if target_line.match?(/^#.*$/)
|
259
|
+
|
260
|
+
target_line.scrub.strip.chomp.delete('(').delete(')').delete('.').delete('+')
|
261
|
+
if target_line.include?('-')
|
262
|
+
split_range = target_line.split('-')
|
263
|
+
if split_range.length == 2
|
264
|
+
from_num = split_range.first.to_i
|
265
|
+
to_num = split_range.last.to_i
|
266
|
+
|
267
|
+
(from_num..to_num).each do |number_in_range|
|
268
|
+
target_range.push(number_in_range)
|
269
|
+
end
|
270
|
+
else
|
271
|
+
target_line.scrub.strip.chomp.delete('-')
|
272
|
+
end
|
273
|
+
else
|
274
|
+
target_range.push(target_line.to_i)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
puts 'complete.'
|
278
|
+
|
279
|
+
# Randomize targets if applicable
|
280
|
+
target_range.shuffle! if randomize
|
281
|
+
|
282
|
+
target_range
|
283
|
+
rescue StandardError => e
|
284
|
+
raise e
|
285
|
+
end
|
286
|
+
|
287
|
+
# Supported Method Parameters::
|
288
|
+
# PWN::Plugins::BareSIP.apply_src_num_rules(
|
289
|
+
# target_num: 'Required - destination number to derive source number',
|
290
|
+
# src_num_rules: 'Optional - Comma-delimited list of rules for src_num format (i.e. self, same_country, same_area, and/or same_prefix [Defaults to random src_num w/ same length as target_num])'
|
291
|
+
# )
|
292
|
+
|
293
|
+
public_class_method def self.apply_src_num_rules(opts = {})
|
294
|
+
src_num_rules = opts[:src_num_rules]
|
295
|
+
target_num = opts[:target_num]
|
296
|
+
|
297
|
+
src_num_rules_arr = []
|
298
|
+
if src_num_rules
|
299
|
+
src_num_rules_arr = src_num_rules.delete("\s").split(',').map(
|
300
|
+
&:to_sym
|
301
|
+
)
|
302
|
+
end
|
303
|
+
|
304
|
+
case target_num.to_s.length
|
305
|
+
when 10
|
306
|
+
# area+prefix+suffix
|
307
|
+
country = ''
|
308
|
+
when 11
|
309
|
+
# 1 digit country+area+prefix+suffix
|
310
|
+
country = format('%0.1s', Random.rand(1..9))
|
311
|
+
country = target_num.to_s.chars.first if src_num_rules_arr.include?(
|
312
|
+
:same_country
|
313
|
+
)
|
314
|
+
when 12
|
315
|
+
# 2 digit country+area+prefix+suffix
|
316
|
+
country = format('%0.2s', Random.rand(1..99))
|
317
|
+
country = target_num.to_s.chars[0..1].join if src_num_rules_arr.include?(
|
318
|
+
:same_country
|
319
|
+
)
|
320
|
+
when 13
|
321
|
+
# 3 digit country+area+prefix+suffix
|
322
|
+
country = format('%0.3s', Random.rand(1..999))
|
323
|
+
country = target_num.to_s.chars[0..2].join if src_num_rules_arr.include?(
|
324
|
+
:same_country
|
325
|
+
)
|
326
|
+
when 14
|
327
|
+
# 4 digit country+area+prefix+suffix
|
328
|
+
country = format('%0.4s', Random.rand(1..9999))
|
329
|
+
country = target_num.to_s.chars[0..3].join if src_num_rules_arr.include?(
|
330
|
+
:same_country
|
331
|
+
)
|
332
|
+
else
|
333
|
+
raise "Target # should be 10-14 digits. Length is: #{target_num.to_s.length}"
|
334
|
+
end
|
335
|
+
|
336
|
+
area = format('%0.3s', Random.rand(200..999))
|
337
|
+
area = target_num.to_s.chars[-10..-8].join if src_num_rules_arr.include?(
|
338
|
+
:same_area
|
339
|
+
)
|
340
|
+
|
341
|
+
prefix = format('%0.3s', Random.rand(200..999))
|
342
|
+
prefix = target_num.to_s.chars[-7..-5].join if src_num_rules_arr.include?(
|
343
|
+
:same_prefix
|
344
|
+
)
|
345
|
+
suffix = format('%0.4s', Random.rand(0..9999))
|
346
|
+
src_num = "#{country}#{area}#{prefix}#{suffix}"
|
347
|
+
src_num = target_num if src_num_rules_arr.include?(:self)
|
348
|
+
|
349
|
+
# TODO: Update ~/.baresip/accounts to apply source number
|
350
|
+
# config_root = baresip_obj[:config_root]
|
351
|
+
# config = "#{config_root}/config"
|
352
|
+
|
353
|
+
src_num
|
354
|
+
rescue StandardError => e
|
355
|
+
raise e
|
356
|
+
end
|
357
|
+
|
358
|
+
# Supported Method Parameters::
|
359
|
+
# PWN::Plugins::BareSIP.recon(
|
360
|
+
# baresip_obj: 'Required - baresip_obj returned from #start method',
|
361
|
+
# target_num: 'Required - target destination to recon (i.e. phone number)',
|
362
|
+
# src_num_rules: 'Optional - Comma-delimited list of rules for src_num format (i.e. self, same_country, same_area, and/or same_prefix [Defaults to random src_num w/ same length as target_num])',
|
363
|
+
# seconds_to_record: 'Optional - Seconds to Record (Defaults to 60)',
|
364
|
+
# sox_bin: 'Optional - Path to SoX Binary, the Swiss Army knife of Audio (Defaults to /usr/bin/sox)'
|
365
|
+
# )
|
366
|
+
|
367
|
+
public_class_method def self.recon(opts = {})
|
368
|
+
baresip_bin = opts[:baresip_bin]
|
369
|
+
session_root = opts[:session_root]
|
370
|
+
session_root ||= Dir.pwd
|
371
|
+
target_file = opts[:target_file]
|
372
|
+
randomize = opts[:randomize]
|
373
|
+
src_num_rules = opts[:src_num_rules]
|
374
|
+
max_threads = opts[:max_threads].to_i
|
375
|
+
max_threads = 3 if max_threads.zero?
|
376
|
+
seconds_to_record = opts[:seconds_to_record].to_i
|
377
|
+
seconds_to_record = 60 if seconds_to_record.zero?
|
378
|
+
sox_bin = opts[:sox_bin] if File.exist?(opts[:sox_bin].to_s)
|
379
|
+
sox_bin ||= '/usr/bin/sox'
|
380
|
+
waveform_bin = 'waveform'
|
381
|
+
|
382
|
+
# Intialize empty baresip obj for ensure block below
|
383
|
+
baresip_obj = {}
|
384
|
+
|
385
|
+
# Colors!
|
386
|
+
red = "\e[31m"
|
387
|
+
green = "\e[32m"
|
388
|
+
yellow = "\e[33m"
|
389
|
+
cayan = "\e[36m"
|
390
|
+
end_of_color = "\e[0m"
|
391
|
+
|
392
|
+
target_range = parse_target_file(
|
393
|
+
target_file: target_file,
|
394
|
+
randomize: randomize
|
395
|
+
)
|
396
|
+
|
397
|
+
results_hash = {
|
398
|
+
session_started: Time.now.strftime('%Y-%m-%d_%H.%M.%S'),
|
399
|
+
data: []
|
400
|
+
}
|
401
|
+
|
402
|
+
# TODO: Multi-thread!
|
403
|
+
# mutex = Mutex.new
|
404
|
+
# PWN::Plugins::ThreadPool.fill(
|
405
|
+
# enumerable_array: target_range,
|
406
|
+
# max_threads: max_threads
|
407
|
+
# ) do |target_num|
|
408
|
+
target_range.each_with_index do |target_num, index|
|
409
|
+
call_count = index + 1
|
410
|
+
puts "Call #{call_count} of #{target_range.length}..."
|
411
|
+
|
412
|
+
# Change to session_root _before_ starting to ensure
|
413
|
+
# wav files are stored in the proper location
|
414
|
+
Dir.chdir(session_root)
|
415
|
+
|
416
|
+
src_num = apply_src_num_rules(
|
417
|
+
target_num: target_num,
|
418
|
+
src_num_rules: src_num_rules
|
419
|
+
)
|
420
|
+
|
421
|
+
call_info_hash = {}
|
422
|
+
|
423
|
+
call_started = Time.now.strftime('%Y-%m-%d_%H.%M.%S')
|
424
|
+
|
425
|
+
call_info_hash[:call_started] = call_started
|
426
|
+
call_info_hash[:src_num] = src_num
|
427
|
+
call_info_hash[:src_num_rules] = src_num_rules
|
428
|
+
call_info_hash[:target_num] = target_num
|
429
|
+
|
430
|
+
target_num_root = "#{session_root}/#{target_num}-#{call_started}"
|
431
|
+
|
432
|
+
# TODO: Determine what to do w/ existing Directory
|
433
|
+
Dir.mkdir(target_num_root)
|
434
|
+
Dir.chdir(target_num_root)
|
435
|
+
|
436
|
+
# Start baresip in detached screen to support commands over HTTP
|
437
|
+
# and call recording to wav files
|
438
|
+
baresip_obj = start(
|
439
|
+
src_num: src_num,
|
440
|
+
baresip_bin: baresip_bin,
|
441
|
+
session_root: target_num_root,
|
442
|
+
screen_session: "#{File.basename($PROGRAM_NAME)}-#{target_num}"
|
443
|
+
)
|
444
|
+
|
445
|
+
# session_root = baresip_obj[:session_root]
|
446
|
+
config_root = baresip_obj[:config_root]
|
447
|
+
config = "#{config_root}/config"
|
448
|
+
|
449
|
+
puts "#{green}#{call_started} >>>#{end_of_color}"
|
450
|
+
puts "#{yellow}dialing #{target_num}#{end_of_color}"
|
451
|
+
|
452
|
+
cmd_resp = baresip_exec(
|
453
|
+
baresip_obj: baresip_obj,
|
454
|
+
cmd: "/dial #{target_num}\r\n"
|
455
|
+
)
|
456
|
+
puts "/dial #{target_num} RESP:"
|
457
|
+
puts cmd_resp.xpath('//pre').text
|
458
|
+
|
459
|
+
cmd_resp = baresip_exec(
|
460
|
+
baresip_obj: baresip_obj,
|
461
|
+
cmd: "/listcalls\r\n"
|
462
|
+
)
|
463
|
+
puts '/listcalls RESP:'
|
464
|
+
puts cmd_resp.xpath('//pre').text
|
465
|
+
|
466
|
+
puts red
|
467
|
+
# Conditions to proceed less than seconds_to_record
|
468
|
+
terminated = 'terminated (duration:'
|
469
|
+
unavail = '503 Service Unavailable'
|
470
|
+
not_found = 'session closed: 404 Not Found'
|
471
|
+
|
472
|
+
reason = 'recording limit reached'
|
473
|
+
seconds_recorded = 0
|
474
|
+
seconds_to_record.downto(1).each do |countdown|
|
475
|
+
seconds_recorded += 1
|
476
|
+
print "#{seconds_to_record}s to record - remaining: #{format('%-9.9s', countdown)}"
|
477
|
+
print "\r"
|
478
|
+
|
479
|
+
if dump_session_data.select { |s| s.include?(terminated) }.length.positive?
|
480
|
+
reason = 'call terminated by other party'
|
481
|
+
break
|
482
|
+
end
|
483
|
+
|
484
|
+
if dump_session_data.select { |s| s.include?(unavail) }.length.positive?
|
485
|
+
reason = 'SIP 503 (service unavailable)'
|
486
|
+
break
|
487
|
+
end
|
488
|
+
|
489
|
+
if dump_session_data.select { |s| s.include?(not_found) }.length.positive?
|
490
|
+
reason = 'SIP 404 (not found)'
|
491
|
+
break
|
492
|
+
end
|
493
|
+
|
494
|
+
sleep 1
|
495
|
+
end
|
496
|
+
call_info_hash[:seconds_recorded] = seconds_recorded
|
497
|
+
puts end_of_color
|
498
|
+
|
499
|
+
call_stopped = Time.now.strftime('%Y-%m-%d_%H.%M.%S')
|
500
|
+
puts "\n#{green}#{call_stopped} >>> #{reason} #{target_num}#{end_of_color}"
|
501
|
+
call_info_hash[:call_stopped] = call_stopped
|
502
|
+
call_info_hash[:reason] = reason
|
503
|
+
|
504
|
+
recording = ''
|
505
|
+
# BUGFIX: Sometimes recordings aren't generated
|
506
|
+
# Perhaps because previous session wasn't stopped properly?
|
507
|
+
Dir.entries(target_num_root).each do |entry|
|
508
|
+
recording = entry if entry.match(/^dump-.+-dec.wav/)
|
509
|
+
File.delete(entry) if entry.match(/^dump-.+-enc\.wav$/) && File.exist?(entry)
|
510
|
+
end
|
511
|
+
|
512
|
+
call_info_hash[:recording] = '--'
|
513
|
+
call_info_hash[:waveform] = '--'
|
514
|
+
call_info_hash[:spectrogram] = '--'
|
515
|
+
|
516
|
+
unless recording.empty?
|
517
|
+
puts cayan
|
518
|
+
|
519
|
+
call_info_hash[:recording] = "#{target_num}-#{call_started}/#{recording}"
|
520
|
+
|
521
|
+
spectrogram = "#{recording}-spectrogram.png"
|
522
|
+
print "Generating Audio Spectrogram for #{recording}..."
|
523
|
+
system(
|
524
|
+
sox_bin,
|
525
|
+
recording,
|
526
|
+
'-n',
|
527
|
+
'spectrogram',
|
528
|
+
'-o',
|
529
|
+
spectrogram,
|
530
|
+
'-d',
|
531
|
+
seconds_to_record.to_s
|
532
|
+
)
|
533
|
+
puts 'complete.'
|
534
|
+
call_info_hash[:spectrogram] = "#{target_num}-#{call_started}/#{spectrogram}"
|
535
|
+
|
536
|
+
waveform = "#{recording}-waveform.png"
|
537
|
+
print "Generating Audio Waveform for #{recording}..."
|
538
|
+
system(
|
539
|
+
waveform_bin,
|
540
|
+
'--method',
|
541
|
+
'peak',
|
542
|
+
'--color',
|
543
|
+
'#FF0000',
|
544
|
+
'--background',
|
545
|
+
'#000000',
|
546
|
+
'--force',
|
547
|
+
recording,
|
548
|
+
waveform
|
549
|
+
)
|
550
|
+
puts 'complete.'
|
551
|
+
call_info_hash[:waveform] = "#{target_num}-#{call_started}/#{waveform}"
|
552
|
+
puts end_of_color
|
553
|
+
end
|
554
|
+
|
555
|
+
# Push Call Results to results_hash[:data]
|
556
|
+
# mutex.synchronize do
|
557
|
+
# results_hash[:data].push(call_info_hash)
|
558
|
+
# end
|
559
|
+
results_hash[:data].push(call_info_hash)
|
560
|
+
|
561
|
+
puts "call termination reason: #{reason}"
|
562
|
+
# Stop call to cd into next session root
|
563
|
+
stop(baresip_obj: baresip_obj)
|
564
|
+
|
565
|
+
seconds_to_delay_between_pool_of_calls = Random.rand(1..9)
|
566
|
+
seconds_to_delay_between_pool_of_calls.downto(1).each do |countdown|
|
567
|
+
print "#{red}ZZZ #{seconds_to_delay_between_pool_of_calls}s until next pool of calls: #{format('%-9.9s', countdown)}#{end_of_color}"
|
568
|
+
print "\r"
|
569
|
+
sleep 1
|
570
|
+
end
|
571
|
+
puts "\n#{green}waking up for next call \\o/#{end_of_color}"
|
572
|
+
puts "\n\n\n"
|
573
|
+
end
|
574
|
+
results_hash[:session_ended] = Time.now.strftime('%Y-%m-%d_%H.%M.%S')
|
575
|
+
|
576
|
+
results_hash
|
577
|
+
rescue StandardError => e
|
578
|
+
raise e
|
579
|
+
ensure
|
580
|
+
stop(baresip_obj: baresip_obj) unless baresip_obj.empty?
|
581
|
+
end
|
582
|
+
|
583
|
+
# Supported Method Parameters::
|
584
|
+
# PWN::Plugins::BareSIP.killall
|
585
|
+
|
586
|
+
public_class_method def self.killall
|
587
|
+
system(
|
588
|
+
'killall',
|
589
|
+
'-15',
|
590
|
+
'baresip'
|
591
|
+
)
|
592
|
+
rescue StandardError => e
|
593
|
+
raise e
|
594
|
+
end
|
595
|
+
|
596
|
+
# Author(s):: 0day Inc. <request.pentest@0dayinc.com>
|
597
|
+
|
598
|
+
public_class_method def self.authors
|
599
|
+
"AUTHOR(S):
|
600
|
+
0day Inc. <request.pentest@0dayinc.com>
|
601
|
+
"
|
602
|
+
end
|
603
|
+
|
604
|
+
# Display Usage for this Module
|
605
|
+
|
606
|
+
public_class_method def self.help
|
607
|
+
puts "USAGE:
|
608
|
+
baresip_obj = #{self}.start(
|
609
|
+
baresip_bin: 'Optional path of baresip binary (Defaults to /usr/bin/baresip)'
|
610
|
+
config_root: 'Optional dir of baresip config (Defaults to ~/.baresip)',
|
611
|
+
screen_session: 'Optional name of screen session (Defaults baresip)'
|
612
|
+
)
|
613
|
+
|
614
|
+
session_data_arr = #{self}.dump_session_data
|
615
|
+
|
616
|
+
cmd_resp = #{self}.baresip_exec(
|
617
|
+
baresip_obj: 'Required - baresip obj returned from #start method',
|
618
|
+
cmd: 'Required - command to send to baresip HTTP daemon'
|
619
|
+
)
|
620
|
+
|
621
|
+
stopped_bool = #{self}.stop(
|
622
|
+
screen_session: 'Required - screen session to stop'
|
623
|
+
)
|
624
|
+
|
625
|
+
#{self}.killall
|
626
|
+
|
627
|
+
#{self}.authors
|
628
|
+
"
|
629
|
+
end
|
630
|
+
end
|
631
|
+
end
|
632
|
+
end
|
data/lib/pwn/plugins/serial.rb
CHANGED
data/lib/pwn/plugins/sock.rb
CHANGED
@@ -47,6 +47,32 @@ module PWN
|
|
47
47
|
raise e
|
48
48
|
end
|
49
49
|
|
50
|
+
# Supported Method Parameters::
|
51
|
+
# PWN::Plugins::Sock.check_port_availability(
|
52
|
+
# port: 'required - target port',
|
53
|
+
# server_ip: 'optional - target host or ip to check (Defaults to 127.0.0.1)',
|
54
|
+
# protocol: 'optional - :tcp || :udp (defaults to tcp)'
|
55
|
+
# )
|
56
|
+
|
57
|
+
public_class_method def self.check_port_availability(opts = {})
|
58
|
+
server_ip = opts[:server_ip]
|
59
|
+
server_ip ||= '127.0.0.1'
|
60
|
+
port = opts[:port]
|
61
|
+
protocol = opts[:protocol]
|
62
|
+
protocol ||= :tcp
|
63
|
+
|
64
|
+
ct = 0.1
|
65
|
+
s = Socket.tcp(server_ip, port, connect_timeout: ct) if protocol == :tcp
|
66
|
+
s = Socket.udp(server_ip, port, connect_timeout: ct) if protocol == :udp
|
67
|
+
s.close
|
68
|
+
|
69
|
+
true
|
70
|
+
rescue Errno::ECONNREFUSED,
|
71
|
+
Errno::EHOSTUNREACH,
|
72
|
+
Errno::ETIMEDOUT
|
73
|
+
false
|
74
|
+
end
|
75
|
+
|
50
76
|
# Supported Method Parameters::
|
51
77
|
# PWN::Plugins::Sock.listen(
|
52
78
|
# server_ip: 'required - target host or ip to listen',
|
@@ -137,6 +163,12 @@ module PWN
|
|
137
163
|
tls: 'optional - boolean connect to target socket using TLS (defaults to false)'
|
138
164
|
)
|
139
165
|
|
166
|
+
#{self}.check_port_availability(
|
167
|
+
port: 'required - target port',
|
168
|
+
server_ip: 'optional - target host or ip to check (Defaults to 127.0.0.1)',
|
169
|
+
protocol: 'optional - :tcp || :udp (defaults to tcp)'
|
170
|
+
)
|
171
|
+
|
140
172
|
#{self}.listen(
|
141
173
|
server_ip: 'required - target host or ip to listen',
|
142
174
|
port: 'required - target port',
|