pwn 0.5.710 → 0.5.712
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/documentation/Installation.md +8 -4
- data/etc/default_skills/pwn/plugins/detect_os/SKILL.md +3 -1
- data/etc/default_skills/pwn/setup/SKILL.md +7 -2
- data/etc/default_skills/pwn/setup/references/urls.md +3 -0
- data/lib/pwn/plugins/credential_attack.rb +1 -1
- data/lib/pwn/plugins/detect_os.rb +224 -15
- data/lib/pwn/plugins/preflight_checker.rb +5 -1
- data/lib/pwn/plugins/recon.rb +1 -1
- data/lib/pwn/setup.rb +195 -16
- data/lib/pwn/version.rb +1 -1
- data/spec/documentation/installation_md_spec.rb +1 -1
- data/spec/lib/pwn/plugins/detect_os_spec.rb +54 -4
- data/spec/lib/pwn/setup_spec.rb +90 -0
- data/third_party/pwn_rdoc.jsonl +19 -4
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 24bf2b4c619ee3d57fe991bff09ab97838506283144fbcde635d39c7e368a3b1
|
|
4
|
+
data.tar.gz: 1ed8d4f63cfc8331fd494172e65724037131a378cd42716df2d36e22dfdce6d5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d8ce0b58ac26c52adacedd0ae58df169357d87d53f1e5040d9c6eba4083bf83545065c8e504a6f2e7baebb84409b807af99fa6ef2cfd148682e898962794b67e
|
|
7
|
+
data.tar.gz: 712a254b7346e7eb3668634c1b47bf336dce6393e960ad1ed24e0d0abc812752ed1cd442f277c44bd6b1c3360472b8d44075418758909fd7bbf435ea2d8ebe2f
|
|
@@ -103,10 +103,14 @@ pwn setup --list-profiles
|
|
|
103
103
|
`--deps` / `--profile` will:
|
|
104
104
|
|
|
105
105
|
1. Resolve the profile → set of native gems + external binaries.
|
|
106
|
-
2. Map those to OS packages for **
|
|
107
|
-
`PWN::
|
|
108
|
-
`
|
|
109
|
-
|
|
106
|
+
2. Map those to OS packages for **this** distro and version
|
|
107
|
+
(`PWN::Plugins::DetectOS.distro` / `.version`, e.g. Kali `2026.3` vs Ubuntu
|
|
108
|
+
`24.04`) via `PWN::Setup.packages_for`. Family fallbacks still live in
|
|
109
|
+
`PWN::Setup::NATIVE_GEMS` / `::TOOLCHAIN`. Tools with no OS package on this
|
|
110
|
+
distro (Frida, one_gadget, ROPgadget, checksec, ropper, kube-hunter, or
|
|
111
|
+
Kali-only names on Ubuntu) install via `pipx` (Debian/Kali/Ubuntu) or `pip`
|
|
112
|
+
/ `gem`, or are skipped so one missing apt name cannot abort the whole
|
|
113
|
+
profile. Each OS package is installed in its own `apt-get`/`dnf`/… command.
|
|
110
114
|
3. Show the exact commands, prompt (unless `--yes`), run them, then
|
|
111
115
|
`gem pristine` / `gem install` any native extension that still fails to
|
|
112
116
|
load, and re-run the doctor.
|
|
@@ -12,7 +12,7 @@ metadata:
|
|
|
12
12
|
|
|
13
13
|
# PWN::Plugins::DetectOS
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
Detect host OS family, distro flavor, and version string.
|
|
16
16
|
|
|
17
17
|
## When to use
|
|
18
18
|
|
|
@@ -36,6 +36,8 @@ PWN::Plugins::DetectOS.type(opts)
|
|
|
36
36
|
- `type`
|
|
37
37
|
- `arch`
|
|
38
38
|
- `endian`
|
|
39
|
+
- `distro`
|
|
40
|
+
- `version`
|
|
39
41
|
- `authors`
|
|
40
42
|
- `help`
|
|
41
43
|
|
|
@@ -28,11 +28,12 @@ Class methods take `(opts = {})` and read `opts`.
|
|
|
28
28
|
|
|
29
29
|
```ruby
|
|
30
30
|
PWN::Setup.help
|
|
31
|
-
PWN::Setup.
|
|
31
|
+
PWN::Setup.packages_for(opts)
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
## Public methods
|
|
35
35
|
|
|
36
|
+
- `packages_for`
|
|
36
37
|
- `pkg_manager`
|
|
37
38
|
- `check`
|
|
38
39
|
- `deps`
|
|
@@ -43,11 +44,15 @@ PWN::Setup.pkg_manager(opts)
|
|
|
43
44
|
- `authors`
|
|
44
45
|
- `help`
|
|
45
46
|
|
|
47
|
+
## References
|
|
48
|
+
|
|
49
|
+
- `references/urls.md` — URLs from source
|
|
50
|
+
|
|
46
51
|
## Source
|
|
47
52
|
|
|
48
53
|
`pwn/setup.rb`
|
|
49
54
|
|
|
50
55
|
## Verification
|
|
51
56
|
|
|
52
|
-
`PWN::Setup.respond_to?(:
|
|
57
|
+
`PWN::Setup.respond_to?(:packages_for)` after the
|
|
53
58
|
module is loaded. Read the source for parameter names.
|
|
@@ -4,10 +4,28 @@ require 'os'
|
|
|
4
4
|
|
|
5
5
|
module PWN
|
|
6
6
|
module Plugins
|
|
7
|
-
#
|
|
7
|
+
# Detect host OS family, distro flavor, and version string.
|
|
8
8
|
module DetectOS
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
ID_ALIASES = {
|
|
10
|
+
'chromiumos' => :chromeos,
|
|
11
|
+
'chromeos' => :chromeos,
|
|
12
|
+
'pop_os' => :pop,
|
|
13
|
+
'pop' => :pop,
|
|
14
|
+
'ol' => :oracle,
|
|
15
|
+
'opensuse-leap' => :opensuse,
|
|
16
|
+
'opensuse-tumbleweed' => :opensuse,
|
|
17
|
+
'sles' => :suse,
|
|
18
|
+
'darwin' => :macos,
|
|
19
|
+
'osx' => :macos,
|
|
20
|
+
'macos' => :macos,
|
|
21
|
+
'iphoneos' => :ios,
|
|
22
|
+
'ipados' => :ios,
|
|
23
|
+
'ios' => :ios,
|
|
24
|
+
'win32' => :windows,
|
|
25
|
+
'mingw32' => :windows,
|
|
26
|
+
'mingw' => :windows,
|
|
27
|
+
'mswin' => :windows
|
|
28
|
+
}.freeze
|
|
11
29
|
|
|
12
30
|
public_class_method def self.type
|
|
13
31
|
os = :cygwin if OS.cygwin?
|
|
@@ -23,18 +41,12 @@ module PWN
|
|
|
23
41
|
raise e
|
|
24
42
|
end
|
|
25
43
|
|
|
26
|
-
# Supported Method Parameters::
|
|
27
|
-
# PWN::Plugins::DetectOS.arch
|
|
28
|
-
|
|
29
44
|
public_class_method def self.arch
|
|
30
45
|
OS.host_cpu
|
|
31
46
|
rescue StandardError => e
|
|
32
47
|
raise e
|
|
33
48
|
end
|
|
34
49
|
|
|
35
|
-
# Supported Method Parameters::
|
|
36
|
-
# PWN::Plugins::DetectOS.endian
|
|
37
|
-
|
|
38
50
|
public_class_method def self.endian
|
|
39
51
|
if [1].pack('I') == [1].pack('N')
|
|
40
52
|
:big
|
|
@@ -45,7 +57,99 @@ module PWN
|
|
|
45
57
|
raise e
|
|
46
58
|
end
|
|
47
59
|
|
|
48
|
-
#
|
|
60
|
+
# Supported Method Parameters::
|
|
61
|
+
# PWN::Plugins::DetectOS.distro(
|
|
62
|
+
# os_release: 'optional - /etc/os-release body (defaults to reading the file)',
|
|
63
|
+
# lsb_release: 'optional - /etc/lsb-release body',
|
|
64
|
+
# build_prop: 'optional - Android /system/build.prop body',
|
|
65
|
+
# type: 'optional - OS family from #type (e.g. :linux :osx :windows :freebsd)',
|
|
66
|
+
# platform: 'optional - RUBY_PLATFORM override',
|
|
67
|
+
# uname: 'optional - uname -s string',
|
|
68
|
+
# sw_vers: 'optional - macOS/iOS product version string',
|
|
69
|
+
# win_ver: 'optional - Windows ver command output'
|
|
70
|
+
# )
|
|
71
|
+
|
|
72
|
+
public_class_method def self.distro(opts = {})
|
|
73
|
+
return normalize_id(id: opts[:distro]) if opts[:distro]
|
|
74
|
+
|
|
75
|
+
platform = (opts[:platform] || RUBY_PLATFORM).to_s.downcase
|
|
76
|
+
uname = (opts[:uname] || live_uname_s).to_s.downcase
|
|
77
|
+
os_type = opts[:type] || type
|
|
78
|
+
osr = parse_kv(content: opts[:os_release] || read_if_present(path: '/etc/os-release') || read_if_present(path: '/usr/lib/os-release'))
|
|
79
|
+
lsb = parse_kv(content: opts[:lsb_release] || read_if_present(path: '/etc/lsb-release'))
|
|
80
|
+
prop = parse_kv(content: opts[:build_prop] || read_if_present(path: '/system/build.prop'))
|
|
81
|
+
|
|
82
|
+
return :ios if ios_host?(platform: platform, uname: uname)
|
|
83
|
+
return :android if android_host?(osr: osr, prop: prop, uname: uname)
|
|
84
|
+
return :chromeos if chromeos_host?(osr: osr, lsb: lsb)
|
|
85
|
+
return :macos if os_type == :osx || platform.include?('darwin')
|
|
86
|
+
return :windows if os_type == :windows || platform.match?(/mswin|mingw|cygwin/)
|
|
87
|
+
return :freebsd if os_type == :freebsd || uname.include?('freebsd')
|
|
88
|
+
return :openbsd if os_type == :openbsd || uname.include?('openbsd')
|
|
89
|
+
return :netbsd if os_type == :netbsd || uname.include?('netbsd')
|
|
90
|
+
return :dragonfly if uname.include?('dragonfly')
|
|
91
|
+
return :cygwin if os_type == :cygwin
|
|
92
|
+
|
|
93
|
+
id = osr['ID'].to_s
|
|
94
|
+
return normalize_id(id: id) unless id.empty?
|
|
95
|
+
|
|
96
|
+
like = osr['ID_LIKE'].to_s.downcase
|
|
97
|
+
return :debian if like.include?('debian')
|
|
98
|
+
return :fedora if like.include?('fedora') || like.include?('rhel')
|
|
99
|
+
return :arch if like.include?('arch')
|
|
100
|
+
return :suse if like.include?('suse')
|
|
101
|
+
|
|
102
|
+
os_type || :unknown
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Supported Method Parameters::
|
|
106
|
+
# PWN::Plugins::DetectOS.version(
|
|
107
|
+
# os_release: 'optional - /etc/os-release body',
|
|
108
|
+
# lsb_release: 'optional - /etc/lsb-release body',
|
|
109
|
+
# build_prop: 'optional - Android build.prop body',
|
|
110
|
+
# sw_vers: 'optional - macOS/iOS product version',
|
|
111
|
+
# win_ver: 'optional - Windows ver output',
|
|
112
|
+
# uname_r: 'optional - uname -r string',
|
|
113
|
+
# type: 'optional - OS family from #type'
|
|
114
|
+
# )
|
|
115
|
+
|
|
116
|
+
public_class_method def self.version(opts = {})
|
|
117
|
+
return opts[:version].to_s if opts[:version]
|
|
118
|
+
|
|
119
|
+
injected = %i[os_release lsb_release build_prop sw_vers win_ver uname_r].any? { |k| opts.key?(k) }
|
|
120
|
+
os_type = opts[:type] || type
|
|
121
|
+
osr = parse_kv(content: opts[:os_release] || (injected ? nil : read_if_present(path: '/etc/os-release') || read_if_present(path: '/usr/lib/os-release')))
|
|
122
|
+
lsb = parse_kv(content: opts[:lsb_release] || (injected ? nil : read_if_present(path: '/etc/lsb-release')))
|
|
123
|
+
prop = parse_kv(content: opts[:build_prop] || (injected ? nil : read_if_present(path: '/system/build.prop')))
|
|
124
|
+
|
|
125
|
+
from_osr = osr['VERSION_ID'].to_s
|
|
126
|
+
return from_osr unless from_osr.empty?
|
|
127
|
+
|
|
128
|
+
chrome = lsb['CHROMEOS_RELEASE_VERSION'].to_s
|
|
129
|
+
return chrome unless chrome.empty?
|
|
130
|
+
|
|
131
|
+
android = prop['ro.build.version.release'].to_s
|
|
132
|
+
return android unless android.empty?
|
|
133
|
+
|
|
134
|
+
if os_type == :osx || opts[:sw_vers]
|
|
135
|
+
mac = (opts[:sw_vers] || live_sw_vers).to_s.strip
|
|
136
|
+
return mac unless mac.empty?
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
if os_type == :windows || opts[:win_ver]
|
|
140
|
+
win = (opts[:win_ver] || live_win_ver).to_s
|
|
141
|
+
m = win.match(/\[Version\s+([^\]]+)\]/i) || win.match(/(\d+\.\d+(?:\.\d+)*)/)
|
|
142
|
+
return m[1] if m
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
rel = (opts[:uname_r] || live_uname_r).to_s.strip
|
|
146
|
+
return rel if %i[freebsd openbsd netbsd].include?(os_type) && !rel.empty?
|
|
147
|
+
|
|
148
|
+
debian = read_if_present(path: '/etc/debian_version').to_s.strip
|
|
149
|
+
return debian unless debian.empty?
|
|
150
|
+
|
|
151
|
+
osr['VERSION'].to_s
|
|
152
|
+
end
|
|
49
153
|
|
|
50
154
|
public_class_method def self.authors
|
|
51
155
|
"AUTHOR(S):
|
|
@@ -53,24 +157,129 @@ module PWN
|
|
|
53
157
|
"
|
|
54
158
|
end
|
|
55
159
|
|
|
56
|
-
# Display Usage for this Module
|
|
57
|
-
|
|
58
160
|
public_class_method def self.help
|
|
59
161
|
puts "USAGE:
|
|
60
|
-
#
|
|
162
|
+
# Return the OS family as a symbol (:linux :osx :windows :freebsd :openbsd :netbsd :cygwin).
|
|
61
163
|
#{self}.type
|
|
62
164
|
|
|
63
|
-
#
|
|
165
|
+
# Return the CPU architecture string from the host (e.g. x86_64, arm64).
|
|
64
166
|
#{self}.arch
|
|
65
167
|
|
|
66
|
-
#
|
|
168
|
+
# Return CPU endianness as :little or :big.
|
|
67
169
|
#{self}.endian
|
|
68
170
|
|
|
171
|
+
# Return the distro flavor as a lowercase symbol (:kali :ubuntu :macos :windows :freebsd …).
|
|
172
|
+
#{self}.distro(
|
|
173
|
+
os_release: 'optional - /etc/os-release body (defaults to reading the file)',
|
|
174
|
+
lsb_release: 'optional - /etc/lsb-release body',
|
|
175
|
+
build_prop: 'optional - Android /system/build.prop body',
|
|
176
|
+
type: 'optional - OS family from #type (e.g. :linux :osx :windows :freebsd)',
|
|
177
|
+
platform: 'optional - RUBY_PLATFORM override (detect iOS/Android/Windows)',
|
|
178
|
+
uname: 'optional - uname -s string',
|
|
179
|
+
sw_vers: 'optional - macOS/iOS product version string',
|
|
180
|
+
win_ver: 'optional - Windows ver command output',
|
|
181
|
+
distro: 'optional - force a distro id instead of detecting'
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
# Return the distro version as a string (e.g. 2026.3, 24.04, 15.1, 14.1-RELEASE).
|
|
185
|
+
#{self}.version(
|
|
186
|
+
os_release: 'optional - /etc/os-release body',
|
|
187
|
+
lsb_release: 'optional - /etc/lsb-release body',
|
|
188
|
+
build_prop: 'optional - Android build.prop body',
|
|
189
|
+
sw_vers: 'optional - macOS/iOS product version',
|
|
190
|
+
win_ver: 'optional - Windows ver output',
|
|
191
|
+
uname_r: 'optional - uname -r string',
|
|
192
|
+
type: 'optional - OS family from #type',
|
|
193
|
+
version: 'optional - force a version string instead of detecting'
|
|
194
|
+
)
|
|
195
|
+
|
|
69
196
|
# Print the AUTHOR(S) string for this module.
|
|
70
197
|
#{self}.authors
|
|
71
198
|
"
|
|
72
199
|
constants.sort
|
|
73
200
|
end
|
|
201
|
+
|
|
202
|
+
private_class_method def self.normalize_id(opts = {})
|
|
203
|
+
raw = opts[:id].to_s.downcase.strip.gsub(/["']/, '')
|
|
204
|
+
return :unknown if raw.empty?
|
|
205
|
+
|
|
206
|
+
ID_ALIASES[raw] || raw.tr('-', '_').to_sym
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
private_class_method def self.parse_kv(opts = {})
|
|
210
|
+
content = opts[:content].to_s
|
|
211
|
+
return {} if content.empty?
|
|
212
|
+
|
|
213
|
+
content.each_line.with_object({}) do |line, acc|
|
|
214
|
+
next if line.strip.empty? || line.strip.start_with?('#')
|
|
215
|
+
|
|
216
|
+
key, val = line.split('=', 2)
|
|
217
|
+
next unless val
|
|
218
|
+
|
|
219
|
+
acc[key.strip] = val.strip.gsub(/\A["']|["']\z/, '')
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
private_class_method def self.read_if_present(opts = {})
|
|
224
|
+
path = opts[:path].to_s
|
|
225
|
+
return unless File.readable?(path)
|
|
226
|
+
|
|
227
|
+
File.read(path)
|
|
228
|
+
rescue StandardError
|
|
229
|
+
nil
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
private_class_method def self.ios_host?(opts = {})
|
|
233
|
+
blob = "#{opts[:platform]} #{opts[:uname]}"
|
|
234
|
+
blob.match?(/iphone|ipad|ios/)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
private_class_method def self.android_host?(opts = {})
|
|
238
|
+
osr = opts[:osr] || {}
|
|
239
|
+
prop = opts[:prop] || {}
|
|
240
|
+
uname = opts[:uname].to_s
|
|
241
|
+
osr['ID'].to_s.downcase == 'android' ||
|
|
242
|
+
!prop['ro.build.version.release'].to_s.empty? ||
|
|
243
|
+
uname.include?('android') ||
|
|
244
|
+
File.exist?('/system/build.prop')
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
private_class_method def self.chromeos_host?(opts = {})
|
|
248
|
+
osr = opts[:osr] || {}
|
|
249
|
+
lsb = opts[:lsb] || {}
|
|
250
|
+
id = osr['ID'].to_s.downcase
|
|
251
|
+
id == 'chromeos' || id == 'chromiumos' ||
|
|
252
|
+
!lsb['CHROMEOS_RELEASE_NAME'].to_s.empty? ||
|
|
253
|
+
!lsb['CHROMEOS_RELEASE_VERSION'].to_s.empty?
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
private_class_method def self.live_uname_s(opts = {})
|
|
257
|
+
opts[:skip]
|
|
258
|
+
`uname -s 2>/dev/null`.to_s
|
|
259
|
+
rescue StandardError
|
|
260
|
+
''
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
private_class_method def self.live_uname_r(opts = {})
|
|
264
|
+
opts[:skip]
|
|
265
|
+
`uname -r 2>/dev/null`.to_s
|
|
266
|
+
rescue StandardError
|
|
267
|
+
''
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
private_class_method def self.live_sw_vers(opts = {})
|
|
271
|
+
opts[:skip]
|
|
272
|
+
`sw_vers -productVersion 2>/dev/null`.to_s
|
|
273
|
+
rescue StandardError
|
|
274
|
+
''
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
private_class_method def self.live_win_ver(opts = {})
|
|
278
|
+
opts[:skip]
|
|
279
|
+
`ver 2>NUL`.to_s
|
|
280
|
+
rescue StandardError
|
|
281
|
+
''
|
|
282
|
+
end
|
|
74
283
|
end
|
|
75
284
|
end
|
|
76
285
|
end
|
|
@@ -23,7 +23,11 @@ module PWN
|
|
|
23
23
|
'PWN::Plugins::BurpSuite' => { bins: %w[burpsuite] },
|
|
24
24
|
'PWN::Plugins::Zaproxy' => { bins: %w[zaproxy] },
|
|
25
25
|
'PWN::Plugins::Packet' => { bins: [], caps: %w[CAP_NET_RAW] },
|
|
26
|
-
'PWN::Plugins::K8s' => { bins: %w[trivy], services: [{ name: 'docker', path: '/var/run/docker.sock' }] }
|
|
26
|
+
'PWN::Plugins::K8s' => { bins: %w[trivy], services: [{ name: 'docker', path: '/var/run/docker.sock' }] },
|
|
27
|
+
'PWN::Plugins::Semgrep' => { bins: %w[semgrep] },
|
|
28
|
+
'PWN::Plugins::ExploitDB' => { bins: %w[searchsploit] },
|
|
29
|
+
'PWN::Plugins::Recon' => { bins: %w[subfinder httpx] },
|
|
30
|
+
'PWN::Plugins::CredentialAttack' => { bins: %w[hydra john hashcat] }
|
|
27
31
|
}.freeze
|
|
28
32
|
|
|
29
33
|
public_class_method def self.required_bins
|
data/lib/pwn/plugins/recon.rb
CHANGED
data/lib/pwn/setup.rb
CHANGED
|
@@ -160,7 +160,7 @@ module PWN
|
|
|
160
160
|
},
|
|
161
161
|
'sqlmap' => {
|
|
162
162
|
apt: %w[sqlmap], dnf: %w[sqlmap], pacman: %w[sqlmap], brew: %w[sqlmap], port: %w[],
|
|
163
|
-
plugins: %w[PWN::Plugins::Fuzz]
|
|
163
|
+
plugins: %w[PWN::Plugins::Sqlmap PWN::Plugins::Fuzz]
|
|
164
164
|
},
|
|
165
165
|
'tor' => {
|
|
166
166
|
apt: %w[tor], dnf: %w[tor], pacman: %w[tor], brew: %w[tor], port: %w[tor],
|
|
@@ -246,23 +246,28 @@ module PWN
|
|
|
246
246
|
plugins: %w[PWN::Plugins::Radare2]
|
|
247
247
|
},
|
|
248
248
|
'checksec' => {
|
|
249
|
-
apt: %w[], dnf: %w[], pacman: %w[], brew: %w[], port: %w[],
|
|
249
|
+
apt: %w[], dnf: %w[], pacman: %w[checksec], brew: %w[], port: %w[],
|
|
250
|
+
pip: 'checksec.py',
|
|
250
251
|
plugins: %w[PWN::Plugins::GDB PWN::Plugins::ExploitDev]
|
|
251
252
|
},
|
|
252
253
|
'ROPgadget' => {
|
|
253
254
|
apt: %w[], dnf: %w[], pacman: %w[], brew: %w[], port: %w[],
|
|
255
|
+
pip: 'ROPGadget',
|
|
254
256
|
plugins: %w[PWN::Plugins::ExploitDev]
|
|
255
257
|
},
|
|
256
258
|
'one_gadget' => {
|
|
257
259
|
apt: %w[], dnf: %w[], pacman: %w[], brew: %w[], port: %w[],
|
|
260
|
+
gem: 'one_gadget',
|
|
258
261
|
plugins: %w[PWN::Plugins::ExploitDev]
|
|
259
262
|
},
|
|
260
263
|
'pwndbg' => {
|
|
261
264
|
apt: %w[pwndbg], dnf: %w[], pacman: %w[pwndbg], brew: %w[], port: %w[],
|
|
265
|
+
script: "curl --proto '=https' --tlsv1.2 -LsSf 'https://install.pwndbg.re' | sh -s -- -t pwndbg-gdb",
|
|
262
266
|
plugins: %w[PWN::Plugins::GDB]
|
|
263
267
|
},
|
|
264
268
|
'ropper' => {
|
|
265
269
|
apt: %w[], dnf: %w[], pacman: %w[], brew: %w[], port: %w[],
|
|
270
|
+
pip: 'ropper',
|
|
266
271
|
plugins: %w[PWN::Plugins::ExploitDev]
|
|
267
272
|
},
|
|
268
273
|
'rizin' => {
|
|
@@ -280,6 +285,7 @@ module PWN
|
|
|
280
285
|
},
|
|
281
286
|
'frida' => {
|
|
282
287
|
apt: %w[], dnf: %w[], pacman: %w[], brew: %w[], port: %w[],
|
|
288
|
+
pip: 'frida-tools',
|
|
283
289
|
plugins: %w[PWN::Plugins::Frida]
|
|
284
290
|
},
|
|
285
291
|
'apktool' => {
|
|
@@ -297,8 +303,90 @@ module PWN
|
|
|
297
303
|
'hydra' => {
|
|
298
304
|
apt: %w[hydra], dnf: %w[hydra], pacman: %w[hydra], brew: %w[thc-hydra], port: %w[],
|
|
299
305
|
plugins: %w[PWN::Plugins::CredentialAttack]
|
|
306
|
+
},
|
|
307
|
+
'afl-fuzz' => {
|
|
308
|
+
apt: %w[afl++], dnf: %w[afl++], pacman: %w[afl++], brew: %w[afl++], port: %w[],
|
|
309
|
+
plugins: %w[PWN::Plugins::AFLplusplus]
|
|
310
|
+
},
|
|
311
|
+
'searchsploit' => {
|
|
312
|
+
apt: %w[exploitdb], dnf: %w[], pacman: %w[exploitdb], brew: %w[], port: %w[],
|
|
313
|
+
plugins: %w[PWN::Plugins::ExploitDB]
|
|
314
|
+
},
|
|
315
|
+
'trivy' => {
|
|
316
|
+
apt: %w[trivy], dnf: %w[trivy], pacman: %w[trivy], brew: %w[trivy], port: %w[],
|
|
317
|
+
plugins: %w[PWN::Plugins::K8s]
|
|
318
|
+
},
|
|
319
|
+
'kube-hunter' => {
|
|
320
|
+
apt: %w[], dnf: %w[], pacman: %w[], brew: %w[], port: %w[],
|
|
321
|
+
pip: 'kube-hunter',
|
|
322
|
+
plugins: %w[PWN::Plugins::K8s]
|
|
323
|
+
},
|
|
324
|
+
'semgrep' => {
|
|
325
|
+
apt: %w[semgrep], dnf: %w[], pacman: %w[], brew: %w[semgrep], port: %w[],
|
|
326
|
+
pip: 'semgrep',
|
|
327
|
+
plugins: %w[PWN::Plugins::Semgrep]
|
|
328
|
+
},
|
|
329
|
+
'vol' => {
|
|
330
|
+
apt: %w[volatility3], dnf: %w[], pacman: %w[volatility3], brew: %w[], port: %w[],
|
|
331
|
+
pip: 'volatility3',
|
|
332
|
+
plugins: %w[PWN::Plugins::Volatility]
|
|
333
|
+
},
|
|
334
|
+
'subfinder' => {
|
|
335
|
+
apt: %w[subfinder], dnf: %w[], pacman: %w[], brew: %w[], port: %w[],
|
|
336
|
+
plugins: %w[PWN::Plugins::Recon]
|
|
337
|
+
},
|
|
338
|
+
'httpx' => {
|
|
339
|
+
apt: %w[httpx-toolkit], dnf: %w[], pacman: %w[], brew: %w[], port: %w[],
|
|
340
|
+
plugins: %w[PWN::Plugins::Recon]
|
|
341
|
+
},
|
|
342
|
+
'masscan' => {
|
|
343
|
+
apt: %w[masscan], dnf: %w[masscan], pacman: %w[masscan], brew: %w[masscan], port: %w[],
|
|
344
|
+
plugins: %w[PWN::Plugins::Recon]
|
|
345
|
+
},
|
|
346
|
+
'naabu' => {
|
|
347
|
+
apt: %w[naabu], dnf: %w[], pacman: %w[], brew: %w[], port: %w[],
|
|
348
|
+
plugins: %w[PWN::Plugins::Recon]
|
|
349
|
+
},
|
|
350
|
+
'amass' => {
|
|
351
|
+
apt: %w[amass], dnf: %w[], pacman: %w[], brew: %w[amass], port: %w[],
|
|
352
|
+
plugins: %w[PWN::Plugins::Recon]
|
|
353
|
+
},
|
|
354
|
+
'john' => {
|
|
355
|
+
apt: %w[john], dnf: %w[john], pacman: %w[john], brew: %w[john], port: %w[john],
|
|
356
|
+
plugins: %w[PWN::Plugins::CredentialAttack]
|
|
357
|
+
},
|
|
358
|
+
'hashcat' => {
|
|
359
|
+
apt: %w[hashcat], dnf: %w[hashcat], pacman: %w[hashcat], brew: %w[hashcat], port: %w[],
|
|
360
|
+
plugins: %w[PWN::Plugins::CredentialAttack]
|
|
361
|
+
},
|
|
362
|
+
'medusa' => {
|
|
363
|
+
apt: %w[medusa], dnf: %w[medusa], pacman: %w[medusa], brew: %w[], port: %w[],
|
|
364
|
+
plugins: %w[PWN::Plugins::CredentialAttack]
|
|
365
|
+
},
|
|
366
|
+
'hashid' => {
|
|
367
|
+
apt: %w[hashid], dnf: %w[], pacman: %w[], brew: %w[], port: %w[],
|
|
368
|
+
pip: 'hashid',
|
|
369
|
+
plugins: %w[PWN::Plugins::CredentialAttack]
|
|
300
370
|
}
|
|
301
|
-
}.
|
|
371
|
+
}.tap do |t|
|
|
372
|
+
%w[burpsuite zaproxy msfconsole nuclei searchsploit httpx subfinder naabu amass apktool jadx rizin pwndbg trivy semgrep vol hashid].each do |bin|
|
|
373
|
+
next unless t[bin]
|
|
374
|
+
|
|
375
|
+
t[bin] = t[bin].merge(ubuntu: [], debian: [])
|
|
376
|
+
end
|
|
377
|
+
{
|
|
378
|
+
'checksec' => %w[checksec],
|
|
379
|
+
'ROPgadget' => %w[python3-ropgadget],
|
|
380
|
+
'ropper' => %w[ropper],
|
|
381
|
+
'pwndbg' => [],
|
|
382
|
+
'semgrep' => [],
|
|
383
|
+
'vol' => []
|
|
384
|
+
}.each do |bin, pkgs|
|
|
385
|
+
next unless t[bin]
|
|
386
|
+
|
|
387
|
+
t[bin] = t[bin].merge(kali: pkgs)
|
|
388
|
+
end
|
|
389
|
+
end.freeze
|
|
302
390
|
|
|
303
391
|
# Capability profiles — how you get the ergonomics of `pwn-full`
|
|
304
392
|
# without shipping a second gem. `pwn setup --profile <name>`.
|
|
@@ -368,6 +456,27 @@ module PWN
|
|
|
368
456
|
OK = "\e[32mok\e[0m"
|
|
369
457
|
MISS = "\e[31mMISSING\e[0m"
|
|
370
458
|
|
|
459
|
+
# Supported Method Parameters::
|
|
460
|
+
# PWN::Setup.packages_for(
|
|
461
|
+
# bin: 'required - TOOLCHAIN key (e.g. nmap or burpsuite)',
|
|
462
|
+
# distro: 'optional - DetectOS.distro symbol (defaults to live detect)',
|
|
463
|
+
# version: 'optional - DetectOS.version string (defaults to live detect)',
|
|
464
|
+
# pm_key: 'optional - package-manager family (:apt :dnf :pacman :brew :port :pkg …)'
|
|
465
|
+
# )
|
|
466
|
+
|
|
467
|
+
public_class_method def self.packages_for(opts = {})
|
|
468
|
+
bin = opts[:bin].to_s
|
|
469
|
+
meta = TOOLCHAIN[bin] || {}
|
|
470
|
+
distro = (opts[:distro] || PWN::Plugins::DetectOS.distro).to_s.to_sym
|
|
471
|
+
version = (opts[:version] || PWN::Plugins::DetectOS.version).to_s
|
|
472
|
+
pm_key = opts[:pm_key] || pkg_manager[:key]
|
|
473
|
+
ver_key = :"#{distro}/#{version}"
|
|
474
|
+
return Array(meta[ver_key]) if meta.key?(ver_key)
|
|
475
|
+
return Array(meta[distro]) if meta.key?(distro)
|
|
476
|
+
|
|
477
|
+
Array(meta[pm_key])
|
|
478
|
+
end
|
|
479
|
+
|
|
371
480
|
# Supported Method Parameters::
|
|
372
481
|
# PWN::Setup.pkg_manager
|
|
373
482
|
|
|
@@ -385,13 +494,39 @@ module PWN
|
|
|
385
494
|
end
|
|
386
495
|
sudo = root || !bin?(name: 'sudo') ? '' : 'sudo '
|
|
387
496
|
|
|
497
|
+
distro = begin
|
|
498
|
+
PWN::Plugins::DetectOS.distro
|
|
499
|
+
rescue StandardError
|
|
500
|
+
nil
|
|
501
|
+
end
|
|
502
|
+
|
|
388
503
|
@pkg_manager =
|
|
389
|
-
if bin?(name: '
|
|
390
|
-
|
|
391
|
-
elsif bin?(name: '
|
|
392
|
-
|
|
393
|
-
elsif bin?(name: '
|
|
394
|
-
|
|
504
|
+
if distro == :freebsd && bin?(name: 'pkg')
|
|
505
|
+
{ key: :pkg, install: "#{sudo}pkg install -y", sudo: !sudo.empty? }
|
|
506
|
+
elsif distro == :openbsd && bin?(name: 'pkg_add')
|
|
507
|
+
{ key: :pkg_add, install: "#{sudo}pkg_add", sudo: !sudo.empty? }
|
|
508
|
+
elsif distro == :netbsd && bin?(name: 'pkgin')
|
|
509
|
+
{ key: :pkgin, install: "#{sudo}pkgin -y install", sudo: !sudo.empty? }
|
|
510
|
+
elsif distro == :alpine && bin?(name: 'apk')
|
|
511
|
+
{ key: :apk, install: "#{sudo}apk add", sudo: !sudo.empty? }
|
|
512
|
+
elsif distro == :android && bin?(name: 'pkg')
|
|
513
|
+
{ key: :pkg, install: 'pkg install -y', sudo: false }
|
|
514
|
+
elsif distro == :windows && bin?(name: 'winget')
|
|
515
|
+
{ key: :winget, install: 'winget install -e --accept-package-agreements --accept-source-agreements', sudo: false }
|
|
516
|
+
elsif distro == :windows && bin?(name: 'choco')
|
|
517
|
+
{ key: :choco, install: 'choco install -y', sudo: false }
|
|
518
|
+
elsif bin?(name: 'apt-get')
|
|
519
|
+
{ key: :apt, install: "#{sudo}apt-get install -y", sudo: !sudo.empty? }
|
|
520
|
+
elsif bin?(name: 'dnf')
|
|
521
|
+
{ key: :dnf, install: "#{sudo}dnf install -y", sudo: !sudo.empty? }
|
|
522
|
+
elsif bin?(name: 'pacman')
|
|
523
|
+
{ key: :pacman, install: "#{sudo}pacman -S --noconfirm", sudo: !sudo.empty? }
|
|
524
|
+
elsif bin?(name: 'brew')
|
|
525
|
+
{ key: :brew, install: 'brew install', sudo: false }
|
|
526
|
+
elsif bin?(name: 'port')
|
|
527
|
+
{ key: :port, install: "#{sudo}port -N install", sudo: !sudo.empty? }
|
|
528
|
+
else
|
|
529
|
+
{ key: :unknown, install: nil, sudo: false }
|
|
395
530
|
end
|
|
396
531
|
end
|
|
397
532
|
|
|
@@ -506,12 +641,14 @@ module PWN
|
|
|
506
641
|
profile = (opts[:profile] || :full).to_sym
|
|
507
642
|
yes = opts[:yes] ? true : false
|
|
508
643
|
dry_run = opts[:dry_run] ? true : false
|
|
644
|
+
distro = opts[:distro] || PWN::Plugins::DetectOS.distro
|
|
645
|
+
version = opts[:version] || PWN::Plugins::DetectOS.version
|
|
509
646
|
io = opts[:io] || $stdout
|
|
510
647
|
|
|
511
648
|
raise "Unknown profile '#{profile}'. Known: #{PROFILES.keys.join(', ')}" unless PROFILES.key?(profile)
|
|
512
649
|
|
|
513
650
|
pm = pkg_manager
|
|
514
|
-
raise 'No supported package manager found (apt / dnf / pacman / brew / port).' if pm[:key] == :unknown
|
|
651
|
+
raise 'No supported package manager found (apt / dnf / pacman / brew / port / pkg / winget / choco).' if pm[:key] == :unknown
|
|
515
652
|
|
|
516
653
|
prof = PROFILES[profile]
|
|
517
654
|
gems = Array(prof[:gems])
|
|
@@ -527,22 +664,25 @@ module PWN
|
|
|
527
664
|
bins = Array(prof[:bins])
|
|
528
665
|
os_pkgs = []
|
|
529
666
|
gems.each { |g| os_pkgs.concat(Array(NATIVE_GEMS.dig(g, pm[:key]))) }
|
|
530
|
-
bins.each { |b| os_pkgs.concat(
|
|
667
|
+
bins.each { |b| os_pkgs.concat(packages_for(bin: b, distro: distro, version: version, pm_key: pm[:key])) }
|
|
531
668
|
os_pkgs = os_pkgs.reject(&:empty?).uniq
|
|
669
|
+
alts = alt_install_cmds(bins: bins, pm_key: pm[:key], distro: distro, version: version)
|
|
670
|
+
os_pkgs = (['pipx'] + os_pkgs).uniq if pm[:key] == :apt && alts.any? { |c| c.include?('pipx install') }
|
|
532
671
|
|
|
533
672
|
io.puts "Profile : #{profile} — #{prof[:desc]}"
|
|
673
|
+
io.puts "Distro : #{distro} #{version}"
|
|
534
674
|
io.puts "Pkg mgr : #{pm[:key]}"
|
|
535
675
|
io.puts "OS pkgs : #{os_pkgs.empty? ? '(none)' : os_pkgs.join(' ')}"
|
|
536
676
|
io.puts "Ruby exts : #{gems.empty? ? '(none)' : gems.join(' ')}"
|
|
537
677
|
io.puts
|
|
538
678
|
|
|
539
|
-
cmds = []
|
|
540
|
-
cmds << "#{pm[:install]} #{os_pkgs.map { |p| Shellwords.escape(p) }.join(' ')}" unless os_pkgs.empty?
|
|
679
|
+
cmds = os_pkgs.map { |pkg| "#{pm[:install]} #{Shellwords.escape(pkg)}" }
|
|
541
680
|
unless gems.empty?
|
|
542
681
|
broken = gems.reject { |g| gem_loadable?(name: g) }
|
|
543
682
|
cmds << "gem pristine #{broken.map { |g| Shellwords.escape(g) }.join(' ')}" unless broken.empty?
|
|
544
683
|
cmds << "gem install #{broken.map { |g| Shellwords.escape(g) }.join(' ')}" unless broken.empty?
|
|
545
684
|
end
|
|
685
|
+
cmds.concat(alts)
|
|
546
686
|
|
|
547
687
|
if cmds.empty?
|
|
548
688
|
io.puts "Nothing to do — profile '#{profile}' is already satisfied."
|
|
@@ -786,6 +926,35 @@ module PWN
|
|
|
786
926
|
false
|
|
787
927
|
end
|
|
788
928
|
|
|
929
|
+
private_class_method def self.alt_install_cmds(opts = {})
|
|
930
|
+
bins = Array(opts[:bins])
|
|
931
|
+
pm_key = opts[:pm_key]
|
|
932
|
+
distro = opts[:distro]
|
|
933
|
+
version = opts[:version]
|
|
934
|
+
bins.filter_map do |b|
|
|
935
|
+
meta = TOOLCHAIN[b] || {}
|
|
936
|
+
next if packages_for(bin: b, pm_key: pm_key, distro: distro, version: version).any?
|
|
937
|
+
|
|
938
|
+
if meta[:pip].to_s != ''
|
|
939
|
+
pip_install_cmd(pkg: meta[:pip], distro: distro)
|
|
940
|
+
elsif meta[:gem].to_s != ''
|
|
941
|
+
"gem install #{Shellwords.escape(meta[:gem])}"
|
|
942
|
+
elsif meta[:script].to_s != ''
|
|
943
|
+
meta[:script].to_s
|
|
944
|
+
end
|
|
945
|
+
end
|
|
946
|
+
end
|
|
947
|
+
|
|
948
|
+
private_class_method def self.pip_install_cmd(opts = {})
|
|
949
|
+
pkg = opts[:pkg].to_s
|
|
950
|
+
distro = opts[:distro].to_s.to_sym
|
|
951
|
+
if %i[kali debian ubuntu mint pop chromeos].include?(distro)
|
|
952
|
+
"pipx install #{Shellwords.escape(pkg)}"
|
|
953
|
+
else
|
|
954
|
+
"python3 -m pip install --user #{Shellwords.escape(pkg)}"
|
|
955
|
+
end
|
|
956
|
+
end
|
|
957
|
+
|
|
789
958
|
private_class_method def self.bin?(opts = {})
|
|
790
959
|
!which(name: opts[:name]).empty?
|
|
791
960
|
end
|
|
@@ -935,21 +1104,31 @@ module PWN
|
|
|
935
1104
|
|
|
936
1105
|
public_class_method def self.help
|
|
937
1106
|
puts "USAGE:
|
|
938
|
-
#
|
|
1107
|
+
# Detect this host's package manager (apt, dnf, pacman, brew, pkg, winget, …).
|
|
939
1108
|
#{self}.pkg_manager
|
|
940
1109
|
|
|
1110
|
+
# Resolve OS package names for a TOOLCHAIN binary on this distro/version.
|
|
1111
|
+
#{self}.packages_for(
|
|
1112
|
+
bin: 'required - TOOLCHAIN key (e.g. nmap or burpsuite)',
|
|
1113
|
+
distro: 'optional - DetectOS.distro symbol (defaults to live detect)',
|
|
1114
|
+
version: 'optional - DetectOS.version string (defaults to live detect)',
|
|
1115
|
+
pm_key: 'optional - package-manager family (:apt :dnf :pacman :brew :port :pkg)'
|
|
1116
|
+
)
|
|
1117
|
+
|
|
941
1118
|
# Run check and return its result
|
|
942
1119
|
#{self}.check(
|
|
943
1120
|
io: 'optional - IO to write the report to (default $stdout)',
|
|
944
1121
|
profile: 'optional - profile value consumed by #check'
|
|
945
1122
|
)
|
|
946
1123
|
|
|
947
|
-
#
|
|
1124
|
+
# Install OS packages + native gems for a capability profile.
|
|
948
1125
|
#{self}.deps(
|
|
949
1126
|
profile: 'optional - one of PROFILES.keys (default :full)',
|
|
950
1127
|
yes: 'optional - non-interactive; assume yes to prompts (default false)',
|
|
951
1128
|
dry_run: 'optional - print commands only, do not execute (default false)',
|
|
952
|
-
io: 'optional - IO to write to (default $stdout)'
|
|
1129
|
+
io: 'optional - IO to write to (default $stdout)',
|
|
1130
|
+
distro: 'optional - DetectOS.distro override (e.g. :kali :ubuntu :macos)',
|
|
1131
|
+
version: 'optional - DetectOS.version override (e.g. 2026.3 or 24.04)'
|
|
953
1132
|
)
|
|
954
1133
|
|
|
955
1134
|
# Opt-in installer for SHIFT+ENTER multi-line support in the pwn-ai /
|
data/lib/pwn/version.rb
CHANGED
|
@@ -146,7 +146,7 @@ RSpec.describe 'documentation/Installation.md' do
|
|
|
146
146
|
pm = PWN::Setup.pkg_manager[:key]
|
|
147
147
|
# every :net bin's package appears in the plan
|
|
148
148
|
PWN::Setup::PROFILES[:net][:bins].each do |b|
|
|
149
|
-
Array(PWN::Setup
|
|
149
|
+
Array(PWN::Setup.packages_for(bin: b, pm_key: pm)).each do |pkg|
|
|
150
150
|
expect(plan).to include(pkg), "profile :net missing own package '#{pkg}'"
|
|
151
151
|
end
|
|
152
152
|
end
|
|
@@ -4,12 +4,62 @@ require 'spec_helper'
|
|
|
4
4
|
|
|
5
5
|
describe PWN::Plugins::DetectOS do
|
|
6
6
|
it 'should display information for authors' do
|
|
7
|
-
|
|
8
|
-
expect(authors_response).to respond_to :authors
|
|
7
|
+
expect(described_class).to respond_to :authors
|
|
9
8
|
end
|
|
10
9
|
|
|
11
10
|
it 'should display information for existing help method' do
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
expect(described_class).to respond_to :help
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
describe '.distro' do
|
|
15
|
+
it 'returns a lowercase symbol on this host' do
|
|
16
|
+
expect(described_class.distro).to be_a(Symbol)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'reads Kali from os-release including rolling versions like 2026.3' do
|
|
20
|
+
body = <<~OS
|
|
21
|
+
ID=kali
|
|
22
|
+
VERSION_ID="2026.3"
|
|
23
|
+
ID_LIKE=debian
|
|
24
|
+
OS
|
|
25
|
+
expect(described_class.distro(os_release: body, type: :linux)).to eq(:kali)
|
|
26
|
+
expect(described_class.version(os_release: body, type: :linux)).to eq('2026.3')
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'reads Ubuntu and Debian' do
|
|
30
|
+
expect(described_class.distro(os_release: "ID=ubuntu\nVERSION_ID=\"24.04\"\n", type: :linux)).to eq(:ubuntu)
|
|
31
|
+
expect(described_class.version(os_release: "ID=ubuntu\nVERSION_ID=\"24.04\"\n", type: :linux)).to eq('24.04')
|
|
32
|
+
expect(described_class.distro(os_release: "ID=debian\nVERSION_ID=\"12\"\n", type: :linux)).to eq(:debian)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'reads Fedora, Arch, Alpine' do
|
|
36
|
+
expect(described_class.distro(os_release: "ID=fedora\nVERSION_ID=41\n", type: :linux)).to eq(:fedora)
|
|
37
|
+
expect(described_class.distro(os_release: "ID=arch\n", type: :linux)).to eq(:arch)
|
|
38
|
+
expect(described_class.distro(os_release: "ID=alpine\nVERSION_ID=3.20.0\n", type: :linux)).to eq(:alpine)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it 'detects ChromeOS from lsb-release' do
|
|
42
|
+
lsb = "CHROMEOS_RELEASE_NAME=Chrome OS\nCHROMEOS_RELEASE_VERSION=15662.64.0\n"
|
|
43
|
+
expect(described_class.distro(lsb_release: lsb, os_release: "ID=chromeos\n", type: :linux)).to eq(:chromeos)
|
|
44
|
+
expect(described_class.version(lsb_release: lsb, os_release: "ID=chromeos\n", type: :linux)).to eq('15662.64.0')
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it 'detects Android from build.prop' do
|
|
48
|
+
prop = "ro.build.version.release=14\nro.product.brand=google\n"
|
|
49
|
+
expect(described_class.distro(build_prop: prop, type: :linux)).to eq(:android)
|
|
50
|
+
expect(described_class.version(build_prop: prop, type: :linux)).to eq('14')
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'detects macOS, iOS, Windows, and BSDs' do
|
|
54
|
+
expect(described_class.distro(type: :osx, sw_vers: '15.1')).to eq(:macos)
|
|
55
|
+
expect(described_class.version(type: :osx, sw_vers: '15.1')).to eq('15.1')
|
|
56
|
+
expect(described_class.distro(type: :linux, platform: 'arm64-darwin-ios')).to eq(:ios)
|
|
57
|
+
expect(described_class.distro(type: :windows, win_ver: 'Microsoft Windows [Version 10.0.26100.1742]')).to eq(:windows)
|
|
58
|
+
expect(described_class.version(type: :windows, win_ver: 'Microsoft Windows [Version 10.0.26100.1742]')).to eq('10.0.26100.1742')
|
|
59
|
+
expect(described_class.distro(type: :freebsd, uname: 'FreeBSD')).to eq(:freebsd)
|
|
60
|
+
expect(described_class.version(type: :freebsd, uname_r: '14.1-RELEASE')).to eq('14.1-RELEASE')
|
|
61
|
+
expect(described_class.distro(type: :openbsd, uname: 'OpenBSD')).to eq(:openbsd)
|
|
62
|
+
expect(described_class.distro(type: :netbsd, uname: 'NetBSD')).to eq(:netbsd)
|
|
63
|
+
end
|
|
14
64
|
end
|
|
15
65
|
end
|
data/spec/lib/pwn/setup_spec.rb
CHANGED
|
@@ -101,4 +101,94 @@ describe PWN::Setup do
|
|
|
101
101
|
expect(body.scan('PWN::Setup.ensure_cron').length).to eq(1)
|
|
102
102
|
expect(body).to include('opts[:migrate] && running')
|
|
103
103
|
end
|
|
104
|
+
|
|
105
|
+
it 'TOOLCHAIN covers every plugin required_bins name' do
|
|
106
|
+
plugin_bins = Dir[File.expand_path('../../../lib/pwn/plugins/*.rb', __dir__)].flat_map do |path|
|
|
107
|
+
src = File.read(path)
|
|
108
|
+
m = src.match(/public_class_method def self\.required_bins\n\s+%w\[([^\]]*)\]/)
|
|
109
|
+
next [] unless m
|
|
110
|
+
|
|
111
|
+
m[1].split
|
|
112
|
+
end.uniq
|
|
113
|
+
missing = plugin_bins - PWN::Setup::TOOLCHAIN.keys
|
|
114
|
+
expect(missing).to eq([]), "pwn setup TOOLCHAIN missing plugin bins: #{missing.join(', ')}"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it 'every TOOLCHAIN row has an OS package or pip/gem installer' do
|
|
118
|
+
PWN::Setup::TOOLCHAIN.each do |bin, meta|
|
|
119
|
+
has_pkg = %i[apt dnf pacman brew port].any? { |k| Array(meta[k]).any? }
|
|
120
|
+
has_alt = meta[:pip].to_s != '' || meta[:gem].to_s != '' || meta[:script].to_s != ''
|
|
121
|
+
expect(has_pkg || has_alt).to be(true), "#{bin} has no apt/dnf/pacman/brew/port/pip/gem installer"
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it 'deps --profile full dry-run emits pipx/gem for bins with no distro package' do
|
|
126
|
+
io = StringIO.new
|
|
127
|
+
r = PWN::Setup.deps(profile: :full, dry_run: true, io: io)
|
|
128
|
+
cmds = Array(r[:ran]).map { |h| h[:cmd] }.join("\n")
|
|
129
|
+
expect(r[:skipped]).not_to be true
|
|
130
|
+
distro = PWN::Plugins::DetectOS.distro
|
|
131
|
+
version = PWN::Plugins::DetectOS.version
|
|
132
|
+
PWN::Setup::TOOLCHAIN.each do |bin, meta|
|
|
133
|
+
next if PWN::Setup.packages_for(bin: bin, distro: distro, version: version).any?
|
|
134
|
+
|
|
135
|
+
token = meta[:pip] || meta[:gem]
|
|
136
|
+
next unless token
|
|
137
|
+
|
|
138
|
+
expect(cmds).to include(token.to_s), "full dry-run missing installer for #{bin} (#{token})"
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
it 'packages_for prefers distro overrides then package-manager family' do
|
|
143
|
+
expect(
|
|
144
|
+
PWN::Setup.packages_for(bin: 'nmap', distro: :ubuntu, version: '24.04', pm_key: :apt)
|
|
145
|
+
).to eq(%w[nmap])
|
|
146
|
+
expect(
|
|
147
|
+
PWN::Setup.packages_for(bin: 'burpsuite', distro: :kali, version: '2026.3', pm_key: :apt)
|
|
148
|
+
).to eq(%w[burpsuite])
|
|
149
|
+
expect(
|
|
150
|
+
PWN::Setup.packages_for(bin: 'burpsuite', distro: :ubuntu, version: '24.04', pm_key: :apt)
|
|
151
|
+
).to eq([])
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
it 'deps skips kali-only apt packages on ubuntu' do
|
|
155
|
+
io = StringIO.new
|
|
156
|
+
r = PWN::Setup.deps(profile: :web, dry_run: true, io: io, distro: :ubuntu, version: '24.04')
|
|
157
|
+
cmds = Array(r[:ran]).map { |h| h[:cmd] }.join(' ')
|
|
158
|
+
expect(cmds).not_to include('burpsuite')
|
|
159
|
+
expect(cmds).not_to include('zaproxy')
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
it 'maps Kali 2026.3 apt names for checksec ROPgadget ropper' do
|
|
163
|
+
k = { distro: :kali, version: '2026.3', pm_key: :apt }
|
|
164
|
+
expect(PWN::Setup.packages_for(k.merge(bin: 'checksec'))).to eq(%w[checksec])
|
|
165
|
+
expect(PWN::Setup.packages_for(k.merge(bin: 'ROPgadget'))).to eq(%w[python3-ropgadget])
|
|
166
|
+
expect(PWN::Setup.packages_for(k.merge(bin: 'ropper'))).to eq(%w[ropper])
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
it 'does not apt-get Kali-missing names and does not pip --user on Kali' do
|
|
170
|
+
k = { distro: :kali, version: '2026.3', pm_key: :apt }
|
|
171
|
+
%w[pwndbg semgrep vol].each do |b|
|
|
172
|
+
expect(PWN::Setup.packages_for(k.merge(bin: b))).to eq([])
|
|
173
|
+
end
|
|
174
|
+
r = PWN::Setup.deps(profile: :full, dry_run: true, io: StringIO.new, distro: :kali, version: '2026.3')
|
|
175
|
+
cmds = Array(r[:ran]).map { |h| h[:cmd] }
|
|
176
|
+
apt = cmds.select { |c| c.include?('apt-get') }.join("\n")
|
|
177
|
+
expect(apt).to include('checksec')
|
|
178
|
+
expect(apt).to include('python3-ropgadget')
|
|
179
|
+
expect(apt).to include('ropper')
|
|
180
|
+
expect(apt).not_to match(/\bpwndbg\b/)
|
|
181
|
+
expect(apt).not_to match(/\bsemgrep\b/)
|
|
182
|
+
expect(apt).not_to include('volatility3')
|
|
183
|
+
expect(cmds.join("\n")).not_to include('pip install --user')
|
|
184
|
+
expect(cmds.join("\n")).to include('install.pwndbg.re')
|
|
185
|
+
expect(cmds.join("\n")).to include('pwndbg-gdb')
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
it 'issues one apt-get per package so a missing name cannot abort the rest' do
|
|
189
|
+
r = PWN::Setup.deps(profile: :net, dry_run: true, io: StringIO.new, distro: :kali, version: '2026.3')
|
|
190
|
+
apt = Array(r[:ran]).map { |h| h[:cmd] }.select { |c| c.include?('apt-get install') }
|
|
191
|
+
expect(apt.length).to be > 1
|
|
192
|
+
apt.each { |c| expect(c).to match(/apt-get install -y \S+\z/) }
|
|
193
|
+
end
|
|
104
194
|
end
|
data/third_party/pwn_rdoc.jsonl
CHANGED
|
@@ -1933,11 +1933,23 @@
|
|
|
1933
1933
|
{"messages":[{"role":"user","content":"PWN::Plugins::DefectDojo.tool_configuration_list Usage"},{"role":"assistant","content":"`PWN::Plugins::DefectDojo.tool_configuration_list`: Supported Method Parameters\n\ntool_configuration_list = PWN::Plugins::DefectDojo.tool_configuration_list(\n\ndd_obj: 'required dd_obj returned from #login method',\nid: 'optional - retrieve single test by id, otherwise return all'\n\n)\n"}]}
|
|
1934
1934
|
{"messages":[{"role":"user","content":"PWN::Plugins::DefectDojo.tool_configuration_resource_uri_by_name Usage"},{"role":"assistant","content":"`PWN::Plugins::DefectDojo.tool_configuration_resource_uri_by_name`: Supported Method Parameters\n\ntool_configuration_resource_uri_by_name(\n\ndd_obj: 'required dd_obj returned from #login method',\ntool_config_name: 'required tool configuration name'\n\n)\n"}]}
|
|
1935
1935
|
{"messages":[{"role":"user","content":"PWN::Plugins::DefectDojo.user_list Usage"},{"role":"assistant","content":"`PWN::Plugins::DefectDojo.user_list`: Supported Method Parameters\n\nuser_list = PWN::Plugins::DefectDojo.user_list(\n\ndd_obj: 'required dd_obj returned from #login method',\nid: 'optional - retrieve single user by id, otherwise return all'\n\n)\n"}]}
|
|
1936
|
-
{"messages":[{"role":"user","content":"PWN::Plugins::DetectOS.
|
|
1937
|
-
{"messages":[{"role":"user","content":"PWN::Plugins::DetectOS.
|
|
1938
|
-
{"messages":[{"role":"user","content":"PWN::Plugins::DetectOS.
|
|
1936
|
+
{"messages":[{"role":"user","content":"PWN::Plugins::DetectOS.android_host? Usage"},{"role":"assistant","content":"`PWN::Plugins::DetectOS.android_host?`: "}]}
|
|
1937
|
+
{"messages":[{"role":"user","content":"PWN::Plugins::DetectOS.arch Usage"},{"role":"assistant","content":"`PWN::Plugins::DetectOS.arch`: "}]}
|
|
1938
|
+
{"messages":[{"role":"user","content":"PWN::Plugins::DetectOS.authors Usage"},{"role":"assistant","content":"`PWN::Plugins::DetectOS.authors`: "}]}
|
|
1939
|
+
{"messages":[{"role":"user","content":"PWN::Plugins::DetectOS.chromeos_host? Usage"},{"role":"assistant","content":"`PWN::Plugins::DetectOS.chromeos_host?`: "}]}
|
|
1940
|
+
{"messages":[{"role":"user","content":"PWN::Plugins::DetectOS.distro Usage"},{"role":"assistant","content":"`PWN::Plugins::DetectOS.distro`: Supported Method Parameters\n\nPWN::Plugins::DetectOS.distro(\n\nos_release: 'optional - /etc/os-release body (defaults to reading the file)',\nlsb_release: 'optional - /etc/lsb-release body',\nbuild_prop: 'optional - Android /system/build.prop body',\ntype: 'optional - OS family from #type (e.g. :linux :osx :windows :freebsd)',\nplatform: 'optional - RUBY_PLATFORM override',\nuname: 'optional - uname -s string',\nsw_vers: 'optional - macOS/iOS product version string',\nwin_ver: 'optional - Windows ver command output'\n\n)\n"}]}
|
|
1941
|
+
{"messages":[{"role":"user","content":"PWN::Plugins::DetectOS.endian Usage"},{"role":"assistant","content":"`PWN::Plugins::DetectOS.endian`: "}]}
|
|
1939
1942
|
{"messages":[{"role":"user","content":"PWN::Plugins::DetectOS.help Usage"},{"role":"assistant","content":"`PWN::Plugins::DetectOS.help`: "}]}
|
|
1940
|
-
{"messages":[{"role":"user","content":"PWN::Plugins::DetectOS.
|
|
1943
|
+
{"messages":[{"role":"user","content":"PWN::Plugins::DetectOS.ios_host? Usage"},{"role":"assistant","content":"`PWN::Plugins::DetectOS.ios_host?`: "}]}
|
|
1944
|
+
{"messages":[{"role":"user","content":"PWN::Plugins::DetectOS.live_sw_vers Usage"},{"role":"assistant","content":"`PWN::Plugins::DetectOS.live_sw_vers`: "}]}
|
|
1945
|
+
{"messages":[{"role":"user","content":"PWN::Plugins::DetectOS.live_uname_r Usage"},{"role":"assistant","content":"`PWN::Plugins::DetectOS.live_uname_r`: "}]}
|
|
1946
|
+
{"messages":[{"role":"user","content":"PWN::Plugins::DetectOS.live_uname_s Usage"},{"role":"assistant","content":"`PWN::Plugins::DetectOS.live_uname_s`: "}]}
|
|
1947
|
+
{"messages":[{"role":"user","content":"PWN::Plugins::DetectOS.live_win_ver Usage"},{"role":"assistant","content":"`PWN::Plugins::DetectOS.live_win_ver`: "}]}
|
|
1948
|
+
{"messages":[{"role":"user","content":"PWN::Plugins::DetectOS.normalize_id Usage"},{"role":"assistant","content":"`PWN::Plugins::DetectOS.normalize_id`: "}]}
|
|
1949
|
+
{"messages":[{"role":"user","content":"PWN::Plugins::DetectOS.parse_kv Usage"},{"role":"assistant","content":"`PWN::Plugins::DetectOS.parse_kv`: "}]}
|
|
1950
|
+
{"messages":[{"role":"user","content":"PWN::Plugins::DetectOS.read_if_present Usage"},{"role":"assistant","content":"`PWN::Plugins::DetectOS.read_if_present`: "}]}
|
|
1951
|
+
{"messages":[{"role":"user","content":"PWN::Plugins::DetectOS.type Usage"},{"role":"assistant","content":"`PWN::Plugins::DetectOS.type`: "}]}
|
|
1952
|
+
{"messages":[{"role":"user","content":"PWN::Plugins::DetectOS.version Usage"},{"role":"assistant","content":"`PWN::Plugins::DetectOS.version`: Supported Method Parameters\n\nPWN::Plugins::DetectOS.version(\n\nos_release: 'optional - /etc/os-release body',\nlsb_release: 'optional - /etc/lsb-release body',\nbuild_prop: 'optional - Android build.prop body',\nsw_vers: 'optional - macOS/iOS product version',\nwin_ver: 'optional - Windows ver output',\nuname_r: 'optional - uname -r string',\ntype: 'optional - OS family from #type'\n\n)\n"}]}
|
|
1941
1953
|
{"messages":[{"role":"user","content":"PWN::Plugins::EIN.authors Usage"},{"role":"assistant","content":"`PWN::Plugins::EIN.authors`: Author(s)\n\n0day Inc. <support@0dayinc.com>\n"}]}
|
|
1942
1954
|
{"messages":[{"role":"user","content":"PWN::Plugins::EIN.generate Usage"},{"role":"assistant","content":"`PWN::Plugins::EIN.generate`: Supported Method Parameters\n\nPWN::Plugins::EIN.generate(\n\ncount: 'required - number of EIN numbers to generate'\n\n)\n"}]}
|
|
1943
1955
|
{"messages":[{"role":"user","content":"PWN::Plugins::EIN.help Usage"},{"role":"assistant","content":"`PWN::Plugins::EIN.help`: "}]}
|
|
@@ -3171,6 +3183,7 @@
|
|
|
3171
3183
|
{"messages":[{"role":"user","content":"PWN::Sessions.to_response_history Usage"},{"role":"assistant","content":"`PWN::Sessions.to_response_history`: Supported Method Parameters\n\nhistory_for_ai = PWN::Sessions.to_response_history(session_id:) (converts transcript to the response_history format used by PWN::AI::* .chat)\n"}]}
|
|
3172
3184
|
{"messages":[{"role":"user","content":"PWN::Setup.ai_active_engine Usage"},{"role":"assistant","content":"`PWN::Setup.ai_active_engine`: "}]}
|
|
3173
3185
|
{"messages":[{"role":"user","content":"PWN::Setup.ai_key_configured? Usage"},{"role":"assistant","content":"`PWN::Setup.ai_key_configured?`: "}]}
|
|
3186
|
+
{"messages":[{"role":"user","content":"PWN::Setup.alt_install_cmds Usage"},{"role":"assistant","content":"`PWN::Setup.alt_install_cmds`: "}]}
|
|
3174
3187
|
{"messages":[{"role":"user","content":"PWN::Setup.authors Usage"},{"role":"assistant","content":"`PWN::Setup.authors`: Author(s)\n\n0day Inc. <support@0dayinc.com>\n"}]}
|
|
3175
3188
|
{"messages":[{"role":"user","content":"PWN::Setup.bin? Usage"},{"role":"assistant","content":"`PWN::Setup.bin?`: "}]}
|
|
3176
3189
|
{"messages":[{"role":"user","content":"PWN::Setup.check Usage"},{"role":"assistant","content":"`PWN::Setup.check`: Supported Method Parameters\n\nPWN::Setup.check(\n\nio: 'optional - IO to write the report to (default $stdout)'\n\n)\n"}]}
|
|
@@ -3181,6 +3194,8 @@
|
|
|
3181
3194
|
{"messages":[{"role":"user","content":"PWN::Setup.help Usage"},{"role":"assistant","content":"`PWN::Setup.help`: "}]}
|
|
3182
3195
|
{"messages":[{"role":"user","content":"PWN::Setup.list_profiles Usage"},{"role":"assistant","content":"`PWN::Setup.list_profiles`: Supported Method Parameters\n\nPWN::Setup.list_profiles(\n\nio: 'optional - IO to write to (default $stdout)'\n\n)\n"}]}
|
|
3183
3196
|
{"messages":[{"role":"user","content":"PWN::Setup.migrate Usage"},{"role":"assistant","content":"`PWN::Setup.migrate`: Supported Method Parameters\n\nPWN::Setup.migrate(\n\nfix: 'optional - also autofix incompatible ~/.pwn files (default false)',\ndry_run: 'optional - print what WOULD happen (default false)',\nyes: 'optional - alias for fix:true (CI-friendly)',\nio: 'optional - IO to write to (default $stdout)'\n\n)\n\nDelegate to PWN::Migrate.run — verify every ~/.pwn state file is compatible with THIS pwn release and (optionally) autofix it. A timestamped backup of ~/.pwn is taken first. See ‘PWN::Migrate.help`.\n"}]}
|
|
3197
|
+
{"messages":[{"role":"user","content":"PWN::Setup.packages_for Usage"},{"role":"assistant","content":"`PWN::Setup.packages_for`: Supported Method Parameters\n\nPWN::Setup.packages_for(\n\nbin: 'required - TOOLCHAIN key (e.g. nmap or burpsuite)',\ndistro: 'optional - DetectOS.distro symbol (defaults to live detect)',\nversion: 'optional - DetectOS.version string (defaults to live detect)',\npm_key: 'optional - package-manager family (:apt :dnf :pacman :brew :port :pkg …)'\n\n)\n"}]}
|
|
3198
|
+
{"messages":[{"role":"user","content":"PWN::Setup.pip_install_cmd Usage"},{"role":"assistant","content":"`PWN::Setup.pip_install_cmd`: "}]}
|
|
3184
3199
|
{"messages":[{"role":"user","content":"PWN::Setup.pkg_manager Usage"},{"role":"assistant","content":"`PWN::Setup.pkg_manager`: Supported Method Parameters\n\nPWN::Setup.pkg_manager\n"}]}
|
|
3185
3200
|
{"messages":[{"role":"user","content":"PWN::Setup.terminal Usage"},{"role":"assistant","content":"`PWN::Setup.terminal`: Supported Method Parameters\n\nPWN::Setup.terminal(\n\nyes: 'optional - assume yes; non-interactive (default false)',\ndry_run: 'optional - print what WOULD happen (default false)',\nio: 'optional - IO to write to (default $stdout)'\n\n)\n\nOpt-in installer for SHIFT+ENTER multi-line support in the pwn-ai / pwn-asm REPL. Never runs implicitly — the user invokes it with ‘pwn setup –terminal` after PWNMultiLineInput#ensure_vte_shift_enter tells them their emulator can’t distinguish Shift+Enter from Enter.\n\nWhat it does:\n\n1. Prints the two `~/.tmux.conf` lines that persist the runtime\n `tmux set -s` calls PWNMultiLineInput already makes.\n2. If running under Terminator (a VTE host), copies\n third_party/terminator/pwn_shift_enter.py to\n ~/.config/terminator/plugins/ and enables the PWNShiftEnter\n plugin in ~/.config/terminator/config — WITH confirmation and\n a .pwnbak backup. This is the only VTE emulator we can fix at\n the GTK layer without patching libvte itself.\n\nEverything here is host-config mutation, which is why it lives in ‘pwn setup` and NOT in the REPL’s runtime path. See GNOME/vte issues #2601 / #2607 for why VTE can’t be fixed with escape sequences alone.\n"}]}
|
|
3186
3201
|
{"messages":[{"role":"user","content":"PWN::Setup.which Usage"},{"role":"assistant","content":"`PWN::Setup.which`: "}]}
|
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.712
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- 0day Inc.
|
|
@@ -5478,6 +5478,7 @@ files:
|
|
|
5478
5478
|
- etc/default_skills/pwn/sdr/son_micro_rfid/SKILL.md
|
|
5479
5479
|
- etc/default_skills/pwn/sessions/SKILL.md
|
|
5480
5480
|
- etc/default_skills/pwn/setup/SKILL.md
|
|
5481
|
+
- etc/default_skills/pwn/setup/references/urls.md
|
|
5481
5482
|
- etc/default_skills/pwn/version/SKILL.md
|
|
5482
5483
|
- etc/default_skills/pwn/www/SKILL.md
|
|
5483
5484
|
- etc/default_skills/pwn/www/app_cobalt_io/SKILL.md
|