pwn 0.5.67 → 0.5.69
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 +16 -18
- data/Gemfile +1 -1
- data/README.md +3 -3
- data/bin/pwn +4 -379
- data/lib/pwn/plugins/monkey_patch.rb +164 -0
- data/lib/pwn/plugins/ollama.rb +11 -567
- data/lib/pwn/plugins/repl.rb +317 -0
- data/lib/pwn/plugins/vault.rb +5 -0
- data/lib/pwn/plugins.rb +2 -0
- data/lib/pwn/version.rb +1 -1
- data/spec/lib/pwn/plugins/monkey_patch_spec.rb +15 -0
- data/spec/lib/pwn/plugins/repl_spec.rb +15 -0
- metadata +7 -3
@@ -0,0 +1,317 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pry'
|
4
|
+
require 'tty-prompt'
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
module PWN
|
8
|
+
module Plugins
|
9
|
+
# This module contains methods related to the pwn REPL Driver.
|
10
|
+
module REPL
|
11
|
+
# Supported Method Parameters::
|
12
|
+
# PWN::Plugins::REPL.refresh_ps1_proc(
|
13
|
+
# mode: 'required - :splat or nil'
|
14
|
+
# )
|
15
|
+
|
16
|
+
public_class_method def self.refresh_ps1_proc(opts = {})
|
17
|
+
mode = opts[:mode]
|
18
|
+
|
19
|
+
proc do |_target_self, _nest_level, pi|
|
20
|
+
pi.config.pwn_repl_line += 1
|
21
|
+
line_pad = format(
|
22
|
+
'%0.3d',
|
23
|
+
pi.config.pwn_repl_line
|
24
|
+
)
|
25
|
+
|
26
|
+
pi.config.prompt_name = :pwn
|
27
|
+
name = "\001\e[1m\002\001\e[31m\002#{pi.config.prompt_name}\001\e[0m\002"
|
28
|
+
version = "\001\e[36m\002v#{PWN::VERSION}\001\e[0m\002"
|
29
|
+
line_count = "\001\e[34m\002#{line_pad}\001\e[0m\002"
|
30
|
+
dchars = "\001\e[32m\002>>>\001\e[0m\002"
|
31
|
+
dchars = "\001\e[33m\002***\001\e[0m\002" if mode == :splat
|
32
|
+
|
33
|
+
if pi.config.pwn_asm
|
34
|
+
pi.config.prompt_name = 'pwn.asm'
|
35
|
+
name = "\001\e[1m\002\001\e[37m\002#{pi.config.prompt_name}\001\e[0m\002"
|
36
|
+
dchars = "\001\e[32m\002>>>\001\e[33m\002"
|
37
|
+
dchars = "\001\e[33m\002***\001\e[33m\002" if mode == :splat
|
38
|
+
end
|
39
|
+
|
40
|
+
if pi.config.pwn_ai
|
41
|
+
pi.config.prompt_name = 'pwn.ai'
|
42
|
+
pi.config.prompt_name = 'pwn.ai.SPEAKING' if pi.config.pwn_ai_speak
|
43
|
+
name = "\001\e[1m\002\001\e[33m\002#{pi.config.prompt_name}\001\e[0m\002"
|
44
|
+
dchars = "\001\e[32m\002>>>\001\e[33m\002"
|
45
|
+
dchars = "\001\e[33m\002***\001\e[33m\002" if mode == :splat
|
46
|
+
if pi.config.pwn_ai_debug
|
47
|
+
dchars = "\001\e[32m\002(DEBUG) >>>\001\e[33m\002"
|
48
|
+
dchars = "\001\e[33m\002(DEBUG) ***\001\e[33m\002" if mode == :splat
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
"#{name}[#{version}]:#{line_count} #{dchars} ".to_s.scrub
|
53
|
+
end
|
54
|
+
rescue StandardError => e
|
55
|
+
raise e
|
56
|
+
end
|
57
|
+
|
58
|
+
# Supported Method Parameters::
|
59
|
+
# PWN::Plugins::REPL.add_commands
|
60
|
+
|
61
|
+
public_class_method def self.add_commands
|
62
|
+
# Define Custom REPL Commands
|
63
|
+
Pry::Commands.create_command 'welcome-banner' do
|
64
|
+
description 'Display the random welcome banner, including basic usage.'
|
65
|
+
|
66
|
+
def process
|
67
|
+
puts PWN::Banner.welcome
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
Pry::Commands.create_command 'toggle-pager' do
|
72
|
+
description 'Toggle less on returned objects surpassing the terminal.'
|
73
|
+
|
74
|
+
def process
|
75
|
+
pi = pry_instance
|
76
|
+
pi.config.pager ? pi.config.pager = false : pi.config.pager = true
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# class PWNCompleter < Pry::InputCompleter
|
81
|
+
# def call(input)
|
82
|
+
# end
|
83
|
+
# end
|
84
|
+
|
85
|
+
Pry::Commands.create_command 'pwn-asm' do
|
86
|
+
description 'Initiate pwn.asm shell.'
|
87
|
+
|
88
|
+
def process
|
89
|
+
pi = pry_instance
|
90
|
+
pi.config.pwn_asm = true
|
91
|
+
pi.custom_completions = proc do
|
92
|
+
prompt = TTY::Prompt.new
|
93
|
+
[pi.input.line_buffer]
|
94
|
+
# prompt.select(pi.input.line_buffer)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
Pry::Commands.create_command 'pwn-ai' do
|
100
|
+
description 'Initiate pwn.ai chat interface.'
|
101
|
+
|
102
|
+
def process
|
103
|
+
pi = pry_instance
|
104
|
+
pi.config.pwn_ai = true
|
105
|
+
pi.config.color = false if pi.config.pwn_ai
|
106
|
+
pi.config.color = true unless pi.config.pwn_ai
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
Pry::Commands.create_command 'toggle-pwn-ai-debug' do
|
111
|
+
description 'Display the response_history object while using pwn.ai'
|
112
|
+
|
113
|
+
def process
|
114
|
+
pi = pry_instance
|
115
|
+
pi.config.pwn_ai_debug ? pi.config.pwn_ai_debug = false : pi.config.pwn_ai_debug = true
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
Pry::Commands.create_command 'toggle-pwn-ai-speaks' do
|
120
|
+
description 'Use speech capabilities within pwn.ai to speak answers.'
|
121
|
+
|
122
|
+
def process
|
123
|
+
pi = pry_instance
|
124
|
+
pi.config.pwn_ai_speak ? pi.config.pwn_ai_speak = false : pi.config.pwn_ai_speak = true
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
Pry::Commands.create_command 'back' do
|
129
|
+
description 'Jump back to pwn REPL when in pwn-asm || pwn-ai.'
|
130
|
+
|
131
|
+
def process
|
132
|
+
pi = pry_instance
|
133
|
+
pi.config.pwn_asm = false if pi.config.pwn_asm
|
134
|
+
pi.config.pwn_ai = false if pi.config.pwn_ai
|
135
|
+
pi.config.pwn_ai_debug = false if pi.config.pwn_ai_debug
|
136
|
+
pi.config.pwn_ai_speak = false if pi.config.pwn_ai_speak
|
137
|
+
pi.config.completer = Pry::InputCompleter
|
138
|
+
end
|
139
|
+
end
|
140
|
+
rescue StandardError => e
|
141
|
+
raise e
|
142
|
+
end
|
143
|
+
|
144
|
+
# Supported Method Parameters::
|
145
|
+
# PWN::Plugins::REPL.add_hooks(
|
146
|
+
# opts: 'required - Hash object passed in via pwn OptParser'
|
147
|
+
# )
|
148
|
+
|
149
|
+
public_class_method def self.add_hooks(opts = {})
|
150
|
+
# Define REPL Hooks
|
151
|
+
# Welcome Banner Hook
|
152
|
+
Pry.config.hooks.add_hook(:before_session, :welcome) do |output, _binding, _pi|
|
153
|
+
output.puts PWN::Banner.welcome
|
154
|
+
end
|
155
|
+
|
156
|
+
# pwn.ai Hooks
|
157
|
+
Pry.config.hooks.add_hook(:before_session, :init_opts) do |_output, _binding, pi|
|
158
|
+
if opts[:yaml_config_path] && File.exist?(opts[:yaml_config_path])
|
159
|
+
yaml_config_path = opts[:yaml_config_path]
|
160
|
+
is_encrypted = PWN::Plugins::Vault.file_encrypted?(file: yaml_config_path)
|
161
|
+
|
162
|
+
if is_encrypted
|
163
|
+
# TODO: Implement "something you know, something you have, && something you are?"
|
164
|
+
decryption_file = opts[:decryption_file] ||= "#{Dir.home}/pwn.decryptor.yaml"
|
165
|
+
yaml_decryptor = YAML.load_file(decryption_file, symbolize_names: true) if File.exist?(decryption_file)
|
166
|
+
|
167
|
+
key = opts[:key] ||= yaml_decryptor[:key] ||= ENV.fetch('PWN_DECRYPTOR_KEY')
|
168
|
+
key = PWN::Plugins::AuthenticationHelper.mask_password(prompt: 'Decryption Key') if key.nil?
|
169
|
+
|
170
|
+
iv = opts[:iv] ||= yaml_decryptor[:iv] ||= ENV.fetch('PWN_DECRYPTOR_IV')
|
171
|
+
iv = PWN::Plugins::AuthenticationHelper.mask_password(prompt: 'Decryption IV') if iv.nil?
|
172
|
+
|
173
|
+
decrypted_yaml_config = PWN::Plugins::Vault.dump(
|
174
|
+
file: yaml_config_path,
|
175
|
+
key: key,
|
176
|
+
iv: iv
|
177
|
+
)
|
178
|
+
yaml_config = YAML.load(decrypted_yaml_config, symbolize_names: true)
|
179
|
+
else
|
180
|
+
yaml_config = YAML.load_file(yaml_config_path, symbolize_names: true)
|
181
|
+
end
|
182
|
+
|
183
|
+
pi.config.pwn_ai_key = yaml_config[:ai_key]
|
184
|
+
Pry.config.pwn_ai_key = pi.config.pwn_ai_key
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
Pry.config.hooks.add_hook(:after_read, :pwn_asm_hook) do |request, pi|
|
189
|
+
if pi.config.pwn_asm && !request.chomp.empty?
|
190
|
+
request = pi.input.line_buffer
|
191
|
+
|
192
|
+
# Analyze request to determine if it should be processed as opcodes or asm.
|
193
|
+
straight_hex = /^[a-fA-F0-9\s]+$/
|
194
|
+
hex_esc_strings = /\\x[\da-fA-F]{2}/
|
195
|
+
hex_comma_delim_w_dbl_qt = /"(?:[0-9a-fA-F]{2})",?/
|
196
|
+
hex_comma_delim_w_sng_qt = /'(?:[0-9a-fA-F]{2})',?/
|
197
|
+
hex_byte_array_as_str = /^\[\s*(?:"[0-9a-fA-F]{2}",\s*)*"[0-9a-fA-F]{2}"\s*\]$/
|
198
|
+
|
199
|
+
if request.match?(straight_hex) ||
|
200
|
+
request.match?(hex_esc_strings) ||
|
201
|
+
request.match?(hex_comma_delim_w_dbl_qt) ||
|
202
|
+
request.match?(hex_comma_delim_w_sng_qt) ||
|
203
|
+
request.match?(hex_byte_array_as_str)
|
204
|
+
|
205
|
+
response = PWN::Plugins::Assembly.opcodes_to_asm(
|
206
|
+
opcodes: request,
|
207
|
+
opcodes_always_strings_obj: true
|
208
|
+
)
|
209
|
+
else
|
210
|
+
response = PWN::Plugins::Assembly.asm_to_opcodes(asm: request)
|
211
|
+
end
|
212
|
+
puts "\001\e[31m\002#{response}\001\e[0m\002"
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
Pry.config.hooks.add_hook(:after_read, :pwn_ai_hook) do |request, pi|
|
217
|
+
if pi.config.pwn_ai && !request.chomp.empty?
|
218
|
+
request = pi.input.line_buffer.to_s
|
219
|
+
debug = pi.config.pwn_ai_debug
|
220
|
+
ai_key = pi.config.pwn_ai_key
|
221
|
+
ai_key ||= ''
|
222
|
+
if ai_key.empty?
|
223
|
+
ai_key = PWN::Plugins::AuthenticationHelper.mask_password(
|
224
|
+
prompt: 'OpenAI API Key'
|
225
|
+
)
|
226
|
+
pi.config.pwn_ai_key = ai_key
|
227
|
+
end
|
228
|
+
|
229
|
+
response_history = pi.config.pwn_ai_response_history
|
230
|
+
speak_answer = pi.config.pwn_ai_speak
|
231
|
+
response = PWN::Plugins::OpenAI.chat(
|
232
|
+
token: ai_key,
|
233
|
+
request: request.chomp,
|
234
|
+
temp: 1,
|
235
|
+
response_history: response_history,
|
236
|
+
speak_answer: speak_answer
|
237
|
+
)
|
238
|
+
last_response = response[:choices].last[:content]
|
239
|
+
puts "\n\001\e[32m\002#{last_response}\001\e[0m\002\n\n"
|
240
|
+
|
241
|
+
response_history = {
|
242
|
+
id: response[:id],
|
243
|
+
object: response[:object],
|
244
|
+
model: response[:model],
|
245
|
+
usage: response[:usage]
|
246
|
+
}
|
247
|
+
response_history[:choices] ||= response[:choices]
|
248
|
+
|
249
|
+
if debug
|
250
|
+
puts 'DEBUG: response_history => '
|
251
|
+
pp response_history
|
252
|
+
puts "\nresponse_history[:choices] Length: #{response_history[:choices].length}\n" unless response_history.nil?
|
253
|
+
end
|
254
|
+
pi.config.pwn_ai_response_history = response_history
|
255
|
+
end
|
256
|
+
end
|
257
|
+
rescue StandardError => e
|
258
|
+
raise e
|
259
|
+
end
|
260
|
+
|
261
|
+
# Supported Method Parameters::
|
262
|
+
# PWN::Plugins::REPL.start(
|
263
|
+
# opts: 'required - Hash object passed in via pwn OptParser'
|
264
|
+
# )
|
265
|
+
|
266
|
+
public_class_method def self.start(opts = {})
|
267
|
+
# Monkey Patch Pry, add commands, && hooks
|
268
|
+
PWN::Plugins::MonkeyPatch.pry
|
269
|
+
add_commands
|
270
|
+
add_hooks(opts)
|
271
|
+
|
272
|
+
# Define PS1 Prompt
|
273
|
+
Pry.config.pwn_repl_line = 0
|
274
|
+
Pry.config.prompt_name = :pwn
|
275
|
+
arrow_ps1_proc = refresh_ps1_proc
|
276
|
+
splat_ps1_proc = refresh_ps1_proc(mode: :splat)
|
277
|
+
ps1 = [arrow_ps1_proc, splat_ps1_proc]
|
278
|
+
prompt = Pry::Prompt.new(:pwn, 'PWN Prototyping REPL', ps1)
|
279
|
+
|
280
|
+
# Start PWN REPL
|
281
|
+
Pry.start(self, prompt: prompt)
|
282
|
+
rescue StandardError => e
|
283
|
+
raise e
|
284
|
+
end
|
285
|
+
|
286
|
+
# Author(s):: 0day Inc. <request.pentest@0dayinc.com>
|
287
|
+
|
288
|
+
public_class_method def self.authors
|
289
|
+
"AUTHOR(S):
|
290
|
+
0day Inc. <request.pentest@0dayinc.com>
|
291
|
+
"
|
292
|
+
end
|
293
|
+
|
294
|
+
# Display Usage for this Module
|
295
|
+
|
296
|
+
public_class_method def self.help
|
297
|
+
puts "USAGE:
|
298
|
+
#{self}.refresh_ps1_proc(
|
299
|
+
mode: 'required - :splat or nil'
|
300
|
+
)
|
301
|
+
|
302
|
+
#{self}.add_commands
|
303
|
+
|
304
|
+
#{self}.add_hooks(
|
305
|
+
opts: 'required - Hash object passed in via pwn OptParser'
|
306
|
+
)
|
307
|
+
|
308
|
+
#{self}.start(
|
309
|
+
opts: 'required - Hash object passed in via pwn OptParser'
|
310
|
+
)
|
311
|
+
|
312
|
+
#{self}.authors
|
313
|
+
"
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
data/lib/pwn/plugins/vault.rb
CHANGED
@@ -72,6 +72,9 @@ module PWN
|
|
72
72
|
|
73
73
|
raise 'ERROR: key and iv parameters are required.' if key.nil? || iv.nil?
|
74
74
|
|
75
|
+
is_encrypted = file_encrypted?(file: file)
|
76
|
+
raise 'ERROR: File is not encrypted.' unless is_encrypted
|
77
|
+
|
75
78
|
cipher = OpenSSL::Cipher.new('aes-256-cbc')
|
76
79
|
cipher.decrypt
|
77
80
|
cipher.key = Base64.strict_decode64(key)
|
@@ -195,6 +198,8 @@ module PWN
|
|
195
198
|
|
196
199
|
file_contents = File.read(file)
|
197
200
|
file_contents.is_a?(String) && Base64.strict_encode64(Base64.strict_decode64(file_contents)) == file_contents
|
201
|
+
rescue ArgumentError
|
202
|
+
false
|
198
203
|
rescue StandardError => e
|
199
204
|
raise e
|
200
205
|
end
|
data/lib/pwn/plugins.rb
CHANGED
@@ -37,6 +37,7 @@ module PWN
|
|
37
37
|
autoload :Log, 'pwn/plugins/log'
|
38
38
|
autoload :MailAgent, 'pwn/plugins/mail_agent'
|
39
39
|
autoload :Metasploit, 'pwn/plugins/metasploit'
|
40
|
+
autoload :MonkeyPatch, 'pwn/plugins/monkey_patch'
|
40
41
|
autoload :MSR206, 'pwn/plugins/msr206'
|
41
42
|
autoload :NessusCloud, 'pwn/plugins/nessus_cloud'
|
42
43
|
autoload :NexposeVulnScan, 'pwn/plugins/nexpose_vuln_scan'
|
@@ -52,6 +53,7 @@ module PWN
|
|
52
53
|
autoload :Pony, 'pwn/plugins/pony'
|
53
54
|
autoload :PS, 'pwn/plugins/ps'
|
54
55
|
autoload :RabbitMQ, 'pwn/plugins/rabbit_mq'
|
56
|
+
autoload :REPL, 'pwn/plugins/repl'
|
55
57
|
autoload :RFIDler, 'pwn/plugins/rfidler'
|
56
58
|
autoload :ScannableCodes, 'pwn/plugins/scannable_codes'
|
57
59
|
autoload :Serial, 'pwn/plugins/serial'
|
data/lib/pwn/version.rb
CHANGED
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe PWN::Plugins::MonkeyPatch do
|
6
|
+
it 'should display information for authors' do
|
7
|
+
authors_response = PWN::Plugins::MonkeyPatch
|
8
|
+
expect(authors_response).to respond_to :authors
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should display information for existing help method' do
|
12
|
+
help_response = PWN::Plugins::MonkeyPatch
|
13
|
+
expect(help_response).to respond_to :help
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe PWN::Plugins::REPL do
|
6
|
+
it 'should display information for authors' do
|
7
|
+
authors_response = PWN::Plugins::REPL
|
8
|
+
expect(authors_response).to respond_to :authors
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should display information for existing help method' do
|
12
|
+
help_response = PWN::Plugins::REPL
|
13
|
+
expect(help_response).to respond_to :help
|
14
|
+
end
|
15
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pwn
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.69
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- 0day Inc.
|
@@ -534,14 +534,14 @@ dependencies:
|
|
534
534
|
requirements:
|
535
535
|
- - '='
|
536
536
|
- !ruby/object:Gem::Version
|
537
|
-
version: 0.
|
537
|
+
version: 0.5.0
|
538
538
|
type: :runtime
|
539
539
|
prerelease: false
|
540
540
|
version_requirements: !ruby/object:Gem::Requirement
|
541
541
|
requirements:
|
542
542
|
- - '='
|
543
543
|
- !ruby/object:Gem::Version
|
544
|
-
version: 0.
|
544
|
+
version: 0.5.0
|
545
545
|
- !ruby/object:Gem::Dependency
|
546
546
|
name: nexpose
|
547
547
|
requirement: !ruby/object:Gem::Requirement
|
@@ -1767,6 +1767,7 @@ files:
|
|
1767
1767
|
- lib/pwn/plugins/log.rb
|
1768
1768
|
- lib/pwn/plugins/mail_agent.rb
|
1769
1769
|
- lib/pwn/plugins/metasploit.rb
|
1770
|
+
- lib/pwn/plugins/monkey_patch.rb
|
1770
1771
|
- lib/pwn/plugins/msr206.rb
|
1771
1772
|
- lib/pwn/plugins/nessus_cloud.rb
|
1772
1773
|
- lib/pwn/plugins/nexpose_vuln_scan.rb
|
@@ -1783,6 +1784,7 @@ files:
|
|
1783
1784
|
- lib/pwn/plugins/ps.rb
|
1784
1785
|
- lib/pwn/plugins/pwn_logger.rb
|
1785
1786
|
- lib/pwn/plugins/rabbit_mq.rb
|
1787
|
+
- lib/pwn/plugins/repl.rb
|
1786
1788
|
- lib/pwn/plugins/rfidler.rb
|
1787
1789
|
- lib/pwn/plugins/scannable_codes.rb
|
1788
1790
|
- lib/pwn/plugins/serial.rb
|
@@ -2094,6 +2096,7 @@ files:
|
|
2094
2096
|
- spec/lib/pwn/plugins/log_spec.rb
|
2095
2097
|
- spec/lib/pwn/plugins/mail_agent_spec.rb
|
2096
2098
|
- spec/lib/pwn/plugins/metasploit_spec.rb
|
2099
|
+
- spec/lib/pwn/plugins/monkey_patch_spec.rb
|
2097
2100
|
- spec/lib/pwn/plugins/msr206_spec.rb
|
2098
2101
|
- spec/lib/pwn/plugins/nessus_cloud_spec.rb
|
2099
2102
|
- spec/lib/pwn/plugins/nexpose_vuln_scan_spec.rb
|
@@ -2110,6 +2113,7 @@ files:
|
|
2110
2113
|
- spec/lib/pwn/plugins/ps_spec.rb
|
2111
2114
|
- spec/lib/pwn/plugins/pwn_logger_spec.rb
|
2112
2115
|
- spec/lib/pwn/plugins/rabbit_mq_spec.rb
|
2116
|
+
- spec/lib/pwn/plugins/repl_spec.rb
|
2113
2117
|
- spec/lib/pwn/plugins/rfidler_spec.rb
|
2114
2118
|
- spec/lib/pwn/plugins/scannable_codes_spec.rb
|
2115
2119
|
- spec/lib/pwn/plugins/serial_spec.rb
|