Module: PWN::Plugins::DetectOS

Defined in:
lib/pwn/plugins/detect_os.rb

Overview

Detect host OS family, distro flavor, and version string.

Constant Summary collapse

ID_ALIASES =
{
  'chromiumos' => :chromeos,
  'chromeos' => :chromeos,
  'pop_os' => :pop,
  'pop' => :pop,
  'ol' => :oracle,
  'opensuse-leap' => :opensuse,
  'opensuse-tumbleweed' => :opensuse,
  'sles' => :suse,
  'darwin' => :macos,
  'osx' => :macos,
  'macos' => :macos,
  'iphoneos' => :ios,
  'ipados' => :ios,
  'ios' => :ios,
  'win32' => :windows,
  'mingw32' => :windows,
  'mingw' => :windows,
  'mswin' => :windows
}.freeze

Class Method Summary collapse

Class Method Details

.archObject



51
52
53
54
55
# File 'lib/pwn/plugins/detect_os.rb', line 51

public_class_method def self.arch
  OS.host_cpu
rescue StandardError => e
  raise e
end

.authorsObject



210
211
212
213
214
# File 'lib/pwn/plugins/detect_os.rb', line 210

public_class_method def self.authors
  "AUTHOR(S):
    0day Inc. <[email protected]>
  "
end

.distro(opts = {}) ⇒ Object

Supported Method Parameters

PWN::Plugins::DetectOS.distro( os_release: 'optional - /etc/os-release body (defaults to reading the file)', lsb_release: 'optional - /etc/lsb-release body', build_prop: 'optional - Android /system/build.prop body', type: 'optional - OS family from #type (e.g. :linux :osx :windows :freebsd)', platform: 'optional - RUBY_PLATFORM override', uname: 'optional - uname -s string', sw_vers: 'optional - macOS/iOS product version string', win_ver: 'optional - Windows ver command output' )



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/pwn/plugins/detect_os.rb', line 79

public_class_method def self.distro(opts = {})
  return normalize_id(id: opts[:distro]) if opts[:distro]

  platform = (opts[:platform] || RUBY_PLATFORM).to_s.downcase
  uname = (opts[:uname] || live_uname_s).to_s.downcase
  os_type = opts[:type] || type
  osr = parse_kv(content: opts[:os_release] || read_if_present(path: '/etc/os-release') || read_if_present(path: '/usr/lib/os-release'))
  lsb = parse_kv(content: opts[:lsb_release] || read_if_present(path: '/etc/lsb-release'))
  prop = parse_kv(content: opts[:build_prop] || read_if_present(path: '/system/build.prop'))

  return :ios if ios_host?(platform: platform, uname: uname)
  return :android if android_host?(osr: osr, prop: prop, uname: uname)
  return :chromeos if chromeos_host?(osr: osr, lsb: lsb)
  return :macos if os_type == :osx || platform.include?('darwin')
  return :windows if os_type == :windows || platform.match?(/mswin|mingw|cygwin/)
  return :freebsd if os_type == :freebsd || uname.include?('freebsd')
  return :openbsd if os_type == :openbsd || uname.include?('openbsd')
  return :netbsd if os_type == :netbsd || uname.include?('netbsd')
  return :dragonfly if uname.include?('dragonfly')
  return :cygwin if os_type == :cygwin

  id = osr['ID'].to_s
  return normalize_id(id: id) unless id.empty?

  like = osr['ID_LIKE'].to_s.downcase
  return :debian if like.include?('debian')
  return :fedora if like.include?('fedora') || like.include?('rhel')
  return :arch if like.include?('arch')
  return :suse if like.include?('suse')

  os_type || :unknown
end

.endianObject



57
58
59
60
61
62
63
64
65
# File 'lib/pwn/plugins/detect_os.rb', line 57

public_class_method def self.endian
  if [1].pack('I') == [1].pack('N')
    :big
  else
    :little
  end
rescue StandardError => e
  raise e
end

.helpObject



216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'lib/pwn/plugins/detect_os.rb', line 216

public_class_method def self.help
  puts "USAGE:
    # Return the OS family as a symbol (:linux :osx :windows :freebsd :openbsd :netbsd :cygwin).
    #{self}.type

    # Return the CPU architecture string from the host (e.g. x86_64, arm64).
    #{self}.arch

    # Return CPU endianness as :little or :big.
    #{self}.endian

    # Return the distro flavor as a lowercase symbol (:kali :ubuntu :macos :windows :freebsd …).
    #{self}.distro(
      os_release: 'optional - /etc/os-release body (defaults to reading the file)',
      lsb_release: 'optional - /etc/lsb-release body',
      build_prop: 'optional - Android /system/build.prop body',
      type: 'optional - OS family from #type (e.g. :linux :osx :windows :freebsd)',
      platform: 'optional - RUBY_PLATFORM override (detect iOS/Android/Windows)',
      uname: 'optional - uname -s string',
      sw_vers: 'optional - macOS/iOS product version string',
      win_ver: 'optional - Windows ver command output',
      distro: 'optional - force a distro id instead of detecting'
    )

    # Return the distro version as a string (e.g. 2026.3, 24.04, 15.1, 14.1-RELEASE).
    #{self}.version(
      os_release: 'optional - /etc/os-release body',
      lsb_release: 'optional - /etc/lsb-release body',
      build_prop: 'optional - Android build.prop body',
      sw_vers: 'optional - macOS/iOS product version',
      win_ver: 'optional - Windows ver output',
      uname_r: 'optional - uname -r string',
      type: 'optional - OS family from #type',
      version: 'optional - force a version string instead of detecting'
    )

    # Inventory extra PATH binaries on this host that pwn plugins do not already wrap.
    #{self}.living_off_the_land(
      os_release: 'optional - /etc/os-release body (defaults to reading the file)',
      lsb_release: 'optional - /etc/lsb-release body',
      build_prop: 'optional - Android /system/build.prop body',
      type: 'optional - OS family from #type',
      which: 'optional - Hash of binary name => true/false (skips live PATH when set)',
      bins: 'optional - extra host binary names to include when present on PATH',
      pwn_bins: 'optional - extra names to treat as already wrapped (skipped from native)',
      arch: 'optional - CPU arch override',
      endian: 'optional - :little or :big override',
      cap_net_raw: 'optional - Boolean CAP_NET_RAW override',
      docker: 'optional - Boolean docker.sock override'
    )

    # Print the AUTHOR(S) string for this module.
    #{self}.authors
  "
  constants.sort
end

.living_off_the_land(opts = {}) ⇒ Object

Supported Method Parameters

PWN::Plugins::DetectOS.living_off_the_land( os_release: 'optional - /etc/os-release body (defaults to reading the file)', which: 'optional - Hash of binary name => present Boolean (skips live PATH)', bins: 'optional - extra host binary names to include when present', pwn_bins: 'optional - Array of names pwn setup already installs (skipped)' )



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/pwn/plugins/detect_os.rb', line 169

public_class_method def self.living_off_the_land(opts = {})
  os_type = opts[:type] || type
  flavor = distro(opts)
  ver = version(opts)
  cpu = opts[:arch] || arch
  endn = opts[:endian] || endian
  found = host_path_names(opts)
  extra = Array(opts[:bins]).map(&:to_s).reject(&:empty?)
  extra.each do |name|
    ok = if opts[:which].is_a?(Hash)
           opts[:which][name] || opts[:which][name.to_sym]
         else
           bin_present?(name: name)
         end
    found << name if ok
  end
  native = found.map(&:to_s).reject(&:empty?).uniq.sort
  cap = if opts.key?(:cap_net_raw)
          opts[:cap_net_raw]
        else
          cap_net_raw_live
        end
  docker = if opts.key?(:docker)
             opts[:docker]
           else
             docker_live
           end
  summary = "#{flavor} #{ver} #{cpu}"
  {
    type: os_type,
    distro: flavor,
    version: ver,
    arch: cpu.to_s,
    endian: endn,
    native: native,
    cap_net_raw: cap,
    docker: docker,
    summary: summary
  }
end

.typeObject



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/pwn/plugins/detect_os.rb', line 30

public_class_method def self.type
  os = :cygwin if OS.cygwin?
  os = :freebsd if OS.freebsd?
  os = :linux if OS.linux?
  os = :netbsd if OS.host_os.to_s.include?('netbsd')
  os = :openbsd if OS.host_os.to_s.include?('openbsd')
  os = :osx if OS.osx?
  os = :windows if OS.windows?
  if os.nil?
    uname = `uname -s 2>/dev/null`.to_s.downcase
    os = :openbsd if uname.include?('openbsd')
    os = :freebsd if uname.include?('freebsd')
    os = :netbsd if uname.include?('netbsd')
    os = :linux if uname.include?('linux')
  end

  os
rescue StandardError => e
  raise e
end

.version(opts = {}) ⇒ Object

Supported Method Parameters

PWN::Plugins::DetectOS.version( os_release: 'optional - /etc/os-release body', lsb_release: 'optional - /etc/lsb-release body', build_prop: 'optional - Android build.prop body', sw_vers: 'optional - macOS/iOS product version', win_ver: 'optional - Windows ver output', uname_r: 'optional - uname -r string', type: 'optional - OS family from #type' )



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/pwn/plugins/detect_os.rb', line 123

public_class_method def self.version(opts = {})
  return opts[:version].to_s if opts[:version]

  injected = %i[os_release lsb_release build_prop sw_vers win_ver uname_r].any? { |k| opts.key?(k) }
  os_type = opts[:type] || type
  osr = parse_kv(content: opts[:os_release] || (injected ? nil : read_if_present(path: '/etc/os-release') || read_if_present(path: '/usr/lib/os-release')))
  lsb = parse_kv(content: opts[:lsb_release] || (injected ? nil : read_if_present(path: '/etc/lsb-release')))
  prop = parse_kv(content: opts[:build_prop] || (injected ? nil : read_if_present(path: '/system/build.prop')))

  from_osr = osr['VERSION_ID'].to_s
  return from_osr unless from_osr.empty?

  chrome = lsb['CHROMEOS_RELEASE_VERSION'].to_s
  return chrome unless chrome.empty?

  android = prop['ro.build.version.release'].to_s
  return android unless android.empty?

  if os_type == :osx || opts[:sw_vers]
    mac = (opts[:sw_vers] || live_sw_vers).to_s.strip
    return mac unless mac.empty?
  end

  if os_type == :windows || opts[:win_ver]
    win = (opts[:win_ver] || live_win_ver).to_s
    m = win.match(/\[Version\s+([^\]]+)\]/i) || win.match(/(\d+\.\d+(?:\.\d+)*)/)
    return m[1] if m
  end

  rel = (opts[:uname_r] || live_uname_r).to_s.strip
  return rel if %i[freebsd openbsd netbsd].include?(os_type) && !rel.empty?

  debian = read_if_present(path: '/etc/debian_version').to_s.strip
  return debian unless debian.empty?

  osr['VERSION'].to_s
end