Porting Metasploit Payloads to Ronin Payloads

This guide explains how to convert a Metasploit payloads into a Ronin payloads.

Table of Contents

Class Definition

Metasploit

Every Metasploit payload is defined as a module named MetasploitModule.

module MetasploitModule

  # ...

end

Ronin

Ronin payloads are defined as classes that must:

  1. Require any classes or modules used.
  2. Be defined in the Ronin::Payloads namespace.
  3. Have a unique CamelCase name similar to the file name.
  4. Inherit from Ronin::Payloads::Payload or one of it’s sub-classes.
  5. Call register with the same name as it’s file name.
# payloads/my_payload.rb
require 'ronin/payloads/payload'

module Ronin
  module Payloads
    class MyPayload < Payload

      register 'my_payload'

      # ...

    end
  end
end

Included Modules

Metasploit

Since Metasploit payloads are defined as modules, they cannot inherit from another base class. Instead, they must include in other Msf::Payload:: modules.

module MetasploitModule
  include Msf::Payload::Adapter
  include Msf::Exploit::Powershell

  # ...
end

Ronin

Since Ronin payloads are defined as classes, they can inherit from specific Ronin::Payloads::Payload base classes which define different types of payloads.

require 'ronin/payloads/powershell_payload'

module Ronin
  module Payloads
    class MyPayload < PowerShellPayload

      # ...

    end
  end
end

Additionally, ronin-payloads provides a set of modules that can be included to add additional functionality, such as working with binary data, using a build directory, bind shell or reverse shell functionality.

Cheat Sheet

Metasploit Ronin
include Msf::Payload::Java class MyPayload < JavaPayload
include Msf::Payload::JSP class MyPayload < JSPPayload
include Msf::Payload::NodeJS class MyPayload < NodeJSPayload
include Msf::Payload::Php class MyPayload < PHPPayload
include Msf::Payload::Python class MyPayload < PythonPayload
include Msf::Payload::Ruby class MyPayload < RubyPayload
include Msf::Payload::Windows::Powershell class MyPayload < PowerShellPayload
include Msf::Exploit::Powershell class MyPayload < PowerShellPayload

Metadata

Metasploit

Metasploit payloads define their metadata in the initialize method by passing a large Hash of Strings:

module MetasploitModule

  # ...

  def initialize(info = {})
    super(merge_info(info,
      'Name'          => 'Linux Command Shell, Reverse TCP Inline',
      'Description'   => 'Connect back to attacker and spawn a command shell',
      'Author'        => 'ricky',
      'License'       => MSF_LICENSE,
      'Platform'      => 'linux',
      'Arch'          => ARCH_X64,
      # ...
    ))
  end
end

Ronin

Ronin payloads define their metadata in the class body by calling class methods:

require 'ronin/payloads/payload'

module Ronin
  module Payloads
    class MyPayload < Payload

      register 'my_payload'

      summary 'A single sentence summary of the payload'
      description <<~EOS
        A longer multi-line or multi-paragraph description of the payload.
        Bla bla bla bla.
      EOS

      author 'Author1'
      author 'Author2', website:  'https://example.com',
                        email:    'author1@example2.com',
                        github:   'author2',
                        mastodon: 'https://example.com/@author2',
                        twitter:  '@author2',
                        discord:  '...'

      references [
        'https://example.com/url1',
        'https://example.com/url2',
        # ...
      ]

      # ...

    end
  end
end

Additional metadata modules can be included to define additional metadata, such as Metadata::Arch or Metadata::OS. If the exploit class inherits from the ASMPayload or ShellcodePayload class, then those modules are already included by default.

require 'ronin/payloads/shellcode_payload'

module Ronin
  module Payloads
    class MyShellcode < ShellcodePayload

      # ...

      arch :x86_64
      os :linux

      # ...

    end
  end
end

See the arch, os, and os_version method documentation for further details.

Cheat Sheet

Metasploit Ronin
Name' => '...',
summary '...'
'Description'    => %q{
   ...
},
description <<~DESC
  ...
DESC
'License'        => MSF_LICENSE,
N/A
'Author'         => [
  'John Doe', # discoverer
  ...
],
author 'John Doe'
...
'References'     =>
  [
    [ 'URL', 'https://example.com/...' ],
    # ...
  ],
references [
  'https://example.com/...',
  # ...
]
'Platform'       => 'Linux',
include Metadata::OS
# ...

os :linux
'Arch'       => ARCH_X64,
include Metadata::Arch
# ...

arch :x86_64

Options vs. Params

Metasploit

Metasploit payloads defines options using the register_options within the initialize method. The register_options method accepts an Array of one or more options. The options are initialized using OptString, OptBool, etc, option classes. The first argument is the option name in all uppercase. The second argument is the option description. The first argument is the optional default value for the option.

module MetasploitModule

  # ...

  def initialize(info = {})
    # ...

    # Register command execution options
    register_options(
      [
        OptString.new('SHELL', [ true, "The shell to execute.", "/bin/sh" ]),
      ])
  end

end

Ronin

Ronin payloads defines options using the param method which can be called multiple times. The first argument is the param name, in lowercase or snake_case. The second argument is the param type class. Additional keyword arguments may define whether the param is required, it’s default value, and it’s description.

require 'ronin/payloads/payload'

module Ronin
  module Payloads
    class MyPayload < Payload

      # ...

      param :shell, String, required: true,
                            default:  '/bin/sh',
                            desc:     'The shell command to execute'

      # ...

    end
  end
end

Note: if the payload class includes the Mixins::BindShell or Mixins::ReverseShell modules, then it will automatically have host and port params.

Cheat Sheet

Metasploit Ronin
OptString.new('FOO', ...) param :foo, String, ...
OptInt.new('FOO', ...) param :foo, Integer, ...
OptBool.new('FOO', ...) param :foo, Boolean, ...
OptEnum.new('FOO', [true, '...', ['value1', 'value2', ...]) param :foo, Enum[:value1, :value2, ...]

generate vs. build/prelaunch/postlaunch/cleanup methods

Metasploit

Most Metasploit payloads will define their payload building logic in a generate method:

module MetasploitModule
  # ...

  def generate(opts = {})
    opts[:arch] ||= module_info['AdaptedArch']
    payload = super

    cmd_psh_payload(payload, ARCH_X86, remove_comspec: true)
  end
end

Note: some Metasploit payloads define their entire payload within the metadata section in the initialize method:

module MetasploitModule

  # ...

  def initialize(info = {})
    super(merge_info(info,
      # ...
      'Payload'       =>
        {
          'Offsets' =>
            {
              'LHOST' => [ 20, 'ADDR' ],
              'LPORT' => [ 18, 'n'    ],
            },
          'Payload' =>
            "\x6a\x29"                     + # pushq  $0x29
            "\x58"                         + # pop    %rax
            "\x99"                         + # cltd
            "\x6a\x02"                     + # pushq  $0x2
            # ...
        }
      ))
  end
end

Ronin

Ronin payloads define the payload’s logic in five main methods.

  1. validate - Perform any additional validations before building the payload.
  2. build - Builds any payload string or file. Must set the @payload instance variable with the built payload String.
  3. prelaunch - Additional logic to execute before the exploit which is using the payload is launched.
  4. postlaunch - Additional logic to execute after the exploit which is using the payload is launched.
  5. cleanup - Perform any extra cleanup tasks after the payload is done being used.
require 'ronin/payloads/payload'

module Ronin
  module Payloads
    class MyPayload < Payload

      # ...

      def build
        @payload = "..."
      end

      def cleanup
        # ...
      end

      def prelaunch
        # ...
      end

      def postlaunch
        # ...
      end

    end
  end
end

Note: the Mixins::BindShell or Mixins::ReverseShell modules define their own prelaunch and postlaunch logic to connect to the remote shell.

Metasploit Ronin
def generate(opts = {})
  # ...
end
def build
  @payload = "..."
end
def initialize(info = {})
  super(merge_info(info,
    # ...
    'Payload'       =>
      {
        'Payload' => "..."
      }
  ))
end
def build
  @payload = "..."
end

Printing

Metasploit

Metasploit provides a set of methods for printing status messages from an exploit:

    print_status "Running the Javascript shell..."
    # ...

    print_warning("Unable to retrieve token")
    # ...

    print_error('Connection failed.')
    # ...

Ronin

Ronin also provides a similar set of methods for printing status messages from a payloads:

    print_status "Sending request to /path ..."
    response = http_get('/path')

    if response.code == '200'
      if response.body.include?('Success')
        print_success "response indicates success! Proceeding ..."
      else
        print_failure "request failed! Trying again ..."
        print_debug response.body
      end
    else
      print_error "Did not receive a 200 response: #{response.code}"
    end

Cheat Sheet

Metasploit Ronin
print_status("Message here")
vprint_status("Message here")
print_info "Message here" /
print_status "Message here"
print_good("Message here")
vprint_good("Message here")
print_good "Message here" /
print_success "Message here" /
print_positive "Message here"
print_bad("Message here")
vprint_bad("Message here")
print_bad "Message here" /
print_failure "Message here" /
print_negative "Message here"
print_warning("Message here")
vprint_warning("Message here")
print_warning "Message here"
print_error("Message here")
print_bad("Message here")
vprint_error("Message here")
vprint_bad("Message here")
print_error "Message here"

Packing

Metasploit

Metasploit payloads use Ruby’s built-in Array#pack method, which packs the contents of an Array using a binary format String:

    pop_r3_ret = [0x00013cd0].pack('V')

Ronin

Ronin adds a it’s own pack method to Array, Integer, and Float which can also accept a binary type Symbol name instead of a pack String:

    pop_r3_ret = 0x00013cd0.pack(:uint32_le)

If the Ronin payload includes Mixins::Binary, then a pack method will be added, which packs a single value based on the payload’s defined arch and os.

require 'ronin/payloads/payload'
require 'ronin/payloads/metadata/arch'
require 'ronin/payloads/metadata/os'
require 'ronin/payloads/mixins/binary'

module Ronin
  module Payloads
    class MyPayload < Payload

      include Metadata::Arch
      include Metadata::OS
      include Mixins::Binary

      arch :x86_64
      os :linux

      def build
        # ...

        @payload = "..."
        @payload << pack(:uint32, 0x12345678)

        # ...
      end

    end
  end
end

Cheat Sheet

Metasploit Ronin
[i].pack('C') i.pack(:uchar)
[i].pack('S') i.pack(:uint16)
[i].pack('L') i.pack(:uint32)
[i].pack('Q') i.pack(:uint64)
[i].pack('c') i.pack(:char)
[i].pack('s') i.pack(:int16)
[i].pack('l') i.pack(:int32)
[i].pack('q') i.pack(:int64)
[i].pack('S<') i.pack(:uint16_le)
[i].pack('L<') i.pack(:uint32_le)
[i].pack('Q<') i.pack(:uint64_le)
[i].pack('s<') i.pack(:int16_le)
[i].pack('l<') i.pack(:int32_le)
[i].pack('q<') i.pack(:int64_le)
[i].pack('S>') i.pack(:uint16_be)
[i].pack('L>') i.pack(:uint32_be)
[i].pack('Q>') i.pack(:uint64_be)
[i].pack('s>') i.pack(:int16_be)
[i].pack('l>') i.pack(:int32_be)
[i].pack('q>') i.pack(:int64_be)
[f].pack('F') / [f].pack('f') f.pack(:float) / f.pack(:float32)
[f].pack('D') / [f].pack('d') f.pack(:double) / f.pack(:float64)
[f].pack('e') f.pack(:float_le) / f.pack(:float32_le)
[f].pack('E') f.pack(:double_le) / f.pack(:float64_le)
[f].pack('g') f.pack(:float_be) / f.pack(:float32_be)
[f].pack('G') f.pack(:double_be) / f.pack(:float64_be)
[i].pack('n') i.pack(:uint16_le)
[i].pack('N') i.pack(:uint32_le)
[i].pack('v') i.pack(:uint16_be)
[i].pack('V') i.pack(:uint32_be)
[...].pack('VVV') [...].pack([:uint32_le, 3])
[...].pack('V*') [...].pack(:uint32_le..)
[...].pack('VS<C') [...].pack(:uint32_le, :uint16_le, :uchar)