Basics of The Metasploit Framework API - IRB Setup

Those of you who have taken my "Automating Metasploit Framework" class all this material should not be new. I have decided to start making a large portion of the class available here in the blog as a series. 

On this post I will cover the basics of setting up IRB so we can start exploring in a general sense the Metasploit Framework API. The API is extensive and sadly it would take quite a bit of time over it all, in the series I will covers the basic API calls and provide enough knowledge so you can continue learning the rest on your own or as needed. 

For this you need to be running a development environment. The Metasploit team has documentation on how to setup one https://github.com/rapid7/metasploit-framework/wiki/Setting-Up-a-Metasploit-Development-Environment 

If you are new to Ruby or come from another language and are learning the syntax here is a Ruby Primer.

What is IRB

IRB is the Interactive Ruby Shell, a REPL (Read -> Eval -> Print Loop) that will allow us to to interact with the Framework in real time allowing us to test and validate ideas quickly. One big advantage of the Metasploit Framework is that we can run IRB from inside msfconsole it self. When invoked from inside msfconsole we are running from the context of the loaded instance of the framework with access to all of the instances of objects inside and libraries. 

To test IRB you can just launch msfconsole and run the irb ruby statements and see the output they generate:

carlos@ubuntu:~$ msfconsole -q
msf > irb
[*] Starting IRB shell...

>> 1 +2
=> 3
>> print_status("hello world")
[*] hello world
=> nil
>> 

To exit from irb and fo back to the msfconsole one simply needs to type exit

>> exit
msf > 

IRB Basics

Lets cover some basic usage of IRB before we go in to extending it. You may find your self SSH or at the console of a server where you have not been able to tune IRB to you liking so having knowledge on some basic concepts in using IRB should be of value.  The following are tips on what I have found useful in helping me understand and explore the ever changing Metasploit Framework when I work form IRB.

Everything in Ruby is an object and the type of object will dictate what we can do with it. We can look at what type an object is by calling the Class attribute. 

>> "This is text".class
=> String
>> 1.class
=> Integer
>> framework.class
=> Msf::Framework
>> framework.db.class
=> Msf::DBManager

Like with any object in Ruby each object will have Methods and Attributes (Properties). Every Attribute of a ruby object is accessed and set via methods. We can take a look at the methods we have available by calling Object.methods

>> framework.methods
=> [:load_config, :on_module_created, :stats, :cache_thread, :cache_thread=, :cache_initialized, :cache_initialized=, :save_config, :init_simplified, :stats=, :on_module_run, :on_module_complete, :on_module_error, :on_module_load, :init_module_paths, :inspect, :options, :version, :plugins, :post, :search, :modules, :datastore, :nops, :encoders, :payloads, :options=, :modules=, :threads, :sessions, :jobs, :db, :events, :events=, :datastore=, :jobs=, :plugins=, :uuid_db, :uuid_db=, :browser_profiles, :browser_profiles=, :exploits, :auxiliary, :auxmgr, :threads?, :auxmgr=, :db=, :synchronize, :mon_try_enter, :try_mon_enter, :mon_enter, :mon_exit, :mon_synchronize, :new_cond, :`, :to_yaml, :to_yaml_properties, :to_json, :psych_to_yaml, :blank?, :present?, :try, :as_json, :presence, :acts_like?, :to_param, :to_query, :try!, :duplicable?, :to_json_with_active_support_encoder, :to_json_without_active_support_encoder, :instance_values, :instance_variable_names, :html_safe?, :deep_dup, :in?, :presence_in, :with_options, :dclone, :old_send, :encode_length, :decode_sequence, :assert_no_remainder, :decode_octet_string, :decode_integer, :decode_tlv, :encode_integer, :encode_octet_string, :encode_sequence, :encode_tlv, :decode_object_id, :decode_ip_address, :decode_timeticks, :build_integer, :decode_integer_value, :decode_uinteger_value, :decode_object_id_value, :integer_to_octets, :encode_tagged_integer, :encode_null, :encode_exception, :encode_object_id, :unloadable, :require_or_load, :require_dependency, :load_dependency, :pretty_print, :pretty_print_cycle, :pretty_print_instance_variables, :pretty_print_inspect, :instance_of?, :public_send, :instance_variable_get, :instance_variable_set, :instance_variable_defined?, :remove_instance_variable, :private_methods, :kind_of?, :instance_variables, :tap, :method, :public_method, :singleton_method, :class_eval, :is_a?, :extend, :pretty_inspect, :require_with_backports, :define_singleton_method, :to_enum, :enum_for, :select, :concern, :<=>, :sleep, :===, :=~, :!~, :suppress_warnings, :eql?, :respond_to?, :freeze, :display, :object_id, :send, :gem, :silence, :silence_warnings, :to_s, :enable_warnings, :with_warnings, :silence_stderr, :silence_stream, :suppress, :capture, :quietly, :nil?, :hash, :class, :singleton_class, :clone, :dup, :itself, :taint, :tainted?, :untaint, :untrust, :trust, :untrusted?, :methods, :protected_methods, :frozen?, :public_methods, :singleton_methods, :!, :==, :!=, :__send__, :equal?, :instance_eval, :instance_exec, :__id__]

We get both the instance methods and the inherited methods on the object. We can look through the methods for those whose name contain a string by using the grep(<regex>) method 

>> framework.methods.grep(/module/)
=> [:on_module_created, :on_module_run, :on_module_complete, :on_module_error, :on_module_load, :init_module_paths, :modules, :modules=]

We get a list of the methods but we are not able to tell what parameters a method may take. To look at method number of parameters we can do Object.method(<method>).arity to look at the parameters we simply change the call to parameters Object.method(<method>).parameters

>> framework.db.method(:add_workspace).arity
=> 1
>> framework.db.method(:add_workspace).parameters
=> [[:req, :name]]

You may wonder why if we see 1 parameter why does the output show an array with a sub array with 2 values. The sub array refers to the parameter where the first param represent the type and the second the name. The types are:

  • req - required argument
  • opt - optional argument
  • rest - rest of arguments as array
  • keyreq - reguired key argument (2.1+)
  • key - key argument
  • keyrest - rest of key arguments as Hash
  • block - block parameter

Sometimes we want to look at the source code of the definition of the method. We can get a better understanding from the source and also we can read comments to get an even better understanding of the function of the method. Looking at the method we only need to look at the source_location method to get the file and its full path.

>> framework.db.method(:add_workspace).source_location
=> ["/opt/metasploit-framework/lib/msf/core/db_manager/workspace.rb", 5]

Configuring IRB

IRB is very useful but we can make it better by using several Ruby Gems and specifying some configuration parameters. There are several Gems that I use when working in IRB specifically with the Metasploit Framework. The following are the Gems I recommend to start with and that with experience you can change for others and tweak their settings. 

  • Wirble - very old gem for adding color to the terminal output among other things. There are newer ports and better gems for this but I have had bad luck getting them to work inside of MSF. This one even if old still gets the job done. 
  • awesome_print - prints in a easier to parse output visually an object. 
  • Code - Lets me look at the code of a object or a method of an object. 

Installing this gems is simple with the gem command:

$ gem install awesome_print wirble code

Once installed I need to allow Metasploit Framework to load them since they are not in the Gemset of the project and I can not add them to the Gemset file since msfupdate will replace the file. To allow this in the project folder we need to create a Gemfile.local file loading the current project gems and our additional gems.

# Include the Gemfile included with the framework. This is very
# important for picking up new gem dependencies.
msf_gemfile = File.join(File.dirname(__FILE__), 'Gemfile')
if File.readable?(msf_gemfile)
  instance_eval(File.read(msf_gemfile))
end

# Create a custom group
group :local do
  gem 'wirble'
  gem 'awesome_print'
  gem 'code'
  gem 'core_docs'
end

Now we need to create a .irbrc in our home folder in Linux or Mac OS to load out settings automatically when IRB is loaded. 

# Print message to show that irbrc loaded.
puts '~/.irbrc has been loaded.'

# Load wirb to colorize the console
require 'wirble'
Wirble.init
Wirble.colorize

# Load awesome_print.
require 'ap'

# Load the Code gem 
require 'code'


# Remove the annoying irb(main):001:0 and replace with >>
IRB.conf[:PROMPT_MODE]  = :SIMPLE

# Tab Completion
require 'irb/completion'

# Automatic Indentation
IRB.conf[:AUTO_INDENT]=true

# Save History between irb sessions
require 'irb/ext/save-history'
IRB.conf[:SAVE_HISTORY] = 100
IRB.conf[:HISTORY_FILE] = "#{ENV['HOME']}/.irb-save-history"

# get all the methods for an object that aren't basic methods from Object
class Object
  def local_methods
    return (methods - Object.instance_methods)
  end
end

Once Wirble is loaded and initialized the output of my console will be of a different color for different elements making it easier to look at. 

color_irb.png

With awesome print I can have a even better view of an object and the output is colorized.

awesome_print.png

I set up IRB so it will indent and allow for tab complete, do be careful MSF is such a big project that tab complete for some stuff may hand and you may have to Crtl-C to stop it. I also have a function that shows me only the local methods so the list I have to go through is much shorter by removing all the inherited methods. 

>> framework.db.methods.count
=> 397
>> framework.db.local_methods.count
=> 261

The code gem will allow me to look at the code of a method to better understand it for a given object. 

code.png

I hope you have found this information useful. In the next series of post I will cover general APIs that should be of value when writing modules, plugins or automating the framework.