Module: Msf::PostMixin

Overview

A mixin used for providing Modules with post-exploitation options and helper methods

Instance Attribute Summary collapse

Attributes included from Module::HasActions

#actions, #default_action, #passive_actions

Instance Method Summary collapse

Methods included from Msf::Post::Common

#clear_screen, #cmd_exec, #cmd_exec_get_pid, #cmd_exec_with_result, #command_exists?, #get_env, #get_envs, #peer, #report_virtualization, #rhost, #rport

Methods included from Module::HasActions

#action, #find_action, #passive_action?

Methods included from Auxiliary::Report

#active_db?, #create_cracked_credential, #create_credential, #create_credential_and_login, #create_credential_login, #db, #db_warning_given?, #get_client, #get_host, #inside_workspace_boundary?, #invalidate_login, #mytask, #myworkspace, #myworkspace_id, #report_auth_info, #report_client, #report_exploit, #report_host, #report_loot, #report_note, #report_service, #report_vuln, #report_web_form, #report_web_page, #report_web_site, #report_web_vuln, #store_cred, #store_local, #store_loot

Methods included from Metasploit::Framework::Require

optionally, optionally_active_record_railtie, optionally_include_metasploit_credential_creation, #optionally_include_metasploit_credential_creation, optionally_require_metasploit_db_gem_engines

Instance Attribute Details

#passiveBoolean

True when this module is passive, false when active

Returns:

  • (Boolean)

See Also:



237
238
239
# File 'lib/msf/core/post_mixin.rb', line 237

def passive
  @passive
end

#session_typesArray

A list of compatible session types

Returns:

  • (Array)


243
244
245
# File 'lib/msf/core/post_mixin.rb', line 243

def session_types
  @session_types
end

Instance Method Details

#check_for_session_readiness(tries = 6) ⇒ Object

Meterpreter sometimes needs a little bit of extra time to actually be responsive for post modules. Default tries and retries for 5 seconds.



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/msf/core/post_mixin.rb', line 77

def check_for_session_readiness(tries=6)
  session_ready_count = 0
  session_ready = false
  until session.sys or session_ready_count > tries
    session_ready_count += 1
    back_off_period = (session_ready_count**2)/10.0
    select(nil,nil,nil,back_off_period)
  end
  session_ready = !!session.sys
  unless session_ready
    raise "The stdapi extension has not been loaded yet." unless session.tlv_enc_key.nil?
    raise "Could not get a hold of the session."
  end
  return session_ready
end

#cleanupObject

Default cleanup handler does nothing



96
97
# File 'lib/msf/core/post_mixin.rb', line 96

def cleanup
end

#command_names_for(command_ids) ⇒ Object (protected)



333
334
335
# File 'lib/msf/core/post_mixin.rb', line 333

def command_names_for(command_ids)
  command_ids.map { |id| Rex::Post::Meterpreter::CommandMapper.get_command_name(id) }.join(', ')
end

#compatible_sessionsArray

Return a (possibly empty) list of all compatible sessions

Returns:

  • (Array)


153
154
155
156
157
158
159
# File 'lib/msf/core/post_mixin.rb', line 153

def compatible_sessions
  sessions = []
  framework.sessions.each do |sid, s|
    sessions << sid if session_compatible?(s)
  end
  sessions
end

#initialize(info = {}) ⇒ Object



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/msf/core/post_mixin.rb', line 12

def initialize(info = {})
  super(
    update_info(
      info,
      'Compat' => {
        'Meterpreter' => {
          'Commands' => %w[
            stdapi_sys_config_sysinfo
          ]
        }
      }
    )
  )

  register_options( [
    Msf::OptInt.new('SESSION', [ true, 'The session to run this module on' ])
  ] , Msf::Post)

  # Default stance is active
  self.passive = info['Passive'] || false
  self.session_types = info['SessionTypes'] || []
end

#meterpreter_session_incompatibility_reasons(session) ⇒ Object (protected)

Return the reasons why a meterpreter session is incompatible. Checks all specified meterpreter commands are provided by the remote session



266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# File 'lib/msf/core/post_mixin.rb', line 266

def meterpreter_session_incompatibility_reasons(session)
  cmd_name_wildcards = module_info.dig('Compat', 'Meterpreter', 'Commands') || []
  cmd_names = Rex::Post::Meterpreter::CommandMapper.get_command_names.select do |cmd_name|
    cmd_name_wildcards.any? { |cmd_name_wildcard| ::File.fnmatch(cmd_name_wildcard, cmd_name) }
  end

  unmatched_wildcards = cmd_name_wildcards.select { |cmd_name_wildcard| cmd_names.none? { |cmd_name| ::File.fnmatch(cmd_name_wildcard, cmd_name) } }
  unless unmatched_wildcards.empty?
    # This implies that there was a typo in one of the wildcards because it didn't match anything. This is a developer mistake.
    wlog("The #{fullname} module specified the following Meterpreter command wildcards that did not match anything: #{ unmatched_wildcards.join(', ') }")
  end

  cmd_ids = cmd_names.map { |name| Rex::Post::Meterpreter::CommandMapper.get_command_id(name) }

  # XXX: Remove this condition once the payloads gem has had another major version bump from 2.x to 3.x and
  # rapid7/metasploit-payloads#451 has been landed to correct the `enumextcmd` behavior on Windows. Until then, skip
  # proactive validation of Windows core commands. This is not the only instance of this workaround.
  if session.base_platform == 'windows'
    cmd_ids = cmd_ids.select do |cmd_id|
      !cmd_id.between?(
        Rex::Post::Meterpreter::ClientCore.extension_id,
        Rex::Post::Meterpreter::ClientCore.extension_id + Rex::Post::Meterpreter::COMMAND_ID_RANGE - 1
      )
    end
  end

  # Windows does not support chmod, but will be defined by default in the file mixin
  if session.base_platform == 'windows'
    cmd_ids -= [Rex::Post::Meterpreter::Extensions::Stdapi::COMMAND_ID_STDAPI_FS_CHMOD]
  end

  missing_cmd_ids = (cmd_ids - session.commands)
  unless missing_cmd_ids.empty?
    # If there are missing commands, try to load the necessary extension.

    # If core_loadlib isn't supported, then extensions can't be loaded
    return ['missing Meterpreter features: core can not be extended'] unless session.commands.include?(Rex::Post::Meterpreter::COMMAND_ID_CORE_LOADLIB)

    # Since core is already loaded, if the missing command is a core command then it's truly missing
    missing_core_cmd_ids = missing_cmd_ids.select do |cmd_id|
      cmd_id.between?(
        Rex::Post::Meterpreter::ClientCore.extension_id,
        Rex::Post::Meterpreter::ClientCore.extension_id + Rex::Post::Meterpreter::COMMAND_ID_RANGE - 1
      )
    end
    if missing_core_cmd_ids.any?
      return ["missing Meterpreter features: #{command_names_for(missing_core_cmd_ids)}"]
    end

    missing_extensions = missing_cmd_ids.map { |cmd_id| Rex::Post::Meterpreter::ExtensionMapper.get_extension_name(cmd_id) }.uniq
    missing_extensions.each do |ext_name|
      # If the extension is already loaded, the command is truly missing
      return ["missing Meterpreter features: #{command_names_for(missing_cmd_ids)}"] if session.ext.aliases.include?(ext_name)

      begin
        session.core.use(ext_name)
      rescue RuntimeError
        return ["unloadable Meterpreter extension: #{ext_name}"]
      end
    end
  end
  missing_cmd_ids -= session.commands
  return ["missing Meterpreter features: #{command_names_for(missing_cmd_ids)}"] unless missing_cmd_ids.empty?

  []
end

#passive?Boolean

Whether this module’s Exploit::Stance is passive

Returns:

  • (Boolean)


145
146
147
# File 'lib/msf/core/post_mixin.rb', line 145

def passive?
  self.passive
end

#post_commandsObject

Can be overridden by individual modules to add new commands



140
141
142
# File 'lib/msf/core/post_mixin.rb', line 140

def post_commands
  {}
end

#sessionMsf::Session? Also known as: client

Return the associated session or nil if there isn’t one

Returns:

  • (Msf::Session)
  • (nil)

    if the id provided in the datastore does not correspond to a session



105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/msf/core/post_mixin.rb', line 105

def session
  # Try the cached one
  return @session if @session and not session_changed?

  if datastore["SESSION"]
    @session = framework.sessions.get(datastore["SESSION"].to_i)
  else
    @session = nil
  end

  @session
end

#session_changed?Boolean (protected)

Returns:

  • (Boolean)


250
251
252
253
254
255
256
257
258
259
# File 'lib/msf/core/post_mixin.rb', line 250

def session_changed?
  @ds_session ||= datastore["SESSION"]

  if (@ds_session != datastore["SESSION"])
    @ds_session = nil
    return true
  else
    return false
  end
end

#session_compatible?(sess_or_sid) ⇒ Boolean

Note:

Because it errs on the side of compatibility, a true return value from this method does not guarantee the module will work with the session. For example, ARCH_CMD modules can work on a variety of platforms and archs and thus return true in this check.

Return false if the given session is not compatible with this module

Checks the session’s type against this module’s module_info["SessionTypes"] as well as examining platform and arch compatibility.

sess_or_sid can be a Session object, Integer, or String. In the latter cases it should be a key in framework.sessions.

Parameters:

  • sess_or_sid (Msf::Session, Integer, String)

    A session or session ID to compare against this module for compatibility.

Returns:

  • (Boolean)


181
182
183
# File 'lib/msf/core/post_mixin.rb', line 181

def session_compatible?(sess_or_sid)
  session_incompatibility_reasons(sess_or_sid).empty?
end

#session_display_infoObject



118
119
120
# File 'lib/msf/core/post_mixin.rb', line 118

def session_display_info
  "Session: #{session.sid} (#{session.session_host})"
end

#session_incompatibility_reasons(sess_or_sid) ⇒ Object

Return the reasons why a session is incompatible.



189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/msf/core/post_mixin.rb', line 189

def session_incompatibility_reasons(sess_or_sid)
  # Normalize the argument to an actual Session
  case sess_or_sid
  when ::Integer, ::String
    s = framework.sessions[sess_or_sid.to_i]
  when ::Msf::Session
    s = sess_or_sid
  end

  issues = []

  # Can't do anything without a session
  unless s
    issues << ['invalid session']
    return issues
  end

  # Can't be compatible if it's the wrong type
  if session_types
    issues << "incompatible session type: #{s.type}" unless session_types.include?(s.type)
  end

  # Check to make sure architectures match
  mod_arch = self.module_info['Arch']
  if mod_arch
    mod_arch = [mod_arch] unless mod_arch.kind_of?(Array)
    # Assume ARCH_CMD modules can work on supported SessionTypes since both shell and meterpreter types can execute commands
    issues << "incompatible session architecture: #{s.arch}" unless mod_arch.include?(s.arch) || mod_arch.include?(ARCH_CMD)
  end

  # Arch is okay, now check the platform.
  if self.platform && self.platform.kind_of?(Msf::Module::PlatformList)
    issues << "incompatible session platform: #{s.platform}" unless self.platform.supports?(Msf::Module::PlatformList.transform(s.platform))
  end

  # Check all specified meterpreter commands are provided by the remote session
  if s.type == 'meterpreter'
    issues += meterpreter_session_incompatibility_reasons(s)
  end

  issues
end

#setupObject

Grabs a session object from the framework or raises OptionValidateError if one doesn’t exist. Initializes user input and output on the session.

Raises:



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/msf/core/post_mixin.rb', line 40

def setup
  alert_user

  unless session || (datastore['SESSION'].blank? && !options['SESSION']&.required)
    raise Msf::OptionValidateError.new(['SESSION'])
  end

  if session
    # Check session readiness before compatibility so the session can be queried
    # for its platform, capabilities, etc.
    check_for_session_readiness if session.type == "meterpreter"

    if session.type.ends_with?(':winpty')
      raise Msf::OptionValidateError.new({
        'SESSION' => 'Session does not support post modules.'
      })
    end

    incompatibility_reasons = session_incompatibility_reasons(session)
    if incompatibility_reasons.any?
      print_warning("SESSION may not be compatible with this module:")
      incompatibility_reasons.each do |reason|
        print_warning(" * #{reason}")
      end
    end
  end

  # Msf::Exploit#setup for exploits, NoMethodError for post modules
  super rescue NoMethodError

  @session.init_ui(self.user_input, self.user_output) if @session
  @sysinfo = nil
end

#sysinfoHash?

Cached sysinfo, returns nil for non-meterpreter sessions

Returns:

  • (Hash, nil)


128
129
130
131
132
133
134
135
# File 'lib/msf/core/post_mixin.rb', line 128

def sysinfo
  begin
    @sysinfo ||= session.sys.config.sysinfo
  rescue NoMethodError
    @sysinfo = nil
  end
  @sysinfo
end